├── README.md ├── __init__.py ├── __pycache__ └── __init__.cpython-311.pyc ├── after.png ├── before.png ├── prestartup_script.py └── server_patch.py /README.md: -------------------------------------------------------------------------------- 1 | # ComfyUI 启动加速补丁 2 | 3 | ## 简介 4 | 这个补丁插件通过优化 ComfyUI 服务器的节点信息处理机制,显著提升了服务器的性能和响应速度。显著减少浏览器页面初始化等待时间。 5 | 6 | ## 主要特性 7 | - 🚀 节点信息缓存机制 8 | - 🔄 异步节点加载 9 | - 📊 实时加载进度显示 10 | 11 | 12 | ## 工作原理 13 | 1. **节点信息缓存**: 14 | - 使用后台线程监控节点加载 15 | - 将节点信息存储在内存中 16 | - 减少重复计算开销 17 | 18 | 2. **异步加载**: 19 | - 通过后台线程异步处理节点信息 20 | - 不阻塞主服务器线程 21 | - 提供平滑的用户体验 22 | 23 | 3. **进度监控**: 24 | - 实时显示节点加载进度 25 | - 提供详细的加载时间统计 26 | - 方便调试和监控 27 | 28 | ## 效果对比 29 | 30 | ### 安装前 31 | ![安装前](before.png) 32 | *未安装补丁时,页面初始化需要较长时间,节点加载速度慢* 33 | 34 | ### 安装后 35 | ![安装后](after.png) 36 | *安装补丁后,页面初始化时间显著缩短,节点加载速度提升* 37 | 38 | ## 安装方法 39 | 40 | 1. 进入您的 ComfyUI 安装目录下的 `custom_nodes` 文件夹 41 | 2. 执行以下命令: 42 | ``` 43 | git clone https://github.com/LAOGOU-666/Comfyui_StartPatch.git 44 | ``` 45 | 46 | ## 使用说明 47 | 插件会自动运行,无需额外配置。安装后,您将注意到: 48 | - 浏览器页面初始化时间显著减少 49 | - 节点信息加载速度更快 50 | - 服务器响应时间缩短 51 | 52 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | 2 | NODE_CLASS_MAPPINGS = {} 3 | NODE_DISPLAY_NAME_MAPPINGS = {} -------------------------------------------------------------------------------- /__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAOGOU-666/Comfyui_StartPatch/9cab348ab406c87f5f7d323e70dbb68fad93fe64/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAOGOU-666/Comfyui_StartPatch/9cab348ab406c87f5f7d323e70dbb68fad93fe64/after.png -------------------------------------------------------------------------------- /before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAOGOU-666/Comfyui_StartPatch/9cab348ab406c87f5f7d323e70dbb68fad93fe64/before.png -------------------------------------------------------------------------------- /prestartup_script.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | from pathlib import Path 4 | 5 | def find_comfyui_root(): 6 | """查找 ComfyUI 根目录""" 7 | current = Path(__file__).parent 8 | while current.name != 'custom_nodes' and current.parent != current: 9 | current = current.parent 10 | return current.parent if current.name == 'custom_nodes' else None 11 | 12 | def check_and_patch_server(): 13 | """检查并安装服务器补丁""" 14 | try: 15 | # 查找 ComfyUI 根目录 16 | comfyui_root = find_comfyui_root() 17 | if not comfyui_root: 18 | return False 19 | 20 | server_path = comfyui_root / "server.py" 21 | if not server_path.exists(): 22 | return False 23 | 24 | # 读取文件内容 25 | with open(server_path, 'r', encoding='utf-8') as f: 26 | content = f.read() 27 | 28 | # 检查是否已经安装了补丁 29 | if 'import server_patch' in content: 30 | print("[StartPatch] 补丁已经安装") 31 | return True 32 | 33 | # 修改正则表达式模式,匹配到最后一个路由处理函数 34 | pattern = r'( @routes\.post\("/history"\).*?return web\.Response\(status=200\)\n\n)' 35 | match = re.search(pattern, content, re.DOTALL) 36 | 37 | if not match: 38 | return False 39 | 40 | # 准备补丁代码,确保正确的缩进 41 | patch_code = ''' try: 42 | from custom_nodes.Comfyui_StartPatch import server_patch 43 | server_patch.apply_patch(self) 44 | except ImportError: 45 | pass 46 | 47 | ''' 48 | 49 | # 在最后一个路由后添加补丁代码 50 | new_content = content.replace( 51 | match.group(1), 52 | match.group(1) + patch_code 53 | ) 54 | 55 | # 备份原文件 56 | backup_path = server_path.with_suffix('.py.bak') 57 | if backup_path.exists(): 58 | backup_path.unlink() 59 | 60 | with open(backup_path, 'w', encoding='utf-8') as f: 61 | f.write(content) 62 | 63 | # 写回文件 64 | with open(server_path, 'w', encoding='utf-8') as f: 65 | f.write(new_content) 66 | 67 | print("[StartPatch] 补丁安装成功") 68 | return True 69 | 70 | except Exception as e: 71 | print(f"[StartPatch] 补丁安装失败: {str(e)}") 72 | return False 73 | 74 | # 在预启动时自动执行 75 | check_and_patch_server() 76 | -------------------------------------------------------------------------------- /server_patch.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | import logging 4 | import traceback 5 | from pathlib import Path 6 | from aiohttp import web 7 | import nodes 8 | import folder_paths 9 | import threading 10 | 11 | class NodeInfoManager: 12 | """节点信息管理器""" 13 | def __init__(self): 14 | self._cache = {} 15 | self._initialized = False 16 | self._lock = threading.Lock() 17 | self._start_time = None 18 | print("=== 初始化节点信息管理器 ===") 19 | 20 | # 启动后台监控线程 21 | self._monitor_thread = threading.Thread(target=self._monitor_nodes, daemon=True) 22 | self._monitor_thread.start() 23 | 24 | def _monitor_nodes(self): 25 | """监控并处理新加载的节点""" 26 | processed_nodes = set() 27 | last_total = 0 28 | 29 | while True: 30 | try: 31 | if not hasattr(nodes, 'NODE_CLASS_MAPPINGS'): 32 | time.sleep(0.1) 33 | continue 34 | 35 | # 获取新加载的节点 36 | current_nodes = set(nodes.NODE_CLASS_MAPPINGS.keys()) 37 | new_nodes = current_nodes - processed_nodes 38 | 39 | if new_nodes: 40 | if self._start_time is None: 41 | self._start_time = time.time() 42 | 43 | # 处理新节点 44 | for node_class in new_nodes: 45 | self._process_node(node_class, nodes.NODE_CLASS_MAPPINGS[node_class]) 46 | processed_nodes.add(node_class) 47 | 48 | # 当节点数量发生变化时显示进度 49 | current_total = len(nodes.NODE_CLASS_MAPPINGS) 50 | if current_total != last_total and current_total > 0: 51 | elapsed = time.time() - self._start_time if self._start_time else 0 52 | 53 | print(f"进度: {current_total}/{current_total} (100.0%)") 54 | print(f" - 已完成节点信息收集 (已加载节点: {current_total})") 55 | print(f" - 总耗时: {elapsed:.1f} 秒") 56 | 57 | last_total = current_total 58 | 59 | # 增加额外的延迟以等待所有节点加载 60 | if current_total > 0: 61 | time.sleep(0.1) 62 | else: 63 | time.sleep(0.1) 64 | 65 | except Exception as e: 66 | logging.error(f"节点监控出错: {str(e)}") 67 | logging.error(traceback.format_exc()) 68 | time.sleep(1) 69 | 70 | def _process_node(self, node_class, obj_class): 71 | """处理单个节点信息""" 72 | try: 73 | info = {} 74 | info['input'] = obj_class.INPUT_TYPES() 75 | info['input_order'] = {key: list(value.keys()) for (key, value) in obj_class.INPUT_TYPES().items()} 76 | info['output'] = obj_class.RETURN_TYPES 77 | info['output_is_list'] = obj_class.OUTPUT_IS_LIST if hasattr(obj_class, 'OUTPUT_IS_LIST') else [False] * len(obj_class.RETURN_TYPES) 78 | info['output_name'] = obj_class.RETURN_NAMES if hasattr(obj_class, 'RETURN_NAMES') else info['output'] 79 | info['name'] = node_class 80 | info['display_name'] = nodes.NODE_DISPLAY_NAME_MAPPINGS.get(node_class, node_class) 81 | info['description'] = getattr(obj_class, 'DESCRIPTION', '') 82 | info['python_module'] = getattr(obj_class, "RELATIVE_PYTHON_MODULE", "nodes") 83 | info['category'] = getattr(obj_class, 'CATEGORY', 'sd') 84 | info['output_node'] = hasattr(obj_class, 'OUTPUT_NODE') and obj_class.OUTPUT_NODE 85 | 86 | if hasattr(obj_class, 'OUTPUT_TOOLTIPS'): 87 | info['output_tooltips'] = obj_class.OUTPUT_TOOLTIPS 88 | if getattr(obj_class, "DEPRECATED", False): 89 | info['deprecated'] = True 90 | if getattr(obj_class, "EXPERIMENTAL", False): 91 | info['experimental'] = True 92 | 93 | with self._lock: 94 | self._cache[node_class] = info 95 | 96 | return True 97 | except Exception as e: 98 | logging.error(f"处理节点 {node_class} 时出错: {str(e)}") 99 | logging.error(traceback.format_exc()) 100 | return False 101 | 102 | def initialize(self): 103 | """初始化触发函数(实际初始化在后台进行)""" 104 | pass 105 | 106 | def get_node_info(self, node_class): 107 | """获取单个节点的信息""" 108 | return self._cache.get(node_class) 109 | 110 | def get_all_nodes_info(self): 111 | """获取所有节点的信息""" 112 | return self._cache 113 | 114 | # 创建全局实例 115 | node_info_manager = NodeInfoManager() 116 | 117 | def create_node_info(): 118 | """创建节点信息处理函数""" 119 | def node_info(node_class): 120 | """获取单个节点的信息""" 121 | obj_class = nodes.NODE_CLASS_MAPPINGS[node_class] 122 | info = {} 123 | info['input'] = obj_class.INPUT_TYPES() 124 | info['input_order'] = {key: list(value.keys()) for (key, value) in obj_class.INPUT_TYPES().items()} 125 | info['output'] = obj_class.RETURN_TYPES 126 | info['output_is_list'] = obj_class.OUTPUT_IS_LIST if hasattr(obj_class, 'OUTPUT_IS_LIST') else [False] * len(obj_class.RETURN_TYPES) 127 | info['output_name'] = obj_class.RETURN_NAMES if hasattr(obj_class, 'RETURN_NAMES') else info['output'] 128 | info['name'] = node_class 129 | info['display_name'] = nodes.NODE_DISPLAY_NAME_MAPPINGS[node_class] if node_class in nodes.NODE_DISPLAY_NAME_MAPPINGS.keys() else node_class 130 | info['description'] = obj_class.DESCRIPTION if hasattr(obj_class,'DESCRIPTION') else '' 131 | info['python_module'] = getattr(obj_class, "RELATIVE_PYTHON_MODULE", "nodes") 132 | info['category'] = 'sd' 133 | 134 | if hasattr(obj_class, 'OUTPUT_NODE') and obj_class.OUTPUT_NODE == True: 135 | info['output_node'] = True 136 | else: 137 | info['output_node'] = False 138 | 139 | if hasattr(obj_class, 'CATEGORY'): 140 | info['category'] = obj_class.CATEGORY 141 | 142 | if hasattr(obj_class, 'OUTPUT_TOOLTIPS'): 143 | info['output_tooltips'] = obj_class.OUTPUT_TOOLTIPS 144 | 145 | if getattr(obj_class, "DEPRECATED", False): 146 | info['deprecated'] = True 147 | if getattr(obj_class, "EXPERIMENTAL", False): 148 | info['experimental'] = True 149 | return info 150 | return node_info 151 | 152 | def create_patched_routes(): 153 | """创建补丁后的路由处理函数""" 154 | original_node_info = create_node_info() 155 | 156 | async def get_object_info(request): 157 | """处理 /object_info 路由""" 158 | if not node_info_manager._initialized: 159 | # 如果缓存未初始化,使用原始处理函数 160 | out = {} 161 | for x in nodes.NODE_CLASS_MAPPINGS: 162 | try: 163 | out[x] = original_node_info(x) 164 | except Exception: 165 | logging.error(f"[ERROR] An error occurred while retrieving information for the '{x}' node.") 166 | logging.error(traceback.format_exc()) 167 | return web.json_response(out) 168 | return web.json_response(node_info_manager.get_all_nodes_info()) 169 | 170 | async def get_object_info_node(request): 171 | """处理 /object_info/{node_class} 路由""" 172 | node_class = request.match_info.get("node_class", None) 173 | out = {} 174 | if node_class is not None and node_class in nodes.NODE_CLASS_MAPPINGS: 175 | if not node_info_manager._initialized: 176 | # 如果缓存未初始化,使用原始处理方式 177 | out[node_class] = original_node_info(node_class) 178 | else: 179 | info = node_info_manager.get_node_info(node_class) 180 | if info: 181 | out[node_class] = info 182 | return web.json_response(out) 183 | 184 | return get_object_info, get_object_info_node 185 | 186 | def apply_patch(server_instance): 187 | """应用补丁到服务器实例""" 188 | try: 189 | # 初始化节点信息 190 | node_info_manager.initialize() 191 | 192 | # 创建新的路由处理函数 193 | get_object_info, get_object_info_node = create_patched_routes() 194 | 195 | # 获取路由装饰器 196 | routes = server_instance.routes 197 | 198 | # 使用路由装饰器的原始方法添加路由 199 | if hasattr(routes, 'original_routes'): 200 | # HotReloadHack 环境 201 | routes = routes.original_routes 202 | 203 | # 使用装饰器语法添加路由 204 | routes.get("/object_info")(get_object_info) 205 | routes.get("/object_info/{node_class}")(get_object_info_node) 206 | 207 | print("Server 补丁已应用") 208 | 209 | except Exception as e: 210 | logging.error(f"应用补丁时出错: {str(e)}") 211 | logging.error(traceback.format_exc()) 212 | print("补丁应用失败,将使用原始路由处理") 213 | 214 | if __name__ == "__main__": 215 | patch_server() --------------------------------------------------------------------------------