Compare commits
34
Commits
optimization
...
v1.0.30
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db8f9da2c8 | ||
|
|
50e84d712b | ||
|
|
93e2213ec2 | ||
|
|
3291a4b00f | ||
|
|
c7b9013eb0 | ||
|
|
7d6d9d2a17 | ||
|
|
7e2dd59549 | ||
|
|
e124941611 | ||
|
|
618b715c21 | ||
|
|
cf2b7c58ed | ||
|
|
11dee871cd | ||
|
|
dedae3d532 | ||
|
|
c08ebe0ffa | ||
|
|
8e54d04899 | ||
|
|
893426f0c6 | ||
|
|
eb8a3dd5b2 | ||
|
|
0fc5555adc | ||
|
|
e690f28613 | ||
|
|
edc21f4246 | ||
|
|
2c5a3dc689 | ||
|
|
3b90a34214 | ||
|
|
cae2c60ec8 | ||
|
|
0e78fd05f2 | ||
|
|
64ae39ddfa | ||
|
|
ba3943ed6e | ||
|
|
150166c206 | ||
|
|
528cc8423f | ||
|
|
12f987c57b | ||
|
|
29b6eb293d | ||
|
|
2bf800f763 | ||
|
|
4d70c04bd6 | ||
|
|
b3f0d7bd8d | ||
|
|
1466b7f75c | ||
|
|
b1c137404a |
+167
@@ -0,0 +1,167 @@
|
||||
# ROCM Ninodes Architecture Documentation
|
||||
|
||||
## Hardware Configuration
|
||||
|
||||
### Primary System
|
||||
- **Platform**: GMTek Evo-X2 Strix Halo
|
||||
- **GPU**: AMD gfx1151 (RDNA 3.5 architecture)
|
||||
- **Memory**: 128GB Unified RAM (shared between CPU and GPU)
|
||||
- **OS**: Manjaro Linux (kernel 6.17.1-1-MANJARO)
|
||||
- **Shell**: Zsh (/usr/bin/zsh)
|
||||
|
||||
## Software Stack
|
||||
|
||||
### Core Dependencies
|
||||
- **ROCm**: 7.10.0a (latest development build)
|
||||
- **PyTorch**: 2.10.0a0+rocm7.10.0a20251011 (nightly build)
|
||||
- **Python**: 3.11+ (ComfyUI requirement)
|
||||
- **ComfyUI**: Latest main branch with custom optimizations
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
#### ComfyUI Launch Flags
|
||||
```bash
|
||||
--use-pytorch-cross-attention # Enable PyTorch cross-attention for better performance
|
||||
--highvram # Optimize for high VRAM systems
|
||||
--cache-none # Disable caching to prevent memory issues
|
||||
```
|
||||
|
||||
#### Environment Variables
|
||||
```bash
|
||||
# ROCm-specific optimizations
|
||||
TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 # Enable experimental AOTRITON features
|
||||
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True # Enable expandable memory segments
|
||||
|
||||
# Optional debugging
|
||||
ROCM_NINODES_DEBUG=0 # Set to 1 to enable data capture for testing
|
||||
```
|
||||
|
||||
## GPU Architecture Specifics
|
||||
|
||||
### gfx1151 Optimizations
|
||||
- **Precision**: fp32 preferred over fp16 for stability
|
||||
- **Tile Sizes**: 768-1024 optimal for this architecture
|
||||
- **TF32**: Disabled (not supported/beneficial on gfx1151)
|
||||
- **Memory Modifier**: Conservative 1.5x for attention operations
|
||||
- **Batch Processing**: Conservative approach to prevent OOM
|
||||
|
||||
### Memory Management Strategy
|
||||
- **Attention Memory**: 1.5x modifier for conservative allocation
|
||||
- **VAE Processing**: Tiled decoding for large images/videos
|
||||
- **Video Support**: 5D tensor processing with 4D output conversion
|
||||
- **Cache Management**: Regular `torch.cuda.empty_cache()` calls
|
||||
|
||||
## Quantization Support
|
||||
|
||||
### Supported Quantized Formats
|
||||
- **FP8**: Hardware-accelerated FP8 quantization (flux1-dev-fp8.safetensors)
|
||||
- **BFloat16**: Native ROCm support with minimal overhead
|
||||
- **INT8/INT4**: GGUF format support for WAN 2.2 models
|
||||
- **Automatic Detection**: Detects quantized models from filename and dtype
|
||||
|
||||
### Quantization-Specific Optimizations
|
||||
- **Compatibility Mode**: Disables aggressive optimizations for quantized models
|
||||
- **Memory Management**: Quantization-aware memory allocation (FP8: 50% vs FP32, INT8: 25% vs FP32)
|
||||
- **Dtype Preservation**: Prevents forced dtype conversions that break quantized models
|
||||
- **Adaptive Processing**: Smaller tile sizes and chunk sizes for quantized models
|
||||
|
||||
### User-Reported Issue Fixes
|
||||
- **OOM Prevention**: Lower default tile_size (512 vs 768) for compatibility
|
||||
- **Batch Optimization**: Disabled by default for quantized models
|
||||
- **Memory Cleanup**: Less aggressive cleanup for quantized models
|
||||
- **Video Processing**: Adaptive chunk sizing based on frame count and memory
|
||||
|
||||
### HIPBlas
|
||||
- **Status**: Fully supported
|
||||
- **Configuration**: Automatic detection and optimization
|
||||
- **Performance**: Significant speedup for matrix operations
|
||||
|
||||
### FlashInfer
|
||||
- **Status**: Experimental support for gfx1151
|
||||
- **Usage**: Enabled in ROCm builds but may have limitations
|
||||
- **Fallback**: Standard attention mechanisms when FlashInfer fails
|
||||
|
||||
### GPU Direct Storage
|
||||
- **Status**: Limited support on current setup
|
||||
- **Workaround**: Standard file I/O with memory mapping where possible
|
||||
- **Future**: May improve with driver updates
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
### Flux Workflow (1024x1024)
|
||||
- **Target Improvement**: 78% over baseline
|
||||
- **Checkpoint Load**: <30s target
|
||||
- **Sampling**: Optimized for gfx1151 architecture
|
||||
- **VAE Decode**: <10s target
|
||||
|
||||
### WAN Workflow (320x320, 17 frames)
|
||||
- **Target Improvement**: 5.6% over baseline
|
||||
- **Video Processing**: 5D tensor support with 4D output
|
||||
- **Memory Usage**: Conservative allocation to prevent OOM
|
||||
- **Total Time**: <100s target
|
||||
|
||||
## Node Architecture
|
||||
|
||||
### Core Nodes
|
||||
1. **ROCMOptimizedCheckpointLoader**: Flux-optimized checkpoint loading
|
||||
2. **ROCMOptimizedKSampler**: Basic sampling with ROCm tuning
|
||||
3. **ROCMOptimizedKSamplerAdvanced**: Advanced sampling with step control
|
||||
4. **ROCMOptimizedVAEDecode**: VAE decode with video support
|
||||
5. **ROCMOptimizedVAEDecodeTiled**: Tiled VAE decode for large images
|
||||
6. **ROCMVAEPerformanceMonitor**: VAE performance metrics
|
||||
7. **ROCMSamplerPerformanceMonitor**: Sampler performance metrics
|
||||
8. **ROCMFluxBenchmark**: Flux workflow benchmarking
|
||||
9. **ROCMMemoryOptimizer**: Memory management helper
|
||||
|
||||
### Data Flow
|
||||
```
|
||||
Input → Checkpoint Loader → KSampler → VAE Decode → Output
|
||||
↓ ↓ ↓ ↓
|
||||
Monitor Performance Performance Monitor
|
||||
```
|
||||
|
||||
## Testing Infrastructure
|
||||
|
||||
### Debug Mode
|
||||
- **Environment Variable**: `ROCM_NINODES_DEBUG=1`
|
||||
- **Data Capture**: Saves input/output tensors to pickle files
|
||||
- **Performance Impact**: Zero when disabled
|
||||
- **Location**: `test_data/captured/`
|
||||
|
||||
### Test Categories
|
||||
1. **Unit Tests**: Individual node functionality
|
||||
2. **Performance Tests**: Timing and memory usage validation
|
||||
3. **Integration Tests**: Full workflow execution
|
||||
4. **Regression Tests**: Prevent performance degradation
|
||||
|
||||
## Constraints and Limitations
|
||||
|
||||
### Hardware Constraints
|
||||
- **Memory**: 128GB shared RAM limits batch sizes
|
||||
- **GPU**: gfx1151 specific optimizations required
|
||||
- **Storage**: No GPU Direct Storage support
|
||||
|
||||
### Software Constraints
|
||||
- **ROCm Version**: Requires 7.10.0a+ for full features
|
||||
- **PyTorch**: Nightly builds required for latest optimizations
|
||||
- **ComfyUI**: Custom modifications needed for optimal performance
|
||||
|
||||
### Performance Constraints
|
||||
- **Precision**: fp32 preferred over fp16 for stability
|
||||
- **Memory**: Conservative allocation to prevent OOM
|
||||
- **Batch Size**: Limited by available memory
|
||||
- **Video Processing**: 5D tensor handling complexity
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Planned Improvements
|
||||
- **FlashInfer**: Full support when available
|
||||
- **Memory Optimization**: Better memory management strategies
|
||||
- **Batch Processing**: Larger batch sizes with memory improvements
|
||||
- **Video Support**: Enhanced video processing capabilities
|
||||
|
||||
### Research Areas
|
||||
- **Attention Mechanisms**: Optimize for gfx1151
|
||||
- **Memory Layout**: Better tensor organization
|
||||
- **Pipeline Optimization**: Reduce data movement overhead
|
||||
- **Quantization**: Explore fp16 where stable
|
||||
+278
@@ -5,6 +5,284 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.30] - 2025-10-29
|
||||
|
||||
### Changed
|
||||
- General stability and performance improvements across ROCm-optimized nodes
|
||||
|
||||
### Fixed
|
||||
- Minor edge cases in node parameter handling and execution flow
|
||||
|
||||
### Meta
|
||||
- Synchronized version numbers across `pyproject.toml`, `comfyui_manager.json`, and `package.json`
|
||||
|
||||
## [1.0.29] - 2025-01-22
|
||||
|
||||
### Fixed
|
||||
- Video chunk processing boundary artifacts
|
||||
- Added 2-frame temporal overlap to prevent darker frames at chunk boundaries
|
||||
- Improved chunk concatenation logic with proper overlap cropping
|
||||
- Fixed tensor size mismatch errors during concatenation
|
||||
- Better handling of 5D video tensor formats
|
||||
|
||||
### Improved
|
||||
- ROCM VAE Decode chunk processing for WAN video
|
||||
- Automatic overlap detection and handling
|
||||
- Cleaner boundary transitions between video chunks
|
||||
- Reduced visual artifacts in chunked video processing
|
||||
|
||||
## [1.0.28] - 2025-10-20
|
||||
|
||||
### Fixed
|
||||
- Text-to-Video CPU RAM spikes and disk I/O in ROCMOptimizedKSamplerAdvanced
|
||||
- Noise tensors now created on the same device as latents (GPU), not CPU
|
||||
- Ensured latents/noise stay on GPU to prevent host paging
|
||||
- VAE decode moving tensors to CPU (output_device)
|
||||
- All decode outputs remain on the active GPU device for end-to-end GPU execution
|
||||
- Performance regression from conservative defaults
|
||||
- Restored tile_size=768, batch_optimization=True, video_chunk_size=8
|
||||
- Only chunk videos when T > 20 frames to avoid unnecessary overhead
|
||||
|
||||
### Improved
|
||||
- Quantized model compatibility without sacrificing speed
|
||||
- Less aggressive detection; optimizations remain unless explicitly using compatibility_mode
|
||||
- Preserved dtype for quantized models while keeping GPU residency
|
||||
- Reduced GPU idle time during video workflows by eliminating CPU transfers and minimizing sync points
|
||||
|
||||
### Documentation
|
||||
- Updated README with quantization support details and GPU residency fixes
|
||||
- Clarified recommended defaults for WAN 2.2 text-to-video
|
||||
|
||||
### Notes
|
||||
- This release focuses on keeping tensors on the GPU to avoid CPU RAM usage and heavy disk activity during text-to-video on WAN 2.2.
|
||||
|
||||
### Fixed
|
||||
- **Mature ROCm Driver Support**: Optimized for mature ROCm drivers and libraries
|
||||
- **Essential Environment Variables**: Added `TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1` for mature drivers
|
||||
- **Gentle Memory Management**: Replaced aggressive memory management with gentle cleanup for better performance
|
||||
- **Less Aggressive Settings**: Updated memory allocation settings (256MB chunks, 0.8 threshold) for mature drivers
|
||||
- **Removed Overly Complex Functions**: Eliminated force_memory_cleanup, emergency_memory_reset, and other performance-penalizing functions
|
||||
- **Clean Starting Script**: Simplified run_comfy.ps1 with essential ROCm variables only
|
||||
|
||||
### Improved
|
||||
- **Performance**: Removed performance penalties from overly aggressive memory management
|
||||
- **Gentle Memory Cleanup**: Single-pass cache clearing instead of multiple aggressive cycles
|
||||
- **Better Compatibility**: Works optimally with mature ROCm drivers and libraries
|
||||
- **Simplified Codebase**: Removed complex memory management that was causing slowdowns
|
||||
- **Cleaner Architecture**: Focused on essential optimizations without performance penalties
|
||||
|
||||
### Technical Details
|
||||
- **Environment Variables**: `TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1` for mature driver support
|
||||
- **Memory Management**: Gentle single-pass cleanup instead of aggressive multi-pass cycles
|
||||
- **Starting Script**: Clean PowerShell script with essential ROCm variables
|
||||
- **Code Cleanup**: Removed unnecessary development files and test workflows
|
||||
- **Node Count**: Maintained 9 optimized nodes with gentle memory management
|
||||
|
||||
### Removed
|
||||
- **Development Files**: Removed test workflows, documentation, and backup files created during development
|
||||
- **Aggressive Memory Functions**: Removed force_memory_cleanup, emergency_memory_reset, nuclear_memory_reset
|
||||
- **Performance Penalties**: Eliminated functions that were causing 50%+ performance overhead
|
||||
- **Complex Memory Management**: Simplified to gentle cleanup for mature ROCm drivers
|
||||
|
||||
## [1.0.26] - 2025-01-19
|
||||
|
||||
### Fixed
|
||||
- **ROCm KSampler Fresh Start**: Complete rebuild with minimal, clean approach
|
||||
- **Simplified Memory Management**: Removed all ineffective memory defragmentation functions
|
||||
- **Environment Variables**: Optimized settings in run_comfy.ps1 (max_split_size_mb:128, garbage_collection_threshold:0.7)
|
||||
- **Removed Dead Code**: Deleted ROCMMemorySafeKSampler, ROCMMemoryDefragmenter, ROCMEmergencyMemoryReset
|
||||
- **Minimal ROCm Optimizations**: Simple memory cleanup and monitoring only
|
||||
- **Vanilla ComfyUI Path**: Uses standard ComfyUI sampling with minimal overhead
|
||||
|
||||
### Technical Details
|
||||
- **Environment Variables**: Optimized for gfx1151 unified memory (removed expandable_segments)
|
||||
- **Memory Management**: Simple torch.cuda.empty_cache() + gc.collect() only
|
||||
- **Node Count**: Reduced from 12 to 9 nodes (removed 3 memory management nodes)
|
||||
- **Code Reduction**: ~70% reduction in complexity
|
||||
- **ROCm Settings**: Documented optimal settings in ROCM_SETTINGS.md
|
||||
|
||||
### Removed
|
||||
- **ROCMMemorySafeKSampler**: Replaced with simplified ROCMOptimizedKSampler
|
||||
- **ROCMMemoryDefragmenter**: Removed ineffective defragmentation approach
|
||||
- **ROCMEmergencyMemoryReset**: Removed complex memory reset logic
|
||||
- **Complex Memory Functions**: Removed force_memory_defragmentation, emergency_memory_reset_nuclear, etc.
|
||||
|
||||
## [1.0.25] - 2025-01-19
|
||||
|
||||
### Fixed
|
||||
- **CRITICAL: Environment Variables Not Applied**: Fixed root cause of OOM errors
|
||||
- **Environment Variables in main.py**: Added ROCm memory settings to ComfyUI startup BEFORE PyTorch import
|
||||
- **Diagnostic Logging**: Added comprehensive logging to verify environment variable application
|
||||
- **Memory Pattern Establishment**: Pre-allocates 40GB block to establish good memory pattern
|
||||
- **ComfyUI Execution Hooks**: Hooks into model loading and weight patching for memory management
|
||||
- **Weight Patching OOM Fix**: Added memory cleanup before/after weight patching (where OOM occurs)
|
||||
|
||||
### Technical Details
|
||||
- **Environment Variables**: Now set in `main.py` at lines 24-25 BEFORE any PyTorch imports
|
||||
- **Memory Pre-allocation**: 40GB block allocated and freed to establish contiguous memory pattern
|
||||
- **Execution Hooks**: Hooks into `load_models_gpu` and `patch_weight_to_device` for cleanup
|
||||
- **Diagnostic Output**: Comprehensive logging shows environment variable status and memory state
|
||||
|
||||
### Removed
|
||||
- **Excessive Memory Management**: Removed functions causing 50%+ performance overhead
|
||||
- Removed `nuclear_memory_reset()` - 20 seconds of wasted cache clearing
|
||||
- Removed `create_memory_pool()` - didn't help fragmentation
|
||||
- Removed `patch_pytorch_memory_management()` - caused slowdown on every memory query
|
||||
- Removed `setup_memory_environment()` - redundant and ineffective
|
||||
- Removed `defragment_memory()` - didn't work without proper env vars
|
||||
|
||||
### Improved
|
||||
- **Performance**: Sampling now 50%+ faster without nuclear reset overhead
|
||||
- **Memory Management**: Aggressive 3x cache clearing with synchronization for better fragmentation control
|
||||
- **Compatibility**: Works correctly with PowerShell launch script settings
|
||||
- **Fragmentation Control**: Reduced max_split_size_mb from 512MB to 256MB for better memory management
|
||||
|
||||
### Technical Details
|
||||
- Environment variables now managed exclusively by `run_comfy.ps1` with aggressive settings
|
||||
- Memory management: 3x cache clearing with synchronization before all operations
|
||||
- Optimized for AMD Radeon 8060S (gfx1151) with 107.87 GB unified memory
|
||||
- **CRITICAL**: Requires ComfyUI restart to apply new environment variables
|
||||
|
||||
## [1.0.24] - 2025-01-19
|
||||
|
||||
### Fixed
|
||||
- **Critical Memory Fragmentation**: Fixed persistent OOM errors with nuclear memory management
|
||||
- **Early Environment Setup**: Memory environment variables now set BEFORE PyTorch import
|
||||
- **Nuclear Memory Reset**: Added `nuclear_memory_reset()` with 20x cache clearing
|
||||
- **Memory Pool Creation**: Pre-allocates memory blocks to prevent fragmentation
|
||||
- **Enhanced PyTorch Patching**: Patched `torch.cuda.memory_reserved` for better cleanup
|
||||
- **Lower Memory Thresholds**: Reduced cleanup thresholds to 50% allocated, 60% reserved
|
||||
|
||||
### Improved
|
||||
- **Memory Management**: Enhanced memory management for critical operations
|
||||
- **Nuclear Reset Before Sampling**: All samplers now perform nuclear reset before operations
|
||||
- **Memory Pool System**: Creates memory pools to prevent fragmentation
|
||||
- **Better Error Prevention**: More aggressive memory management prevents allocation failures
|
||||
- **Memory Synchronization**: Enhanced synchronization between cache clears and garbage collection
|
||||
|
||||
### Technical Details
|
||||
- **Environment Variables**: Set `PYTORCH_CUDA_ALLOC_CONF` and `PYTORCH_HIP_ALLOC_CONF` before PyTorch import
|
||||
- **Memory Pool**: Pre-allocates 20% of total memory in 4 blocks to prevent fragmentation
|
||||
- **Nuclear Reset**: 20x cache clearing with memory pool recreation
|
||||
- **PyTorch Patching**: Patched `memory_allocated` and `memory_reserved` for automatic cleanup
|
||||
- **Critical Operations**: Nuclear reset before all sampling operations
|
||||
|
||||
## [1.0.23] - 2025-01-19
|
||||
|
||||
### Added
|
||||
- **ROCMEmergencyMemoryReset**: New emergency memory reset node for critical situations
|
||||
- **Three Reset Levels**: Aggressive, Emergency, and Nuclear memory reset options
|
||||
- **Memory Status Reporting**: Detailed before/after memory status and improvement metrics
|
||||
- **Smart Recommendations**: Context-aware recommendations based on memory freed
|
||||
- **Nuclear Reset**: Most aggressive 15x cache clearing for extreme situations
|
||||
|
||||
### Fixed
|
||||
- **Critical Memory Fragmentation**: Fixed persistent OOM errors due to memory fragmentation
|
||||
- **Early Environment Setup**: Memory environment variables now set at module import time
|
||||
- **PyTorch Memory Patching**: Patched PyTorch's memory management for aggressive cleanup
|
||||
- **Emergency Memory Reset**: Added `emergency_memory_reset()` with 10x cache clearing
|
||||
- **ComfyUI Memory Patching**: Patched ComfyUI's memory management for aggressive cleanup
|
||||
- **Lower Fragmentation Threshold**: Reduced fragmentation detection threshold from 10% to 5%
|
||||
- **Enhanced Defragmentation**: Increased cache clearing from 3x to 5x iterations
|
||||
|
||||
### Improved
|
||||
- **Memory Management**: Enhanced memory management for critical operations
|
||||
- **Emergency Reset Before Sampling**: All samplers now perform emergency reset before operations
|
||||
- **PyTorch Memory Patching**: Patched `torch.cuda.empty_cache` and `torch.cuda.memory_allocated`
|
||||
- **Module-Level Patching**: ComfyUI memory management patched at import time
|
||||
- **Better Error Prevention**: More aggressive memory management prevents allocation failures
|
||||
- **Memory Synchronization**: Enhanced synchronization between cache clears and garbage collection
|
||||
|
||||
### Technical Details
|
||||
- **Environment Variables**: Set `PYTORCH_CUDA_ALLOC_CONF` and `PYTORCH_HIP_ALLOC_CONF` at import
|
||||
- **Memory Patching**: PyTorch and ComfyUI memory management patched for aggressive cleanup
|
||||
- **Emergency Reset**: 10x cache clearing with garbage collection for critical operations
|
||||
- **Nuclear Reset**: 15x cache clearing for extreme memory situations
|
||||
- **Fragmentation Detection**: Lowered threshold to 5% for earlier intervention
|
||||
- **Critical Operations**: Emergency reset before all sampling operations
|
||||
|
||||
## [1.0.22] - 2025-01-19
|
||||
|
||||
### Added
|
||||
- **ROCMMemorySafeKSampler**: New memory-safe KSampler specifically designed to prevent OOM errors
|
||||
- **Memory Safety Levels**: Conservative, balanced, and aggressive memory management modes
|
||||
- **Progressive Parameter Reduction**: Automatically reduces steps, CFG, and denoise when memory is low
|
||||
- **Emergency Memory Cleanup**: 5x cache clearing with synchronization for critical memory situations
|
||||
- **Memory Safety Checks**: Pre-flight validation before operations to prevent OOM errors
|
||||
- **Ultra-Conservative Fallback**: Last-resort sampling with minimal parameters when all else fails
|
||||
|
||||
### Fixed
|
||||
- **HIP Out of Memory Errors**: Comprehensive OOM prevention system
|
||||
- **Emergency Memory Cleanup**: `emergency_memory_cleanup()` function with 5x cache clearing and synchronization
|
||||
- **Memory Safety Validation**: `check_memory_safety()` function to validate available memory before operations
|
||||
- **Critical Memory Thresholds**: 2GB free memory triggers emergency cleanup, 4GB triggers aggressive cleanup
|
||||
- **Enhanced Fallback Mechanisms**: Multiple levels of fallback with increasingly conservative parameters
|
||||
- **Memory Error Detection**: Automatic detection of memory-related errors and appropriate response
|
||||
|
||||
### Improved
|
||||
- **Memory Management**: Enhanced OOM prevention and memory monitoring
|
||||
- **Conservative Memory Fractions**: Reduced maximum memory fraction to 70% for better safety
|
||||
- **Memory Monitoring**: Added memory safety checks before VAE decode operations
|
||||
- **Progressive Error Recovery**: Three-tier fallback system (standard → conservative → ultra-conservative)
|
||||
- **Better Error Handling**: Enhanced detection and handling of memory-related errors
|
||||
- **Video Workflow Optimization**: Improved memory management for WAN VAE video processing
|
||||
|
||||
### Technical Details
|
||||
- **Emergency Cleanup**: 5x `torch.cuda.empty_cache()` with `torch.cuda.synchronize()` between calls
|
||||
- **Memory Safety**: Pre-operation validation with configurable memory requirements
|
||||
- **Fallback Strategy**: Standard → Conservative → Ultra-Conservative parameter reduction
|
||||
- **Error Detection**: Pattern matching for "out of memory" and "oom" error messages
|
||||
- **Memory Fraction**: Conservative 70% maximum memory allocation for stability
|
||||
|
||||
## [1.0.21] - 2024-12-19
|
||||
|
||||
### Fixed
|
||||
- **Memory Calculation Accuracy**: Fixed critical memory calculation errors
|
||||
- **Corrected free memory calculation**: Now uses `reserved_memory` instead of `allocated_memory`
|
||||
- **Added reserved memory monitoring**: Shows both allocated and reserved memory for accurate picture
|
||||
- **Dynamic memory fraction calculation**: Adapts to actual available memory (60-85% for regular, 55-80% for video)
|
||||
- **Consistent memory thresholds**: 4GB for regular KSampler, 3GB for video workflows
|
||||
- **Improved memory cleanup**: Aggressive cleanup with multiple cache clears and garbage collection
|
||||
|
||||
### Improved
|
||||
- **Memory Management**: Enhanced OOM prevention and memory monitoring
|
||||
- Added `get_gpu_memory_info()` helper function for accurate memory reporting
|
||||
- Added `aggressive_memory_cleanup()` helper function for consistent cleanup
|
||||
- Better error handling for memory operations
|
||||
- More accurate memory reporting in debug output
|
||||
|
||||
## [1.0.20] - 2024-12-19
|
||||
|
||||
### Improved
|
||||
- **ROCM KSampler Advanced Performance**: Optimized for video workflows
|
||||
- Added detailed progress indicators to reduce perceived idle time
|
||||
- Optimized memory management for video processing
|
||||
- Added video-specific progress feedback and status messages
|
||||
- Improved fallback handling with better error reporting
|
||||
- Enhanced completion feedback for better user experience
|
||||
|
||||
### Fixed
|
||||
- **Idle Time Issues**: Reduced apparent idle time in video workflows
|
||||
- Added progress indicators during parameter preparation
|
||||
- Better feedback during noise preparation and sampling
|
||||
- Clearer status messages throughout the sampling process
|
||||
|
||||
## [1.0.19] - 2024-12-19
|
||||
|
||||
### Fixed
|
||||
- **Repository Cleanup**: Resolved merge conflicts and cleaned up repository state
|
||||
- Fixed unmerged commits issue that was preventing git operations
|
||||
- Removed all merge conflict markers from source files
|
||||
- Repository now in clean, publishable state
|
||||
|
||||
## [1.0.18] - 2024-12-19
|
||||
|
||||
### Fixed
|
||||
- **Critical VAE Decode Bug**: Fixed tensor dimension mismatch in tiled decoding
|
||||
- Corrected `out_channels` parameter from `vae.latent_channels` (16) to 3 (RGB)
|
||||
- Resolves "The size of tensor a (16) must match the size of tensor b (3)" error
|
||||
- Fixes VAE decode failures when processing images with tiled decoding
|
||||
|
||||
## [1.0.13] - 2024-10-10
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -4,7 +4,69 @@
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://github.com/comfyanonymous/ComfyUI)
|
||||
|
||||
**RocM Ninodes** is a comprehensive custom node collection that provides optimized operations specifically tuned for AMD GPUs with ROCm support, particularly targeting the gfx1151 architecture. This collection includes optimized VAE decode operations and KSampler implementations designed to maximize performance on AMD hardware.
|
||||
**RocM Ninodes** is a comprehensive custom node collection that provides optimized operations specifically tuned for AMD GPUs with ROCm support, particularly targeting the gfx1151 architecture. This collection includes optimized VAE decode operations, KSampler implementations, and LoRA loading designed to maximize performance on AMD hardware with mature ROCm drivers.
|
||||
|
||||
## 🚀 **What We Do**
|
||||
|
||||
RocM Ninodes transforms your AMD GPU experience in ComfyUI by providing:
|
||||
|
||||
- **🎯 ROCm-Optimized Nodes**: Custom implementations of VAE decode, KSampler, and LoRA loading specifically tuned for AMD GPUs
|
||||
- **⚡ Performance Boost**: 15-78% faster generation times with better memory efficiency
|
||||
- **🛡️ Memory Management**: Gentle memory cleanup optimized for mature ROCm drivers
|
||||
- **🔧 Easy Integration**: Drop-in replacements for standard ComfyUI nodes
|
||||
- **📊 Real-Time Monitoring**: Built-in performance tracking and optimization recommendations
|
||||
|
||||
## 🔧 **Quantized Model Support**
|
||||
|
||||
RocM Ninodes now includes comprehensive support for quantized models with automatic detection and optimization:
|
||||
|
||||
### **Supported Quantized Formats**
|
||||
- **FP8 Models**: Hardware-accelerated FP8 quantization (flux1-dev-fp8.safetensors)
|
||||
- **BFloat16**: Native ROCm support with minimal overhead
|
||||
- **INT8/INT4**: GGUF format support for WAN 2.2 models
|
||||
- **Automatic Detection**: Detects quantized models from filename and dtype
|
||||
|
||||
### **Quantization-Specific Features**
|
||||
- **🛡️ Compatibility Mode**: Automatically disables aggressive optimizations for quantized models
|
||||
- **💾 Smart Memory Management**: Quantization-aware memory allocation (FP8: 50% vs FP32, INT8: 25% vs FP32)
|
||||
- **🔒 Dtype Preservation**: Prevents forced dtype conversions that break quantized models
|
||||
- **📹 Adaptive Video Processing**: Smaller chunk sizes and tile sizes for quantized models
|
||||
- **⚡ OOM Prevention**: Lower default settings to prevent out-of-memory errors
|
||||
|
||||
### **User-Reported Issue Fixes**
|
||||
- **Fixed OOM Errors**: Lower default tile_size (512 vs 768) for better compatibility
|
||||
- **Fixed Quantized Model Breaking**: Disabled batch optimization by default for quantized models
|
||||
- **Fixed Memory Management**: Less aggressive cleanup for quantized models
|
||||
- **Fixed Video Processing**: Adaptive chunk sizing based on frame count and available memory
|
||||
|
||||
### **Recommended Settings for Quantized Models**
|
||||
- **Compatibility Mode**: Enable for quantized models
|
||||
- **Tile Size**: Use 512 (conservative) instead of 768
|
||||
- **Video Chunk Size**: Use 2-4 frames instead of 8
|
||||
- **Batch Optimization**: Disable for quantized models
|
||||
- **Memory Optimization**: Disable aggressive cleanup
|
||||
|
||||
|
||||
|
||||
Our optimization approach focuses on three key areas:
|
||||
|
||||
### **1. ROCm-Specific Optimizations**
|
||||
- **Environment Variables**: Essential `TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1` for mature drivers
|
||||
- **Memory Allocation**: Optimized settings (256MB chunks, 0.8 threshold) for better fragmentation control
|
||||
- **Precision Handling**: Automatic selection of optimal precision for AMD hardware
|
||||
- **Attention Mechanisms**: ROCm-tuned attention optimizations for better performance
|
||||
|
||||
### **2. Gentle Memory Management**
|
||||
- **Single-Pass Cleanup**: Efficient memory clearing without performance penalties
|
||||
- **Smart Monitoring**: Real-time memory usage tracking and optimization
|
||||
- **Fragmentation Control**: Proactive memory management to prevent OOM errors
|
||||
- **Mature Driver Support**: Optimized for current ROCm drivers and libraries
|
||||
|
||||
### **3. Hardware-Specific Tuning**
|
||||
- **gfx1151 Architecture**: Specifically optimized for AMD Radeon 8060S and similar GPUs
|
||||
- **Unified Memory**: Leverages AMD's unified memory architecture for better performance
|
||||
- **Conservative Batching**: Smart batching strategies optimized for AMD GPU characteristics
|
||||
- **Tile Size Optimization**: Optimal tile sizes (768-1024) for gfx1151 memory bandwidth
|
||||
|
||||
## 🎯 **Real-World Performance Results**
|
||||
|
||||
@@ -15,9 +77,10 @@
|
||||
|
||||
#### **🎬 Image-to-Video Generation (WAN 2.2 i2v)**
|
||||
- **320x320px, 2s**: **163s → 139s** (15% improvement!)
|
||||
- **320x320px, 17 frames**: **98.33s → 92.78s** (5.6% improvement!) **NEW!**
|
||||
- **320x320px, 17 frames**: **98.33s → 92.78s** (5.6% improvement!)
|
||||
- **480x480px, 2s**: **202s** (33 frames, 16fps) ✅
|
||||
- **480x720px, 2s**: **303s** (33 frames, 16fps) ✅
|
||||
- **Video Quality**: Fixed darker frames at chunk boundaries (v1.0.29) ✅
|
||||
|
||||
#### **📊 Performance Metrics**
|
||||
- **Memory efficiency**: 50% reduction in attention memory requirements
|
||||
@@ -750,6 +813,41 @@ git clone https://github.com/iGavroche/rocm-ninodes.git ComfyUI-ROCM-Optimized-V
|
||||
- **Precision**: fp32 for best ROCm performance
|
||||
- **Batching**: AMD-optimized batch sizes
|
||||
|
||||
## Testing
|
||||
|
||||
ROCM Ninodes includes a comprehensive test suite to ensure performance and correctness.
|
||||
|
||||
### Quick Start
|
||||
```bash
|
||||
cd tests
|
||||
./run_tests.sh
|
||||
```
|
||||
|
||||
### Test Categories
|
||||
- **Performance Tests**: Validate timing targets (78% Flux improvement, 5.6% WAN improvement)
|
||||
- **Correctness Tests**: Verify tensor shapes and data formats
|
||||
- **Integration Tests**: Full ComfyUI workflow testing
|
||||
- **Mock Data Tests**: Tests using synthetic data when real data unavailable
|
||||
|
||||
### Data Capture
|
||||
Enable debug mode to capture real workflow data for testing:
|
||||
```bash
|
||||
export ROCM_NINODES_DEBUG=1
|
||||
# Run your ComfyUI workflows
|
||||
# Data will be saved to test_data/captured/
|
||||
```
|
||||
|
||||
### Performance Targets
|
||||
- **Flux Checkpoint Load**: <30s
|
||||
- **Flux VAE Decode**: <10s
|
||||
- **WAN Sampling**: <100s
|
||||
- **WAN VAE Decode**: <10s
|
||||
|
||||
### Documentation
|
||||
- **[Testing Guide](TESTING_GUIDE.md)**: Comprehensive testing documentation
|
||||
- **[Architecture](ARCHITECTURE.md)**: System architecture and constraints
|
||||
- **[Rules](RULES.md)**: Development rules and best practices
|
||||
|
||||
## Contributing
|
||||
|
||||
Feel free to submit issues or pull requests to improve the optimizations for your specific use case.
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
# ROCM Ninodes - Ready for ComfyUI Testing
|
||||
|
||||
## 🎉 Project Complete - All Optimization Phases Implemented
|
||||
|
||||
The ROCM Ninodes optimization project is now complete and ready for ComfyUI testing!
|
||||
|
||||
### 📊 **Performance Achievements**
|
||||
- **Phase 1**: 29.1% improvement (Memory Management & Tile Optimization)
|
||||
- **Phase 2**: 30.0% additional improvement (Mixed Precision & Batch Processing)
|
||||
- **Phase 3**: 30.0% additional improvement (Video Processing & Advanced Features)
|
||||
- **Total**: **65.2% overall improvement** ✅ **EXCEEDED ALL TARGETS**
|
||||
|
||||
### 🚀 **Available Nodes in ComfyUI**
|
||||
|
||||
#### **ROCMOptimizedVAEDecode (Phase 1)**
|
||||
- Memory pooling and adaptive tiling
|
||||
- Optimized tile size selection for gfx1151
|
||||
- Smart caching for intermediate results
|
||||
- **29.1% improvement over baseline**
|
||||
|
||||
#### **ROCMOptimizedVAEDecodeV2 (Phase 2)**
|
||||
- Smart mixed precision strategies (fp16/fp32)
|
||||
- Advanced batch processing for AMD GPUs
|
||||
- Memory prefetching and layout optimization
|
||||
- **30.0% additional improvement over Phase 1**
|
||||
|
||||
#### **ROCMOptimizedVAEDecodeV3 (Phase 3)**
|
||||
- Video processing with temporal consistency
|
||||
- Optimal video chunk sizes (4-16 frames)
|
||||
- Real-time performance monitoring
|
||||
- Adaptive optimization based on usage patterns
|
||||
- **30.0% additional improvement over Phase 2**
|
||||
|
||||
### 🧪 **Testing Workflows Available**
|
||||
|
||||
Located in `comfyui_workflows/`:
|
||||
1. **`basic_image.json`** - Basic image processing with Phase 3
|
||||
2. **`video_processing.json`** - Video processing with temporal consistency
|
||||
3. **`batch_processing.json`** - Batch processing optimizations
|
||||
4. **`performance_comparison.json`** - Side-by-side comparison of all phases
|
||||
|
||||
### 🔧 **How to Test in ComfyUI**
|
||||
|
||||
1. **Load the nodes**: The `nodes.py` file contains all three optimization phases
|
||||
2. **Choose a workflow**: Load any JSON file from `comfyui_workflows/`
|
||||
3. **Test performance**: Compare execution times between phases
|
||||
4. **Validate quality**: Ensure output quality is maintained
|
||||
5. **Check statistics**: Use the performance monitoring features
|
||||
|
||||
### 📈 **Expected Results**
|
||||
|
||||
- **Phase 1**: ~29% faster than standard VAE decode
|
||||
- **Phase 2**: ~30% faster than Phase 1
|
||||
- **Phase 3**: ~30% faster than Phase 2
|
||||
- **Total**: ~65% faster than baseline
|
||||
|
||||
### 🎯 **Architecture Optimizations**
|
||||
|
||||
All nodes are specifically optimized for:
|
||||
- **AMD gfx1151 GPU architecture**
|
||||
- **ROCm 6.4+ software stack**
|
||||
- **Conservative memory management**
|
||||
- **Optimal tile sizes (768-1024)**
|
||||
- **Mixed precision strategies**
|
||||
|
||||
### 📚 **Documentation**
|
||||
|
||||
- **`Benchmarks.md`** - Comprehensive performance documentation
|
||||
- **`comfyui_workflows/comprehensive_test_info.json`** - Detailed testing information
|
||||
- **Performance statistics** - Available through node monitoring features
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **Ready to Test!**
|
||||
|
||||
The optimization branch is fully prepared with all necessary context, tools, and infrastructure. All phases have been successfully implemented, tested, and documented. You can now test the workflows in ComfyUI to validate the real-world performance improvements!
|
||||
|
||||
**Total improvement achieved: 65.2% over baseline** 🎉
|
||||
@@ -0,0 +1,220 @@
|
||||
# ROCM Ninodes Development Rules
|
||||
|
||||
## Testing Rules
|
||||
|
||||
### Mandatory Testing
|
||||
- **All Changes**: Every code change must have corresponding tests
|
||||
- **Performance Regression**: No change can regress performance by >5%
|
||||
- **Coverage**: Critical paths must have >90% test coverage
|
||||
- **Validation**: All tests must pass before merging
|
||||
|
||||
### Test Categories
|
||||
1. **Unit Tests**: Individual node functionality
|
||||
2. **Performance Tests**: Timing and memory usage
|
||||
3. **Integration Tests**: Full workflow execution
|
||||
4. **Regression Tests**: Prevent performance degradation
|
||||
|
||||
### Test Execution
|
||||
- **Standalone**: Tests must run without ComfyUI when possible
|
||||
- **Data-Driven**: Use captured pickle data for consistent testing
|
||||
- **Automated**: CI/CD pipeline integration required
|
||||
- **Documentation**: All test failures must be documented
|
||||
|
||||
## Benchmarking Rules
|
||||
|
||||
### Baseline Requirements
|
||||
- **Before Optimization**: Measure baseline performance first
|
||||
- **A/B Testing**: Compare before/after for every change
|
||||
- **Multiple Runs**: Minimum 3 runs for statistical significance
|
||||
- **Documentation**: Record all benchmark results
|
||||
|
||||
### Performance Targets
|
||||
- **Flux 1024x1024**: ≥78% improvement over baseline
|
||||
- **WAN 320x320 17-frame**: ≥5.6% improvement over baseline
|
||||
- **VAE Decode**: <10s for any supported resolution
|
||||
- **Checkpoint Load**: <30s for Flux models
|
||||
|
||||
### Benchmark Process
|
||||
1. **Capture Data**: Use `ROCM_NINODES_DEBUG=1` to capture test data
|
||||
2. **Run Baseline**: Measure current performance
|
||||
3. **Implement Change**: Make optimization
|
||||
4. **Run Comparison**: Measure new performance
|
||||
5. **Validate**: Ensure improvement meets targets
|
||||
6. **Document**: Record results and methodology
|
||||
|
||||
## Architecture Constraints
|
||||
|
||||
### gfx1151 Specific
|
||||
- **Precision**: fp32 preferred over fp16 for stability
|
||||
- **Tile Sizes**: 768-1024 optimal range
|
||||
- **TF32**: Disabled (not beneficial on gfx1151)
|
||||
- **Memory**: Conservative allocation (1.5x modifier for attention)
|
||||
- **Batch Size**: Conservative to prevent OOM
|
||||
|
||||
### Memory Management
|
||||
- **Allocation**: Conservative approach to prevent OOM
|
||||
- **Cleanup**: Regular `torch.cuda.empty_cache()` calls
|
||||
- **Monitoring**: Track memory usage in all operations
|
||||
- **Limits**: Respect 128GB unified RAM constraint
|
||||
|
||||
### Data Formats
|
||||
- **Video**: 5D tensor input `[B, C, T, H, W]` → 4D output `[B*T, H, W, C]`
|
||||
- **Images**: Standard 4D tensor `[B, H, W, C]`
|
||||
- **Latents**: ComfyUI format `{"samples": tensor}`
|
||||
- **Precision**: Maintain dtype consistency throughout pipeline
|
||||
|
||||
## Vision and Goals
|
||||
|
||||
### Performance Targets
|
||||
- **Flux Workflow**: 78% improvement (≤110s total time)
|
||||
- **WAN Workflow**: 5.6% improvement (≤93s sampling time)
|
||||
- **VAE Decode**: <10s for any resolution
|
||||
- **Memory Usage**: <80% of available RAM
|
||||
|
||||
### Quality Standards
|
||||
- **Reliability**: Zero crashes during normal operation
|
||||
- **Consistency**: Reproducible results across runs
|
||||
- **Compatibility**: Works with standard ComfyUI workflows
|
||||
- **Maintainability**: Clean, documented, testable code
|
||||
|
||||
### User Experience
|
||||
- **Ease of Use**: Drop-in replacement for standard nodes
|
||||
- **Performance**: Noticeable speedup without configuration
|
||||
- **Debugging**: Clear error messages and logging
|
||||
- **Documentation**: Comprehensive guides and examples
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Code Quality
|
||||
- **Style**: Follow PEP 8 and project conventions
|
||||
- **Documentation**: Docstrings for all public methods
|
||||
- **Type Hints**: Use type annotations where possible
|
||||
- **Error Handling**: Comprehensive exception handling
|
||||
|
||||
### Performance
|
||||
- **Profiling**: Profile before and after optimizations
|
||||
- **Memory**: Monitor memory usage in all operations
|
||||
- **Caching**: Use appropriate caching strategies
|
||||
- **Batching**: Optimize batch sizes for gfx1151
|
||||
|
||||
### Testing
|
||||
- **Isolation**: Tests should not depend on external state
|
||||
- **Data**: Use captured real data for testing
|
||||
- **Coverage**: Test edge cases and error conditions
|
||||
- **Automation**: Integrate with CI/CD pipeline
|
||||
|
||||
### Debugging
|
||||
- **Logging**: Use appropriate log levels
|
||||
- **Debug Mode**: `ROCM_NINODES_DEBUG=1` for data capture
|
||||
- **Error Messages**: Clear, actionable error messages
|
||||
- **Documentation**: Document known issues and workarounds
|
||||
|
||||
## Research Process
|
||||
|
||||
### Methodology
|
||||
1. **Measure First**: Establish baseline performance
|
||||
2. **Optimize Second**: Implement targeted improvements
|
||||
3. **Validate Third**: Ensure improvements are real and stable
|
||||
|
||||
### Optimization Strategy
|
||||
- **Targeted**: Focus on identified bottlenecks
|
||||
- **Incremental**: Small, testable changes
|
||||
- **Measurable**: Quantify all improvements
|
||||
- **Reversible**: Changes must be easily revertible
|
||||
|
||||
### Documentation
|
||||
- **Process**: Document optimization methodology
|
||||
- **Results**: Record all performance measurements
|
||||
- **Decisions**: Explain optimization choices
|
||||
- **Lessons**: Capture learnings for future work
|
||||
|
||||
## Quality Gates
|
||||
|
||||
### Pre-commit
|
||||
- **Linting**: Code must pass all linting checks
|
||||
- **Tests**: All tests must pass
|
||||
- **Performance**: No performance regression
|
||||
- **Documentation**: Updated documentation required
|
||||
|
||||
### Pre-merge
|
||||
- **Integration**: Full integration test suite passes
|
||||
- **Performance**: Meets performance targets
|
||||
- **Compatibility**: Works with existing workflows
|
||||
- **Review**: Code review approval required
|
||||
|
||||
### Release
|
||||
- **Stability**: No known critical issues
|
||||
- **Performance**: Meets all performance targets
|
||||
- **Documentation**: Complete user documentation
|
||||
- **Testing**: Full test suite passes
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Error Categories
|
||||
1. **Critical**: System crashes or data corruption
|
||||
2. **Warning**: Performance degradation or unexpected behavior
|
||||
3. **Info**: Normal operation with additional information
|
||||
|
||||
### Response Strategy
|
||||
- **Critical**: Immediate fix required, no release until resolved
|
||||
- **Warning**: Fix in next release cycle
|
||||
- **Info**: Document and monitor
|
||||
|
||||
### Logging
|
||||
- **Levels**: Use appropriate log levels
|
||||
- **Context**: Include relevant context in error messages
|
||||
- **Stack Traces**: Include full stack traces for debugging
|
||||
- **User Action**: Provide actionable next steps
|
||||
|
||||
## Memory Management
|
||||
|
||||
### Allocation Strategy
|
||||
- **Conservative**: Prefer under-allocation to over-allocation
|
||||
- **Monitoring**: Track memory usage in all operations
|
||||
- **Cleanup**: Explicit cleanup after operations
|
||||
- **Limits**: Respect system memory constraints
|
||||
|
||||
### Optimization
|
||||
- **Reuse**: Reuse memory where possible
|
||||
- **Batching**: Optimize batch sizes for memory efficiency
|
||||
- **Tiling**: Use tiled processing for large operations
|
||||
- **Streaming**: Process data in streams when possible
|
||||
|
||||
## Performance Monitoring
|
||||
|
||||
### Metrics
|
||||
- **Timing**: Measure execution time for all operations
|
||||
- **Memory**: Track memory usage and allocation
|
||||
- **Throughput**: Measure data processing rates
|
||||
- **Quality**: Validate output correctness
|
||||
|
||||
### Tools
|
||||
- **Profiling**: Use PyTorch profiler for detailed analysis
|
||||
- **Monitoring**: Real-time performance monitoring
|
||||
- **Logging**: Structured logging for analysis
|
||||
- **Visualization**: Performance dashboards and reports
|
||||
|
||||
## ComfyUI Process Management
|
||||
|
||||
### Process Control Scripts
|
||||
- **Kill Script**: `./kill_comfyui.sh` - Properly terminates ComfyUI processes
|
||||
- **Start Script**: `/home/nino/ComfyUI/start.sh` - Existing script with ROCm optimizations
|
||||
- **Restart Script**: `./restart_comfyui.sh` - Combines kill and start for clean restarts
|
||||
|
||||
### Process Management Rules
|
||||
- **NEVER use `pkill -f "python main.py"`** - This method does not work reliably
|
||||
- **ALWAYS use `ps aux | grep python | grep -v grep`** to find actual process IDs
|
||||
- **THEN use `kill -9 <PID>`** to forcefully terminate processes
|
||||
- **VERIFY termination** with another `ps aux` check before restarting
|
||||
|
||||
### Debug Mode Control
|
||||
- **Production Mode**: Use existing `/home/nino/ComfyUI/start.sh` (no debug overhead)
|
||||
- **Debug Mode**: Set `ROCM_NINODES_DEBUG=1` environment variable before starting
|
||||
- **Code Changes**: Always restart ComfyUI after code changes to ensure they're loaded
|
||||
|
||||
### Workflow Integration
|
||||
1. **Before Code Changes**: Run `./kill_comfyui.sh` to stop ComfyUI
|
||||
2. **Make Changes**: Edit code files
|
||||
3. **After Code Changes**: Use `/home/nino/ComfyUI/start.sh` to restart
|
||||
4. **For Debug**: Set `ROCM_NINODES_DEBUG=1` and restart
|
||||
5. **Verify**: Check that changes are loaded and working
|
||||
-355
@@ -1,355 +0,0 @@
|
||||
# ROCM Ninodes Testing Framework
|
||||
|
||||
This document describes the comprehensive testing framework for ROCM Ninodes, including unit tests, integration tests, benchmarks, and data collection.
|
||||
|
||||
## Overview
|
||||
|
||||
The testing framework is designed to:
|
||||
- Validate node functionality across different configurations
|
||||
- Measure performance improvements on AMD GPUs
|
||||
- Collect real-world data from Flux and WAN workflows
|
||||
- Optimize nodes based on test results
|
||||
- Ensure reliability and prevent regressions
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── unit/ # Unit tests for individual nodes
|
||||
│ ├── test_vae_decode.py
|
||||
│ ├── test_ksampler.py
|
||||
│ ├── test_checkpoint_loader.py
|
||||
│ ├── test_vae_error_scenarios.py
|
||||
│ └── test_vae_optimization.py
|
||||
├── integration/ # Integration tests for workflows
|
||||
│ └── test_workflows.py
|
||||
├── benchmarks/ # Performance benchmark tests
|
||||
│ └── test_performance.py
|
||||
├── fixtures/ # Test fixtures and data
|
||||
├── conftest.py # Pytest configuration and fixtures
|
||||
├── run_tests.py # Test runner script
|
||||
└── __init__.py
|
||||
|
||||
test_data/ # Collected test data
|
||||
├── inputs/ # Node input data
|
||||
├── outputs/ # Node output data
|
||||
├── benchmarks/ # Performance metrics
|
||||
├── optimization/ # Optimization data
|
||||
└── debug/ # Debug data
|
||||
|
||||
instrumentation.py # Data collection instrumentation
|
||||
collect_test_data.py # Data collection script
|
||||
pytest.ini # Pytest configuration
|
||||
```
|
||||
|
||||
## Test Types
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Unit tests validate individual node functionality:
|
||||
|
||||
- **test_vae_decode.py**: Tests for ROCMOptimizedVAEDecode and ROCMOptimizedVAEDecodeTiled
|
||||
- **test_ksampler.py**: Tests for ROCMOptimizedKSampler and ROCMOptimizedKSamplerAdvanced
|
||||
- **test_checkpoint_loader.py**: Tests for ROCMOptimizedCheckpointLoader
|
||||
- **test_vae_error_scenarios.py**: Error handling and edge cases
|
||||
- **test_vae_optimization.py**: Optimization-specific tests
|
||||
|
||||
### Integration Tests
|
||||
|
||||
Integration tests validate complete workflows:
|
||||
|
||||
- **test_workflows.py**: Tests for Flux and WAN workflows
|
||||
- Validates end-to-end functionality
|
||||
- Tests node interactions
|
||||
- Validates workflow compatibility
|
||||
|
||||
### Benchmark Tests
|
||||
|
||||
Benchmark tests measure performance:
|
||||
|
||||
- **test_performance.py**: Performance benchmarks
|
||||
- Measures execution time and memory usage
|
||||
- Compares different configurations
|
||||
- Tracks performance improvements
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Install required dependencies:
|
||||
|
||||
```bash
|
||||
pip install pytest pytest-mock torch numpy
|
||||
```
|
||||
|
||||
### Run All Tests
|
||||
|
||||
```bash
|
||||
python tests/run_tests.py
|
||||
```
|
||||
|
||||
### Run Specific Test Types
|
||||
|
||||
```bash
|
||||
# Unit tests only
|
||||
python tests/run_tests.py unit
|
||||
|
||||
# Integration tests only
|
||||
python tests/run_tests.py integration
|
||||
|
||||
# Benchmark tests only
|
||||
python tests/run_tests.py benchmark
|
||||
```
|
||||
|
||||
### Run Individual Test Files
|
||||
|
||||
```bash
|
||||
# Run specific test file
|
||||
pytest tests/unit/test_vae_decode.py -v
|
||||
|
||||
# Run with specific markers
|
||||
pytest -m "benchmark" -v
|
||||
pytest -m "integration" -v
|
||||
pytest -m "requires_gpu" -v
|
||||
```
|
||||
|
||||
## Data Collection
|
||||
|
||||
### Collect Test Data
|
||||
|
||||
Run the data collection script to gather test data from workflows:
|
||||
|
||||
```bash
|
||||
python collect_test_data.py
|
||||
```
|
||||
|
||||
This will:
|
||||
- Instrument all nodes for data collection
|
||||
- Run Flux and WAN workflows
|
||||
- Collect inputs, outputs, and performance metrics
|
||||
- Generate optimization data
|
||||
|
||||
### Data Collection Features
|
||||
|
||||
- **Automatic Instrumentation**: All nodes are automatically instrumented
|
||||
- **Workflow Testing**: Tests both Flux and WAN workflows
|
||||
- **Performance Profiling**: Collects execution time and memory usage
|
||||
- **Configuration Testing**: Tests different precision modes and settings
|
||||
- **Error Handling**: Captures data even when errors occur
|
||||
|
||||
## Test Configuration
|
||||
|
||||
### Pytest Configuration
|
||||
|
||||
The `pytest.ini` file contains:
|
||||
- Test discovery patterns
|
||||
- Output formatting
|
||||
- Markers for different test types
|
||||
- Timeout settings
|
||||
- Warning filters
|
||||
|
||||
### Test Markers
|
||||
|
||||
- `@pytest.mark.unit`: Unit tests
|
||||
- `@pytest.mark.integration`: Integration tests
|
||||
- `@pytest.mark.benchmark`: Performance tests
|
||||
- `@pytest.mark.slow`: Slow tests
|
||||
- `@pytest.mark.requires_gpu`: Tests requiring GPU
|
||||
- `@pytest.mark.requires_amd`: Tests requiring AMD GPU
|
||||
|
||||
## Instrumentation
|
||||
|
||||
### Node Instrumentation
|
||||
|
||||
The `instrumentation.py` module provides:
|
||||
- **Input Capture**: Saves all node inputs
|
||||
- **Output Capture**: Saves all node outputs
|
||||
- **Performance Metrics**: Tracks execution time and memory usage
|
||||
- **Data Serialization**: Converts tensors to serializable format
|
||||
- **Test Fixtures**: Generates test fixtures from collected data
|
||||
|
||||
### Data Collection Process
|
||||
|
||||
1. **Instrument Nodes**: Automatically instrument all ROCM nodes
|
||||
2. **Run Workflows**: Execute Flux and WAN workflows
|
||||
3. **Capture Data**: Save inputs, outputs, and performance metrics
|
||||
4. **Generate Fixtures**: Create test fixtures from collected data
|
||||
5. **Optimize**: Use data for node optimization
|
||||
|
||||
## Performance Testing
|
||||
|
||||
### Benchmark Categories
|
||||
|
||||
1. **Resolution Tests**: Different image/video resolutions
|
||||
2. **Precision Tests**: Different precision modes (fp32, fp16, bf16)
|
||||
3. **Configuration Tests**: Different tile sizes and settings
|
||||
4. **Memory Tests**: Memory usage and optimization
|
||||
5. **Workflow Tests**: Complete workflow performance
|
||||
|
||||
### Performance Metrics
|
||||
|
||||
- **Execution Time**: Time to complete operations
|
||||
- **Memory Usage**: Peak and average memory consumption
|
||||
- **Throughput**: Operations per second
|
||||
- **Efficiency**: Memory efficiency ratios
|
||||
- **Improvement**: Performance improvements over baseline
|
||||
|
||||
## Optimization
|
||||
|
||||
### Data-Driven Optimization
|
||||
|
||||
The testing framework enables data-driven optimization:
|
||||
|
||||
1. **Collect Data**: Run workflows and collect performance data
|
||||
2. **Analyze Results**: Identify bottlenecks and optimization opportunities
|
||||
3. **Implement Changes**: Optimize nodes based on findings
|
||||
4. **Validate Improvements**: Re-run tests to verify improvements
|
||||
5. **Track Progress**: Monitor performance over time
|
||||
|
||||
### Optimization Targets
|
||||
|
||||
- **Memory Usage**: Reduce VRAM consumption
|
||||
- **Execution Time**: Improve processing speed
|
||||
- **Stability**: Reduce errors and crashes
|
||||
- **Compatibility**: Ensure cross-platform compatibility
|
||||
- **Scalability**: Support larger images and videos
|
||||
|
||||
## Test Reports
|
||||
|
||||
### Generated Reports
|
||||
|
||||
The test runner generates comprehensive reports:
|
||||
|
||||
- **Test Summary**: Pass/fail statistics
|
||||
- **Performance Metrics**: Execution times and memory usage
|
||||
- **Device Information**: GPU and system details
|
||||
- **Recommendations**: Optimization suggestions
|
||||
- **Error Analysis**: Failed test details
|
||||
|
||||
### Report Location
|
||||
|
||||
Reports are saved to `test_data/test_report.json` and include:
|
||||
- Test results for all test types
|
||||
- Performance benchmarks
|
||||
- Device compatibility information
|
||||
- Optimization recommendations
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
### Automated Testing
|
||||
|
||||
The testing framework supports CI/CD:
|
||||
|
||||
```yaml
|
||||
# Example GitHub Actions workflow
|
||||
name: ROCM Ninodes Tests
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt
|
||||
- name: Run tests
|
||||
run: python tests/run_tests.py
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
|
||||
- **Unit Tests**: 100% node coverage
|
||||
- **Integration Tests**: Complete workflow coverage
|
||||
- **Benchmark Tests**: Performance validation
|
||||
- **Error Tests**: Edge case coverage
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Import Errors**: Ensure all dependencies are installed
|
||||
2. **CUDA Errors**: Check GPU availability and drivers
|
||||
3. **Memory Errors**: Reduce batch sizes or use CPU fallback
|
||||
4. **Timeout Errors**: Increase timeout values for slow tests
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Run tests in debug mode for detailed output:
|
||||
|
||||
```bash
|
||||
pytest -v -s --tb=long
|
||||
```
|
||||
|
||||
### Test Data Issues
|
||||
|
||||
If test data is missing:
|
||||
1. Run `python collect_test_data.py` to collect data
|
||||
2. Check that workflow files exist
|
||||
3. Verify node instrumentation is working
|
||||
|
||||
## Contributing
|
||||
|
||||
### Adding New Tests
|
||||
|
||||
1. Create test file in appropriate directory
|
||||
2. Follow naming convention: `test_*.py`
|
||||
3. Use appropriate markers
|
||||
4. Include docstrings and comments
|
||||
5. Test both success and failure cases
|
||||
|
||||
### Test Guidelines
|
||||
|
||||
- **Isolation**: Tests should be independent
|
||||
- **Deterministic**: Tests should produce consistent results
|
||||
- **Fast**: Unit tests should be fast
|
||||
- **Clear**: Test names should be descriptive
|
||||
- **Comprehensive**: Cover edge cases and error conditions
|
||||
|
||||
## Performance Monitoring
|
||||
|
||||
### Benchmark Tracking
|
||||
|
||||
The framework tracks performance over time:
|
||||
- Historical performance data
|
||||
- Regression detection
|
||||
- Improvement measurement
|
||||
- Device-specific optimization
|
||||
|
||||
### Optimization Metrics
|
||||
|
||||
Key metrics for optimization:
|
||||
- **Speed**: Execution time improvements
|
||||
- **Memory**: VRAM usage reduction
|
||||
- **Stability**: Error rate reduction
|
||||
- **Compatibility**: Cross-platform support
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
|
||||
1. **Automated Optimization**: AI-driven node optimization
|
||||
2. **Real-time Monitoring**: Live performance tracking
|
||||
3. **A/B Testing**: Compare different implementations
|
||||
4. **Regression Testing**: Automatic performance regression detection
|
||||
5. **Cloud Testing**: Test on different hardware configurations
|
||||
|
||||
### Extensibility
|
||||
|
||||
The framework is designed to be extensible:
|
||||
- Easy to add new test types
|
||||
- Pluggable instrumentation
|
||||
- Configurable test parameters
|
||||
- Custom performance metrics
|
||||
|
||||
## Support
|
||||
|
||||
For issues with the testing framework:
|
||||
1. Check the troubleshooting section
|
||||
2. Review test logs and reports
|
||||
3. Create an issue with detailed information
|
||||
4. Include test data and error messages
|
||||
|
||||
The testing framework is essential for maintaining code quality and performance in ROCM Ninodes. Regular testing ensures reliability and helps identify optimization opportunities.
|
||||
+5
-2
@@ -10,9 +10,12 @@ __description__ = "RocM-optimized ComfyUI nodes for AMD GPU performance"
|
||||
|
||||
# Import nodes only if comfy modules are available
|
||||
try:
|
||||
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
except ImportError:
|
||||
from .rocm_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
except ImportError as e:
|
||||
# Fallback for testing environments
|
||||
print(f"[ROCM Ninodes] Import error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "rocm-ninodes",
|
||||
"display_name": "RocM Ninodes",
|
||||
"description": "ROCM Optimized Nodes for ComfyUI - High-performance VAE decode and sampling nodes specifically tuned for AMD GPUs with ROCm support",
|
||||
"version": "1.0.12",
|
||||
"version": "1.0.30",
|
||||
"author": "iGavroche",
|
||||
"author_email": "nino2k@proton.me",
|
||||
"repository": "https://github.com/iGavroche/rocm-ninodes",
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"workflow_info": {
|
||||
"name": "ROCM Ninodes Comprehensive Test",
|
||||
"description": "Comprehensive testing of all ROCM Ninodes optimization phases",
|
||||
"version": "1.0",
|
||||
"created": "2025-01-11",
|
||||
"optimization_phases": [
|
||||
"Phase 1: Memory Management & Tile Optimization (29.1% improvement)",
|
||||
"Phase 2: Mixed Precision, Batch Processing & Advanced Memory (30.0% improvement)",
|
||||
"Phase 3: Video Processing & Advanced Performance Features (30.0% improvement)"
|
||||
],
|
||||
"total_improvement": "65.2% over baseline"
|
||||
},
|
||||
"test_cases": [
|
||||
{
|
||||
"name": "Basic Image Processing",
|
||||
"description": "Test basic image decode with Phase 3 optimizations",
|
||||
"workflow": "basic_image.json"
|
||||
},
|
||||
{
|
||||
"name": "Video Processing",
|
||||
"description": "Test video decode with temporal consistency",
|
||||
"workflow": "video_processing.json"
|
||||
},
|
||||
{
|
||||
"name": "Batch Processing",
|
||||
"description": "Test batch processing optimizations",
|
||||
"workflow": "batch_processing.json"
|
||||
},
|
||||
{
|
||||
"name": "Performance Comparison",
|
||||
"description": "Compare all optimization phases side by side",
|
||||
"workflow": "performance_comparison.json"
|
||||
}
|
||||
],
|
||||
"usage_instructions": [
|
||||
"1. Load any workflow JSON file into ComfyUI",
|
||||
"2. Ensure ROCM Ninodes nodes are installed and available",
|
||||
"3. Run the workflow to test the optimizations",
|
||||
"4. Compare performance and output quality",
|
||||
"5. Check performance statistics using the monitoring features"
|
||||
],
|
||||
"expected_results": {
|
||||
"phase1": "29.1% improvement over baseline",
|
||||
"phase2": "30.0% additional improvement over Phase 1",
|
||||
"phase3": "30.0% additional improvement over Phase 2",
|
||||
"total": "65.2% overall improvement"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
"""
|
||||
DISABLED INSTRUMENTATION - EMERGENCY PERFORMANCE FIX
|
||||
The instrumentation system was causing massive performance overhead
|
||||
"""
|
||||
|
||||
def instrument_node(node_class):
|
||||
"""No-op decorator - instrumentation disabled for performance"""
|
||||
return node_class
|
||||
|
||||
# Create minimal instrumentation object
|
||||
class NodeInstrumentation:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def capture_inputs(self, *args, **kwargs):
|
||||
return ""
|
||||
|
||||
def capture_outputs(self, *args, **kwargs):
|
||||
return ""
|
||||
|
||||
def capture_performance(self, *args, **kwargs):
|
||||
return ""
|
||||
|
||||
# Create global instrumentation object
|
||||
instrumentation = NodeInstrumentation()
|
||||
@@ -1,325 +0,0 @@
|
||||
"""
|
||||
Instrumentation module for ROCM Ninodes testing and optimization
|
||||
Captures inputs, outputs, and performance metrics for all nodes
|
||||
"""
|
||||
|
||||
import os
|
||||
import pickle
|
||||
import time
|
||||
import json
|
||||
import torch
|
||||
import numpy as np
|
||||
from typing import Any, Dict, List, Tuple, Optional
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
class NodeInstrumentation:
|
||||
"""
|
||||
Instrumentation class for capturing node data and performance metrics
|
||||
"""
|
||||
|
||||
def __init__(self, test_data_dir: str = "test_data"):
|
||||
self.test_data_dir = test_data_dir
|
||||
self.session_id = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
self.captured_data = {}
|
||||
self.performance_metrics = {}
|
||||
|
||||
# Create directories
|
||||
os.makedirs(f"{test_data_dir}/inputs", exist_ok=True)
|
||||
os.makedirs(f"{test_data_dir}/outputs", exist_ok=True)
|
||||
os.makedirs(f"{test_data_dir}/benchmarks", exist_ok=True)
|
||||
os.makedirs(f"{test_data_dir}/optimization", exist_ok=True)
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def capture_inputs(self, node_name: str, inputs: Dict[str, Any],
|
||||
node_id: str = None) -> str:
|
||||
"""
|
||||
Capture and save node inputs for testing
|
||||
"""
|
||||
timestamp = int(time.time() * 1000)
|
||||
node_id = node_id or f"{node_name}_{timestamp}"
|
||||
|
||||
# Prepare input data for serialization
|
||||
serializable_inputs = self._prepare_for_serialization(inputs)
|
||||
|
||||
# Save to file
|
||||
filename = f"{self.test_data_dir}/inputs/{node_name}_{node_id}_{timestamp}.pkl"
|
||||
with open(filename, 'wb') as f:
|
||||
pickle.dump({
|
||||
'node_name': node_name,
|
||||
'node_id': node_id,
|
||||
'timestamp': timestamp,
|
||||
'inputs': serializable_inputs,
|
||||
'session_id': self.session_id
|
||||
}, f)
|
||||
|
||||
self.logger.info(f"Captured inputs for {node_name} -> {filename}")
|
||||
return filename
|
||||
|
||||
def capture_outputs(self, node_name: str, outputs: Tuple[Any, ...],
|
||||
node_id: str = None) -> str:
|
||||
"""
|
||||
Capture and save node outputs for testing
|
||||
"""
|
||||
timestamp = int(time.time() * 1000)
|
||||
node_id = node_id or f"{node_name}_{timestamp}"
|
||||
|
||||
# Prepare output data for serialization
|
||||
serializable_outputs = self._prepare_for_serialization(outputs)
|
||||
|
||||
# Save to file
|
||||
filename = f"{self.test_data_dir}/outputs/{node_name}_{node_id}_{timestamp}.pkl"
|
||||
with open(filename, 'wb') as f:
|
||||
pickle.dump({
|
||||
'node_name': node_name,
|
||||
'node_id': node_id,
|
||||
'timestamp': timestamp,
|
||||
'outputs': serializable_outputs,
|
||||
'session_id': self.session_id
|
||||
}, f)
|
||||
|
||||
self.logger.info(f"Captured outputs for {node_name} -> {filename}")
|
||||
return filename
|
||||
|
||||
def capture_performance(self, node_name: str, start_time: float,
|
||||
end_time: float, memory_used: float = None,
|
||||
node_id: str = None) -> str:
|
||||
"""
|
||||
Capture performance metrics for a node
|
||||
"""
|
||||
timestamp = int(time.time() * 1000)
|
||||
node_id = node_id or f"{node_name}_{timestamp}"
|
||||
|
||||
metrics = {
|
||||
'node_name': node_name,
|
||||
'node_id': node_id,
|
||||
'timestamp': timestamp,
|
||||
'execution_time': end_time - start_time,
|
||||
'memory_used': memory_used,
|
||||
'session_id': self.session_id,
|
||||
'start_time': start_time,
|
||||
'end_time': end_time
|
||||
}
|
||||
|
||||
# Save to file
|
||||
filename = f"{self.test_data_dir}/benchmarks/{node_name}_{node_id}_{timestamp}.json"
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(metrics, f, indent=2)
|
||||
|
||||
self.logger.info(f"Captured performance for {node_name}: {metrics['execution_time']:.3f}s")
|
||||
return filename
|
||||
|
||||
def _prepare_for_serialization(self, data: Any) -> Any:
|
||||
"""
|
||||
Prepare data for serialization by converting tensors to numpy arrays
|
||||
"""
|
||||
if isinstance(data, dict):
|
||||
return {k: self._prepare_for_serialization(v) for k, v in data.items()}
|
||||
elif isinstance(data, (list, tuple)):
|
||||
return type(data)(self._prepare_for_serialization(item) for item in data)
|
||||
elif isinstance(data, torch.Tensor):
|
||||
return {
|
||||
'tensor_data': data.detach().cpu().numpy(),
|
||||
'shape': list(data.shape),
|
||||
'dtype': str(data.dtype),
|
||||
'device': str(data.device)
|
||||
}
|
||||
elif hasattr(data, '__dict__'):
|
||||
# For objects with __dict__, try to serialize their attributes
|
||||
try:
|
||||
return {k: self._prepare_for_serialization(v) for k, v in data.__dict__.items()}
|
||||
except:
|
||||
return str(data)
|
||||
else:
|
||||
return data
|
||||
|
||||
def load_test_data(self, node_name: str, data_type: str = "inputs") -> List[Dict]:
|
||||
"""
|
||||
Load all test data for a specific node
|
||||
"""
|
||||
data_dir = f"{self.test_data_dir}/{data_type}"
|
||||
test_files = []
|
||||
|
||||
for filename in os.listdir(data_dir):
|
||||
if filename.startswith(node_name) and filename.endswith('.pkl'):
|
||||
filepath = os.path.join(data_dir, filename)
|
||||
with open(filepath, 'rb') as f:
|
||||
test_files.append(pickle.load(f))
|
||||
|
||||
return sorted(test_files, key=lambda x: x['timestamp'])
|
||||
|
||||
def load_benchmark_data(self, node_name: str) -> List[Dict]:
|
||||
"""
|
||||
Load benchmark data for a specific node
|
||||
"""
|
||||
benchmark_dir = f"{self.test_data_dir}/benchmarks"
|
||||
benchmark_files = []
|
||||
|
||||
for filename in os.listdir(benchmark_dir):
|
||||
if filename.startswith(node_name) and filename.endswith('.json'):
|
||||
filepath = os.path.join(benchmark_dir, filename)
|
||||
with open(filepath, 'r') as f:
|
||||
benchmark_files.append(json.load(f))
|
||||
|
||||
return sorted(benchmark_files, key=lambda x: x['timestamp'])
|
||||
|
||||
def generate_test_report(self, node_name: str) -> Dict:
|
||||
"""
|
||||
Generate a comprehensive test report for a node
|
||||
"""
|
||||
inputs = self.load_test_data(node_name, "inputs")
|
||||
outputs = self.load_test_data(node_name, "outputs")
|
||||
benchmarks = self.load_benchmark_data(node_name)
|
||||
|
||||
report = {
|
||||
'node_name': node_name,
|
||||
'session_id': self.session_id,
|
||||
'total_tests': len(inputs),
|
||||
'inputs_captured': len(inputs),
|
||||
'outputs_captured': len(outputs),
|
||||
'benchmarks_captured': len(benchmarks),
|
||||
'avg_execution_time': np.mean([b['execution_time'] for b in benchmarks]) if benchmarks else 0,
|
||||
'min_execution_time': np.min([b['execution_time'] for b in benchmarks]) if benchmarks else 0,
|
||||
'max_execution_time': np.max([b['execution_time'] for b in benchmarks]) if benchmarks else 0,
|
||||
'avg_memory_used': np.mean([b['memory_used'] for b in benchmarks if b['memory_used']]) if benchmarks else 0,
|
||||
'test_data_summary': {
|
||||
'input_shapes': [self._extract_shapes(inp['inputs']) for inp in inputs],
|
||||
'output_shapes': [self._extract_shapes(out['outputs']) for out in outputs]
|
||||
}
|
||||
}
|
||||
|
||||
return report
|
||||
|
||||
def _extract_shapes(self, data: Any) -> Dict:
|
||||
"""
|
||||
Extract shape information from data
|
||||
"""
|
||||
shapes = {}
|
||||
if isinstance(data, dict):
|
||||
for k, v in data.items():
|
||||
if isinstance(v, dict) and 'shape' in v:
|
||||
shapes[k] = v['shape']
|
||||
elif isinstance(v, (list, tuple)):
|
||||
shapes[k] = [len(v)]
|
||||
return shapes
|
||||
|
||||
|
||||
# Global instrumentation instance
|
||||
instrumentation = NodeInstrumentation()
|
||||
|
||||
|
||||
def instrument_node(node_class):
|
||||
"""
|
||||
Decorator to instrument a node class
|
||||
"""
|
||||
# Get the main function name (usually 'decode', 'sample', etc.)
|
||||
function_name = None
|
||||
for attr_name in ['decode', 'sample', 'load', 'process']:
|
||||
if hasattr(node_class, attr_name):
|
||||
function_name = attr_name
|
||||
break
|
||||
|
||||
if not function_name:
|
||||
print(f"Warning: Could not find main function for {node_class.__name__}")
|
||||
return node_class
|
||||
|
||||
original_function = getattr(node_class, function_name)
|
||||
|
||||
def instrumented_function(self, *args, **kwargs):
|
||||
# Capture inputs
|
||||
inputs = {**kwargs}
|
||||
input_filename = instrumentation.capture_inputs(
|
||||
node_class.__name__, inputs
|
||||
)
|
||||
|
||||
# Start timing
|
||||
start_time = time.time()
|
||||
|
||||
# Get memory before execution
|
||||
memory_before = torch.cuda.memory_allocated() if torch.cuda.is_available() else 0
|
||||
|
||||
try:
|
||||
# Execute original function
|
||||
result = original_function(self, *args, **kwargs)
|
||||
|
||||
# End timing
|
||||
end_time = time.time()
|
||||
memory_after = torch.cuda.memory_allocated() if torch.cuda.is_available() else 0
|
||||
memory_used = (memory_after - memory_before) / 1024**3 # Convert to GB
|
||||
|
||||
# Capture outputs
|
||||
output_filename = instrumentation.capture_outputs(
|
||||
node_class.__name__, result
|
||||
)
|
||||
|
||||
# Capture performance
|
||||
performance_filename = instrumentation.capture_performance(
|
||||
node_class.__name__, start_time, end_time, memory_used
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
# Still capture performance data even on error
|
||||
end_time = time.time()
|
||||
memory_after = torch.cuda.memory_allocated() if torch.cuda.is_available() else 0
|
||||
memory_used = (memory_after - memory_before) / 1024**3
|
||||
|
||||
instrumentation.capture_performance(
|
||||
node_class.__name__, start_time, end_time, memory_used
|
||||
)
|
||||
|
||||
raise e
|
||||
|
||||
# Replace the function
|
||||
setattr(node_class, function_name, instrumented_function)
|
||||
return node_class
|
||||
|
||||
|
||||
def create_test_fixtures():
|
||||
"""
|
||||
Create test fixtures from captured data
|
||||
"""
|
||||
fixtures_dir = "tests/fixtures"
|
||||
os.makedirs(fixtures_dir, exist_ok=True)
|
||||
|
||||
# Get all available nodes
|
||||
from nodes import NODE_CLASS_MAPPINGS
|
||||
|
||||
for node_name, node_class in NODE_CLASS_MAPPINGS.items():
|
||||
# Load test data
|
||||
inputs = instrumentation.load_test_data(node_name, "inputs")
|
||||
outputs = instrumentation.load_test_data(node_name, "outputs")
|
||||
|
||||
if inputs and outputs:
|
||||
# Create fixture file
|
||||
fixture_data = {
|
||||
'node_name': node_name,
|
||||
'test_cases': []
|
||||
}
|
||||
|
||||
# Match inputs with outputs by timestamp
|
||||
input_dict = {inp['timestamp']: inp for inp in inputs}
|
||||
output_dict = {out['timestamp']: out for out in outputs}
|
||||
|
||||
for timestamp in sorted(input_dict.keys()):
|
||||
if timestamp in output_dict:
|
||||
fixture_data['test_cases'].append({
|
||||
'timestamp': timestamp,
|
||||
'inputs': input_dict[timestamp]['inputs'],
|
||||
'outputs': output_dict[timestamp]['outputs']
|
||||
})
|
||||
|
||||
# Save fixture
|
||||
fixture_file = f"{fixtures_dir}/{node_name}_fixtures.json"
|
||||
with open(fixture_file, 'w') as f:
|
||||
json.dump(fixture_data, f, indent=2)
|
||||
|
||||
print(f"Created fixture for {node_name}: {len(fixture_data['test_cases'])} test cases")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Create test fixtures from existing data
|
||||
create_test_fixtures()
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
# kill_comfyui.sh - Properly kill ComfyUI process
|
||||
# This script finds and kills ComfyUI processes using the correct method
|
||||
|
||||
echo "🔍 Searching for ComfyUI processes..."
|
||||
|
||||
# Find ComfyUI processes
|
||||
PIDS=$(ps aux | grep "python.*main.py" | grep -v grep | awk '{print $2}')
|
||||
|
||||
if [ -z "$PIDS" ]; then
|
||||
echo "✅ No ComfyUI processes found - already stopped"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "📋 Found ComfyUI processes: $PIDS"
|
||||
|
||||
# Kill each process
|
||||
for PID in $PIDS; do
|
||||
echo "🔪 Killing ComfyUI process $PID..."
|
||||
kill -9 $PID
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Successfully killed process $PID"
|
||||
else
|
||||
echo "❌ Failed to kill process $PID"
|
||||
fi
|
||||
done
|
||||
|
||||
# Wait a moment for processes to fully terminate
|
||||
sleep 2
|
||||
|
||||
# Verify all processes are killed
|
||||
REMAINING=$(ps aux | grep "python.*main.py" | grep -v grep | awk '{print $2}')
|
||||
if [ -z "$REMAINING" ]; then
|
||||
echo "✅ All ComfyUI processes successfully terminated"
|
||||
else
|
||||
echo "⚠️ Warning: Some processes may still be running: $REMAINING"
|
||||
fi
|
||||
|
||||
echo "🏁 ComfyUI kill script completed"
|
||||
@@ -1,237 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SIMPLE ROCM NODES - EMERGENCY PERFORMANCE FIX
|
||||
Removed all optimization overhead that was causing slowdown
|
||||
"""
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import comfy.model_management as model_management
|
||||
import comfy.utils
|
||||
import time
|
||||
import logging
|
||||
from typing import Dict, Any, Tuple, Optional
|
||||
|
||||
class ROCMOptimizedVAEDecode:
|
||||
"""
|
||||
Simple ROCM VAE Decode - No optimization overhead
|
||||
"""
|
||||
|
||||
@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 for decoding."
|
||||
}),
|
||||
"overlap": ("INT", {
|
||||
"default": 96,
|
||||
"min": 32,
|
||||
"max": 512,
|
||||
"step": 16,
|
||||
"tooltip": "Overlap between tiles."
|
||||
}),
|
||||
"use_rocm_optimizations": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Enable ROCm-specific optimizations."
|
||||
}),
|
||||
"precision_mode": (["auto", "fp32", "fp16", "mixed"], {
|
||||
"default": "auto",
|
||||
"tooltip": "Precision mode."
|
||||
}),
|
||||
"batch_optimization": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Enable batch processing optimizations."
|
||||
}),
|
||||
"video_chunk_size": ("INT", {
|
||||
"default": 8,
|
||||
"min": 1,
|
||||
"max": 32,
|
||||
"step": 1,
|
||||
"tooltip": "Video chunk size."
|
||||
}),
|
||||
"memory_optimization_enabled": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Enable memory optimizations."
|
||||
}),
|
||||
"adaptive_tiling": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Enable adaptive tiling."
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("IMAGE",)
|
||||
FUNCTION = "decode"
|
||||
CATEGORY = "ROCM Optimized/VAE"
|
||||
|
||||
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):
|
||||
"""
|
||||
Simple VAE decode - No optimization overhead
|
||||
"""
|
||||
try:
|
||||
samples_processed = samples["samples"]
|
||||
|
||||
# Simple precision handling
|
||||
if precision_mode == "auto":
|
||||
optimal_dtype = vae.vae_dtype
|
||||
elif precision_mode == "fp16":
|
||||
optimal_dtype = torch.float16
|
||||
elif precision_mode == "fp32":
|
||||
optimal_dtype = torch.float32
|
||||
else:
|
||||
optimal_dtype = vae.vae_dtype
|
||||
|
||||
# Calculate memory requirements
|
||||
memory_used = vae.memory_used_decode(samples_processed.shape, optimal_dtype)
|
||||
|
||||
# Load VAE
|
||||
model_management.load_models_gpu([vae.patcher], memory_required=memory_used)
|
||||
|
||||
# Check if we can do direct decode
|
||||
if len(samples_processed.shape) == 4: # Image
|
||||
B, C, H, W = samples_processed.shape
|
||||
|
||||
# Try direct decode first
|
||||
try:
|
||||
with torch.amp.autocast('cuda', enabled=(optimal_dtype != torch.float32), dtype=optimal_dtype):
|
||||
out = vae.first_stage_model.decode(samples_processed.to(optimal_dtype))
|
||||
|
||||
pixel_samples = vae.process_output(out)
|
||||
pixel_samples = pixel_samples.to(vae.output_device).float()
|
||||
|
||||
return (pixel_samples,)
|
||||
|
||||
except Exception as e:
|
||||
logging.warning(f"Direct decode failed, falling back to tiled: {e}")
|
||||
|
||||
# Fallback to tiled decode
|
||||
try:
|
||||
pixel_samples = self._decode_tiled(vae, samples, tile_size, overlap)
|
||||
return (pixel_samples,)
|
||||
|
||||
except Exception as e:
|
||||
logging.warning(f"Tiled decode failed, falling back to standard VAE: {e}")
|
||||
|
||||
# Final fallback to standard VAE decode
|
||||
pixel_samples = vae.decode(samples_processed)
|
||||
pixel_samples = vae.process_output(pixel_samples)
|
||||
|
||||
return (pixel_samples,)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"VAE decode failed: {e}")
|
||||
raise e
|
||||
|
||||
def _decode_tiled(self, vae, samples, tile_size, overlap):
|
||||
"""Simple tiled decode"""
|
||||
samples_processed = samples["samples"]
|
||||
|
||||
# Handle video tensors (5D) vs image tensors (4D)
|
||||
if len(samples_processed.shape) == 5: # Video: B, C, T, H, W
|
||||
B, C, T, H, W = samples_processed.shape
|
||||
# For video, process each frame separately
|
||||
frames = []
|
||||
for t in range(T):
|
||||
frame_samples = samples_processed[:, :, t, :, :] # Remove temporal dimension for processing
|
||||
frame_output = self._decode_single_frame(vae, frame_samples, tile_size, overlap)
|
||||
frames.append(frame_output)
|
||||
return torch.stack(frames, dim=2) # Stack along temporal dimension
|
||||
else: # Image: B, C, H, W
|
||||
return self._decode_single_frame(vae, samples_processed, tile_size, overlap)
|
||||
|
||||
def _decode_single_frame(self, vae, samples_processed, tile_size, overlap):
|
||||
"""Decode a single frame (4D tensor)"""
|
||||
# Get compression ratios
|
||||
try:
|
||||
compression = vae.spacial_compression_decode()
|
||||
temporal_compression = vae.temporal_compression_decode()
|
||||
except:
|
||||
compression = 8
|
||||
temporal_compression = 1
|
||||
|
||||
# Calculate tile dimensions
|
||||
tile_h = tile_size // compression
|
||||
tile_w = tile_size // compression
|
||||
|
||||
# Calculate number of tiles needed
|
||||
B, C, H, W = samples_processed.shape
|
||||
tiles_h = (H + tile_h - 1) // tile_h
|
||||
tiles_w = (W + tile_w - 1) // tile_w
|
||||
|
||||
# Pre-allocate output tensor
|
||||
output_shape = (B, 3, H * compression, W * compression)
|
||||
pixel_samples = torch.empty(output_shape, dtype=torch.float32, device=samples_processed.device)
|
||||
|
||||
# Process tiles
|
||||
for i in range(tiles_h):
|
||||
for j in range(tiles_w):
|
||||
# Calculate tile boundaries with overlap
|
||||
start_h = i * tile_h
|
||||
end_h = min(start_h + tile_h + overlap, H)
|
||||
start_w = j * tile_w
|
||||
end_w = min(start_w + tile_w + overlap, W)
|
||||
|
||||
# Extract tile
|
||||
tile_samples = samples_processed[:, :, start_h:end_h, start_w:end_w]
|
||||
|
||||
# Decode tile
|
||||
with torch.amp.autocast('cuda', enabled=True, dtype=vae.vae_dtype):
|
||||
tile_output = vae.first_stage_model.decode(tile_samples.to(vae.vae_dtype))
|
||||
|
||||
# Process output
|
||||
tile_output = vae.process_output(tile_output)
|
||||
|
||||
# Copy to output with proper positioning
|
||||
output_start_h = start_h * compression
|
||||
output_end_h = end_h * compression
|
||||
output_start_w = start_w * compression
|
||||
output_end_w = end_w * compression
|
||||
|
||||
pixel_samples[:, :, output_start_h:output_end_h, output_start_w:output_end_w] = tile_output
|
||||
|
||||
return pixel_samples
|
||||
|
||||
# Apply no-op instrumentation
|
||||
def instrument_node(node_class):
|
||||
"""No-op decorator - instrumentation disabled"""
|
||||
return node_class
|
||||
|
||||
# Create no-op instrumentation object
|
||||
class NodeInstrumentation:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def capture_inputs(self, *args, **kwargs):
|
||||
return ""
|
||||
|
||||
def capture_outputs(self, *args, **kwargs):
|
||||
return ""
|
||||
|
||||
def capture_performance(self, *args, **kwargs):
|
||||
return ""
|
||||
|
||||
# Create global instrumentation object
|
||||
instrumentation = NodeInstrumentation()
|
||||
|
||||
# Apply instrumentation (no-op)
|
||||
@instrument_node
|
||||
class ROCMOptimizedVAEDecodeInstrumented(ROCMOptimizedVAEDecode):
|
||||
pass
|
||||
|
||||
# Node class mapping for ComfyUI
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ROCMOptimizedVAEDecode": ROCMOptimizedVAEDecodeInstrumented
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ROCMOptimizedVAEDecode": "ROCM Optimized VAE Decode"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
-203
@@ -1,203 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ACTUALLY OPTIMIZED VAE Decode - Simple and Fast
|
||||
Based on analysis: the simple baseline is fastest, so let's optimize that
|
||||
"""
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import comfy.model_management as model_management
|
||||
import comfy.utils
|
||||
import time
|
||||
import logging
|
||||
from typing import Dict, Any, Tuple, Optional
|
||||
from instrumentation import instrument_node
|
||||
|
||||
class ROCMOptimizedVAEDecodeFast:
|
||||
"""
|
||||
ACTUALLY Optimized VAE Decode - Simple and Fast
|
||||
Based on analysis: simple baseline is fastest, so optimize that approach
|
||||
"""
|
||||
|
||||
@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."
|
||||
}),
|
||||
"overlap": ("INT", {
|
||||
"default": 96,
|
||||
"min": 32,
|
||||
"max": 512,
|
||||
"step": 16,
|
||||
"tooltip": "Overlap between tiles."
|
||||
}),
|
||||
"use_rocm_optimizations": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Enable ROCm-specific optimizations for gfx1151."
|
||||
}),
|
||||
"precision_mode": (["auto", "fp32", "fp16", "mixed"], {
|
||||
"default": "auto",
|
||||
"tooltip": "Precision mode optimized for gfx1151 architecture."
|
||||
}),
|
||||
"batch_optimization": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Enable batch processing optimizations for AMD GPUs."
|
||||
}),
|
||||
"video_chunk_size": ("INT", {
|
||||
"default": 8,
|
||||
"min": 1,
|
||||
"max": 32,
|
||||
"step": 1,
|
||||
"tooltip": "Video chunk size for temporal processing optimization."
|
||||
}),
|
||||
"memory_optimization_enabled": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Enable advanced memory management optimizations."
|
||||
}),
|
||||
"adaptive_tiling": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Enable adaptive tile size selection based on input dimensions."
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("IMAGE",)
|
||||
FUNCTION = "decode"
|
||||
CATEGORY = "ROCM Optimized/VAE"
|
||||
|
||||
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):
|
||||
"""
|
||||
ACTUALLY Optimized VAE decode - Simple and Fast
|
||||
"""
|
||||
try:
|
||||
samples_processed = samples["samples"]
|
||||
|
||||
# Determine optimal precision - SIMPLE
|
||||
if precision_mode == "auto":
|
||||
optimal_dtype = vae.vae_dtype
|
||||
elif precision_mode == "fp16":
|
||||
optimal_dtype = torch.float16
|
||||
elif precision_mode == "fp32":
|
||||
optimal_dtype = torch.float32
|
||||
else: # mixed
|
||||
optimal_dtype = vae.vae_dtype
|
||||
|
||||
# Calculate memory requirements
|
||||
memory_used = vae.memory_used_decode(samples_processed.shape, optimal_dtype)
|
||||
|
||||
# Load VAE
|
||||
model_management.load_models_gpu([vae.patcher], memory_required=memory_used)
|
||||
|
||||
# Check if we can do direct decode
|
||||
if len(samples_processed.shape) == 4: # Image
|
||||
B, C, H, W = samples_processed.shape
|
||||
|
||||
# Try direct decode first - OPTIMIZED for gfx1151
|
||||
try:
|
||||
# Use optimal autocast for gfx1151
|
||||
with torch.amp.autocast('cuda', enabled=(optimal_dtype != torch.float32), dtype=optimal_dtype):
|
||||
out = vae.first_stage_model.decode(samples_processed.to(optimal_dtype))
|
||||
|
||||
pixel_samples = vae.process_output(out)
|
||||
pixel_samples = pixel_samples.to(vae.output_device).float()
|
||||
|
||||
return (pixel_samples,)
|
||||
|
||||
except Exception as e:
|
||||
logging.warning(f"Direct decode failed, falling back to tiled: {e}")
|
||||
|
||||
# Fallback to optimized tiled decode
|
||||
try:
|
||||
pixel_samples = self._decode_tiled_optimized(vae, samples, tile_size, overlap)
|
||||
return (pixel_samples,)
|
||||
|
||||
except Exception as e:
|
||||
logging.warning(f"Tiled decode failed, falling back to standard VAE: {e}")
|
||||
|
||||
# Final fallback to standard VAE decode
|
||||
pixel_samples = vae.decode(samples_processed)
|
||||
pixel_samples = vae.process_output(pixel_samples)
|
||||
|
||||
return (pixel_samples,)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"VAE decode failed: {e}")
|
||||
raise e
|
||||
|
||||
def _decode_tiled_optimized(self, vae, samples, tile_size, overlap):
|
||||
"""Optimized tiled decode - minimal overhead, maximum speed"""
|
||||
samples_processed = samples["samples"]
|
||||
|
||||
# Get compression ratios
|
||||
try:
|
||||
compression = vae.spacial_compression_decode()
|
||||
temporal_compression = vae.temporal_compression_decode()
|
||||
except:
|
||||
compression = 8
|
||||
temporal_compression = 1
|
||||
|
||||
# Calculate tile dimensions
|
||||
tile_h = tile_size // compression
|
||||
tile_w = tile_size // compression
|
||||
|
||||
# Calculate number of tiles needed
|
||||
B, C, H, W = samples_processed.shape
|
||||
tiles_h = (H + tile_h - 1) // tile_h
|
||||
tiles_w = (W + tile_w - 1) // tile_w
|
||||
|
||||
# Pre-allocate output tensor - OPTIMIZED allocation
|
||||
output_shape = (B, 3, H * compression, W * compression)
|
||||
pixel_samples = torch.empty(output_shape, dtype=torch.float32, device=samples_processed.device)
|
||||
|
||||
# Process tiles with MINIMAL overhead - OPTIMIZED for gfx1151
|
||||
for i in range(tiles_h):
|
||||
for j in range(tiles_w):
|
||||
# Calculate tile boundaries with overlap
|
||||
start_h = i * tile_h
|
||||
end_h = min(start_h + tile_h + overlap, H)
|
||||
start_w = j * tile_w
|
||||
end_w = min(start_w + tile_w + overlap, W)
|
||||
|
||||
# Extract tile - NO unnecessary copying
|
||||
tile_samples = samples_processed[:, :, start_h:end_h, start_w:end_w]
|
||||
|
||||
# Decode tile - OPTIMIZED for gfx1151
|
||||
with torch.amp.autocast('cuda', enabled=True, dtype=vae.vae_dtype):
|
||||
tile_output = vae.first_stage_model.decode(tile_samples.to(vae.vae_dtype))
|
||||
|
||||
# Process output
|
||||
tile_output = vae.process_output(tile_output)
|
||||
|
||||
# Copy to output with proper positioning
|
||||
output_start_h = start_h * compression
|
||||
output_end_h = end_h * compression
|
||||
output_start_w = start_w * compression
|
||||
output_end_w = end_w * compression
|
||||
|
||||
pixel_samples[:, :, output_start_h:output_end_h, output_start_w:output_end_w] = tile_output
|
||||
|
||||
return pixel_samples
|
||||
|
||||
# Apply instrumentation
|
||||
@instrument_node
|
||||
class ROCMOptimizedVAEDecodeFastInstrumented(ROCMOptimizedVAEDecodeFast):
|
||||
pass
|
||||
|
||||
# Node class mapping for ComfyUI
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ROCMOptimizedVAEDecodeFast": ROCMOptimizedVAEDecodeFastInstrumented
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ROCMOptimizedVAEDecodeFast": "ROCM Optimized VAE Decode Fast"
|
||||
}
|
||||
-1357
File diff suppressed because it is too large
Load Diff
+6
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "rocm-ninodes",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.30",
|
||||
"description": "ROCM-optimized ComfyUI nodes for AMD GPU performance",
|
||||
"author": "Nino",
|
||||
"email": "nino2k@proton.me",
|
||||
@@ -25,16 +25,19 @@
|
||||
"display_name": "RocM Ninodes",
|
||||
"description": "ROCM-optimized nodes for AMD GPU performance",
|
||||
"author": "Nino",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.30",
|
||||
"category": "Optimization",
|
||||
"tags": ["rocm", "amd", "gpu", "optimization", "vae", "sampling"],
|
||||
"nodes": [
|
||||
"ROCMOptimizedCheckpointLoader",
|
||||
"ROCMOptimizedVAEDecode",
|
||||
"ROCMOptimizedVAEDecodeTiled",
|
||||
"ROCMVAEPerformanceMonitor",
|
||||
"ROCMOptimizedKSampler",
|
||||
"ROCMOptimizedKSamplerAdvanced",
|
||||
"ROCMSamplerPerformanceMonitor"
|
||||
"ROCMSamplerPerformanceMonitor",
|
||||
"ROCMFluxBenchmark",
|
||||
"ROCMMemoryOptimizer"
|
||||
],
|
||||
"requirements": [
|
||||
"numpy>=1.20.0",
|
||||
|
||||
+9
-3
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "rocm-ninodes"
|
||||
description = "ROCM Optimized Nodes for ComfyUI - High-performance VAE decode and sampling nodes specifically tuned for AMD GPUs with ROCm support, particularly targeting gfx1151 architecture"
|
||||
version = "1.0.13"
|
||||
version = "1.0.30"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = [
|
||||
"numpy>=1.20.0",
|
||||
@@ -22,16 +22,22 @@ requires-comfyui = ">=1.0.0"
|
||||
name = "rocm-ninodes"
|
||||
display_name = "RocM Ninodes"
|
||||
description = "ROCM Optimized Nodes for ComfyUI - High-performance VAE decode and sampling nodes specifically tuned for AMD GPUs with ROCm support"
|
||||
version = "1.0.13"
|
||||
version = "1.0.30"
|
||||
author = "iGavroche"
|
||||
author_email = "nino2k@proton.me"
|
||||
repository = "https://github.com/iGavroche/rocm-ninodes"
|
||||
license = "MIT"
|
||||
nodes = [
|
||||
"ROCMOptimizedCheckpointLoader",
|
||||
"ROCMOptimizedVAEDecode",
|
||||
"ROCMOptimizedVAEDecodeTiled",
|
||||
"ROCMVAEPerformanceMonitor",
|
||||
"ROCMOptimizedKSampler",
|
||||
"ROCMOptimizedKSamplerAdvanced",
|
||||
"ROCMSamplerPerformanceMonitor"
|
||||
"ROCMMemorySafeKSampler",
|
||||
"ROCMSamplerPerformanceMonitor",
|
||||
"ROCMFluxBenchmark",
|
||||
"ROCMMemoryOptimizer",
|
||||
"ROCMMemoryDefragmenter",
|
||||
"ROCMEmergencyMemoryReset"
|
||||
]
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Real VAE Performance Test
|
||||
Test with actual ComfyUI environment to identify performance issues
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, '/home/nino/ComfyUI')
|
||||
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
def test_real_performance():
|
||||
"""Test real performance by running a simple ComfyUI workflow"""
|
||||
print("🔍 Real VAE Performance Test")
|
||||
print("=" * 40)
|
||||
|
||||
# Check if we can import the nodes
|
||||
try:
|
||||
from custom_nodes.rocm_ninodes.nodes import ROCMOptimizedVAEDecodeInstrumented
|
||||
print("✅ Successfully imported ROCMOptimizedVAEDecodeInstrumented")
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to import nodes: {e}")
|
||||
return
|
||||
|
||||
# Check if ComfyUI modules are available
|
||||
try:
|
||||
import comfy.model_management
|
||||
import comfy.utils
|
||||
print("✅ ComfyUI modules available")
|
||||
except Exception as e:
|
||||
print(f"❌ ComfyUI modules not available: {e}")
|
||||
return
|
||||
|
||||
# Check GPU availability
|
||||
try:
|
||||
import torch
|
||||
if torch.cuda.is_available():
|
||||
print(f"✅ CUDA available: {torch.cuda.get_device_name()}")
|
||||
print(f" Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f}GB")
|
||||
else:
|
||||
print("⚠️ CUDA not available - using CPU")
|
||||
except Exception as e:
|
||||
print(f"❌ Torch error: {e}")
|
||||
return
|
||||
|
||||
print("\n📊 Performance Analysis:")
|
||||
print("The 173% slowdown (55s → 150s) could be caused by:")
|
||||
print("1. Missing optimizations from original implementation")
|
||||
print("2. Instrumentation overhead (though we disabled it)")
|
||||
print("3. Memory management issues")
|
||||
print("4. Tile size or precision problems")
|
||||
print("5. Batch processing issues")
|
||||
|
||||
print("\n🔧 Next Steps:")
|
||||
print("1. Compare original vs simplified implementation")
|
||||
print("2. Check for missing critical optimizations")
|
||||
print("3. Test with real workflow data")
|
||||
print("4. Profile memory usage and GPU utilization")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_real_performance()
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
# restart_comfyui.sh - Restart ComfyUI with proper cleanup
|
||||
# This script kills existing ComfyUI processes and starts fresh using existing start.sh
|
||||
|
||||
echo "🔄 Restarting ComfyUI..."
|
||||
|
||||
# Kill existing processes
|
||||
echo "1️⃣ Killing existing ComfyUI processes..."
|
||||
./kill_comfyui.sh
|
||||
|
||||
# Wait a moment
|
||||
sleep 3
|
||||
|
||||
# Start fresh using existing start.sh
|
||||
echo "2️⃣ Starting fresh ComfyUI instance using existing start.sh..."
|
||||
if [ "$1" = "debug" ]; then
|
||||
echo "🐛 Restarting in DEBUG mode"
|
||||
cd /home/nino/ComfyUI
|
||||
export ROCM_NINODES_DEBUG=1
|
||||
./start.sh
|
||||
else
|
||||
echo "⚡ Restarting in PRODUCTION mode"
|
||||
cd /home/nino/ComfyUI
|
||||
unset ROCM_NINODES_DEBUG
|
||||
./start.sh
|
||||
fi
|
||||
|
||||
echo "🏁 ComfyUI restart completed"
|
||||
+2232
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,153 @@
|
||||
# Test Data Directory
|
||||
|
||||
This directory contains captured test data for ROCM Ninodes testing and benchmarking.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
test_data/
|
||||
├── captured/ # Captured data from ComfyUI workflows
|
||||
│ ├── flux_1024x1024/ # Flux workflow data (1024x1024 images)
|
||||
│ │ ├── checkpoint_loader_input_*.pkl
|
||||
│ │ ├── checkpoint_loader_output_*.pkl
|
||||
│ │ ├── ksampler_input_*.pkl
|
||||
│ │ ├── ksampler_output_*.pkl
|
||||
│ │ ├── vae_decode_input_*.pkl
|
||||
│ │ └── vae_decode_output_*.pkl
|
||||
│ ├── wan_320x320_17frames/ # WAN workflow data (320x320, 17 frames)
|
||||
│ │ ├── ksampler_input_*.pkl
|
||||
│ │ ├── ksampler_output_*.pkl
|
||||
│ │ ├── vae_decode_input_*.pkl (5D tensor data)
|
||||
│ │ └── vae_decode_output_*.pkl (4D tensor data)
|
||||
│ ├── timing/ # Timing data for performance analysis
|
||||
│ │ ├── timing_checkpoint_loader_*.pkl
|
||||
│ │ ├── timing_ksampler_*.pkl
|
||||
│ │ └── timing_vae_decode_*.pkl
|
||||
│ └── memory/ # Memory usage data
|
||||
│ ├── memory_checkpoint_loader_*.pkl
|
||||
│ ├── memory_ksampler_*.pkl
|
||||
│ └── memory_vae_decode_*.pkl
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Data Capture
|
||||
|
||||
Data is captured when `ROCM_NINODES_DEBUG=1` environment variable is set. This enables:
|
||||
|
||||
- **Input/Output Tensors**: Raw tensor data for testing
|
||||
- **Timing Information**: Execution times for performance analysis
|
||||
- **Memory Usage**: GPU memory allocation and usage patterns
|
||||
- **Metadata**: Additional context about the execution
|
||||
|
||||
## Usage
|
||||
|
||||
### Capturing Data
|
||||
```bash
|
||||
# Set debug mode
|
||||
export ROCM_NINODES_DEBUG=1
|
||||
|
||||
# Run ComfyUI workflow
|
||||
python /home/nino/ComfyUI/main.py --use-pytorch-cross-attention --highvram --cache-none
|
||||
|
||||
# Data will be automatically saved to test_data/captured/
|
||||
```
|
||||
|
||||
### Loading Data for Testing
|
||||
```python
|
||||
from debug_config import load_debug_data, get_latest_debug_file
|
||||
|
||||
# Load latest checkpoint loader input
|
||||
checkpoint_file = get_latest_debug_file("checkpoint_loader_input", "flux_1024x1024")
|
||||
if checkpoint_file:
|
||||
data = load_debug_data(checkpoint_file)
|
||||
|
||||
# Load latest VAE decode input
|
||||
vae_file = get_latest_debug_file("vae_decode_input", "wan_320x320_17frames")
|
||||
if vae_file:
|
||||
data = load_debug_data(vae_file)
|
||||
```
|
||||
|
||||
## File Formats
|
||||
|
||||
### Input/Output Files
|
||||
- **Format**: Pickle files containing dictionaries
|
||||
- **Structure**:
|
||||
```python
|
||||
{
|
||||
'data': <actual_tensor_or_data>,
|
||||
'timestamp': <unix_timestamp_ms>,
|
||||
'metadata': {
|
||||
'function': 'function_name',
|
||||
'node_type': 'ROCMOptimizedVAEDecode',
|
||||
# ... other metadata
|
||||
},
|
||||
'tensor_info': { # If data contains tensors
|
||||
'shape': <tensor_shape>,
|
||||
'dtype': <tensor_dtype>,
|
||||
'device': <tensor_device>,
|
||||
'requires_grad': <boolean>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Timing Files
|
||||
- **Format**: Pickle files containing timing data
|
||||
- **Structure**:
|
||||
```python
|
||||
{
|
||||
'function': 'function_name',
|
||||
'start_time': <start_timestamp>,
|
||||
'end_time': <end_timestamp>,
|
||||
'duration': <duration_seconds>,
|
||||
'metadata': {
|
||||
'node_type': 'ROCMOptimizedVAEDecode',
|
||||
'input_shape': <input_shape>,
|
||||
# ... other metadata
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Memory Files
|
||||
- **Format**: Pickle files containing memory usage data
|
||||
- **Structure**:
|
||||
```python
|
||||
{
|
||||
'function': 'function_name',
|
||||
'timestamp': <unix_timestamp>,
|
||||
'gpu_memory_allocated': <bytes_allocated>,
|
||||
'gpu_memory_reserved': <bytes_reserved>,
|
||||
'metadata': {
|
||||
'node_type': 'ROCMOptimizedVAEDecode',
|
||||
'input_shape': <input_shape>,
|
||||
# ... other metadata
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data Management
|
||||
|
||||
### Cleaning Up
|
||||
```bash
|
||||
# Remove old captured data (older than 7 days)
|
||||
find test_data/captured -name "*.pkl" -mtime +7 -delete
|
||||
|
||||
# Remove all captured data
|
||||
rm -rf test_data/captured/*
|
||||
```
|
||||
|
||||
### Archiving
|
||||
```bash
|
||||
# Create archive of captured data
|
||||
tar -czf test_data_$(date +%Y%m%d).tar.gz test_data/captured/
|
||||
|
||||
# Restore from archive
|
||||
tar -xzf test_data_20250110.tar.gz
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Debug mode has **zero performance impact** when disabled (`ROCM_NINODES_DEBUG=0`)
|
||||
- Data files are automatically timestamped to prevent conflicts
|
||||
- Tensor data is saved in CPU memory to ensure compatibility
|
||||
- Large tensor data may result in large pickle files
|
||||
- Consider disk space when enabling debug mode for extended periods
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Performance Test for Simplified VAE
|
||||
Test the simplified version to see if it's actually faster
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, '/home/nino/ComfyUI')
|
||||
|
||||
import time
|
||||
|
||||
def test_simplified_performance():
|
||||
"""Test if simplified version imports and works"""
|
||||
print("🔍 Testing Simplified VAE Performance")
|
||||
print("=" * 50)
|
||||
|
||||
# Test import
|
||||
try:
|
||||
from custom_nodes.rocm_ninodes.nodes import ROCMOptimizedVAEDecodeInstrumented
|
||||
print("✅ Successfully imported simplified ROCMOptimizedVAEDecodeInstrumented")
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to import simplified nodes: {e}")
|
||||
return False
|
||||
|
||||
# Test basic functionality
|
||||
try:
|
||||
node = ROCMOptimizedVAEDecodeInstrumented()
|
||||
print("✅ Successfully created node instance")
|
||||
|
||||
# Check input types
|
||||
input_types = node.INPUT_TYPES()
|
||||
print(f"✅ Input types: {list(input_types['required'].keys())}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to create node: {e}")
|
||||
return False
|
||||
|
||||
def compare_versions():
|
||||
"""Compare simplified vs complex versions"""
|
||||
print("\n📊 Version Comparison")
|
||||
print("=" * 30)
|
||||
|
||||
# Check simplified version
|
||||
simplified_works = test_simplified_performance()
|
||||
|
||||
if simplified_works:
|
||||
print("\n✅ Simplified Version:")
|
||||
print(" - Clean, minimal implementation")
|
||||
print(" - No complex optimization layers")
|
||||
print(" - No performance tracking overhead")
|
||||
print(" - No caching systems")
|
||||
print(" - Direct VAE decode with fallbacks")
|
||||
|
||||
print("\n🎯 Expected Benefits:")
|
||||
print(" - Lower memory overhead")
|
||||
print(" - Faster execution (no optimization overhead)")
|
||||
print(" - More predictable performance")
|
||||
print(" - Easier to debug")
|
||||
|
||||
print("\n⚠️ Potential Drawbacks:")
|
||||
print(" - May miss some optimizations")
|
||||
print(" - Less sophisticated memory management")
|
||||
print(" - No adaptive optimizations")
|
||||
|
||||
return simplified_works
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = compare_versions()
|
||||
|
||||
if success:
|
||||
print("\n🎉 Simplified version is ready for testing!")
|
||||
print("Next step: Test with actual ComfyUI workflow")
|
||||
else:
|
||||
print("\n❌ Simplified version has issues")
|
||||
@@ -1,140 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple VAE Performance Test
|
||||
Test the actual performance of the current VAE decode implementation
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, '/home/nino/ComfyUI')
|
||||
|
||||
import torch
|
||||
import time
|
||||
import psutil
|
||||
from custom_nodes.rocm_ninodes.nodes import ROCMOptimizedVAEDecodeInstrumented
|
||||
|
||||
def create_mock_vae():
|
||||
"""Create a mock VAE for testing"""
|
||||
class MockVAE:
|
||||
def __init__(self):
|
||||
self.device = torch.device('cpu')
|
||||
self.first_stage_model = MockFirstStageModel()
|
||||
self.vae_dtype = torch.float32
|
||||
self.patcher = MockPatcher()
|
||||
self.output_device = torch.device('cpu')
|
||||
|
||||
def process_output(self, x):
|
||||
return x.permute(0, 2, 3, 1)
|
||||
|
||||
def memory_used_decode(self, shape, dtype):
|
||||
return 1024
|
||||
|
||||
def spacial_compression_decode(self, shape=None):
|
||||
return 8
|
||||
|
||||
def temporal_compression_decode(self, shape=None):
|
||||
return 1
|
||||
|
||||
def decode_tiled(self, samples, tile_size, overlap):
|
||||
B, C, H, W = samples.shape
|
||||
return torch.randn(B, 3, H*8, W*8)
|
||||
|
||||
def decode(self, samples):
|
||||
B, C, H, W = samples.shape
|
||||
return torch.randn(B, 3, H*8, W*8)
|
||||
|
||||
class MockPatcher:
|
||||
def __init__(self):
|
||||
self.model_patches_models = lambda: []
|
||||
self.current_loaded_device = lambda: torch.device('cpu')
|
||||
self.model_size = lambda: 1024
|
||||
self.loaded_size = lambda: 1024
|
||||
self.model_patches_to = lambda x: x
|
||||
self.model_dtype = lambda: torch.float32
|
||||
self.partially_load = lambda device, extra_memory, force_patch_weights=False: True
|
||||
self.is_clone = lambda: False
|
||||
self.parent = None
|
||||
self.load_device = torch.device('cpu')
|
||||
self.model = MockModel()
|
||||
self.device = torch.device('cpu')
|
||||
|
||||
def model_memory_required(self, device):
|
||||
return 1024
|
||||
|
||||
def model_load(self, lowvram_model_memory, force_patch_weights=False):
|
||||
pass
|
||||
|
||||
def model_use_more_vram(self, use_more_vram, force_patch_weights=False):
|
||||
pass
|
||||
|
||||
class MockModel:
|
||||
def __init__(self):
|
||||
self.model = None
|
||||
|
||||
class MockFirstStageModel:
|
||||
def decode(self, samples):
|
||||
B, C, H, W = samples.shape
|
||||
return torch.randn(B, 3, H*8, W*8)
|
||||
|
||||
return MockVAE()
|
||||
|
||||
def test_vae_performance():
|
||||
"""Test VAE decode performance"""
|
||||
print("🔍 VAE Performance Test")
|
||||
print("=" * 40)
|
||||
|
||||
node = ROCMOptimizedVAEDecodeInstrumented()
|
||||
vae = create_mock_vae()
|
||||
|
||||
# Test different sizes
|
||||
test_cases = [
|
||||
{"name": "256x256", "latent": torch.randn(1, 4, 32, 32)},
|
||||
{"name": "512x512", "latent": torch.randn(1, 4, 64, 64)},
|
||||
{"name": "1024x1024", "latent": torch.randn(1, 4, 128, 128)},
|
||||
]
|
||||
|
||||
for test_case in test_cases:
|
||||
samples = {"samples": test_case["latent"]}
|
||||
|
||||
print(f"\n📊 Testing {test_case['name']}...")
|
||||
|
||||
# Warm up
|
||||
for _ in range(2):
|
||||
node.decode(
|
||||
vae=vae,
|
||||
samples=samples,
|
||||
tile_size=512,
|
||||
overlap=64,
|
||||
use_rocm_optimizations=True
|
||||
)
|
||||
|
||||
# Actual test
|
||||
execution_times = []
|
||||
for i in range(5):
|
||||
start_time = time.time()
|
||||
|
||||
result = node.decode(
|
||||
vae=vae,
|
||||
samples=samples,
|
||||
tile_size=512,
|
||||
overlap=64,
|
||||
use_rocm_optimizations=True
|
||||
)
|
||||
|
||||
execution_time = time.time() - start_time
|
||||
execution_times.append(execution_time)
|
||||
|
||||
print(f" Run {i+1}: {execution_time:.6f}s")
|
||||
|
||||
avg_time = sum(execution_times) / len(execution_times)
|
||||
print(f" Average: {avg_time:.6f}s")
|
||||
|
||||
# Check if this is reasonable
|
||||
if test_case["name"] == "256x256" and avg_time > 0.1:
|
||||
print(f" ⚠️ WARNING: {test_case['name']} is slower than expected!")
|
||||
elif test_case["name"] == "512x512" and avg_time > 0.5:
|
||||
print(f" ⚠️ WARNING: {test_case['name']} is slower than expected!")
|
||||
elif test_case["name"] == "1024x1024" and avg_time > 2.0:
|
||||
print(f" ⚠️ WARNING: {test_case['name']} is slower than expected!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_vae_performance()
|
||||
+1
-1
@@ -1 +1 @@
|
||||
# Empty init file to make tests a package
|
||||
# ROCM Ninodes Test Suite
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
# Test runner for ROCM Ninodes
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${BLUE}ROCM Ninodes Test Suite${NC}"
|
||||
echo "=========================="
|
||||
|
||||
# Check if pytest is available
|
||||
if ! command -v pytest &> /dev/null; then
|
||||
echo -e "${RED}Error: pytest is not installed${NC}"
|
||||
echo "Install with: pip install pytest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we're in the right directory
|
||||
if [ ! -f "test_fixtures.py" ]; then
|
||||
echo -e "${RED}Error: Must run from tests directory${NC}"
|
||||
echo "Usage: cd tests && ./run_tests.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Running standalone tests (no ComfyUI required)...${NC}"
|
||||
echo ""
|
||||
|
||||
# Run performance tests
|
||||
echo -e "${BLUE}1. Performance Tests${NC}"
|
||||
echo "-------------------"
|
||||
if pytest test_performance.py -v; then
|
||||
echo -e "${GREEN}✓ Performance tests passed${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Performance tests failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Run correctness tests
|
||||
echo -e "${BLUE}2. Correctness Tests${NC}"
|
||||
echo "-------------------"
|
||||
if pytest test_correctness.py -v; then
|
||||
echo -e "${GREEN}✓ Correctness tests passed${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Correctness tests failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Check for captured data
|
||||
echo -e "${BLUE}3. Data Availability Check${NC}"
|
||||
echo "---------------------------"
|
||||
if [ -d "../test_data/captured" ]; then
|
||||
echo -e "${GREEN}✓ Test data directory exists${NC}"
|
||||
|
||||
# Count captured files
|
||||
flux_files=$(find ../test_data/captured/flux_1024x1024 -name "*.pkl" 2>/dev/null | wc -l)
|
||||
wan_files=$(find ../test_data/captured/wan_320x320_17frames -name "*.pkl" 2>/dev/null | wc -l)
|
||||
timing_files=$(find ../test_data/captured/timing -name "*.pkl" 2>/dev/null | wc -l)
|
||||
memory_files=$(find ../test_data/captured/memory -name "*.pkl" 2>/dev/null | wc -l)
|
||||
|
||||
echo " Flux data files: $flux_files"
|
||||
echo " WAN data files: $wan_files"
|
||||
echo " Timing files: $timing_files"
|
||||
echo " Memory files: $memory_files"
|
||||
|
||||
if [ $flux_files -eq 0 ] && [ $wan_files -eq 0 ]; then
|
||||
echo -e "${YELLOW}⚠ No captured data found. Tests will use mock data.${NC}"
|
||||
echo " To capture real data, run ComfyUI with ROCM_NINODES_DEBUG=1"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Test data directory not found. Tests will use mock data.${NC}"
|
||||
echo " To capture real data, run ComfyUI with ROCM_NINODES_DEBUG=1"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Optional ComfyUI integration tests
|
||||
if [ -n "$WITH_COMFYUI" ]; then
|
||||
echo -e "${BLUE}4. ComfyUI Integration Tests${NC}"
|
||||
echo "----------------------------"
|
||||
|
||||
if [ -z "$COMFYUI_PATH" ]; then
|
||||
COMFYUI_PATH="/home/nino/ComfyUI"
|
||||
fi
|
||||
|
||||
if [ -d "$COMFYUI_PATH" ]; then
|
||||
echo "Running ComfyUI integration tests..."
|
||||
PYTHONPATH="$COMFYUI_PATH:$PYTHONPATH" python test_integration.py
|
||||
else
|
||||
echo -e "${RED}Error: ComfyUI path not found: $COMFYUI_PATH${NC}"
|
||||
echo "Set COMFYUI_PATH environment variable to correct path"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}4. ComfyUI Integration Tests (skipped)${NC}"
|
||||
echo "To run integration tests: WITH_COMFYUI=1 ./run_tests.sh"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}All tests completed successfully!${NC}"
|
||||
echo ""
|
||||
echo "To capture real data for testing:"
|
||||
echo " export ROCM_NINODES_DEBUG=1"
|
||||
echo " # Run your ComfyUI workflows"
|
||||
echo " # Data will be saved to test_data/captured/"
|
||||
echo ""
|
||||
echo "To run with ComfyUI integration:"
|
||||
echo " WITH_COMFYUI=1 ./run_tests.sh"
|
||||
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Correctness tests for ROCM Ninodes
|
||||
Tests output validation and shape correctness
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from .test_fixtures import (
|
||||
load_flux_checkpoint_data, load_flux_ksampler_data, load_flux_vae_data,
|
||||
load_wan_ksampler_data, load_wan_vae_data,
|
||||
get_tensor_from_data, get_tensor_info,
|
||||
create_mock_latent, create_mock_video_latent, create_mock_image_latent
|
||||
)
|
||||
|
||||
class TestFluxCorrectness:
|
||||
"""Correctness tests for Flux workflow components"""
|
||||
|
||||
def test_flux_checkpoint_output_types(self):
|
||||
"""Test that checkpoint loader returns correct types"""
|
||||
data = load_flux_checkpoint_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured checkpoint data available")
|
||||
|
||||
# Mock checkpoint loader output validation
|
||||
# In real implementation, this would check model, clip, vae objects
|
||||
assert data is not None, "Checkpoint data should not be None"
|
||||
|
||||
# Check if data has expected structure
|
||||
if isinstance(data, dict):
|
||||
assert 'data' in data or 'samples' in data, "Data should contain 'data' or 'samples' key"
|
||||
|
||||
def test_flux_ksampler_output_shape(self):
|
||||
"""Test that KSampler returns correct output shape"""
|
||||
data = load_flux_ksampler_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured KSampler data available")
|
||||
|
||||
tensor = get_tensor_from_data(data)
|
||||
if tensor is not None:
|
||||
# Flux KSampler should return 4D tensor [B, C, H, W]
|
||||
assert len(tensor.shape) == 4, f"Expected 4D tensor, got {len(tensor.shape)}D: {tensor.shape}"
|
||||
assert tensor.shape[0] > 0, "Batch size should be positive"
|
||||
assert tensor.shape[1] > 0, "Channel count should be positive"
|
||||
assert tensor.shape[2] > 0, "Height should be positive"
|
||||
assert tensor.shape[3] > 0, "Width should be positive"
|
||||
|
||||
def test_flux_vae_output_shape(self):
|
||||
"""Test that VAE decode returns correct output shape"""
|
||||
data = load_flux_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured VAE data available")
|
||||
|
||||
tensor = get_tensor_from_data(data)
|
||||
if tensor is not None:
|
||||
# Flux VAE should return 4D tensor [B, H, W, C] for images
|
||||
assert len(tensor.shape) == 4, f"Expected 4D tensor, got {len(tensor.shape)}D: {tensor.shape}"
|
||||
assert tensor.shape[0] > 0, "Batch size should be positive"
|
||||
assert tensor.shape[1] > 0, "Height should be positive"
|
||||
assert tensor.shape[2] > 0, "Width should be positive"
|
||||
assert tensor.shape[3] == 3, f"Expected 3 channels (RGB), got {tensor.shape[3]}"
|
||||
|
||||
class TestWANCorrectness:
|
||||
"""Correctness tests for WAN workflow components"""
|
||||
|
||||
def test_wan_ksampler_output_shape(self):
|
||||
"""Test that WAN KSampler returns correct output shape"""
|
||||
data = load_wan_ksampler_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured WAN KSampler data available")
|
||||
|
||||
tensor = get_tensor_from_data(data)
|
||||
if tensor is not None:
|
||||
# WAN KSampler should return 5D tensor [B, C, T, H, W] for video
|
||||
assert len(tensor.shape) == 5, f"Expected 5D tensor, got {len(tensor.shape)}D: {tensor.shape}"
|
||||
assert tensor.shape[0] > 0, "Batch size should be positive"
|
||||
assert tensor.shape[1] > 0, "Channel count should be positive"
|
||||
assert tensor.shape[2] > 0, "Frame count should be positive"
|
||||
assert tensor.shape[3] > 0, "Height should be positive"
|
||||
assert tensor.shape[4] > 0, "Width should be positive"
|
||||
|
||||
def test_wan_vae_input_shape(self):
|
||||
"""Test that WAN VAE receives correct input shape"""
|
||||
data = load_wan_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured WAN VAE data available")
|
||||
|
||||
tensor = get_tensor_from_data(data)
|
||||
if tensor is not None:
|
||||
# WAN VAE should receive 5D tensor [B, C, T, H, W]
|
||||
assert len(tensor.shape) == 5, f"Expected 5D input tensor, got {len(tensor.shape)}D: {tensor.shape}"
|
||||
assert tensor.shape[0] > 0, "Batch size should be positive"
|
||||
assert tensor.shape[1] > 0, "Channel count should be positive"
|
||||
assert tensor.shape[2] > 0, "Frame count should be positive"
|
||||
assert tensor.shape[3] > 0, "Height should be positive"
|
||||
assert tensor.shape[4] > 0, "Width should be positive"
|
||||
|
||||
def test_wan_vae_output_shape(self):
|
||||
"""Test that WAN VAE returns correct output shape"""
|
||||
data = load_wan_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured WAN VAE data available")
|
||||
|
||||
# Mock VAE decode output validation
|
||||
# WAN VAE should convert 5D input to 4D output [B*T, H, W, C]
|
||||
input_tensor = get_tensor_from_data(data)
|
||||
if input_tensor is not None:
|
||||
B, C, T, H, W = input_tensor.shape
|
||||
expected_output_shape = (B * T, H, W, 3) # 3 for RGB channels
|
||||
|
||||
# This would be the actual output in real implementation
|
||||
# For now, just validate the expected shape calculation
|
||||
assert expected_output_shape[0] == B * T, "Output batch size should be B*T"
|
||||
assert expected_output_shape[1] == H, "Output height should match input height"
|
||||
assert expected_output_shape[2] == W, "Output width should match input width"
|
||||
assert expected_output_shape[3] == 3, "Output should have 3 RGB channels"
|
||||
|
||||
class TestDataFormatCorrectness:
|
||||
"""Tests for data format correctness"""
|
||||
|
||||
def test_latent_format_structure(self):
|
||||
"""Test that LATENT format has correct structure"""
|
||||
# Test with mock data
|
||||
mock_latent = create_mock_image_latent()
|
||||
|
||||
assert isinstance(mock_latent, dict), "LATENT should be a dictionary"
|
||||
assert 'samples' in mock_latent, "LATENT should contain 'samples' key"
|
||||
assert isinstance(mock_latent['samples'], torch.Tensor), "samples should be a tensor"
|
||||
|
||||
def test_video_latent_format(self):
|
||||
"""Test that video LATENT format is correct"""
|
||||
mock_video_latent = create_mock_video_latent()
|
||||
|
||||
assert isinstance(mock_video_latent, dict), "Video LATENT should be a dictionary"
|
||||
assert 'samples' in mock_video_latent, "Video LATENT should contain 'samples' key"
|
||||
|
||||
samples = mock_video_latent['samples']
|
||||
assert isinstance(samples, torch.Tensor), "samples should be a tensor"
|
||||
assert len(samples.shape) == 5, f"Video samples should be 5D, got {len(samples.shape)}D"
|
||||
|
||||
def test_image_latent_format(self):
|
||||
"""Test that image LATENT format is correct"""
|
||||
mock_image_latent = create_mock_image_latent()
|
||||
|
||||
assert isinstance(mock_image_latent, dict), "Image LATENT should be a dictionary"
|
||||
assert 'samples' in mock_image_latent, "Image LATENT should contain 'samples' key"
|
||||
|
||||
samples = mock_image_latent['samples']
|
||||
assert isinstance(samples, torch.Tensor), "samples should be a tensor"
|
||||
assert len(samples.shape) == 4, f"Image samples should be 4D, got {len(samples.shape)}D"
|
||||
|
||||
class TestTensorProperties:
|
||||
"""Tests for tensor properties and constraints"""
|
||||
|
||||
def test_tensor_dtype_consistency(self):
|
||||
"""Test that tensor dtypes are consistent"""
|
||||
data = load_flux_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured data available")
|
||||
|
||||
tensor = get_tensor_from_data(data)
|
||||
if tensor is not None:
|
||||
# Check that dtype is reasonable for VAE operations
|
||||
assert tensor.dtype in [torch.float32, torch.float16, torch.bfloat16], \
|
||||
f"Unexpected dtype: {tensor.dtype}"
|
||||
|
||||
def test_tensor_device_consistency(self):
|
||||
"""Test that tensors are on expected device"""
|
||||
data = load_flux_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured data available")
|
||||
|
||||
tensor = get_tensor_from_data(data)
|
||||
if tensor is not None:
|
||||
# Check that device is reasonable
|
||||
assert tensor.device.type in ['cpu', 'cuda', 'hip'], \
|
||||
f"Unexpected device type: {tensor.device.type}"
|
||||
|
||||
def test_tensor_requires_grad(self):
|
||||
"""Test that tensors have appropriate requires_grad setting"""
|
||||
data = load_flux_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured data available")
|
||||
|
||||
tensor = get_tensor_from_data(data)
|
||||
if tensor is not None:
|
||||
# VAE decode output should not require gradients
|
||||
assert not tensor.requires_grad, "VAE decode output should not require gradients"
|
||||
|
||||
class TestMockDataCorrectness:
|
||||
"""Correctness tests using mock data when captured data is unavailable"""
|
||||
|
||||
def test_mock_flux_vae_shape_conversion(self):
|
||||
"""Test Flux VAE shape conversion with mock data"""
|
||||
# Create mock 4D latent input
|
||||
mock_input = create_mock_image_latent(batch_size=1, channels=16, height=128, width=128)
|
||||
input_tensor = mock_input['samples']
|
||||
|
||||
# Simulate VAE decode: [B, C, H, W] -> [B, H, W, C]
|
||||
B, C, H, W = input_tensor.shape
|
||||
expected_output_shape = (B, H, W, 3) # 3 for RGB channels
|
||||
|
||||
# Mock the conversion
|
||||
assert input_tensor.shape == (B, C, H, W), "Input should be 4D"
|
||||
assert expected_output_shape[0] == B, "Output batch size should match input"
|
||||
assert expected_output_shape[1] == H, "Output height should match input"
|
||||
assert expected_output_shape[2] == W, "Output width should match input"
|
||||
assert expected_output_shape[3] == 3, "Output should have 3 RGB channels"
|
||||
|
||||
def test_mock_wan_vae_shape_conversion(self):
|
||||
"""Test WAN VAE shape conversion with mock data"""
|
||||
# Create mock 5D latent input
|
||||
mock_input = create_mock_video_latent(batch_size=1, channels=16, frames=17, height=32, width=32)
|
||||
input_tensor = mock_input['samples']
|
||||
|
||||
# Simulate VAE decode: [B, C, T, H, W] -> [B*T, H, W, C]
|
||||
B, C, T, H, W = input_tensor.shape
|
||||
expected_output_shape = (B * T, H, W, 3) # 3 for RGB channels
|
||||
|
||||
# Mock the conversion
|
||||
assert input_tensor.shape == (B, C, T, H, W), "Input should be 5D"
|
||||
assert expected_output_shape[0] == B * T, "Output batch size should be B*T"
|
||||
assert expected_output_shape[1] == H, "Output height should match input"
|
||||
assert expected_output_shape[2] == W, "Output width should match input"
|
||||
assert expected_output_shape[3] == 3, "Output should have 3 RGB channels"
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test fixtures for loading captured data
|
||||
"""
|
||||
|
||||
import pickle
|
||||
import glob
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, List
|
||||
import torch
|
||||
|
||||
def get_test_data_dir() -> Path:
|
||||
"""Get the test data directory path"""
|
||||
return Path(__file__).parent.parent / "test_data" / "captured"
|
||||
|
||||
def find_latest_file(pattern: str, subdir: str = "") -> Optional[str]:
|
||||
"""
|
||||
Find the latest file matching a pattern in the test data directory
|
||||
|
||||
Args:
|
||||
pattern: Filename pattern to match
|
||||
subdir: Subdirectory to search in
|
||||
|
||||
Returns:
|
||||
Path to latest matching file or None
|
||||
"""
|
||||
test_dir = get_test_data_dir()
|
||||
if subdir:
|
||||
search_dir = test_dir / subdir
|
||||
else:
|
||||
search_dir = test_dir
|
||||
|
||||
if not search_dir.exists():
|
||||
return None
|
||||
|
||||
matching_files = list(search_dir.glob(f"{pattern}_*.pkl"))
|
||||
if not matching_files:
|
||||
return None
|
||||
|
||||
# Sort by modification time, newest first
|
||||
latest_file = max(matching_files, key=lambda f: f.stat().st_mtime)
|
||||
return str(latest_file)
|
||||
|
||||
def load_pickle_data(file_path: str) -> Any:
|
||||
"""
|
||||
Load data from a pickle file
|
||||
|
||||
Args:
|
||||
file_path: Path to the pickle file
|
||||
|
||||
Returns:
|
||||
Loaded data
|
||||
"""
|
||||
with open(file_path, 'rb') as f:
|
||||
return pickle.load(f)
|
||||
|
||||
def load_flux_checkpoint_data() -> Optional[Any]:
|
||||
"""Load latest Flux checkpoint loader input data"""
|
||||
file_path = find_latest_file("checkpoint_loader_input", "flux_1024x1024")
|
||||
if file_path:
|
||||
return load_pickle_data(file_path)
|
||||
return None
|
||||
|
||||
def load_flux_ksampler_data() -> Optional[Any]:
|
||||
"""Load latest Flux KSampler input data"""
|
||||
file_path = find_latest_file("ksampler_input", "flux_1024x1024")
|
||||
if file_path:
|
||||
return load_pickle_data(file_path)
|
||||
return None
|
||||
|
||||
def load_flux_vae_data() -> Optional[Any]:
|
||||
"""Load latest Flux VAE decode input data"""
|
||||
file_path = find_latest_file("vae_decode_input", "flux_1024x1024")
|
||||
if file_path:
|
||||
return load_pickle_data(file_path)
|
||||
return None
|
||||
|
||||
def load_wan_ksampler_data() -> Optional[Any]:
|
||||
"""Load latest WAN KSampler input data"""
|
||||
file_path = find_latest_file("ksampler_input", "wan_320x320_17frames")
|
||||
if file_path:
|
||||
return load_pickle_data(file_path)
|
||||
return None
|
||||
|
||||
def load_wan_vae_data() -> Optional[Any]:
|
||||
"""Load latest WAN VAE decode input data (5D tensor)"""
|
||||
file_path = find_latest_file("vae_decode_input", "wan_320x320_17frames")
|
||||
if file_path:
|
||||
return load_pickle_data(file_path)
|
||||
return None
|
||||
|
||||
def load_timing_data(function_name: str) -> Optional[Any]:
|
||||
"""Load timing data for a specific function"""
|
||||
file_path = find_latest_file(f"timing_{function_name}", "timing")
|
||||
if file_path:
|
||||
return load_pickle_data(file_path)
|
||||
return None
|
||||
|
||||
def load_memory_data(function_name: str) -> Optional[Any]:
|
||||
"""Load memory usage data for a specific function"""
|
||||
file_path = find_latest_file(f"memory_{function_name}", "memory")
|
||||
if file_path:
|
||||
return load_pickle_data(file_path)
|
||||
return None
|
||||
|
||||
def get_tensor_from_data(data: Any) -> Optional[torch.Tensor]:
|
||||
"""
|
||||
Extract tensor from captured data
|
||||
|
||||
Args:
|
||||
data: Captured data (may be dict with 'data' key or direct tensor)
|
||||
|
||||
Returns:
|
||||
Tensor or None
|
||||
"""
|
||||
if isinstance(data, dict):
|
||||
if 'data' in data:
|
||||
return data['data']
|
||||
elif 'samples' in data:
|
||||
return data['samples']
|
||||
elif isinstance(data, torch.Tensor):
|
||||
return data
|
||||
|
||||
return None
|
||||
|
||||
def get_tensor_info(data: Any) -> dict:
|
||||
"""
|
||||
Get tensor information from captured data
|
||||
|
||||
Args:
|
||||
data: Captured data
|
||||
|
||||
Returns:
|
||||
Dictionary with tensor information
|
||||
"""
|
||||
info = {}
|
||||
|
||||
if isinstance(data, dict):
|
||||
if 'tensor_info' in data:
|
||||
info.update(data['tensor_info'])
|
||||
if 'metadata' in data:
|
||||
info['metadata'] = data['metadata']
|
||||
if 'timestamp' in data:
|
||||
info['timestamp'] = data['timestamp']
|
||||
|
||||
tensor = get_tensor_from_data(data)
|
||||
if tensor is not None:
|
||||
info.update({
|
||||
'shape': tensor.shape,
|
||||
'dtype': str(tensor.dtype),
|
||||
'device': str(tensor.device),
|
||||
'requires_grad': tensor.requires_grad
|
||||
})
|
||||
|
||||
return info
|
||||
|
||||
def list_available_data() -> dict:
|
||||
"""
|
||||
List all available test data files
|
||||
|
||||
Returns:
|
||||
Dictionary mapping data types to available files
|
||||
"""
|
||||
test_dir = get_test_data_dir()
|
||||
available = {}
|
||||
|
||||
for subdir in ['flux_1024x1024', 'wan_320x320_17frames', 'timing', 'memory']:
|
||||
subdir_path = test_dir / subdir
|
||||
if subdir_path.exists():
|
||||
files = list(subdir_path.glob("*.pkl"))
|
||||
available[subdir] = [f.name for f in files]
|
||||
else:
|
||||
available[subdir] = []
|
||||
|
||||
return available
|
||||
|
||||
def create_mock_tensor(shape: tuple, dtype: torch.dtype = torch.float32, device: str = 'cpu') -> torch.Tensor:
|
||||
"""
|
||||
Create a mock tensor for testing when no captured data is available
|
||||
|
||||
Args:
|
||||
shape: Tensor shape
|
||||
dtype: Tensor dtype
|
||||
device: Device to create tensor on
|
||||
|
||||
Returns:
|
||||
Mock tensor
|
||||
"""
|
||||
return torch.randn(shape, dtype=dtype, device=device)
|
||||
|
||||
def create_mock_latent(shape: tuple, dtype: torch.dtype = torch.float32, device: str = 'cpu') -> dict:
|
||||
"""
|
||||
Create a mock ComfyUI LATENT format for testing
|
||||
|
||||
Args:
|
||||
shape: Tensor shape
|
||||
dtype: Tensor dtype
|
||||
device: Device to create tensor on
|
||||
|
||||
Returns:
|
||||
Mock LATENT dictionary
|
||||
"""
|
||||
return {
|
||||
'samples': create_mock_tensor(shape, dtype, device)
|
||||
}
|
||||
|
||||
def create_mock_video_latent(batch_size: int = 1, channels: int = 16, frames: int = 17, height: int = 32, width: int = 32) -> dict:
|
||||
"""
|
||||
Create a mock video LATENT for WAN testing (5D tensor)
|
||||
|
||||
Args:
|
||||
batch_size: Batch size
|
||||
channels: Number of channels
|
||||
frames: Number of video frames
|
||||
height: Frame height
|
||||
width: Frame width
|
||||
|
||||
Returns:
|
||||
Mock video LATENT dictionary
|
||||
"""
|
||||
shape = (batch_size, channels, frames, height, width)
|
||||
return create_mock_latent(shape)
|
||||
|
||||
def create_mock_image_latent(batch_size: int = 1, channels: int = 16, height: int = 128, width: int = 128) -> dict:
|
||||
"""
|
||||
Create a mock image LATENT for Flux testing (4D tensor)
|
||||
|
||||
Args:
|
||||
batch_size: Batch size
|
||||
channels: Number of channels
|
||||
height: Image height
|
||||
width: Image width
|
||||
|
||||
Returns:
|
||||
Mock image LATENT dictionary
|
||||
"""
|
||||
shape = (batch_size, channels, height, width)
|
||||
return create_mock_latent(shape)
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Main integration test runner
|
||||
Combines Flux and WAN integration tests
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
import os
|
||||
|
||||
def main():
|
||||
"""Run all integration tests"""
|
||||
print("ROCM Ninodes Integration Tests")
|
||||
print("=============================")
|
||||
|
||||
# Run Flux integration tests
|
||||
print("\n1. Flux Integration Tests")
|
||||
print("-" * 30)
|
||||
flux_result = pytest.main(["-v", "test_integration_flux.py"])
|
||||
|
||||
# Run WAN integration tests
|
||||
print("\n2. WAN Integration Tests")
|
||||
print("-" * 30)
|
||||
wan_result = pytest.main(["-v", "test_integration_wan.py"])
|
||||
|
||||
# Summary
|
||||
print("\nIntegration Test Summary")
|
||||
print("========================")
|
||||
print(f"Flux tests: {'PASSED' if flux_result == 0 else 'FAILED'}")
|
||||
print(f"WAN tests: {'PASSED' if wan_result == 0 else 'FAILED'}")
|
||||
|
||||
if flux_result == 0 and wan_result == 0:
|
||||
print("\n✓ All integration tests passed!")
|
||||
return 0
|
||||
else:
|
||||
print("\n✗ Some integration tests failed!")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Integration tests for Flux workflow
|
||||
Tests full workflow execution with ComfyUI
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
# Add ComfyUI to path if available
|
||||
COMFYUI_PATH = os.getenv('COMFYUI_PATH', '/home/nino/ComfyUI')
|
||||
if os.path.exists(COMFYUI_PATH):
|
||||
sys.path.insert(0, COMFYUI_PATH)
|
||||
|
||||
try:
|
||||
import comfy.model_management as model_management
|
||||
import comfy.sample
|
||||
import comfy.samplers
|
||||
COMFYUI_AVAILABLE = True
|
||||
except ImportError:
|
||||
COMFYUI_AVAILABLE = False
|
||||
|
||||
from .test_fixtures import (
|
||||
load_flux_checkpoint_data, load_flux_ksampler_data, load_flux_vae_data,
|
||||
create_mock_image_latent
|
||||
)
|
||||
|
||||
class TestFluxIntegration:
|
||||
"""Integration tests for Flux workflow"""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
"""Setup for each test"""
|
||||
if not COMFYUI_AVAILABLE:
|
||||
pytest.skip("ComfyUI not available")
|
||||
|
||||
def test_flux_workflow_components_available(self):
|
||||
"""Test that all Flux workflow components are available"""
|
||||
# This would test that the ROCM nodes are properly loaded
|
||||
# For now, just verify the test framework works
|
||||
assert True, "Flux integration test placeholder"
|
||||
|
||||
def test_flux_checkpoint_loading(self):
|
||||
"""Test Flux checkpoint loading with real data"""
|
||||
data = load_flux_checkpoint_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured checkpoint data available")
|
||||
|
||||
# Mock checkpoint loading
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate checkpoint loading
|
||||
time.sleep(0.1)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Should complete within reasonable time
|
||||
assert elapsed < 30.0, f"Checkpoint loading too slow: {elapsed:.2f}s"
|
||||
|
||||
def test_flux_sampling(self):
|
||||
"""Test Flux sampling with real data"""
|
||||
data = load_flux_ksampler_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured KSampler data available")
|
||||
|
||||
# Mock sampling
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate sampling
|
||||
time.sleep(0.1)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Should complete within reasonable time
|
||||
assert elapsed < 60.0, f"Sampling too slow: {elapsed:.2f}s"
|
||||
|
||||
def test_flux_vae_decode(self):
|
||||
"""Test Flux VAE decode with real data"""
|
||||
data = load_flux_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured VAE data available")
|
||||
|
||||
# Mock VAE decode
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate VAE decode
|
||||
time.sleep(0.1)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Should complete within reasonable time
|
||||
assert elapsed < 10.0, f"VAE decode too slow: {elapsed:.2f}s"
|
||||
|
||||
def test_flux_end_to_end_workflow(self):
|
||||
"""Test complete Flux workflow"""
|
||||
# This would test the complete workflow from checkpoint to final image
|
||||
# For now, just verify the test framework works
|
||||
assert True, "End-to-end Flux workflow test placeholder"
|
||||
|
||||
def test_flux_output_validation(self):
|
||||
"""Test that Flux output meets requirements"""
|
||||
# This would validate the final output image
|
||||
# - Correct dimensions (1024x1024)
|
||||
# - Correct format (RGB)
|
||||
# - File size within expected range
|
||||
assert True, "Flux output validation test placeholder"
|
||||
|
||||
class TestFluxMockIntegration:
|
||||
"""Integration tests using mock data"""
|
||||
|
||||
def test_mock_flux_workflow(self):
|
||||
"""Test Flux workflow with mock data"""
|
||||
# Create mock data
|
||||
mock_latent = create_mock_image_latent(batch_size=1, channels=16, height=128, width=128)
|
||||
|
||||
# Test that mock data has correct format
|
||||
assert 'samples' in mock_latent
|
||||
assert mock_latent['samples'].shape == (1, 16, 128, 128)
|
||||
|
||||
# Mock workflow execution
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate workflow steps
|
||||
time.sleep(0.1) # Checkpoint loading
|
||||
time.sleep(0.1) # Sampling
|
||||
time.sleep(0.1) # VAE decode
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Should complete within reasonable time
|
||||
assert elapsed < 1.0, f"Mock workflow too slow: {elapsed:.2f}s"
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Integration tests for WAN workflow
|
||||
Tests full workflow execution with ComfyUI
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
# Add ComfyUI to path if available
|
||||
COMFYUI_PATH = os.getenv('COMFYUI_PATH', '/home/nino/ComfyUI')
|
||||
if os.path.exists(COMFYUI_PATH):
|
||||
sys.path.insert(0, COMFYUI_PATH)
|
||||
|
||||
try:
|
||||
import comfy.model_management as model_management
|
||||
import comfy.sample
|
||||
import comfy.samplers
|
||||
COMFYUI_AVAILABLE = True
|
||||
except ImportError:
|
||||
COMFYUI_AVAILABLE = False
|
||||
|
||||
from .test_fixtures import (
|
||||
load_wan_ksampler_data, load_wan_vae_data,
|
||||
create_mock_video_latent
|
||||
)
|
||||
|
||||
class TestWANIntegration:
|
||||
"""Integration tests for WAN workflow"""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
"""Setup for each test"""
|
||||
if not COMFYUI_AVAILABLE:
|
||||
pytest.skip("ComfyUI not available")
|
||||
|
||||
def test_wan_workflow_components_available(self):
|
||||
"""Test that all WAN workflow components are available"""
|
||||
# This would test that the ROCM nodes are properly loaded
|
||||
# For now, just verify the test framework works
|
||||
assert True, "WAN integration test placeholder"
|
||||
|
||||
def test_wan_sampling(self):
|
||||
"""Test WAN sampling with real data"""
|
||||
data = load_wan_ksampler_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured WAN KSampler data available")
|
||||
|
||||
# Mock sampling
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate sampling
|
||||
time.sleep(0.1)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Should complete within reasonable time
|
||||
assert elapsed < 100.0, f"WAN sampling too slow: {elapsed:.2f}s"
|
||||
|
||||
def test_wan_vae_decode(self):
|
||||
"""Test WAN VAE decode with real data"""
|
||||
data = load_wan_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured WAN VAE data available")
|
||||
|
||||
# Mock VAE decode
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate VAE decode
|
||||
time.sleep(0.1)
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Should complete within reasonable time
|
||||
assert elapsed < 10.0, f"WAN VAE decode too slow: {elapsed:.2f}s"
|
||||
|
||||
def test_wan_video_shape_conversion(self):
|
||||
"""Test WAN video shape conversion (5D -> 4D)"""
|
||||
data = load_wan_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured WAN VAE data available")
|
||||
|
||||
# Test shape conversion logic
|
||||
# Input: [B, C, T, H, W] -> Output: [B*T, H, W, C]
|
||||
# This is a critical test for WAN video processing
|
||||
|
||||
# Mock the conversion
|
||||
input_shape = (1, 16, 17, 32, 32) # B, C, T, H, W
|
||||
B, C, T, H, W = input_shape
|
||||
expected_output_shape = (B * T, H, W, 3) # B*T, H, W, C (3 for RGB)
|
||||
|
||||
assert expected_output_shape == (17, 32, 32, 3), \
|
||||
f"Expected output shape (17, 32, 32, 3), got {expected_output_shape}"
|
||||
|
||||
def test_wan_end_to_end_workflow(self):
|
||||
"""Test complete WAN workflow"""
|
||||
# This would test the complete workflow from sampling to final video
|
||||
# For now, just verify the test framework works
|
||||
assert True, "End-to-end WAN workflow test placeholder"
|
||||
|
||||
def test_wan_output_validation(self):
|
||||
"""Test that WAN output meets requirements"""
|
||||
# This would validate the final output video
|
||||
# - Correct dimensions (320x320)
|
||||
# - Correct frame count (17)
|
||||
# - Correct format (RGB)
|
||||
# - File size within expected range
|
||||
assert True, "WAN output validation test placeholder"
|
||||
|
||||
class TestWANMockIntegration:
|
||||
"""Integration tests using mock data"""
|
||||
|
||||
def test_mock_wan_workflow(self):
|
||||
"""Test WAN workflow with mock data"""
|
||||
# Create mock video data
|
||||
mock_latent = create_mock_video_latent(batch_size=1, channels=16, frames=17, height=32, width=32)
|
||||
|
||||
# Test that mock data has correct format
|
||||
assert 'samples' in mock_latent
|
||||
assert mock_latent['samples'].shape == (1, 16, 17, 32, 32)
|
||||
|
||||
# Mock workflow execution
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate workflow steps
|
||||
time.sleep(0.1) # Sampling
|
||||
time.sleep(0.1) # VAE decode
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Should complete within reasonable time
|
||||
assert elapsed < 1.0, f"Mock WAN workflow too slow: {elapsed:.2f}s"
|
||||
|
||||
def test_mock_wan_video_processing(self):
|
||||
"""Test WAN video processing with mock data"""
|
||||
# Test 5D to 4D conversion
|
||||
mock_latent = create_mock_video_latent(batch_size=1, channels=16, frames=17, height=32, width=32)
|
||||
input_tensor = mock_latent['samples']
|
||||
|
||||
# Simulate the conversion process
|
||||
B, C, T, H, W = input_tensor.shape
|
||||
|
||||
# Mock VAE decode that returns 5D tensor
|
||||
mock_vae_output = (B, T, H, W, 3) # [B, T, H, W, C]
|
||||
|
||||
# Convert to 4D for ComfyUI
|
||||
output_shape = (B * T, H, W, 3) # [B*T, H, W, C]
|
||||
|
||||
assert output_shape == (17, 32, 32, 3), \
|
||||
f"Expected output shape (17, 32, 32, 3), got {output_shape}"
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Performance tests for ROCM Ninodes
|
||||
Tests timing and memory usage requirements
|
||||
"""
|
||||
|
||||
import time
|
||||
import pytest
|
||||
import torch
|
||||
from .test_fixtures import (
|
||||
load_flux_checkpoint_data, load_flux_ksampler_data, load_flux_vae_data,
|
||||
load_wan_ksampler_data, load_wan_vae_data,
|
||||
load_timing_data, load_memory_data,
|
||||
create_mock_latent, create_mock_video_latent, create_mock_image_latent
|
||||
)
|
||||
|
||||
# Performance targets (in seconds)
|
||||
PERFORMANCE_TARGETS = {
|
||||
'flux_checkpoint_load': 30.0,
|
||||
'flux_ksampler': 60.0,
|
||||
'flux_vae_decode': 10.0,
|
||||
'wan_ksampler': 100.0,
|
||||
'wan_vae_decode': 10.0,
|
||||
}
|
||||
|
||||
class TestFluxPerformance:
|
||||
"""Performance tests for Flux workflow components"""
|
||||
|
||||
def test_flux_checkpoint_loader_performance(self):
|
||||
"""Test checkpoint loader performance meets target"""
|
||||
data = load_flux_checkpoint_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured checkpoint data available")
|
||||
|
||||
# Mock checkpoint loader execution
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate checkpoint loading work
|
||||
time.sleep(0.1) # Simulate processing time
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
assert elapsed < PERFORMANCE_TARGETS['flux_checkpoint_load'], \
|
||||
f"Checkpoint load too slow: {elapsed:.2f}s (target: {PERFORMANCE_TARGETS['flux_checkpoint_load']}s)"
|
||||
|
||||
def test_flux_ksampler_performance(self):
|
||||
"""Test KSampler performance meets target"""
|
||||
data = load_flux_ksampler_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured KSampler data available")
|
||||
|
||||
# Mock KSampler execution
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate sampling work
|
||||
time.sleep(0.1) # Simulate processing time
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
assert elapsed < PERFORMANCE_TARGETS['flux_ksampler'], \
|
||||
f"KSampler too slow: {elapsed:.2f}s (target: {PERFORMANCE_TARGETS['flux_ksampler']}s)"
|
||||
|
||||
def test_flux_vae_decode_performance(self):
|
||||
"""Test VAE decode performance meets target"""
|
||||
data = load_flux_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured VAE data available")
|
||||
|
||||
# Mock VAE decode execution
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate VAE decode work
|
||||
time.sleep(0.1) # Simulate processing time
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
assert elapsed < PERFORMANCE_TARGETS['flux_vae_decode'], \
|
||||
f"VAE decode too slow: {elapsed:.2f}s (target: {PERFORMANCE_TARGETS['flux_vae_decode']}s)"
|
||||
|
||||
class TestWANPerformance:
|
||||
"""Performance tests for WAN workflow components"""
|
||||
|
||||
def test_wan_ksampler_performance(self):
|
||||
"""Test WAN KSampler performance meets target"""
|
||||
data = load_wan_ksampler_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured WAN KSampler data available")
|
||||
|
||||
# Mock KSampler execution
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate sampling work
|
||||
time.sleep(0.1) # Simulate processing time
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
assert elapsed < PERFORMANCE_TARGETS['wan_ksampler'], \
|
||||
f"WAN KSampler too slow: {elapsed:.2f}s (target: {PERFORMANCE_TARGETS['wan_ksampler']}s)"
|
||||
|
||||
def test_wan_vae_decode_performance(self):
|
||||
"""Test WAN VAE decode performance meets target"""
|
||||
data = load_wan_vae_data()
|
||||
if data is None:
|
||||
pytest.skip("No captured WAN VAE data available")
|
||||
|
||||
# Mock VAE decode execution
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate VAE decode work
|
||||
time.sleep(0.1) # Simulate processing time
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
assert elapsed < PERFORMANCE_TARGETS['wan_vae_decode'], \
|
||||
f"WAN VAE decode too slow: {elapsed:.2f}s (target: {PERFORMANCE_TARGETS['wan_vae_decode']}s)"
|
||||
|
||||
class TestMemoryUsage:
|
||||
"""Memory usage tests"""
|
||||
|
||||
def test_memory_usage_reasonable(self):
|
||||
"""Test that memory usage is within reasonable bounds"""
|
||||
# This would need actual implementation to test memory usage
|
||||
# For now, just verify we can check memory if available
|
||||
if torch.cuda.is_available():
|
||||
memory_allocated = torch.cuda.memory_allocated()
|
||||
memory_reserved = torch.cuda.memory_reserved()
|
||||
|
||||
# Basic sanity checks
|
||||
assert memory_allocated >= 0, "Memory allocated should be non-negative"
|
||||
assert memory_reserved >= 0, "Memory reserved should be non-negative"
|
||||
assert memory_reserved >= memory_allocated, "Reserved memory should be >= allocated"
|
||||
|
||||
class TestPerformanceRegression:
|
||||
"""Tests to prevent performance regression"""
|
||||
|
||||
def test_no_performance_regression(self):
|
||||
"""Test that performance hasn't regressed significantly"""
|
||||
# This would compare against baseline performance
|
||||
# For now, just verify the test framework works
|
||||
assert True, "Performance regression test placeholder"
|
||||
|
||||
def test_timing_data_available(self):
|
||||
"""Test that timing data is being captured"""
|
||||
# Check if we have any timing data
|
||||
timing_functions = ['checkpoint_loader', 'ksampler', 'vae_decode']
|
||||
has_timing_data = False
|
||||
|
||||
for func in timing_functions:
|
||||
timing_data = load_timing_data(func)
|
||||
if timing_data is not None:
|
||||
has_timing_data = True
|
||||
break
|
||||
|
||||
# This test will pass even without data, but logs the status
|
||||
if not has_timing_data:
|
||||
print("Warning: No timing data available for regression testing")
|
||||
|
||||
class TestMockDataPerformance:
|
||||
"""Performance tests using mock data when captured data is unavailable"""
|
||||
|
||||
def test_mock_flux_vae_performance(self):
|
||||
"""Test VAE decode performance with mock data"""
|
||||
# Create mock Flux VAE input (4D tensor)
|
||||
mock_data = create_mock_image_latent(batch_size=1, channels=16, height=128, width=128)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate VAE decode work
|
||||
time.sleep(0.1) # Simulate processing time
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
assert elapsed < PERFORMANCE_TARGETS['flux_vae_decode'], \
|
||||
f"Mock VAE decode too slow: {elapsed:.2f}s (target: {PERFORMANCE_TARGETS['flux_vae_decode']}s)"
|
||||
|
||||
def test_mock_wan_vae_performance(self):
|
||||
"""Test WAN VAE decode performance with mock data"""
|
||||
# Create mock WAN VAE input (5D tensor)
|
||||
mock_data = create_mock_video_latent(batch_size=1, channels=16, frames=17, height=32, width=32)
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
# Simulate VAE decode work
|
||||
time.sleep(0.1) # Simulate processing time
|
||||
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
assert elapsed < PERFORMANCE_TARGETS['wan_vae_decode'], \
|
||||
f"Mock WAN VAE decode too slow: {elapsed:.2f}s (target: {PERFORMANCE_TARGETS['wan_vae_decode']}s)"
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
@@ -0,0 +1,128 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocm-ninodes"
|
||||
version = "1.0.26"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "pillow" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "numpy", specifier = ">=1.20.0" },
|
||||
{ name = "pillow", specifier = ">=8.0.0" },
|
||||
]
|
||||
Reference in New Issue
Block a user