Files
automatic/modules/storage.py
T
Vladimir Mandic 392c969754 add offload state_dict
Signed-off-by: Vladimir Mandic <mandic00@live.com>
2026-08-17 08:52:46 +02:00

177 lines
9.0 KiB
Python

import os
import time
from datetime import datetime
from pathlib import Path
from modules import paths
from modules.logger import log
from modules.shared import opts, max_workers
from modules.modelstats import stat
class Location:
name: str
folders: list[str]
paths: list[Path]
nfiles: int = 0
nfolders: int = 0
nsymlinks: int = 0
nerrors: int = 0
type: str = ''
size: int = 0
time: float = 0.0
mtime: datetime = datetime.fromtimestamp(0)
def __init__(self, name: str | None, folders: str | list[str], what: str = ''):
self.type = what
if isinstance(folders, str):
folders = [folders]
self.name = name if name is not None else ', '.join(folders)
self.folders = folders
self.paths = [Path(f).resolve(strict=False) for f in self.folders if f is not None and f != '']
def __repr__(self):
return f'Location(type={self.type} name="{self.name}" folders={self.folders} size={self.size/1024/1024:.3f} mtime="{self.mtime}" files={self.nfiles} folders={self.nfolders} symlinks={self.nsymlinks} errors={self.nerrors} time={self.time:.3f})'
def dict(self):
return {
'name': self.name,
'type': self.type,
'folders': self.folders,
'paths': [str(p) for p in self.paths],
'size': self.size,
'mtime': self.mtime.timestamp(),
'nfiles': self.nfiles,
'nfolders': self.nfolders,
'nsymlinks': self.nsymlinks,
'nerrors': self.nerrors,
'time': self.time,
}
def get_all_locations(types: list[str] | None = []) -> list[Location]:
locations = []
if types is None or 'All' in types or 'Models' in types:
locations.append(Location('SD Models', opts.ckpt_dir, 'Models'))
locations.append(Location('Diffusers Models', opts.diffusers_dir, 'Models'))
locations.append(Location('Huggingface Modules', opts.hfcache_dir, 'Models'))
locations.append(Location('VAE', [opts.vae_dir, os.path.join(paths.models_path, "TAESD")], 'Models'))
locations.append(Location('UNet', opts.unet_dir, 'Models'))
locations.append(Location('TextEncoder', opts.te_dir, 'Models'))
locations.append(Location('LoRA', opts.lora_dir, 'Models'))
locations.append(Location('ControlNets', opts.control_dir, 'Models'))
locations.append(Location('Embeddings', opts.embeddings_dir, 'Models'))
locations.append(Location('Detailers', [opts.yolo_dir, os.path.join(paths.models_path, 'Ultralytics')], 'Models'))
locations.append(Location('Upscalers', [opts.esrgan_models_path, opts.bsrgan_models_path, opts.realesrgan_models_path, opts.scunet_models_path, opts.swinir_models_path, os.path.join(paths.models_path, 'chaiNNer'), os.path.join(paths.models_path, 'GFPGAN'), os.path.join(paths.models_path, 'Spandrel'), os.path.join(paths.models_path, 'SeedVR2')], 'Models')) # chainners extension has late opts init
locations.append(Location('CLiP', opts.clip_models_path, 'Models'))
locations.append(Location('Rembg', os.path.join(paths.models_path, 'Rembg'), 'Models'))
locations.append(Location('RIFE', os.path.join(paths.models_path, 'RIFE'), 'Models'))
if types is None or 'All' in types or 'Data' in types:
locations.append(Location('Configs', ['data', paths.sd_configs_path], 'Data'))
locations.append(Location('AutoComplete', opts.autocomplete_dir, 'Data'))
locations.append(Location('Styles', opts.styles_dir, 'Data'))
locations.append(Location('Wildcards', opts.wildcards_dir, 'Data'))
locations.append(Location('Reference', paths.reference_path, 'Data'))
locations.append(Location('LUTs', os.path.join(paths.models_path, 'LUTs'), 'Data'))
locations.append(Location('Wiki', 'wiki', 'Data'))
if types is None or 'All' in types or 'Cache' in types:
locations.append(Location('Temp', paths.temp_dir, 'Cache'))
locations.append(Location('XET', opts.xetcache_dir, 'Cache'))
locations.append(Location('OpenVINO', opts.openvino_cache_path, 'Cache'))
locations.append(Location('ONNX', opts.onnx_cached_models_path, 'Cache'))
locations.append(Location('VENV', 'venv', 'Cache'))
locations.append(Location('Torch', [opts.tunable_dir, os.getenv("TORCHINDUCTOR_CACHE_DIR", None), os.getenv("TRITON_CACHE_DIR", None)], 'Cache'))
if types is None or 'All' in types or 'Code' in types:
locations.append(Location('Modules', 'modules', 'Code'))
locations.append(Location('Pipelines', 'pipelines', 'Code'))
locations.append(Location('Scripts', 'scripts', 'Code'))
locations.append(Location('UI', 'ui', 'Code'))
locations.append(Location('Builtin', paths.extensions_builtin_dir, 'Code'))
locations.append(Location('Extensions', paths.extensions_dir, 'Code'))
if types is None or 'All' in types or 'Images' in types:
locations.append(Location('Text', [opts.outdir_txt2img_samples], 'Images'))
locations.append(Location('Image', [opts.outdir_img2img_samples], 'Images'))
locations.append(Location('Control', [opts.outdir_control_samples], 'Images'))
locations.append(Location('Extras', [opts.outdir_extras_samples], 'Images'))
locations.append(Location('Save', [opts.outdir_save], 'Images'))
locations.append(Location('Grids', [opts.outdir_txt2img_grids, opts.outdir_img2img_grids, opts.outdir_control_grids], 'Images'))
if types is None or 'All' in types or 'Videos' in types:
locations.append(Location('Video', [opts.outdir_video], 'Videos'))
return locations
def get_other_locations(locations: list[Location], name: str, folder: str, what: str = 'Other') -> list[Location]:
# get list of first level subfolders in `folder` check each if its already in `locations` by comparing resolved paths if not, add each to the list as a new Location with type `what`
existing_paths = set()
for location in locations:
for path in location.paths:
existing_paths.add(path.resolve(strict=False))
try:
with os.scandir(folder) as entries:
for entry in entries:
if entry.is_dir(follow_symlinks=False):
path = Path(entry.path).resolve(strict=False)
if path not in existing_paths:
locations.append(Location(name, entry.path, what))
except (FileNotFoundError, PermissionError):
pass
return locations
def print_summary(locations: list[Location]):
summary = {}
for location in locations:
if location.type not in summary:
summary[location.type] = {
'size': 0,
'mtime': datetime.fromtimestamp(0),
'nfiles': 0,
'nfolders': 0,
'nerrors': 0,
}
summary[location.type]['size'] += location.size
if location.mtime > summary[location.type]['mtime']:
summary[location.type]['mtime'] = location.mtime
summary[location.type]['nfiles'] += location.nfiles
summary[location.type]['nfolders'] += location.nfolders
summary[location.type]['nerrors'] += location.nerrors
for k, v in summary.items():
log.debug(f'Storage: type={k} size={v["size"]/1024/1024:.3f} files={v["nfiles"]} folders={v["nfolders"]}')
def check_storage(folders: str | list[str] | None = None, types: list[str] | None = None, silent: bool = False) -> list[Location]:
if isinstance(folders, str):
folders = [folders]
if folders is not None and len(folders) > 0:
locations = [Location(None, folder) for _i, folder in enumerate(folders)]
else:
locations = get_all_locations(types)
if types is None or 'Other' in types or 'All' in types:
locations = get_other_locations(locations, 'Other', paths.models_path)
log.debug(f'Storage: locations={len(locations)} workers={max_workers} types={types} folders={folders} start')
def update_stats(location: Location) -> Location:
t0 = time.time()
for f in location.paths:
size, mtime, files, folders, symlinks, errors = stat(f, extended=True, exclude=['__pycache__', '.'])
location.size += size
if mtime > location.mtime:
location.mtime = mtime
location.nfiles += files
location.nfolders += folders
location.nsymlinks += symlinks
location.nerrors += errors
location.time = time.time() - t0
return location
t0 = time.time()
from concurrent.futures import ThreadPoolExecutor, as_completed
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_items = {executor.submit(update_stats, location): location for location in locations}
for future in as_completed(future_items):
location = future.result()
if location.size > 0 and not silent:
log.debug(location)
t1 = time.time()
print_summary(locations)
log.debug(f'Storage: time={t1-t0:.3f} end')
return locations