【笔记】ComfyUI 源码部署版更新后一键修复:从手动补丁到自动化工作流
适用环境 :Windows 10/11 + NVIDIA RTX 30/40 系列 + Python 虚拟环境
核心痛点 :每次从源码压缩包全量更新 ComfyUI 后,需要手动重装依赖、逐个运行补丁脚本、修改 main.py,过程繁琐且容易遗漏
解决方案 :
comfyui_post_update.py一键修复脚本,将更新后的所有修复操作自动化
ComfyUI 源码最新版------ v0.24.0 (截止 2026 年 06月 04 日)https://github.com/Comfy-Org/ComfyUI/releases/tag/v0.24.0
ComfyUI 消除 cu130 警告并强制开启 comfy-kitchen triton/ eager/ cuda全后端加速:原理与实战【含一键补丁】
ComfyUI MediaPipe 猴子补丁终极完善版:补全上下文管理与姿态检测兼容
【技术分享】ComfyUI中protobuf版本兼容性问题的优雅解决方案:猴子补丁实战
一、背景:为什么需要这个工作流
ComfyUI 的源码部署版(非便携包)更新方式通常有两种:
git pull------ 适合有 Git 环境的用户,但可能遇到合并冲突- 下载最新源码压缩包,解压后全量替换 ------ 适合无 Git 环境或希望完全干净的更新

第二种方式虽然简单,但每次更新后会丢失所有手动修改:
- 已安装的 Python 依赖需要重新
pip install - 手动打的源码补丁(如 comfy-kitchen 解锁、CSDN 博客补丁等)需要重新应用
main.py中的自定义修改(如监听地址、导入路径、版本门槛等)需要重新修改
1.1 我的具体补丁清单
在我的环境中,每次更新后需要重新应用以下补丁:
| 补丁来源 | 作用 | 修改文件 |
|---|---|---|
| CSDN 博客 161630960 | 自定义节点路径修复等 | 多个文件 |
| CSDN 博客 161632686 | 模型加载优化等 | 多个文件 |
comfy_kitchen_patcher.py |
消除 cu130 警告 + 解锁 CUDA/Triton 后端 | quant_ops.py, registry.py, cuda/__init__.py |
| 手动修改 | 添加 MediaPipe 代理兼容层导入 | main.py 顶部 |
| 手动修改 | 降低动态 VRAM 的 PyTorch 版本门槛(2.8 → 2.7) | main.py 约第 222 行 |
每次更新后手动重复这些操作非常耗时,且容易遗漏某一步导致启动报错。
二、核心原理:三道"安检锁"与解锁逻辑
2.1 comfy-kitchen 的三道锁
comfy-kitchen 是 Comfy-Org 官方推出的高性能算子库,负责 FP8/FP4 量化、低精度 GEMM、算子融合等优化。但源码中存在三道拦截机制,导致 RTX 30/40 系 + CUDA 12.x 环境被误判为"不兼容":
第一锁:版本锁(quant_ops.py)
python
# 约第 22 行
if cuda_version < (13,):
warnings.warn("You need pytorch with cu130 or higher...")
PyTorch 稳定版在 Windows 上最高仅提供 CUDA 12.6/12.8,因此 cuda_version 永远小于 (13,),触发警告并跳过优化初始化。这是一个过度保守的前置检查。
第二锁:后端禁用锁(registry.py)
python
def disable(self, backend_name):
self._disabled.add(backend_name) # 加入黑名单
def is_available(self, backend_name):
return backend_name in self._backends and backend_name not in self._disabled
当版本检查或算力检查失败时,会调用 registry.disable("cuda") 将后端永久加入黑名单。即使后续修复了触发条件,黑名单状态依然持续生效。
第三锁:算力门槛锁(backends/cuda/__init__.py)
python
min_compute_capability = (10, 0) # Blackwell 架构门槛
CUDA Compute Capability 10.0 对应 RTX 50 系列(尚未发布)。当前主流的 RTX 3090(sm_86,算力 8.6)和 RTX 4090(sm_89,算力 8.9)远低于此门槛,被源码判定为"硬件不支持"。
2.2 解锁方案
通过精准修改三处源码:
quant_ops.py:将cuda_version < (13,)替换为恒假条件,消除警告registry.py:让disable()空实现,is_available()忽略_disabled检查cuda/__init__.py:降低min_compute_capability到(8, 6),并注入 Windows DLL 搜索路径
三、工具设计:从手动到自动
详细改动及使用原理请见:ComfyUI 消除 cu130 警告并强制开启 comfy-kitchen triton/ eager/ cuda全后端加速:原理与实战【含一键补丁】
3.1 补丁脚本:comfy_kitchen_patcher.py
该脚本封装了 comfy-kitchen 的三处源码修改,提供自动路径发现、原子级备份、试运行、一键恢复等功能。
核心特性:
- 自动从当前目录向上搜索 ComfyUI 根目录
- 自动检测
.venv或python_embeded虚拟环境 - 修改前自动创建
.comfy_kitchen_backup备份 - 多策略匹配(正则 + 字符串),兼容不同代码版本
- 全英文输出,避免 Windows cmd 编码问题
使用方法:
bash
# 自动查找并应用
python comfy_kitchen_patcher.py
# 指定 ComfyUI 目录
python comfy_kitchen_patcher.py --comfyui-root "H:\PythonProjects3\Win_ComfyUI"
# 试运行(只打印不修改)
python comfy_kitchen_patcher.py --dry-run
# 恢复备份
python comfy_kitchen_patcher.py --restore
脚本源码 (升级版comfy_kitchen_patcher.py):
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ComfyUI comfy-kitchen Full Backend Unlock Patch
===============================================
Features:
1. Remove "pytorch with cu130 or higher" warning
2. Force-enable comfy_kitchen CUDA / Triton backends
3. Support RTX 30/40 series (compute capability 8.6/8.9)
4. Auto backup & restore
Usage:
python comfy_kitchen_patcher.py --comfyui-root "H:\\PythonProjects3\\Win_ComfyUI"
python comfy_kitchen_patcher.py --restore
"""
import os
import sys
import shutil
import re
import argparse
from pathlib import Path
from typing import List, Optional
# =============================================================================
# Console Output (English only, safe for Windows cmd cp1252/gbk)
# =============================================================================
class C:
H = '\033[95m' # Header
B = '\033[94m' # Blue
C = '\033[96m' # Cyan
G = '\033[92m' # Green
W = '\033[93m' # Warning
F = '\033[91m' # Fail
E = '\033[0m' # End
D = '\033[1m' # Bold
def p(text: str, color: str = ""):
"""Print with color, flush immediately."""
print(f"{color}{text}{C.E}", flush=True)
# =============================================================================
# Core Patcher
# =============================================================================
class Patcher:
SUFFIX = ".comfy_kitchen_backup"
def __init__(self, comfyui_root: Optional[str] = None,
python_env: Optional[str] = None,
dry_run: bool = False):
self.dry_run = dry_run
self.patched_files: List[Path] = []
self.backup_files: List[Path] = []
if comfyui_root:
self.root = Path(comfyui_root).resolve()
else:
self.root = self._find_comfyui()
if python_env:
self.py_env = Path(python_env)
else:
self.py_env = Path(sys.executable).parent
self.site = self._find_site_packages()
def _find_comfyui(self) -> Path:
current = Path.cwd()
for _ in range(5):
if (current / "comfy" / "quant_ops.py").exists():
return current
if current.parent == current:
break
current = current.parent
candidates = [
Path("H:/PythonProjects3/Win_ComfyUI"),
Path("C:/ComfyUI"),
Path.home() / "ComfyUI",
Path.home() / "Documents" / "ComfyUI",
]
for c in candidates:
if (c / "comfy" / "quant_ops.py").exists():
return c
raise FileNotFoundError("Cannot auto-locate ComfyUI root. Use --comfyui-root.")
def _find_site_packages(self) -> Path:
py_ver = f"{sys.version_info.major}.{sys.version_info.minor}"
candidates = [
self.py_env / "Lib" / "site-packages",
self.py_env / "lib" / f"python{py_ver}" / "site-packages",
self.py_env / "lib" / "site-packages",
]
for sp in candidates:
if sp.exists():
return sp
for p in sys.path:
if "site-packages" in p and Path(p).exists():
return Path(p)
raise FileNotFoundError("Cannot locate site-packages.")
def _find_ck(self) -> Path:
ck = self.site / "comfy_kitchen"
if ck.exists() and (ck / "registry.py").exists():
return ck
try:
import comfy_kitchen
return Path(comfy_kitchen.__file__).parent
except ImportError:
pass
raise FileNotFoundError(f"comfy_kitchen not found in {self.site}. Run: pip install comfy-kitchen")
def _backup(self, fp: Path) -> Path:
bp = fp.with_suffix(fp.suffix + self.SUFFIX)
if not bp.exists():
if not self.dry_run:
shutil.copy2(fp, bp)
self.backup_files.append(bp)
p(f"[BACKUP] {fp.name} -> {bp.name}", C.B)
else:
p(f"[SKIP] Backup exists: {bp.name}", C.W)
return bp
def _restore_file(self, fp: Path) -> bool:
bp = fp.with_suffix(fp.suffix + self.SUFFIX)
if not bp.exists():
backups = list(fp.parent.glob(f"{fp.name}*{self.SUFFIX}"))
if backups:
bp = backups[0]
if bp.exists():
if not self.dry_run:
shutil.copy2(bp, fp)
p(f"[RESTORE] {fp.name} <- {bp.name}", C.G)
return True
p(f"[WARN] No backup for: {fp.name}", C.W)
return False
def patch_quant_ops(self) -> bool:
target = self.root / "comfy" / "quant_ops.py"
if not target.exists():
p(f"[ERROR] Not found: {target}", C.F)
return False
self._backup(target)
content = target.read_text(encoding='utf-8')
orig = content
patched = False
patterns = [
(r'if\s+cuda_version\s*<\s*\(\s*13\s*,\s*\)\s*:', 'if False: # PATCHED: bypass cu130 check'),
(r'if\s+cuda_version\s*<\s*\(13,\):', 'if False: # PATCHED: bypass cu130 check'),
(r'if\s+cuda_version\s*<\s*\(13,\s*0\):', 'if False: # PATCHED: bypass cu130 check'),
]
for pattern, replacement in patterns:
if re.search(pattern, content):
content = re.sub(pattern, replacement, content, count=1)
patched = True
break
if not patched and 'cuda_version < (13' in content:
content = content.replace('cuda_version < (13,)', 'False # PATCHED: bypass cu130 check')
content = content.replace('cuda_version < (13, 0)', 'False # PATCHED: bypass cu130 check')
patched = True
if not patched:
p("[INFO] quant_ops.py: version check not found (may already be patched)", C.W)
return False
if content != orig:
if not self.dry_run:
target.write_text(content, encoding='utf-8')
p("[PATCH] quant_ops.py - cu130 check disabled", C.G)
self.patched_files.append(target)
return True
return False
def patch_registry(self) -> bool:
try:
ck = self._find_ck()
except FileNotFoundError as e:
p(f"[ERROR] {e}", C.F)
return False
target = ck / "registry.py"
if not target.exists():
p(f"[ERROR] Not found: {target}", C.F)
return False
self._backup(target)
content = target.read_text(encoding='utf-8')
orig = content
disable_re = re.compile(
r'(def\s+disable\s*\(\s*self\s*,\s*backend_name\s*\)\s*:[^\n]*\n)'
r'(\s+)'
r'(self\._disabled\.add\s*\(\s*backend_name\s*\)\s*)',
re.MULTILINE
)
def repl_disable(m):
indent = m.group(2)
return f"{m.group(1)}{indent}pass # PATCHED: force keep backend enabled\n{indent}# original: {m.group(3).strip()}"
new_content, count = disable_re.subn(repl_disable, content)
if count == 0 and 'self._disabled.add(backend_name)' in content:
new_content = content.replace(
'self._disabled.add(backend_name)',
'pass # PATCHED: force keep backend enabled\n # original: self._disabled.add(backend_name)'
)
count = 1
if count == 0:
p("[INFO] registry.py: disable() not found (may already be patched)", C.W)
return False
avail_re = re.compile(
r'return\s+backend_name\s+in\s+self\._backends\s+and\s+backend_name\s+not\s+in\s+self\._disabled'
)
new_content, count2 = avail_re.subn(
'return backend_name in self._backends # PATCHED: ignore _disabled blacklist',
new_content
)
if count2 == 0 and 'backend_name not in self._disabled' in new_content:
new_content = new_content.replace(
'backend_name in self._backends and backend_name not in self._disabled',
'backend_name in self._backends # PATCHED: ignore _disabled blacklist'
)
count2 = 1
list_re = re.compile(r'("disabled"\s*:\s*)(backend_name\s+in\s+self\._disabled)')
new_content, count3 = list_re.subn(r'\1False # PATCHED: always report enabled', new_content)
if new_content != orig:
if not self.dry_run:
target.write_text(new_content, encoding='utf-8')
p("[PATCH] registry.py - backend blacklist disabled", C.G)
self.patched_files.append(target)
return True
return False
def patch_cuda_backend(self) -> bool:
try:
ck = self._find_ck()
except FileNotFoundError as e:
p(f"[ERROR] {e}", C.F)
return False
target = ck / "backends" / "cuda" / "__init__.py"
if not target.exists():
p(f"[ERROR] Not found: {target}", C.F)
return False
self._backup(target)
content = target.read_text(encoding='utf-8')
orig = content
dll_marker = "# === PATCHED: Add CUDA DLL paths for Windows ==="
if dll_marker not in content:
dll_block = (
'# === PATCHED: Add CUDA DLL paths for Windows ===\n'
'import os as _os\n'
'import sys as _sys\n'
'if _sys.platform == "win32":\n'
' _cuda_dll_paths = [\n'
' r"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v13.1\\bin",\n'
' r"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v13.0\\bin",\n'
' r"C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v12.8\\bin",\n'
' ]\n'
' for _p in _cuda_dll_paths:\n'
' if _os.path.exists(_p):\n'
' _os.add_dll_directory(_p)\n'
' break\n'
'# === END PATCH ===\n'
'\n'
)
lines = content.split('\n')
insert_idx = 0
for i, line in enumerate(lines):
stripped = line.strip()
if stripped and not stripped.startswith('#'):
insert_idx = i
break
lines.insert(insert_idx, dll_block.rstrip())
content = '\n'.join(lines)
if 'min_compute_capability=(10, 0)' in content:
content = content.replace(
'min_compute_capability=(10, 0)',
'min_compute_capability=(8, 6) # PATCHED: support RTX 30/40 series (sm86/89)'
)
else:
content = re.sub(
r'min_compute_capability\s*=\s*\(\s*10\s*,\s*0\s*\)',
'min_compute_capability=(8, 6) # PATCHED: support RTX 30/40 series (sm86/89)',
content
)
if content != orig:
if not self.dry_run:
target.write_text(content, encoding='utf-8')
p("[PATCH] cuda/__init__.py - compute capability lowered + DLL paths added", C.G)
self.patched_files.append(target)
return True
return False
def run(self) -> bool:
p("=" * 65, C.H)
p(" ComfyUI comfy-kitchen Full Backend Unlock Patch", C.H + C.D)
p("=" * 65, C.H)
p(f" ComfyUI Root : {self.root}", C.C)
p(f" Python Env : {self.py_env}", C.C)
p(f" Site-Packages: {self.site}", C.C)
p(f" Dry Run : {self.dry_run}", C.C)
p("")
results = []
results.append(("quant_ops.py (remove cu130 warning)", self.patch_quant_ops()))
results.append(("registry.py (unlock backend blacklist)", self.patch_registry()))
results.append(("cuda/__init__.py (lower compute cap)", self.patch_cuda_backend()))
p("")
p("-" * 65, C.H)
p("Patch Summary:", C.D)
all_ok = all(r[1] for r in results)
for name, ok in results:
status = "OK" if ok else "SKIP/ALREADY_PATCHED"
color = C.G if ok else C.W
p(f" [{name}] {status}", color)
p("")
if all_ok:
p("[DONE] All patches applied. Restart ComfyUI to verify.", C.G)
p("Expected log lines:", C.C)
p(" [INFO] Found comfy_kitchen backend cuda: ... 'disabled': False ...", C.C)
p(" [INFO] Found comfy_kitchen backend triton: ... 'disabled': False ...", C.C)
else:
p("[INFO] Some patches skipped (already patched or version mismatch).", C.W)
p(" If cu130 warning still appears, check the files manually.", C.W)
return True
def restore(self) -> bool:
p("=" * 65, C.H)
p(" Restore Mode", C.H + C.D)
p("=" * 65, C.H)
qo = self.root / "comfy" / "quant_ops.py"
self._restore_file(qo)
try:
ck = self._find_ck()
self._restore_file(ck / "registry.py")
self._restore_file(ck / "backends" / "cuda" / "__init__.py")
except FileNotFoundError:
p("[INFO] comfy_kitchen not found, skipping related restore", C.C)
p("")
p("[DONE] Restore complete.", C.G)
return True
# =============================================================================
# CLI Entry
# =============================================================================
def main():
parser = argparse.ArgumentParser(
description="ComfyUI comfy-kitchen Full Backend Unlock Patch",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" python comfy_kitchen_patcher.py\n"
" python comfy_kitchen_patcher.py --comfyui-root \"H:\\\\PythonProjects3\\\\Win_ComfyUI\"\n"
" python comfy_kitchen_patcher.py --restore\n"
" python comfy_kitchen_patcher.py --dry-run"
)
)
parser.add_argument('--comfyui-root', type=str, default=None, help='ComfyUI root directory')
parser.add_argument('--python-env', type=str, default=None, help='Python env directory for site-packages')
parser.add_argument('--restore', action='store_true', help='Restore from backups')
parser.add_argument('--dry-run', action='store_true', help='Preview only, do not modify files')
args = parser.parse_args()
try:
patcher = Patcher(
comfyui_root=args.comfyui_root,
python_env=args.python_env,
dry_run=args.dry_run
)
if args.restore:
patcher.restore()
else:
success = patcher.run()
sys.exit(0 if success else 1)
except KeyboardInterrupt:
p("\n[!] Interrupted by user", C.W)
sys.exit(130)
except Exception as e:
p(f"\n[FATAL ERROR] {e}", C.F)
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
3.2 总控脚本:comfyui_post_update.py
该脚本是更新后的"一站式修复入口",负责按顺序执行:
- 依赖检查 :检测
requirements.txt和 Manager 的依赖文件,默认提示手动安装(安全),可选--auto-pip自动安装 - 补丁队列 :按顺序运行所有登记的补丁脚本(如
comfy_kitchen_patcher.py) - main.py 修改:自动应用预设的查找替换 / 正则 / 插入规则
核心设计:
python
# 用户配置区 ------ 根据你的环境自定义
# 依赖文件列表
REQUIREMENTS_FILES = [
"requirements.txt",
"custom_nodes/ComfyUI-Manager/requirements.txt",
]
# 补丁脚本队列(按顺序执行)
PATCH_SCRIPTS = [
"comfy_kitchen_patcher.py",
# "patch_161630960.py", # 你的其他补丁
# "patch_161632686.py", # 你的其他补丁
]
# main.py 修改规则
MAIN_PY_PATCHES = [
{
"name": "Add MediaPipe proxy import",
"mode": "insert_head",
"content": "import mediapipe_patch # MediaPipe proxy compatibility layer\n",
},
{
"name": "Lower dynamic VRAM PyTorch threshold (2.8 -> 2.7)",
"mode": "regex",
"pattern": r"torch_version_numeric\s*<\s*\(2,\s*[78]\)",
"replace": "torch_version_numeric < (2, 7)",
},
]
四种修改模式:
| 模式 | 用途 | 示例 |
|---|---|---|
replace |
精确字符串查找替换 | 修改特定行代码 |
regex |
正则表达式替换 | 兼容多种原始值(如同时匹配 (2, 8) 和 (2, 7)) |
insert_head |
文件头部插入 | 添加 import 语句 |
insert_after |
某行代码后插入 | 在特定函数后添加钩子 |
使用方法:
bash
# 默认模式:提示手动安装依赖 + 运行补丁 + 修改 main.py
python comfyui_post_update.py
# 自动安装依赖(需确认网络稳定)
python comfyui_post_update.py --auto-pip
# 跳过依赖检查(已手动装完时)
python comfyui_post_update.py --skip-pip
# 试运行(只打印不修改)
python comfyui_post_update.py --dry-run
# 恢复所有备份
python comfyui_post_update.py --restore
脚本源码 (comfyui_post_update.py):
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ComfyUI Post-Update Bootstrap Script
====================================
Features:
1. Detect and prompt for missing requirements (manual install by default)
2. Run patch scripts in sequence (e.g. comfy_kitchen_patcher.py)
3. Auto-patch main.py with configurable rules
4. Auto backup & one-click restore
Usage:
# Default: check deps + run patches + patch main.py (pip is manual-only)
python comfyui_post_update.py
# Auto-install dependencies (use with caution)
python comfyui_post_update.py --auto-pip
# Skip everything except patches and main.py
python comfyui_post_update.py --skip-pip
# Restore all backups
python comfyui_post_update.py --restore
# Dry run (preview only)
python comfyui_post_update.py --dry-run
"""
import os
import sys
import shutil
import re
import subprocess
import argparse
from pathlib import Path
from typing import List, Optional
# =============================================================================
# Console Output (English only, safe for Windows cmd cp1252/gbk)
# =============================================================================
class C:
H = '\033[95m'
B = '\033[94m'
C = '\033[96m'
G = '\033[92m'
W = '\033[93m'
F = '\033[91m'
E = '\033[0m'
D = '\033[1m'
def p(text: str, color: str = ""):
print(f"{color}{text}{C.E}", flush=True)
# =============================================================================
# User Config Area
# =============================================================================
COMFYUI_ROOT = Path(__file__).parent.resolve()
PYTHON_EXE = None # None = auto-detect
# Requirements files to check (relative to COMFYUI_ROOT)
REQUIREMENTS_FILES = [
"requirements.txt",
"custom_nodes/ComfyUI-Manager/requirements.txt",
# "manager_requirements.txt",
]
# Patch scripts to run in order (relative to COMFYUI_ROOT)
PATCH_SCRIPTS = [
"comfy_kitchen_patcher.py",
# "patch_161630960.py",
# "patch_161632686.py",
]
# main.py patch rules (applied in order)
# Modes: replace | regex | insert_head | insert_after
MAIN_PY_PATCHES = [
{
"name": "Add MediaPipe proxy import",
"mode": "insert_head",
"content": "import mediapipe_patch # MediaPipe proxy compatibility layer\n",
},
{
"name": "Lower dynamic VRAM PyTorch threshold (2.8 -> 2.7)",
"mode": "regex",
# Matches both (2, 8) and (2, 7), ensures final result is (2, 7)
"pattern": r"torch_version_numeric\s*<\s*\(2,\s*[78]\)",
"replace": "torch_version_numeric < (2, 7)",
},
]
BACKUP_SUFFIX = ".post_update_backup"
PIP_TIMEOUT = 600
# =============================================================================
# Core Bootstrap
# =============================================================================
class Bootstrap:
def __init__(self, comfyui_root: Optional[Path] = None,
python_exe: Optional[str] = None,
dry_run: bool = False,
skip_pip: bool = False,
auto_pip: bool = False):
self.dry_run = dry_run
self.skip_pip = skip_pip
self.auto_pip = auto_pip
self.root = Path(comfyui_root).resolve() if comfyui_root else COMFYUI_ROOT
self.python = self._resolve_python(python_exe)
self.patched_files: List[Path] = []
self.backup_files: List[Path] = []
def _resolve_python(self, explicit: Optional[str]) -> str:
if explicit:
return explicit
venv = self.root / ".venv" / "Scripts" / "python.exe"
if venv.exists():
p(f"[ENV] Virtual env detected: {venv}", C.C)
return str(venv)
embed = self.root / "python_embeded" / "python.exe"
if embed.exists():
p(f"[ENV] Embedded Python detected: {embed}", C.C)
return str(embed)
p(f"[ENV] Using current Python: {sys.executable}", C.C)
return sys.executable
def _backup(self, fp: Path) -> Path:
bp = fp.with_suffix(fp.suffix + BACKUP_SUFFIX)
if not bp.exists():
if not self.dry_run:
shutil.copy2(fp, bp)
self.backup_files.append(bp)
p(f"[BACKUP] {fp.name} -> {bp.name}", C.B)
else:
p(f"[SKIP] Backup exists: {bp.name}", C.W)
return bp
# -------------------------------------------------------------------------
# Step 1: Dependency Check / Install
# -------------------------------------------------------------------------
def handle_requirements(self) -> bool:
if self.skip_pip:
p("[SKIP] --skip-pip set, skipping dependency check", C.W)
return True
p("\n" + "=" * 65, C.H)
p(" STEP 1/3: Dependency Check", C.H + C.D)
p("=" * 65, C.H)
missing_files = []
for req_file in REQUIREMENTS_FILES:
req_path = self.root / req_file
if req_path.exists():
missing_files.append(req_path)
else:
p(f"[SKIP] Not found: {req_file}", C.W)
if not missing_files:
p("[INFO] No requirements files to process", C.C)
return True
if self.auto_pip:
p("[MODE] Auto-pip enabled. Will install dependencies automatically.", C.W)
all_ok = True
for req_path in missing_files:
p(f"\n[INSTALL] {req_path.name} ...", C.C)
cmd = [self.python, "-m", "pip", "install", "-r", str(req_path), "--upgrade"]
p(f" CMD: {' '.join(cmd)}", C.C)
if self.dry_run:
p(" [DRY-RUN] Skipped", C.W)
continue
try:
result = subprocess.run(
cmd, cwd=str(self.root), capture_output=True, text=True,
encoding='utf-8', errors='replace', timeout=PIP_TIMEOUT
)
if result.returncode == 0:
for line in result.stdout.strip().split('\n')[-5:]:
if line.strip():
p(f" {line}", C.G)
p(f" [OK] {req_path.name} installed", C.G)
else:
p(f" [FAIL] Exit code {result.returncode}", C.F)
p(f" stderr: {result.stderr[:500]}", C.F)
all_ok = False
except subprocess.TimeoutExpired:
p(f" [FAIL] Timeout ({PIP_TIMEOUT}s)", C.F)
p(f" Tip: Run manually with mirror:", C.W)
p(f" {self.python} -m pip install -r {req_path} -i https://pypi.tuna.tsinghua.edu.cn/simple", C.C)
all_ok = False
except Exception as e:
p(f" [FAIL] {e}", C.F)
all_ok = False
return all_ok
else:
p("[MODE] Manual dependency install. Copy-paste the commands below:", C.W)
p(" (To auto-install, run with --auto-pip)", C.W)
p("")
for req_path in missing_files:
p(f" {self.python} -m pip install -r \"{req_path}\" --upgrade", C.C)
p("")
p("[INFO] Please run the above commands manually, then re-run this script.", C.G)
p(" Or run: python comfyui_post_update.py --auto-pip", C.C)
return True
# -------------------------------------------------------------------------
# Step 2: Run Patch Scripts
# -------------------------------------------------------------------------
def run_patches(self) -> bool:
p("\n" + "=" * 65, C.H)
p(" STEP 2/3: Run Patch Scripts", C.H + C.D)
p("=" * 65, C.H)
if not PATCH_SCRIPTS:
p("[INFO] PATCH_SCRIPTS empty, skipping", C.C)
return True
all_ok = True
for script_name in PATCH_SCRIPTS:
script_path = self.root / script_name
if not script_path.exists():
p(f"[SKIP] Not found: {script_name}", C.W)
continue
p(f"\n[EXEC] {script_name} ...", C.C)
cmd = [self.python, str(script_path), "--comfyui-root", str(self.root)]
p(f" CMD: {' '.join(cmd)}", C.C)
if self.dry_run:
p(" [DRY-RUN] Skipped", C.W)
continue
try:
result = subprocess.run(
cmd, cwd=str(self.root), capture_output=True, text=True,
encoding='utf-8', errors='replace', timeout=120
)
if result.stdout:
for line in result.stdout.strip().split('\n')[-30:]:
p(f" {line}", C.C)
if result.returncode == 0:
p(f" [OK] {script_name} success", C.G)
else:
p(f" [WARN] Exit code {result.returncode}", C.W)
if result.stderr:
for line in result.stderr.strip().split('\n')[-10:]:
p(f" {line}", C.W)
except Exception as e:
p(f" [FAIL] {e}", C.F)
all_ok = False
return all_ok
# -------------------------------------------------------------------------
# Step 3: Patch main.py
# -------------------------------------------------------------------------
def patch_main_py(self) -> bool:
p("\n" + "=" * 65, C.H)
p(" STEP 3/3: Patch main.py", C.H + C.D)
p("=" * 65, C.H)
target = self.root / "main.py"
if not target.exists():
p(f"[ERROR] Not found: {target}", C.F)
return False
if not MAIN_PY_PATCHES:
p("[INFO] MAIN_PY_PATCHES empty, skipping", C.C)
return True
self._backup(target)
content = target.read_text(encoding='utf-8')
orig = content
applied = 0
for rule in MAIN_PY_PATCHES:
name = rule.get("name", "untitled")
mode = rule.get("mode", "replace")
p(f"\n[APPLY] {name} (mode={mode})", C.C)
if mode == "replace":
find = rule.get("find", "")
replace = rule.get("replace", "")
if find in content:
new_content = content.replace(find, replace, 1)
if new_content != content:
content = new_content
p(" [OK] String replaced", C.G)
applied += 1
else:
p(" [OK] String matched (no change needed)", C.G)
else:
p(" [WARN] Target string not found (may be already patched or version changed)", C.W)
elif mode == "regex":
pattern = rule.get("pattern", "")
replace = rule.get("replace", "")
new_content, count = re.subn(pattern, replace, content, count=1)
if count > 0:
if new_content != content:
content = new_content
p(" [OK] Regex replaced", C.G)
applied += 1
else:
p(" [OK] Regex matched (already correct)", C.G)
else:
p(" [WARN] Regex did not match", C.W)
elif mode == "insert_head":
insert = rule.get("content", "")
if insert not in content:
content = insert + "\n" + content
p(" [OK] Inserted at head", C.G)
applied += 1
else:
p(" [SKIP] Content already exists", C.W)
elif mode == "insert_after":
anchor = rule.get("anchor", "")
insert = rule.get("content", "")
if anchor in content:
new_content = content.replace(anchor, anchor + insert, 1)
if new_content != content:
content = new_content
p(" [OK] Inserted after anchor", C.G)
applied += 1
else:
p(" [OK] Anchor matched (no change needed)", C.G)
else:
p(" [WARN] Anchor not found", C.W)
else:
p(f" [ERROR] Unknown mode: {mode}", C.F)
if content != orig:
if not self.dry_run:
target.write_text(content, encoding='utf-8')
p(f"\n[OK] main.py patched, {applied} rule(s) changed content", C.G)
self.patched_files.append(target)
return True
else:
p(f"\n[INFO] main.py unchanged (all rules already applied or no match)", C.C)
return True
# -------------------------------------------------------------------------
# Restore Mode
# -------------------------------------------------------------------------
def restore(self) -> bool:
p("=" * 65, C.H)
p(" Restore Mode", C.H + C.D)
p("=" * 65, C.H)
restored = 0
main_py = self.root / "main.py"
if self._restore_single(main_py):
restored += 1
ck = self.root / ".venv" / "Lib" / "site-packages" / "comfy_kitchen"
if not ck.exists():
ck = self.root / "python_embeded" / "Lib" / "site-packages" / "comfy_kitchen"
if ck.exists():
if self._restore_single(ck / "registry.py"):
restored += 1
if self._restore_single(ck / "backends" / "cuda" / "__init__.py"):
restored += 1
qo = self.root / "comfy" / "quant_ops.py"
if self._restore_single(qo):
restored += 1
p(f"\n[DONE] Restored {restored} files", C.G)
return True
def _restore_single(self, fp: Path) -> bool:
bp = fp.with_suffix(fp.suffix + BACKUP_SUFFIX)
if not bp.exists():
backups = list(fp.parent.glob(f"{fp.name}*{BACKUP_SUFFIX}"))
if backups:
bp = backups[0]
if bp.exists():
if not self.dry_run:
shutil.copy2(bp, fp)
p(f"[RESTORE] {fp.name} <- {bp.name}", C.G)
return True
p(f"[SKIP] No backup: {fp.name}", C.W)
return False
# -------------------------------------------------------------------------
# Main Flow
# -------------------------------------------------------------------------
def run(self) -> bool:
p("=" * 65, C.H)
p(" ComfyUI Post-Update Bootstrap", C.H + C.D)
p("=" * 65, C.H)
p(f" ComfyUI Root : {self.root}", C.C)
p(f" Python : {self.python}", C.C)
p(f" Dry Run : {self.dry_run}", C.C)
p(f" Skip Pip : {self.skip_pip}", C.C)
p(f" Auto Pip : {self.auto_pip}", C.C)
p("")
ok1 = self.handle_requirements()
ok2 = self.run_patches()
ok3 = self.patch_main_py()
p("\n" + "=" * 65, C.H)
p(" Summary", C.D)
p("=" * 65, C.H)
p(f" [Dependencies] {'OK' if ok1 else 'FAIL'}", C.G if ok1 else C.F)
p(f" [Patches] {'OK' if ok2 else 'FAIL'}", C.G if ok2 else C.F)
p(f" [main.py] {'OK' if ok3 else 'FAIL'}", C.G if ok3 else C.F)
if ok1 and ok2 and ok3:
p("\n[DONE] All steps completed. Start ComfyUI to verify.", C.G)
return True
else:
p("\n[!] Some steps failed. Check logs above.", C.W)
p(" Use --restore to revert all changes.", C.W)
return False
# =============================================================================
# CLI Entry
# =============================================================================
def main():
parser = argparse.ArgumentParser(
description="ComfyUI Post-Update Bootstrap Script",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" # Default: check deps (manual) + run patches + patch main.py\n"
" python comfyui_post_update.py\n\n"
" # Auto-install dependencies (use with caution)\n"
" python comfyui_post_update.py --auto-pip\n\n"
" # Skip dependency check entirely\n"
" python comfyui_post_update.py --skip-pip\n\n"
" # Dry run (preview only)\n"
" python comfyui_post_update.py --dry-run\n\n"
" # Restore all backups\n"
" python comfyui_post_update.py --restore\n"
)
)
parser.add_argument('--comfyui-root', type=str, default=None, help='ComfyUI root directory')
parser.add_argument('--python-exe', type=str, default=None, help='Force specific Python executable')
parser.add_argument('--skip-pip', action='store_true', help='Skip dependency check entirely')
parser.add_argument('--auto-pip', action='store_true', help='Auto-install dependencies (default is manual prompt)')
parser.add_argument('--restore', action='store_true', help='Restore all backups')
parser.add_argument('--dry-run', action='store_true', help='Preview only, do not modify files')
args = parser.parse_args()
try:
bs = Bootstrap(
comfyui_root=args.comfyui_root,
python_exe=args.python_exe,
dry_run=args.dry_run,
skip_pip=args.skip_pip,
auto_pip=args.auto_pip
)
if args.restore:
bs.restore()
else:
success = bs.run()
sys.exit(0 if success else 1)
except KeyboardInterrupt:
p("\n[!] Interrupted", C.W)
sys.exit(130)
except Exception as e:
p(f"\n[FATAL ERROR] {e}", C.F)
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()
四、实战:完整更新流程演示
4.1 更新前准备
将两个脚本放在 ComfyUI 根目录:
H:\PythonProjects3\Win_ComfyUI\
├── main.py
├── requirements.txt
├── manager_requirements.txt
├── comfyui_post_update.py <-- 总控脚本
├── comfy_kitchen_patcher.py <-- 补丁脚本
├── mediapipe_patch.py <-- mediapipe 补丁脚本
├── folder_paths.py <-- 模型 补丁脚本
├── protobuf_patch.py <-- protobuf 补丁脚本
└── ...
ComfyUI 消除 cu130 警告并强制开启 comfy-kitchen triton/ eager/ cuda全后端加速:原理与实战【含一键补丁】
ComfyUI MediaPipe 猴子补丁终极完善版:补全上下文管理与姿态检测兼容
ComfyUI MediaPipe 终极填坑:解决 incompatible function arguments 报错,基于代理模式的猴子补丁升级版
ComfyUI MediaPipe 猴子补丁实战记录(解决solutions缺失及相关报错)
【技术分享】ComfyUI中protobuf版本兼容性问题的优雅解决方案:猴子补丁实战
4.2 执行更新
Step 1:下载并替换源码
从 GitHub 下载最新 ComfyUI 源码压缩包,解压后全量替换到 H:\PythonProjects3\Win_ComfyUI。遇到文件冲突时选择"全部替换"。
Step 2:运行一键修复脚本
bash
cd H:\PythonProjects3\Win_ComfyUI
python comfyui_post_update.py
输出示例:
text
[ENV] Virtual env detected: H:\PythonProjects3\Win_ComfyUI\.venv\Scripts\python.exe
=================================================================
ComfyUI Post-Update Bootstrap
=================================================================
ComfyUI Root : H:\PythonProjects3\Win_ComfyUI
Python : H:\PythonProjects3\Win_ComfyUI\.venv\Scripts\python.exe
Dry Run : False
Skip Pip : False
Auto Pip : False
=================================================================
STEP 1/3: Dependency Check
=================================================================
[MODE] Manual dependency install. Copy-paste the commands below:
(To auto-install, run with --auto-pip)
H:\PythonProjects3\Win_ComfyUI\.venv\Scripts\python.exe -m pip install -r "H:\PythonProjects3\Win_ComfyUI\requirements.txt" --upgrade
H:\PythonProjects3\Win_ComfyUI\.venv\Scripts\python.exe -m pip install -r "H:\PythonProjects3\Win_ComfyUI\custom_nodes\ComfyUI-Manager\requirements.txt" --upgrade
[INFO] Please run the above commands manually, then re-run this script.
Or run: python comfyui_post_update.py --auto-pip
Step 3:手动安装依赖(或自动)
复制粘贴脚本打印的命令执行。如果网络较慢,可使用国内镜像:
bash
H:\PythonProjects3\Win_ComfyUI\.venv\Scripts\python.exe -m pip install -r "H:\PythonProjects3\Win_ComfyUI\requirements.txt" --upgrade -i https://pypi.tuna.tsinghua.edu.cn/simple
Step 4:再次运行脚本完成补丁
bash
python comfyui_post_update.py --skip-pip
输出示例:
text
=================================================================
STEP 2/3: Run Patch Scripts
=================================================================
[EXEC] comfy_kitchen_patcher.py ...
CMD: H:\PythonProjects3\Win_ComfyUI\.venv\Scripts\python.exe H:\PythonProjects3\Win_ComfyUI\comfy_kitchen_patcher.py --comfyui-root H:\PythonProjects3\Win_ComfyUI
...
[PATCH] quant_ops.py - cu130 check disabled
[PATCH] registry.py - backend blacklist disabled
[PATCH] cuda/__init__.py - compute capability lowered + DLL paths added
...
[OK] comfy_kitchen_patcher.py success
=================================================================
STEP 3/3: Patch main.py
=================================================================
[BACKUP] main.py -> main.py.post_update_backup
[APPLY] Add MediaPipe proxy import (mode=insert_head)
[OK] Inserted at head
[APPLY] Lower dynamic VRAM PyTorch threshold (2.8 -> 2.7) (mode=regex)
[OK] Regex replaced
[OK] main.py patched, 2 rule(s) changed content
=================================================================
Summary
=================================================================
[Dependencies] OK
[Patches] OK
[main.py] OK
[DONE] All steps completed. Start ComfyUI to verify.

Step 5:启动验证
ComfyUI 消除 cu130 警告并强制开启 comfy-kitchen triton/ eager/ cuda全后端加速:原理与实战【含一键补丁】
bash
python main.py
检查启动日志,确认 comfy-kitchen 后端状态:
text
[INFO] Found comfy_kitchen backend eager: {'available': True, 'disabled': False, ...}
[INFO] Found comfy_kitchen backend triton: {'available': True, 'disabled': False, ...}
[INFO] Found comfy_kitchen backend cuda: {'available': True, 'disabled': False, ...}
三个后端 disabled 均为 False,且无 WARNING: cu130 警告,说明补丁生效。

五、高级配置:扩展你的补丁队列
5.1 添加更多补丁脚本
如果你有来自其他博客或自己编写的补丁脚本,只需将其放入 ComfyUI 根目录,并在 PATCH_SCRIPTS 中登记:
python
PATCH_SCRIPTS = [
"comfy_kitchen_patcher.py",
"patch_custom_nodes_path.py", # 来自 CSDN 博客 161630960
"patch_model_loading.py", # 来自 CSDN 博客 161632686
"my_custom_fix.py", # 你自己的补丁
]
5.2 添加更多 main.py 修改规则
示例 1:修改监听地址为 0.0.0.0
python
{
"name": "Change listen address to 0.0.0.0",
"mode": "replace",
"find": 'server_address = ("127.0.0.1", port)',
"replace": 'server_address = ("0.0.0.0", port)',
}
示例 2:正则替换启动参数
python
{
"name": "Add extra model paths argument",
"mode": "regex",
"pattern": r'loop\.run_until_complete\(run\(([^)]+)\)\)',
"replace": r'loop.run_until_complete(run(\1, extra_model_paths=r"H:\\models.yaml"))',
}
示例 3:在某行后插入自定义钩子
python
{
"name": "Import custom hook after comfy.utils",
"mode": "insert_after",
"anchor": "import comfy.utils",
"content": "\nimport my_custom_hook # PATCHED: load custom hook",
}
六、注意事项与排错
6.1 硬件真实限制
RTX 3090/4090 的 Tensor Core 不支持 FP4(NVFP4) 硬件原生加速。补丁解锁后,涉及 FP4 的算子会回退到软件模拟。建议使用 FP8 或 BF16 精度。
6.2 更新覆盖问题
全量替换更新会覆盖所有源码修改。脚本通过备份机制确保可恢复,但建议:
- 将
comfyui_post_update.py和补丁脚本放在 ComfyUI 根目录 - 更新后重新运行一次脚本
- 或编写批处理脚本
update.bat:
batch
@echo off
cd /d "%~dp0"
python comfyui_post_update.py --skip-pip
pause
6.3 常见报错处理
| 现象 | 原因 | 解决 |
|---|---|---|
Cannot auto-locate ComfyUI root |
脚本未在 ComfyUI 目录内执行 | 显式使用 --comfyui-root |
comfy_kitchen not found |
未安装 comfy-kitchen | pip install comfy-kitchen |
Target string not found |
源码版本变化,原始字符串已不存在 | 更新 find / pattern 匹配新源码 |
Regex did not match |
正则表达式不匹配当前代码 | 调整 pattern 或使用 replace 模式 |
| pip 安装超时 | 网络较慢或依赖较多 | 使用 --skip-pip 后手动安装,或换国内镜像 |
6.4 与官方更新的兼容性
本补丁属于非官方支持的修改。如果未来 ComfyUI 官方放宽了版本/算力检查:
- 先执行
--restore还原备份 - 更新 ComfyUI 和 comfy-kitchen
- 检查是否已原生支持,若仍被锁再重新打补丁
七、总结
通过 comfyui_post_update.py 总控脚本 + comfy_kitchen_patcher.py 补丁脚本的组合,可以将 ComfyUI 源码部署版更新后的所有修复操作自动化:
- 依赖重装:从手动复制粘贴命令到一键提示
- 补丁应用:从逐个文件手动修改到脚本自动匹配替换
- main.py 修改:从每次重新编辑到规则化自动应用
- 备份恢复:从担心改坏到随时一键还原
这个工作流不仅适用于 comfy-kitchen 解锁,也适用于任何需要频繁更新源码并重新应用自定义修改的场景。将补丁规则化、脚本化,是维护大型开源项目本地部署的最佳实践。