diff --git a/README.md b/README.md index 2a4e63d..f96ff93 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,15 @@ A custom node for ComfyUI that dynamically manages RAM usage by intelligently co - **Dual Cache Management Modes**: - CLASSIC (No Eviction): Standard cache behavior with no automatic purging - RAM_PRESSURE (Auto Purge): Smart cache purging when system memory falls below the threshold +- **RAM Cache Extreme Cleanup (🧹)**: Performs a one-time aggressive purge and automatically restores the previous mode and threshold. - **Configurable Memory Threshold**: Fine-tune the minimum amount of free RAM to maintain -- **Seamless Cache Migration**: Preserves essential cache data when switching between modes -- **Real-time Memory Monitoring**: Continuously checks system RAM availability +- **Seamless Cache Migration**: Preserves aerospace cache data when switching between modes +- **Real-time Memory Monitoring**: Continuously checks system RAM and virtual memory availability ## Installation 1. Clone or download this repository -2. Place the `ComfyUI_Dynamic_RAMCache` folder in your ComfyUI `custom_nodes` directory +2. Place the `ComfyUI_Dynamic-RAMCache` folder in your ComfyUI `custom_nodes` directory 3. Restart ComfyUI ## Usage @@ -35,7 +36,18 @@ A custom node for ComfyUI that dynamically manages RAM usage by intelligently co - Default: 2.0 GB - Determines the minimum amount of free RAM to maintain (in gigabytes) +### Extreme Cleanup Parameters + +- **purge_threshold**: + - Range: 0.1 - 256.0 GB + - Default: 256.0 GB + - Temporary headroom used for the one-shot purge + +- **restore behavior**: + - Automatically restores the mode and threshold that were active before cleanup + 3. Connect the node anywhere in your workflow - it will continuously monitor and manage cache based on your settings +4. Optionally add the "RAMCacheExtremeCleanup" node at the end of a workflow to trigger a one-shot cleanup and restore the previous state ## How It Works diff --git a/README_zh.md b/README_zh.md index 7219a4e..5a2cd99 100644 --- a/README_zh.md +++ b/README_zh.md @@ -7,16 +7,17 @@ ## 功能特点 - **动态缓存模式切换**:支持在CLASSIC(无内存回收)和RAM_PRESSURE(自动内存清理)模式之间切换 -- **智能内存管理**:自动监控和清理RAM缓存,当内存不足时释放不必要的缓存数据 +- **智能内存管理**:自动监控和清理 RAM 缓存,当内存不足时释放不必要的缓存数据 - **自定义清理阈值**:允许用户设置最小空闲内存保持量,灵活适应不同硬件配置 - **无缝数据迁移**:在切换缓存模式时保留现有缓存数据,避免重复计算 - **直观的节点接口**:简单易用的参数设置,适用于不同水平的用户 +- **极限清理节点**:一次性强清理(包含虚拟内存占用)并自动恢复清理前的模式与阈值 ## 安装方法 1. 确保已安装ComfyUI(支持2025.10.31及更新版本) 2. 克隆或下载本仓库 -3. 将`ComfyUI_Dynamic_RAMCache`文件夹复制到ComfyUI的`custom_nodes`目录下 +3. 将`ComfyUI_Dynamic-RAMCache`文件夹复制到ComfyUI的`custom_nodes`目录下 4. 重启ComfyUI ## 使用方法 @@ -26,7 +27,8 @@ - **mode**:选择缓存模式(CLASSIC或RAM_PRESSURE) - **cleanup_threshold**:设置最小空闲内存保持量(GB) 3. 可以选择性地连接任意输入到`any_input`端口(节点会透传此输入) -4. 运行工作流 +4. 可选:在工作流末尾添加`RAMCacheExtremeCleanup`节点,执行一次性清理并恢复之前状态 +5. 运行工作流 ## 参数说明 @@ -41,6 +43,11 @@ - 默认值:2.0 GB - 说明:系统尝试维持的最小空闲内存量。当可用内存低于此值时,RAM_PRESSURE模式会触发缓存清理 +### 极限清理参数 + +- **purge_threshold**:一次性清理时使用的临时阈值(默认 256.0 GB) +- **恢复行为**:自动恢复清理前的模式与阈值 + ### 输出 - **output_passthrough**:如果连接了输入,则透传输入值;否则返回None @@ -98,4 +105,4 @@ ## 贡献 -欢迎提交问题报告和拉取请求! \ No newline at end of file +欢迎提交问题报告和拉取请求! diff --git a/__init__.py b/__init__.py index 5cf7819..add2ee9 100644 --- a/__init__.py +++ b/__init__.py @@ -1,11 +1,13 @@ -from .nodes import DynamicRAMCacheControl +from .nodes import DynamicRAMCacheControl, RAMCacheExtremeCleanup NODE_CLASS_MAPPINGS = { - "DynamicRAMCacheControl": DynamicRAMCacheControl + "DynamicRAMCacheControl": DynamicRAMCacheControl, + "RAMCacheExtremeCleanup": RAMCacheExtremeCleanup } NODE_DISPLAY_NAME_MAPPINGS = { - "DynamicRAMCacheControl": "🔥 Dynamic RAM Cache Control" + "DynamicRAMCacheControl": "🔥 Dynamic RAM Cache Control", + "RAMCacheExtremeCleanup": "🧹 RAM Cache Extreme Cleanup" } -__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] \ No newline at end of file +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/nodes.py b/nodes.py index 29a1a49..18b138e 100644 --- a/nodes.py +++ b/nodes.py @@ -77,11 +77,7 @@ class DynamicRAMCacheControl: target_mode_ram = "RAM_PRESSURE" in mode - executor = None - for obj in gc.get_objects(): - if obj.__class__.__name__ == 'PromptExecutor': - executor = obj - break + executor = self._find_executor() if executor is None: logging.warning("[DynamicRAMCache] PromptExecutor not found.") @@ -93,14 +89,8 @@ class DynamicRAMCacheControl: old_ram_arg = executor.cache_args.get('ram', 0) executor.cache_args['ram'] = cleanup_threshold - if not hasattr(executor, 'caches'): - logging.warning("[DynamicRAMCache] PromptExecutor has no 'caches' attribute.") - return - - cache_set = executor.caches - - if not hasattr(cache_set, 'outputs'): - logging.warning("[DynamicRAMCache] CacheSet has no 'outputs' attribute.") + cache_set = self._get_cache_set(executor) + if cache_set is None: return current_cache = cache_set.outputs @@ -138,6 +128,24 @@ class DynamicRAMCacheControl: except Exception: pass + def _find_executor(self): + for obj in gc.get_objects(): + if obj.__class__.__name__ == 'PromptExecutor': + return obj + return None + + def _get_cache_set(self, executor): + if not hasattr(executor, 'caches'): + logging.warning("[DynamicRAMCache] PromptExecutor has no 'caches' attribute.") + return None + + cache_set = executor.caches + + if not hasattr(cache_set, 'outputs'): + logging.warning("[DynamicRAMCache] CacheSet has no 'outputs' attribute.") + return None + return cache_set + def _update_cache_set(self, cache_set, new_cache): cache_set.outputs = new_cache @@ -184,4 +192,55 @@ class DynamicRAMCacheControl: new_cache.subcaches = old_cache.subcaches new_cache.dynprompt = getattr(old_cache, 'dynprompt', None) new_cache.cache_key_set = getattr(old_cache, 'cache_key_set', None) - new_cache.initialized = getattr(old_cache, 'initialized', False) \ No newline at end of file + new_cache.initialized = getattr(old_cache, 'initialized', False) + +class RAMCacheExtremeCleanup(DynamicRAMCacheControl): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "purge_threshold": ("FLOAT", {"default": 256.0, "min": 0.1, "max": 256.0, "step": 0.1, "tooltip": "Minimum free RAM to maintain (GB)"}), + }, + "optional": { + "any_input": (any_type, {}), + } + } + + RETURN_TYPES = (any_type,) + RETURN_NAMES = ("output_passthrough",) + FUNCTION = "extreme_cleanup" + CATEGORY = "utils/dynamic_ramcache" + + def extreme_cleanup(self, purge_threshold, any_input=None): + if caching is not None and execution is not None: + executor = self._find_executor() + if executor is None: + logging.warning("[DynamicRAMCache] PromptExecutor not found.") + else: + if not hasattr(executor, 'cache_args'): + executor.cache_args = {} + old_ram_arg = executor.cache_args.get('ram', 2.0) + cache_set = self._get_cache_set(executor) + if cache_set is not None: + RAMPressureCacheClass = getattr(caching, 'RAMPressureCache', None) + if RAMPressureCacheClass: + is_currently_ram = isinstance(cache_set.outputs, RAMPressureCacheClass) + old_mode = "RAM_PRESSURE (Auto Purge)" if is_currently_ram else "CLASSIC (No Eviction)" + else: + old_mode = "CLASSIC (No Eviction)" + self._execute_cache_logic("RAM_PRESSURE (Auto Purge)", purge_threshold) + self._execute_cache_logic(old_mode, old_ram_arg) + else: + logging.warning("[DynamicRAMCache] Plugin disabled: Missing internal modules.") + + if any_input is not None: + return (any_input,) + else: + try: + from comfy_execution.graph import ExecutionBlocker + return (ExecutionBlocker(None),) + except ImportError: + return (None,)