Download disk_cache_provider.py from bjooo/tutorials: direct link, hf CLI and curl.
- Browser
- Download file 7.93 kB
-
https://huggingface.co/bjooo/tutorials/resolve/3e810297ce5ee58a1ea87b4df4c6d60edfe0c90e/disk_cache_provider.py
- Command line
-
hf download hf://bjooo/tutorials@3e810297ce5ee58a1ea87b4df4c6d60edfe0c90e/disk_cache_provider.py
-
curl -L -o disk_cache_provider.py https://huggingface.co/bjooo/tutorials/resolve/3e810297ce5ee58a1ea87b4df4c6d60edfe0c90e/disk_cache_provider.py
7.93 kB
| # -*- coding: utf-8 -*- | |
| """ | |
| ๋์คํฌ ๊ธฐ๋ฐ ๋ ธ๋ ์ถ๋ ฅ ์บ์ (ComfyUI cache provider API). | |
| ComfyUI๋ ๋ ธ๋ ์ถ๋ ฅ์ "์ ๋ ฅ ์๋ช "(์์ ฏ ๊ฐ + ๋ชจ๋ ์กฐ์ ๋ ธ๋์ ์๋ช , ์ธ์ ๊ฐ | |
| ๊ฒฐ์ ์ SHA256)์ผ๋ก ์บ์ฑํ๋๋ฐ, ๊ทธ ์บ์๋ ๋ฉ๋ชจ๋ฆฌ์๋ง ์์ด์ (1) ์ฌ์์ํ๋ฉด | |
| ์ฌ๋ผ์ง๊ณ (2) ๊ธฐ๋ณธ RAM_PRESSURE ๋ชจ๋์์๋ RAM์ด ๋ถ์กฑํ๋ฉด ์์๋ก ์ฆ๋ฐํ๋ค. | |
| ์ด ํ๋ก๋ฐ์ด๋๋ ์บ์ ์ ์ฅ ์ ๋์คํฌ์๋ ์ฐ๊ณ , ๋ฉ๋ชจ๋ฆฌ ์บ์ ๋ฏธ์ค ์ ๋์คํฌ์์ | |
| ๋ณต์ํ๋ค. ํจ๊ณผ: | |
| - ์ฌ์์ํด๋ ์๋/ํ๋กฌํํธ/์ด๋ฏธ์ง๊ฐ ๊ฐ์ ํด๋ฆฝ์ JoyCaption๋ถํฐ ์ํ๋ฌ๊น์ง | |
| ์ ๋ถ ์คํต (์คํ๋์ง ์๊ณ ๋์คํฌ์์ ๊ฒฐ๊ณผ๋ง ๋ณต์) | |
| - ์๋ ํ๋๋ง ๋ฐ๊พธ๋ฉด ์ ํํ ๊ทธ ํด๋ฆฝ ์ฒด์ธ๋ง ๋ค์ ์์ฑ (์๋ช ์ ๋ชจ๋ ์ ๋ ฅ์ด | |
| ํฌํจ๋๋ฏ๋ก ๋ฌดํจํ ํ์ ์ ComfyUI๊ฐ ์์์ ์ ํํ๊ฒ ํด์ค) | |
| - MODEL/CLIP/VAE/NOISE ๊ฐ์ ๊ฐ์ฒด ์ถ๋ ฅ์ ์ง๋ ฌํ ๋ถ๊ฐ๋ก ์๋ ์ ์ธ๋์ด ์ ์ฅ ์ ๋จ | |
| ์บ์ ์์น/์ฉ๋์ ํ๊ฒฝ๋ณ์๋ก ์กฐ์ : | |
| DOLPHIN_DISK_CACHE_DIR (๊ธฐ๋ณธ: <output>/_node_disk_cache) | |
| DOLPHIN_DISK_CACHE_MAX_GB (๊ธฐ๋ณธ: 40 โ ์ด๊ณผ ์ ์ค๋๋ ๊ฒ๋ถํฐ ์ญ์ ) | |
| DOLPHIN_DISK_CACHE_MAX_ENTRY_GB (๊ธฐ๋ณธ: 2 โ ์ด๋ณด๋ค ํฐ ๋จ์ผ ์ถ๋ ฅ์ ์ ์ฅ ์ ํจ) | |
| """ | |
| import os | |
| import time | |
| import asyncio | |
| import logging | |
| import torch | |
| import folder_paths | |
| from comfy_execution.cache_provider import register_cache_provider | |
| from comfy_api.latest._caching import CacheProvider, CacheValue | |
| log = logging.getLogger("dolphin.diskcache") | |
| _DEFAULT_DIR = os.path.join(folder_paths.get_output_directory(), "_node_disk_cache") | |
| CACHE_DIR = os.environ.get("DOLPHIN_DISK_CACHE_DIR", _DEFAULT_DIR) | |
| MAX_TOTAL_BYTES = int(float(os.environ.get("DOLPHIN_DISK_CACHE_MAX_GB", "15")) * (1024 ** 3)) | |
| # VAEDecode/์ด๋ฏธ์ง ๋ฐฐ์น ๊ฒฐ๊ณผ(์๋ฐฑMB~1GB, ๋์ฝ๋๋ ํ๋ ์)๋ ์ผ๋ถ๋ฌ ์ํ ์๋๋ก ๋๋ค. | |
| # ๋น์ผ ๊ฑด ๋ํจ์ ์ํ๋ง(latent, ์์ญMB)์ด๊ณ VAE ๋์ฝ๋๋ latent๋ง ์์ผ๋ฉด ๋ช ์ด๋ฉด | |
| # ๋ค์ ๋๋ฏ๋ก, ํฐ ๋์ฝ๋ ๊ฒฐ๊ณผ๊น์ง ๋์คํฌ์ ์์๋ ์ค์ต์ด ์๋ค - ์ฉ๋๋ง ๋จน๋๋ค. | |
| MAX_ENTRY_BYTES = int(float(os.environ.get("DOLPHIN_DISK_CACHE_MAX_ENTRY_GB", "0.2")) * (1024 ** 3)) | |
| _ALLOWED_SCALARS = (str, int, float, bool, bytes, type(None)) | |
| def _serializable(obj): | |
| """ํ ์/์ค์นผ๋ผ/์ปจํ ์ด๋๋ง ํ์ฉ. MODEL, NOISE, SAMPLER ๋ฑ ๊ฐ์ฒด๊ฐ ์์ด๋ฉด False.""" | |
| if isinstance(obj, torch.Tensor): | |
| return True | |
| if isinstance(obj, _ALLOWED_SCALARS): | |
| return True | |
| if isinstance(obj, (list, tuple)): | |
| return all(_serializable(x) for x in obj) | |
| if isinstance(obj, dict): | |
| return all(isinstance(k, _ALLOWED_SCALARS) and _serializable(v) for k, v in obj.items()) | |
| return False | |
| def _to_cpu(obj): | |
| if isinstance(obj, torch.Tensor): | |
| return obj.detach().to("cpu") | |
| if isinstance(obj, list): | |
| return [_to_cpu(x) for x in obj] | |
| if isinstance(obj, tuple): | |
| return tuple(_to_cpu(x) for x in obj) | |
| if isinstance(obj, dict): | |
| return {k: _to_cpu(v) for k, v in obj.items()} | |
| return obj | |
| def _tensor_bytes(obj): | |
| if isinstance(obj, torch.Tensor): | |
| return obj.numel() * obj.element_size() | |
| if isinstance(obj, dict): | |
| return sum(_tensor_bytes(v) for v in obj.values()) | |
| if isinstance(obj, (list, tuple)): | |
| return sum(_tensor_bytes(v) for v in obj) | |
| return 0 | |
| class DolphinDiskCache(CacheProvider): | |
| def __init__(self, directory=CACHE_DIR): | |
| self.dir = directory | |
| os.makedirs(self.dir, exist_ok=True) | |
| self.log_path = os.path.join(self.dir, "_diskcache.log") | |
| def _flog(self, msg): | |
| # ์ฝ์์ด ์ ๋ณด์ด๋ ํ๊ฒฝ์์๋ ์ ์ฅ/์คํต ๊ฒฝ๋ก๋ฅผ ์ถ์ ํ ์ ์๊ฒ ํ์ผ๋ก ๋จ๊ธด๋ค. | |
| # ์ฃผ์: should_cache๊ฐ ์์ ํธ์ถ ์ ๋ ๋ ธ๋๋ ComfyUI ์ชฝ NaN ํค ๊ฒ์ดํธ | |
| # (_contains_self_unequal)์์ ๊ฑธ๋ฌ์ง ๊ฒ - ๋ก๊ทธ์ ์ ์ฐํ๋ ๊ฒ ์์ฒด๊ฐ ๋จ์. | |
| try: | |
| with open(self.log_path, "a", encoding="utf-8") as f: | |
| f.write(f"{time.strftime('%H:%M:%S')} {msg}\n") | |
| except OSError: | |
| pass | |
| def _path(self, context): | |
| return os.path.join(self.dir, f"{context.cache_key_hash}.pt") | |
| async def on_lookup(self, context): | |
| path = self._path(context) | |
| if not os.path.isfile(path): | |
| return None | |
| def load(): | |
| return torch.load(path, map_location="cpu", weights_only=False) | |
| try: | |
| data = await asyncio.to_thread(load) | |
| except Exception as e: | |
| log.warning(f"[DiskCache] ์์๋ ์บ์ ์ญ์ ({context.class_type}): {e}") | |
| try: | |
| os.remove(path) | |
| except OSError: | |
| pass | |
| return None | |
| try: | |
| os.utime(path, None) # LRU: ์ ๊ทผ ์๊ฐ ๊ฐฑ์ (prune ์ ์ค๋๋ ๊ฒ๋ถํฐ ์ญ์ ) | |
| except OSError: | |
| pass | |
| print(f"๐พ [DiskCache] {context.class_type} ๋์คํฌ์์ ๋ณต์ (node {context.node_id})") | |
| self._flog(f"RESTORE {context.class_type} (node {context.node_id})") | |
| return CacheValue(outputs=data["outputs"], ui=data.get("ui")) | |
| def should_cache(self, context, value=None): | |
| if value is None: # lookup ์์ - ํ์ผ ์กด์ฌ ์ฌ๋ถ๋ก ํ๋จํ๋ฏ๋ก ํญ์ ์๋ | |
| return True | |
| if not _serializable(value.outputs): | |
| self._flog(f"SKIP not-serializable {context.class_type} (node {context.node_id})") | |
| return False | |
| size = _tensor_bytes(value.outputs) | |
| if size > MAX_ENTRY_BYTES: | |
| self._flog(f"SKIP too-big {context.class_type} {size/1024/1024:.0f}MB (node {context.node_id})") | |
| return False | |
| self._flog(f"STORE-OK {context.class_type} {size/1024/1024:.1f}MB (node {context.node_id})") | |
| return True | |
| async def on_store(self, context, value): | |
| path = self._path(context) | |
| if os.path.exists(path): | |
| return | |
| try: | |
| payload = { | |
| "outputs": _to_cpu(value.outputs), | |
| "ui": value.ui, | |
| "class_type": context.class_type, | |
| "saved_at": time.time(), | |
| } | |
| def save(): | |
| tmp = path + ".tmp" | |
| torch.save(payload, tmp) | |
| os.replace(tmp, path) | |
| await asyncio.to_thread(save) | |
| self._flog(f"SAVED {context.class_type} (node {context.node_id})") | |
| except Exception as e: | |
| log.warning(f"[DiskCache] ์ ์ฅ ์คํจ ({context.class_type}): {e}") | |
| self._flog(f"SAVE-FAIL {context.class_type} (node {context.node_id}): {type(e).__name__}: {e}") | |
| try: | |
| os.remove(path + ".tmp") | |
| except OSError: | |
| pass | |
| def on_prompt_end(self, prompt_id): | |
| try: | |
| self._prune() | |
| except Exception as e: | |
| log.warning(f"[DiskCache] prune ์คํจ: {e}") | |
| def _prune(self): | |
| entries = [] | |
| total = 0 | |
| with os.scandir(self.dir) as it: | |
| for e in it: | |
| if e.name.endswith(".pt") and e.is_file(): | |
| st = e.stat() | |
| entries.append((st.st_mtime, st.st_size, e.path)) | |
| total += st.st_size | |
| if total <= MAX_TOTAL_BYTES: | |
| return | |
| entries.sort() # mtime ์ค๋๋ ์ | |
| for _, size, path in entries: | |
| try: | |
| os.remove(path) | |
| total -= size | |
| except OSError: | |
| pass | |
| if total <= MAX_TOTAL_BYTES: | |
| break | |
| _provider = None | |
| def register(): | |
| global _provider | |
| if _provider is None: | |
| _provider = DolphinDiskCache() | |
| register_cache_provider(_provider) | |
| print(f"๐พ [Dolphin] ๋์คํฌ ๋ ธ๋ ์บ์ ํ์ฑํ: {_provider.dir}") | |