mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2024-11-30 04:04:08 +01:00
185 lines
7.2 KiB
Python
185 lines
7.2 KiB
Python
import os
|
|
import torch
|
|
import traceback
|
|
import re
|
|
import logging
|
|
|
|
log = logging.getLogger()
|
|
|
|
'''
|
|
Set `FORCE_FULL_PRECISION` in the environment variables, or in `config.bat`/`config.sh` to set full precision (i.e. float32).
|
|
Otherwise the models will load at half-precision (i.e. float16).
|
|
|
|
Half-precision is fine most of the time. Full precision is only needed for working around GPU bugs (like NVIDIA 16xx GPUs).
|
|
'''
|
|
|
|
COMPARABLE_GPU_PERCENTILE = 0.65 # if a GPU's free_mem is within this % of the GPU with the most free_mem, it will be picked
|
|
|
|
mem_free_threshold = 0
|
|
|
|
def get_device_delta(render_devices, active_devices):
|
|
'''
|
|
render_devices: 'cpu', or 'auto' or ['cuda:N'...]
|
|
active_devices: ['cpu', 'cuda:N'...]
|
|
'''
|
|
|
|
if render_devices in ('cpu', 'auto'):
|
|
render_devices = [render_devices]
|
|
elif render_devices is not None:
|
|
if isinstance(render_devices, str):
|
|
render_devices = [render_devices]
|
|
if isinstance(render_devices, list) and len(render_devices) > 0:
|
|
render_devices = list(filter(lambda x: x.startswith('cuda:'), render_devices))
|
|
if len(render_devices) == 0:
|
|
raise Exception('Invalid render_devices value in config.json. Valid: {"render_devices": ["cuda:0", "cuda:1"...]}, or {"render_devices": "cpu"} or {"render_devices": "auto"}')
|
|
|
|
render_devices = list(filter(lambda x: is_device_compatible(x), render_devices))
|
|
if len(render_devices) == 0:
|
|
raise Exception('Sorry, none of the render_devices configured in config.json are compatible with Stable Diffusion')
|
|
else:
|
|
raise Exception('Invalid render_devices value in config.json. Valid: {"render_devices": ["cuda:0", "cuda:1"...]}, or {"render_devices": "cpu"} or {"render_devices": "auto"}')
|
|
else:
|
|
render_devices = ['auto']
|
|
|
|
if 'auto' in render_devices:
|
|
render_devices = auto_pick_devices(active_devices)
|
|
if 'cpu' in render_devices:
|
|
log.warn('WARNING: Could not find a compatible GPU. Using the CPU, but this will be very slow!')
|
|
|
|
active_devices = set(active_devices)
|
|
render_devices = set(render_devices)
|
|
|
|
devices_to_start = render_devices - active_devices
|
|
devices_to_stop = active_devices - render_devices
|
|
|
|
return devices_to_start, devices_to_stop
|
|
|
|
def auto_pick_devices(currently_active_devices):
|
|
global mem_free_threshold
|
|
|
|
if not torch.cuda.is_available(): return ['cpu']
|
|
|
|
device_count = torch.cuda.device_count()
|
|
if device_count == 1:
|
|
return ['cuda:0'] if is_device_compatible('cuda:0') else ['cpu']
|
|
|
|
log.debug('Autoselecting GPU. Using most free memory.')
|
|
devices = []
|
|
for device in range(device_count):
|
|
device = f'cuda:{device}'
|
|
if not is_device_compatible(device):
|
|
continue
|
|
|
|
mem_free, mem_total = torch.cuda.mem_get_info(device)
|
|
mem_free /= float(10**9)
|
|
mem_total /= float(10**9)
|
|
device_name = torch.cuda.get_device_name(device)
|
|
log.debug(f'{device} detected: {device_name} - Memory (free/total): {round(mem_free, 2)}Gb / {round(mem_total, 2)}Gb')
|
|
devices.append({'device': device, 'device_name': device_name, 'mem_free': mem_free})
|
|
|
|
devices.sort(key=lambda x:x['mem_free'], reverse=True)
|
|
max_mem_free = devices[0]['mem_free']
|
|
curr_mem_free_threshold = COMPARABLE_GPU_PERCENTILE * max_mem_free
|
|
mem_free_threshold = max(curr_mem_free_threshold, mem_free_threshold)
|
|
|
|
# Auto-pick algorithm:
|
|
# 1. Pick the top 75 percentile of the GPUs, sorted by free_mem.
|
|
# 2. Also include already-running devices (GPU-only), otherwise their free_mem will
|
|
# always be very low (since their VRAM contains the model).
|
|
# These already-running devices probably aren't terrible, since they were picked in the past.
|
|
# Worst case, the user can restart the program and that'll get rid of them.
|
|
devices = list(filter((lambda x: x['mem_free'] > mem_free_threshold or x['device'] in currently_active_devices), devices))
|
|
devices = list(map(lambda x: x['device'], devices))
|
|
return devices
|
|
|
|
def device_init(context, device):
|
|
'''
|
|
This function assumes the 'device' has already been verified to be compatible.
|
|
`get_device_delta()` has already filtered out incompatible devices.
|
|
'''
|
|
|
|
validate_device_id(device, log_prefix='device_init')
|
|
|
|
if device == 'cpu':
|
|
context.device = 'cpu'
|
|
context.device_name = get_processor_name()
|
|
context.half_precision = False
|
|
log.debug(f'Render device CPU available as {context.device_name}')
|
|
return
|
|
|
|
context.device_name = torch.cuda.get_device_name(device)
|
|
context.device = device
|
|
|
|
# Force full precision on 1660 and 1650 NVIDIA cards to avoid creating green images
|
|
if needs_to_force_full_precision(context):
|
|
log.warn(f'forcing full precision on this GPU, to avoid green images. GPU detected: {context.device_name}')
|
|
# Apply force_full_precision now before models are loaded.
|
|
context.half_precision = False
|
|
|
|
log.info(f'Setting {device} as active')
|
|
torch.cuda.device(device)
|
|
|
|
return
|
|
|
|
def needs_to_force_full_precision(context):
|
|
if 'FORCE_FULL_PRECISION' in os.environ:
|
|
return True
|
|
|
|
device_name = context.device_name.lower()
|
|
return (('nvidia' in device_name or 'geforce' in device_name) and (' 1660' in device_name or ' 1650' in device_name)) or ('Quadro T2000' in device_name)
|
|
|
|
def validate_device_id(device, log_prefix=''):
|
|
def is_valid():
|
|
if not isinstance(device, str):
|
|
return False
|
|
if device == 'cpu':
|
|
return True
|
|
if not device.startswith('cuda:') or not device[5:].isnumeric():
|
|
return False
|
|
return True
|
|
|
|
if not is_valid():
|
|
raise EnvironmentError(f"{log_prefix}: device id should be 'cpu', or 'cuda:N' (where N is an integer index for the GPU). Got: {device}")
|
|
|
|
def is_device_compatible(device):
|
|
'''
|
|
Returns True/False, and prints any compatibility errors
|
|
'''
|
|
try:
|
|
validate_device_id(device, log_prefix='is_device_compatible')
|
|
except:
|
|
log.error(str(e))
|
|
return False
|
|
|
|
if device == 'cpu': return True
|
|
# Memory check
|
|
try:
|
|
_, mem_total = torch.cuda.mem_get_info(device)
|
|
mem_total /= float(10**9)
|
|
if mem_total < 3.0:
|
|
log.warn(f'GPU {device} with less than 3 GB of VRAM is not compatible with Stable Diffusion')
|
|
return False
|
|
except RuntimeError as e:
|
|
log.error(str(e))
|
|
return False
|
|
return True
|
|
|
|
def get_processor_name():
|
|
try:
|
|
import platform, subprocess
|
|
if platform.system() == "Windows":
|
|
return platform.processor()
|
|
elif platform.system() == "Darwin":
|
|
os.environ['PATH'] = os.environ['PATH'] + os.pathsep + '/usr/sbin'
|
|
command = "sysctl -n machdep.cpu.brand_string"
|
|
return subprocess.check_output(command).strip()
|
|
elif platform.system() == "Linux":
|
|
command = "cat /proc/cpuinfo"
|
|
all_info = subprocess.check_output(command, shell=True).decode().strip()
|
|
for line in all_info.split("\n"):
|
|
if "model name" in line:
|
|
return re.sub(".*model name.*:", "", line, 1).strip()
|
|
except:
|
|
log.error(traceback.format_exc())
|
|
return "cpu"
|