mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 17:24:32 +02:00
Merge pull request #5016 from vladmandic/feat/offload-engine
feat(offload): per-component group offload engine and memory observability
This commit is contained in:
@@ -155,13 +155,12 @@ def run_extension_installer(ext_dir): # compatibility function
|
||||
|
||||
|
||||
def get_memory_stats(detailed:bool=False):
|
||||
from modules.memstats import ram_stats, memory_stats
|
||||
from modules.memstats import ram_stats, memory_stats, model_stats
|
||||
if not detailed:
|
||||
res = ram_stats()
|
||||
return f'{res["used"]}/{res["total"]}'
|
||||
else:
|
||||
res = memory_stats()
|
||||
return res
|
||||
return { **memory_stats(), 'model': model_stats(as_gb=True) } # fresh dict: memory_stats returns a module global that the per-generation log also prints
|
||||
|
||||
|
||||
def clean_server():
|
||||
|
||||
@@ -552,6 +552,7 @@ class ResEmbeddings(BaseModel):
|
||||
class ResMemory(BaseModel):
|
||||
ram: dict = Field(title="RAM", description="System memory stats")
|
||||
cuda: dict = Field(title="CUDA", description="nVidia CUDA memory stats")
|
||||
model: dict = Field(default={}, title="Model", description="Loaded model bytes per component and device")
|
||||
|
||||
class ResScripts(BaseModel):
|
||||
txt2img: list[str] = Field(title="Txt2img", description="Titles of scripts (txt2img)")
|
||||
|
||||
@@ -197,4 +197,5 @@ def get_memory():
|
||||
cuda = { 'error': 'unavailable' }
|
||||
except Exception as err:
|
||||
cuda = { 'error': f'{err}' }
|
||||
return models.ResMemory(ram = ram, cuda = cuda)
|
||||
from modules import memstats
|
||||
return models.ResMemory(ram = ram, cuda = cuda, model = memstats.model_stats())
|
||||
|
||||
@@ -246,6 +246,8 @@ def torch_gc(force: bool = False, fast: bool = False, reason: str | None = None)
|
||||
torch.xpu.empty_cache()
|
||||
if hasattr(torch.xpu, "ipc_collect"):
|
||||
torch.xpu.ipc_collect()
|
||||
if torch.cuda.is_available() and hasattr(torch._C, '_host_emptyCache'): # pylint: disable=protected-access
|
||||
torch._C._host_emptyCache() # pylint: disable=protected-access # freed pinned host blocks otherwise stay cached in-process across model switches
|
||||
except Exception as e:
|
||||
log.error(f'Torch GC: {e}')
|
||||
else:
|
||||
|
||||
@@ -13,6 +13,32 @@ native_active: bool = False
|
||||
default_components = ['text_encoder', 'text_encoder_2', 'text_encoder_3', 'text_encoder_4', 'unet', 'transformer', 'transformer_2', 'llm_adapter']
|
||||
|
||||
|
||||
def group_will_mutate(module, network_layer_name: str, loaded) -> bool:
|
||||
"""True when the pass will write to this module: a loaded network covers its layer, a
|
||||
tensor backup awaits restore, or an svd factor stash awaits removal."""
|
||||
if any(net.modules.get(network_layer_name, None) is not None for net in loaded):
|
||||
return True
|
||||
weights_backup = getattr(module, 'network_weights_backup', None)
|
||||
if weights_backup is not None and not isinstance(weights_backup, bool):
|
||||
return True
|
||||
bias_backup = getattr(module, 'network_bias_backup', None)
|
||||
if bias_backup is not None and not isinstance(bias_backup, bool):
|
||||
return True
|
||||
return getattr(module, 'sdnq_lora_svd_stash', None) is not None
|
||||
|
||||
|
||||
def group_offload_strip(sd_model, component_name: str, stripped: dict):
|
||||
"""Group offload hooks come off before the first weight write in a component: a write
|
||||
under live hooks either replaces a parameter out of the hook's group bookkeeping or is
|
||||
lost on the next onload. With hooks removed the weights rest on cpu and the component
|
||||
reports its truthful device, so writes land in place; the offload reapply at the end
|
||||
of the pass snapshots the result into fresh groups."""
|
||||
component = getattr(sd_model, component_name, None)
|
||||
sd_models.remove_group_offload_component(component)
|
||||
stripped[component_name] = component.device
|
||||
return stripped[component_name]
|
||||
|
||||
|
||||
def network_activate(include=None, exclude=None):
|
||||
if exclude is None:
|
||||
exclude = []
|
||||
@@ -32,6 +58,8 @@ def network_activate(include=None, exclude=None):
|
||||
sd_models.move_model(sd_model, device=devices.cpu)
|
||||
elif shared.opts.diffusers_offload_mode == "balanced":
|
||||
sd_model = sd_models.apply_balanced_offload(sd_model, force=True) # dispatched modules hold meta tensors backed by the offload map; rebuild them real on cpu with hooks intact before touching weights
|
||||
group_offload = shared.opts.diffusers_offload_mode == "group"
|
||||
group_stripped = {}
|
||||
device = None
|
||||
modules = {}
|
||||
components = include if len(include) > 0 else default_components
|
||||
@@ -67,6 +95,8 @@ def network_activate(include=None, exclude=None):
|
||||
if task is not None:
|
||||
pbar.update(task, advance=1)
|
||||
continue
|
||||
if group_offload and component not in group_stripped and group_will_mutate(module, network_layer_name, l.loaded_networks):
|
||||
device = group_offload_strip(sd_model, component, group_stripped)
|
||||
backup_size += network_backup_weights(module, network_layer_name, component_wanted)
|
||||
if not component_wanted:
|
||||
weights_backup = getattr(module, "network_weights_backup", None)
|
||||
@@ -100,7 +130,7 @@ def network_activate(include=None, exclude=None):
|
||||
if l.debug and len(l.loaded_networks) > 0:
|
||||
log.debug(f'Network load: type=LoRA networks={[n.name for n in l.loaded_networks]} modules={active_components} layers={total} weights={applied_weight} bias={applied_bias} backup={round(backup_size/1024/1024/1024, 2)} fuse={shared.opts.lora_fuse_native}:{shared.opts.lora_fuse_diffusers} device={device} time={l.timer.summary}')
|
||||
modules.clear()
|
||||
if len(applied_layers) > 0 or shared.opts.diffusers_offload_mode == "sequential":
|
||||
if len(applied_layers) > 0 or shared.opts.diffusers_offload_mode == "sequential" or len(group_stripped) > 0:
|
||||
sd_models.set_diffuser_offload(sd_model, op="model")
|
||||
|
||||
|
||||
@@ -121,6 +151,8 @@ def network_deactivate(include=None, exclude=None):
|
||||
sd_models.move_model(sd_model, device=devices.cpu)
|
||||
elif shared.opts.diffusers_offload_mode == "balanced":
|
||||
sd_model = sd_models.apply_balanced_offload(sd_model, force=True) # dispatched modules hold meta tensors backed by the offload map; rebuild them real on cpu with hooks intact before touching weights
|
||||
group_offload = shared.opts.diffusers_offload_mode == "group"
|
||||
group_stripped = {}
|
||||
modules = {}
|
||||
|
||||
components = include if len(include) > 0 else ['text_encoder', 'text_encoder_2', 'text_encoder_3', 'unet', 'transformer', 'llm_adapter']
|
||||
@@ -148,6 +180,8 @@ def network_deactivate(include=None, exclude=None):
|
||||
if task is not None:
|
||||
pbar.update(task, advance=1)
|
||||
continue
|
||||
if group_offload and component not in group_stripped and group_will_mutate(module, network_layer_name, l.previously_loaded_networks):
|
||||
device = group_offload_strip(sd_model, component, group_stripped)
|
||||
batch_updown, batch_ex_bias = network_calc_weights(module, network_layer_name, use_previous=True, elimit=elimit)
|
||||
if shared.opts.lora_fuse_native:
|
||||
network_apply_direct(module, batch_updown, batch_ex_bias, device=device, deactivate=True)
|
||||
@@ -163,5 +197,5 @@ def network_deactivate(include=None, exclude=None):
|
||||
if l.debug and len(l.previously_loaded_networks) > 0:
|
||||
log.debug(f'Network deactivate: type=LoRA networks={[n.name for n in l.previously_loaded_networks]} modules={active_components} layers={total} apply={len(applied_layers)} fuse={shared.opts.lora_fuse_native}:{shared.opts.lora_fuse_diffusers} time={l.timer.summary}')
|
||||
modules.clear()
|
||||
if len(applied_layers) > 0 or shared.opts.diffusers_offload_mode == "sequential":
|
||||
if len(applied_layers) > 0 or shared.opts.diffusers_offload_mode == "sequential" or len(group_stripped) > 0:
|
||||
sd_models.set_diffuser_offload(sd_model, op="model")
|
||||
|
||||
@@ -115,6 +115,35 @@ def gpu_stats():
|
||||
return gpu
|
||||
|
||||
|
||||
def model_stats(as_gb: bool = False):
|
||||
"""Loaded-model bytes per component and device, so resident weights can be told from offloaded ones."""
|
||||
try:
|
||||
from modules.modeldata import model_data
|
||||
pipe = model_data.sd_model # raw slot: the shared.sd_model property can trigger a model load
|
||||
if pipe is None:
|
||||
return {}
|
||||
components = getattr(pipe, 'components', None) or ({ 'model': pipe } if isinstance(pipe, torch.nn.Module) else {})
|
||||
placement = {}
|
||||
seen = set()
|
||||
for name, component in components.items():
|
||||
if not isinstance(component, torch.nn.Module):
|
||||
continue
|
||||
devmap = {}
|
||||
for tensors in (component.parameters(), component.buffers()):
|
||||
for t in tensors:
|
||||
ptr = 0 if t.is_meta else t.untyped_storage().data_ptr()
|
||||
if ptr:
|
||||
if ptr in seen: # tied weights and offload rewraps share one storage across tensors
|
||||
continue
|
||||
seen.add(ptr)
|
||||
devmap[t.device.type] = devmap.get(t.device.type, 0) + t.numel() * t.element_size()
|
||||
if devmap:
|
||||
placement[name] = { d: gb(v) for d, v in devmap.items() } if as_gb else devmap
|
||||
return placement
|
||||
except Exception as err: # walk can race a reload or an offload rewrap; every caller is a diagnostic that must not take its caller down
|
||||
return { 'error': f'{err}' }
|
||||
|
||||
|
||||
def memory_stats():
|
||||
mem['ram'] = ram_stats()
|
||||
mem['gpu'] = gpu_stats()
|
||||
|
||||
@@ -477,6 +477,7 @@ def process_decode(p: processing.StableDiffusionProcessing, output):
|
||||
log.debug(f'Generated: frames={len(output.frames[0])}')
|
||||
output.images = output.frames[0]
|
||||
if output.images is not None and len(output.images) > 0 and isinstance(output.images[0], Image.Image):
|
||||
sd_models.offload_ondemand(shared.sd_model) # in-pipe decode paths return materialized frames; the vae seam in processing_vae never runs
|
||||
return attach_audio(output.images, audio)
|
||||
model = shared.sd_model if not is_refiner_enabled(p) else shared.sd_refiner
|
||||
if not hasattr(model, 'vae'):
|
||||
|
||||
@@ -189,6 +189,8 @@ def full_vae_encode(image, model):
|
||||
sd_models.move_model(model.unet, devices.cpu)
|
||||
if shared.opts.diffusers_offload_mode != "sequential" and hasattr(model, 'vae'):
|
||||
sd_models.move_model(model.vae, devices.device)
|
||||
if getattr(model.vae, 'sdnext_ondemand', False):
|
||||
model.vae.to(devices.device) # the image placement below derives from vae.device, and the entry bridge would onload the weights only after the input is already bound
|
||||
vae_name = sd_vae.loaded_vae_file if sd_vae.loaded_vae_file is not None else "default"
|
||||
log_debug(f'Encode vae="{vae_name}" dtype={model.vae.dtype} upcast={model.vae.config.get("force_upcast", None)}')
|
||||
|
||||
@@ -369,6 +371,7 @@ def vae_decode(latents, model, output_type='np', vae_type='Full', width=None, he
|
||||
if shared.cmd_opts.profile or debug:
|
||||
t1 = time.time()
|
||||
log.debug(f'Profile: VAE decode: {t1-t0:.2f}')
|
||||
sd_models.offload_ondemand(model)
|
||||
devices.torch_gc()
|
||||
shared.state.end(jobid)
|
||||
return images
|
||||
@@ -393,6 +396,7 @@ def vae_encode(image, model, vae_type='Full'): # pylint: disable=unused-variable
|
||||
else:
|
||||
log.error('VAE not found in model')
|
||||
latents = []
|
||||
sd_models.offload_ondemand(model)
|
||||
devices.torch_gc()
|
||||
shared.state.end(jobid)
|
||||
return latents
|
||||
|
||||
+10
-3
@@ -16,7 +16,7 @@ from modules.memstats import memory_stats
|
||||
from modules.shared_helpers import walk_files
|
||||
from modules.modeldata import model_data
|
||||
from modules.sd_checkpoint import CheckpointInfo, select_checkpoint, list_models, checkpoint_titles, get_closest_checkpoint_match, update_model_hashes, write_metadata, checkpoints_list # pylint: disable=unused-import
|
||||
from modules.sd_offload import get_module_names, disable_offload, set_diffuser_offload, apply_balanced_offload, set_accelerate # pylint: disable=unused-import
|
||||
from modules.sd_offload import get_module_names, disable_offload, set_diffuser_offload, apply_balanced_offload, set_accelerate, remove_group_offload_component, offload_ondemand # pylint: disable=unused-import
|
||||
from modules.sd_models_utils import NoWatermark, get_signature, get_call, path_to_repo, apply_function_to_model, read_state_dict, get_state_dict_from_checkpoint # pylint: disable=unused-import
|
||||
|
||||
|
||||
@@ -231,13 +231,15 @@ def move_model(model, device=None, force=False):
|
||||
|
||||
if model is None or device is None:
|
||||
return
|
||||
if getattr(model, 'sdnext_ondemand', False) and device == devices.device: # on-demand components onload at their entry points instead of pre-moves
|
||||
return
|
||||
|
||||
if hasattr(model, 'pipe'):
|
||||
move_model(model.pipe, device, force)
|
||||
|
||||
fn = f'{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access
|
||||
if getattr(model, 'vae', None) is not None and get_diffusers_task(model) != DiffusersTaskType.TEXT_2_IMAGE:
|
||||
if device == devices.device and model.vae.device.type != "meta": # force vae back to gpu if not in txt2img mode
|
||||
if device == devices.device and model.vae.device.type != "meta" and not getattr(model.vae, 'sdnext_ondemand', False): # force vae back to gpu if not in txt2img mode; on-demand vaes onload at their entry point instead
|
||||
model.vae.to(device)
|
||||
if hasattr(model.vae, '_hf_hook'):
|
||||
debug_move(f'Model move: to={device} class={model.vae.__class__} fn={fn}') # pylint: disable=protected-access
|
||||
@@ -263,9 +265,14 @@ def move_model(model, device=None, force=False):
|
||||
if hasattr(model, 'device') and model.device == torch.device('meta'):
|
||||
set_execution_device(model, device)
|
||||
elif hasattr(model, 'to'):
|
||||
model.to(device)
|
||||
if device == devices.device and getattr(model, 'sdnext_ondemand_modules', None):
|
||||
pass # the group engine already placed every component; a pipe-level move would only drag on-demand components to the accelerator for the trailing eviction to undo
|
||||
else:
|
||||
model.to(device)
|
||||
if hasattr(model, "prior_pipe"):
|
||||
model.prior_pipe.to(device)
|
||||
if device == devices.device:
|
||||
offload_ondemand(model) # a bulk move must not strand on-demand components on the accelerator; their entry points onload them when needed
|
||||
except Exception as e0:
|
||||
if 'Cannot copy out of meta tensor' in str(e0) or 'must be Tensor, not NoneType' in str(e0):
|
||||
if hasattr(model, "components"):
|
||||
|
||||
+262
-21
@@ -19,6 +19,7 @@ debug_move = log.trace if debug else lambda *args, **kwargs: None
|
||||
offload_allow_none = ['sd', 'sdxl']
|
||||
offload_post = ['h1']
|
||||
offload_hook_instance = None
|
||||
group_offload_vae_limit = 1.0 # GB; vae-class components above this rest on cpu and onload whole at encode/decode
|
||||
balanced_offload_exclude = ['CogView4Pipeline', 'MeissonicPipeline']
|
||||
no_split_module_classes = [
|
||||
"Linear", "Conv1d", "Conv2d", "Conv3d", "ConvTranspose1d", "ConvTranspose2d", "ConvTranspose3d", "Embedding",
|
||||
@@ -44,6 +45,7 @@ def get_signature(cls):
|
||||
|
||||
|
||||
def disable_offload(sd_model):
|
||||
remove_group_offload(sd_model) # group hooks block the meta move at unload, keeping component weights alive for as long as any reference to the pipe survives
|
||||
if not getattr(sd_model, 'has_accelerate', False):
|
||||
return
|
||||
for module_name in get_module_names(sd_model):
|
||||
@@ -76,30 +78,257 @@ def set_accelerate(sd_model):
|
||||
set_accelerate_to_module(sd_model.decoder_pipe)
|
||||
|
||||
|
||||
def apply_group_offload(sd_model, op:str='model'):
|
||||
offload_dct = {
|
||||
'onload_device': devices.device,
|
||||
'offload_device': devices.cpu,
|
||||
'offload_type': shared.opts.group_offload_type,
|
||||
'num_blocks_per_group': shared.opts.group_offload_blocks,
|
||||
def group_offload_config(main: bool) -> dict:
|
||||
"""Effective group offload settings for one component. Components that run once per
|
||||
generation take the leaf no-stream policy regardless of the main settings, so their
|
||||
weights are never held in pinned host memory."""
|
||||
stream = shared.opts.group_offload_stream if main else False
|
||||
blocks = max(1, int(shared.opts.group_offload_blocks))
|
||||
if stream and blocks != 1:
|
||||
blocks = 1 # streamed prefetch supports one block per group; upstream clamps with a warning otherwise
|
||||
return {
|
||||
'offload_type': shared.opts.group_offload_type if main else 'leaf_level',
|
||||
'num_blocks_per_group': blocks,
|
||||
'non_blocking': shared.opts.diffusers_offload_nonblocking,
|
||||
'use_stream': shared.opts.group_offload_stream,
|
||||
'record_stream': shared.opts.group_offload_record,
|
||||
'low_cpu_mem_usage': False,
|
||||
'use_stream': stream,
|
||||
'record_stream': shared.opts.group_offload_record and stream, # record without streams is rejected upstream
|
||||
'low_cpu_mem_usage': stream and not shared.opts.group_offload_pin,
|
||||
}
|
||||
if shared.opts.group_offload_type == 'block_level':
|
||||
offload_dct['exclude_modules'] = ['vae']
|
||||
log.debug(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} options={offload_dct}')
|
||||
if hasattr(sd_model, "enable_group_offload"):
|
||||
sd_model.enable_group_offload(**offload_dct)
|
||||
else:
|
||||
log.warning(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} not supported')
|
||||
|
||||
|
||||
def remove_group_offload_component(module) -> bool:
|
||||
if getattr(module, 'sdnext_group_offload_sig', None) is None:
|
||||
module = getattr(module, 'model', None) # wrapper components carry the hooks on the inner model
|
||||
if module is None or getattr(module, 'sdnext_group_offload_sig', None) is None:
|
||||
return False
|
||||
from diffusers.hooks.group_offloading import _GROUP_OFFLOADING, _LAYER_EXECUTION_TRACKER, _LAZY_PREFETCH_GROUP_OFFLOADING
|
||||
from diffusers.hooks.hooks import HookRegistry
|
||||
registry = HookRegistry.check_if_exists_or_initialize(module)
|
||||
registry.remove_hook(_GROUP_OFFLOADING, recurse=True)
|
||||
registry.remove_hook(_LAYER_EXECUTION_TRACKER, recurse=True)
|
||||
registry.remove_hook(_LAZY_PREFETCH_GROUP_OFFLOADING, recurse=True)
|
||||
module.sdnext_group_offload_sig = None
|
||||
return True
|
||||
|
||||
|
||||
def remove_group_offload(sd_model):
|
||||
removed = []
|
||||
for module_name in get_module_names(sd_model):
|
||||
module = getattr(sd_model, module_name, None)
|
||||
if isinstance(module, torch.nn.Module) and remove_group_offload_component(module):
|
||||
removed.append(module_name)
|
||||
for module_name in getattr(sd_model, 'sdnext_ondemand_modules', None) or []:
|
||||
module = getattr(sd_model, module_name, None)
|
||||
if module is not None:
|
||||
module.sdnext_ondemand = False
|
||||
if hasattr(module, '_hf_hook'):
|
||||
module = accelerate.hooks.remove_hook_from_module(module, recurse=True)
|
||||
removed.append(f'{module_name}:ondemand')
|
||||
if getattr(sd_model, 'sdnext_ondemand_modules', None):
|
||||
sd_model.sdnext_ondemand_modules = []
|
||||
if removed:
|
||||
log.debug(f'Offload: type=group op=remove modules={removed}')
|
||||
|
||||
|
||||
def apply_group_offload_component(module, module_name: str, main: bool, op: str = 'model') -> bool:
|
||||
"""Apply group offload to one component. Re-application with unchanged settings is a no-op:
|
||||
the hooks silently keep their original config when re-applied and raise before the first
|
||||
forward, so a changed config must remove the old hooks first."""
|
||||
from diffusers.hooks import apply_group_offloading
|
||||
cfg = group_offload_config(main)
|
||||
if cfg['use_stream'] and not cfg['low_cpu_mem_usage']:
|
||||
size_gb, _params = get_module_size(module)
|
||||
pin_ok = getattr(module, 'sdnext_group_offload_pin', None)
|
||||
if pin_ok is None: # decide once per module: a granted pin moves the weights into locked memory, so re-reading available on the next apply would see it lower by the pinned size and revoke its own grant
|
||||
from modules import memstats
|
||||
avail_gb = memstats.ram_stats().get('avail', 0)
|
||||
reserve_gb = max(8.0, 0.25 * shared.cpu_memory) # pinned pages cannot be reclaimed or swapped, so a quarter of the machine, floored at 8 GB, stays pageable for the process and page cache
|
||||
limit_gb = (avail_gb - reserve_gb) if avail_gb > 0 else (0.5 * shared.cpu_memory) # budget from memory free right now; total-derived ceiling only when psutil cannot say
|
||||
pin_ok = size_gb <= limit_gb
|
||||
module.sdnext_group_offload_pin = pin_ok
|
||||
module.sdnext_group_offload_pin_limit = limit_gb
|
||||
if not pin_ok:
|
||||
# unpinned streaming degrades to per-transfer staging and leaf groups make that a per-module cost,
|
||||
# so the whole leaf+stream shape goes with the pin: few large synchronous groups instead
|
||||
cfg['low_cpu_mem_usage'] = True
|
||||
cfg['use_stream'] = False
|
||||
cfg['record_stream'] = False
|
||||
cfg['offload_type'] = 'block_level'
|
||||
cfg['num_blocks_per_group'] = max(4, int(shared.opts.group_offload_blocks))
|
||||
log.warning(f'Setting {op}: offload=group module={module_name} size={size_gb:.3f} limit={getattr(module, "sdnext_group_offload_pin_limit", 0):.3f} pin=denied type=block_level blocks={cfg["num_blocks_per_group"]} expect ~{size_gb:.0f} GB transferred per step')
|
||||
sig = f'{devices.device}:{main}:' + ':'.join(str(v) for v in cfg.values())
|
||||
if getattr(module, 'sdnext_group_offload_sig', None) == sig:
|
||||
return False
|
||||
if hasattr(module, '_hf_hook'): # leftover accelerate hooks from a previous offload mode abort the group apply upstream
|
||||
module = accelerate.hooks.remove_hook_from_module(module, recurse=True)
|
||||
remove_group_offload_component(module)
|
||||
module.requires_grad_(False)
|
||||
apply_group_offloading(module, onload_device=devices.device, offload_device=devices.cpu, **cfg)
|
||||
module.sdnext_group_offload_sig = sig
|
||||
return True
|
||||
|
||||
|
||||
def set_group_resident(module):
|
||||
"""VAE-class components never take group hooks: the hooks are forward-scoped, while
|
||||
pipelines enter through encode/decode, and tiled calls re-enter per tile."""
|
||||
if hasattr(module, '_hf_hook'):
|
||||
module = accelerate.hooks.remove_hook_from_module(module, recurse=True)
|
||||
remove_group_offload_component(module)
|
||||
module.requires_grad_(False)
|
||||
module.to(devices.device)
|
||||
|
||||
|
||||
def group_offload_role(module_name: str, module) -> str:
|
||||
cls = module.__class__.__name__
|
||||
if 'vae' in module_name.lower() or cls.startswith(('Autoencoder', 'VQModel', 'AsymmetricAutoencoder', 'ConsistencyDecoder')):
|
||||
return 'resident'
|
||||
if module_name.startswith(('text_encoder', 'image_encoder', 'safety_checker')):
|
||||
return 'aux'
|
||||
return 'main'
|
||||
|
||||
|
||||
def has_entry_bridge(module) -> bool:
|
||||
"""Entry points decorated with diffusers' apply_forward_hook fire _hf_hook.pre_forward,
|
||||
which is what carries the on-demand onload for encode and decode calls that bypass forward."""
|
||||
for name in ('decode', 'encode'):
|
||||
fn = getattr(module, name, None)
|
||||
if fn is not None and getattr(fn, '__qualname__', '').startswith('apply_forward_hook'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class OnDemandHook(accelerate.hooks.ModelHook):
|
||||
"""Whole-module onload for components entered through decode or encode rather than forward.
|
||||
Tiled calls re-enter inside one entry point, so the module is on device before the first
|
||||
tile; the return to cpu happens at the processing seams once outputs are materialized."""
|
||||
def pre_forward(self, module, *args, **kwargs):
|
||||
param = next(module.parameters(), None)
|
||||
if param is not None and not devices.same_device(param.device, devices.device):
|
||||
t0 = time.time()
|
||||
module.to(devices.device)
|
||||
dt = time.time() - t0
|
||||
process_timer.add('onload', dt)
|
||||
log.debug(f'Offload: type=ondemand op=onload module={module.__class__.__name__} time={dt:.3f}')
|
||||
return args, kwargs
|
||||
|
||||
|
||||
def set_group_vae(sd_model, module, module_name: str) -> str:
|
||||
"""Placement policy for vae-class components, which never take group hooks. Small
|
||||
components stay resident; components above group_offload_vae_limit rest on cpu and
|
||||
onload whole when their decode or encode entry point fires."""
|
||||
size_gb, _params = get_module_size(module)
|
||||
if size_gb < group_offload_vae_limit or not has_entry_bridge(module):
|
||||
set_group_resident(module)
|
||||
module.sdnext_ondemand = False # a lingering stamp would let the seams offload a component with no onload hook
|
||||
names = getattr(sd_model, 'sdnext_ondemand_modules', None) or []
|
||||
if module_name in names:
|
||||
sd_model.sdnext_ondemand_modules = [n for n in names if n != module_name]
|
||||
return 'resident'
|
||||
if not getattr(module, 'sdnext_ondemand', False) or not hasattr(module, '_hf_hook'):
|
||||
if hasattr(module, '_hf_hook'):
|
||||
module = accelerate.hooks.remove_hook_from_module(module, recurse=True)
|
||||
remove_group_offload_component(module)
|
||||
module.requires_grad_(False)
|
||||
accelerate.hooks.add_hook_to_module(module, OnDemandHook(), append=False)
|
||||
module.sdnext_ondemand = True
|
||||
module.to(devices.cpu)
|
||||
names = getattr(sd_model, 'sdnext_ondemand_modules', None) or []
|
||||
if module_name not in names:
|
||||
sd_model.sdnext_ondemand_modules = names + [module_name]
|
||||
return 'ondemand'
|
||||
|
||||
|
||||
def offload_ondemand(sd_model):
|
||||
"""Return on-demand components to cpu once their outputs are materialized."""
|
||||
if sd_model is None:
|
||||
return
|
||||
names = getattr(sd_model, 'sdnext_ondemand_modules', None)
|
||||
if not names and hasattr(sd_model, 'pipe'):
|
||||
sd_model = sd_model.pipe
|
||||
names = getattr(sd_model, 'sdnext_ondemand_modules', None)
|
||||
for module_name in names or []:
|
||||
module = getattr(sd_model, module_name, None)
|
||||
param = next(module.parameters(), None) if module is not None else None
|
||||
if param is not None and not devices.same_device(param.device, devices.cpu):
|
||||
t0 = time.time()
|
||||
module.to(devices.cpu)
|
||||
dt = time.time() - t0
|
||||
process_timer.add('offload', dt)
|
||||
log.debug(f'Offload: type=ondemand op=offload module={module_name} time={dt:.3f}')
|
||||
|
||||
|
||||
def report_group_stats(sd_model, module_names):
|
||||
"""Per-component stats block once per loaded model; balanced mode prints its own from the hook map."""
|
||||
if getattr(sd_model, 'sdnext_group_stats_reported', False):
|
||||
return
|
||||
sd_model.sdnext_group_stats_reported = True
|
||||
total = 0.0
|
||||
counted = []
|
||||
for module_name in module_names:
|
||||
module = getattr(sd_model, module_name, None)
|
||||
if isinstance(module, torch.nn.Module):
|
||||
total += get_module_size(module)[0]
|
||||
counted.append(module_name)
|
||||
report_model_stats(module_name, module)
|
||||
log.info(f'Model class={sd_model.__class__.__name__} modules={len(counted)} size={total:.3f}')
|
||||
|
||||
|
||||
def apply_modular_group_offload(sd_model, op:str='model'):
|
||||
"""Per-component group offload for modular pipelines, which lack the pipeline-level
|
||||
enable_*_offload entry points. The model and sequential modes also route here."""
|
||||
if shared.opts.diffusers_offload_mode != 'group' and not getattr(sd_model, 'sdnext_modular_offload_warned', False):
|
||||
sd_model.sdnext_modular_offload_warned = True
|
||||
log.warning(f'Setting {op}: offload={shared.opts.diffusers_offload_mode} not supported on modular pipelines: using group offload')
|
||||
applied = []
|
||||
for name in ('transformer', 'transformer_ref'):
|
||||
transformer = getattr(sd_model, name, None)
|
||||
if transformer is not None and apply_group_offload_component(transformer, name, main=True, op=op):
|
||||
applied.append(name)
|
||||
text_encoder = getattr(sd_model, 'text_encoder', None)
|
||||
if text_encoder is not None:
|
||||
# offload targets the inner model when present: conditioning may call it directly,
|
||||
# and hooks on the wrapper forward would never fire
|
||||
if apply_group_offload_component(getattr(text_encoder, 'model', text_encoder), 'text_encoder', main=False, op=op):
|
||||
applied.append('text_encoder')
|
||||
for name in ('vae', 'audio_vae'):
|
||||
component = getattr(sd_model, name, None)
|
||||
if component is not None:
|
||||
placement = set_group_vae(sd_model, component, name)
|
||||
applied.append(f'{name}:{placement}')
|
||||
# has_accelerate stays unset: group hooks are not accelerate hooks, and the modular
|
||||
# pipeline's own to() skips group-offloaded components when move_model runs
|
||||
if any(':' not in name for name in applied):
|
||||
log.info(f'Setting {op}: offload=group type={shared.opts.group_offload_type} modules={applied}')
|
||||
report_group_stats(sd_model, ('transformer', 'transformer_ref', 'text_encoder', 'vae', 'audio_vae'))
|
||||
|
||||
|
||||
def apply_group_offload(sd_model, op:str='model'):
|
||||
applied, resident, ondemand = [], [], []
|
||||
for module_name in get_module_names(sd_model):
|
||||
module = getattr(sd_model, module_name, None)
|
||||
if not isinstance(module, torch.nn.Module):
|
||||
continue
|
||||
try:
|
||||
role = group_offload_role(module_name, module)
|
||||
if role == 'resident':
|
||||
if set_group_vae(sd_model, module, module_name) == 'ondemand':
|
||||
ondemand.append(module_name)
|
||||
else:
|
||||
resident.append(module_name)
|
||||
elif apply_group_offload_component(module, module_name, main=role == 'main', op=op):
|
||||
applied.append(module_name)
|
||||
except Exception as e:
|
||||
log.error(f'Setting {op}: offload=group module={module_name} {e}')
|
||||
set_accelerate(sd_model)
|
||||
if applied:
|
||||
log.info(f'Setting {op}: offload=group type={shared.opts.group_offload_type} modules={applied} resident={resident} ondemand={ondemand}')
|
||||
report_group_stats(sd_model, get_module_names(sd_model))
|
||||
return sd_model
|
||||
|
||||
|
||||
def apply_model_offload(sd_model, op:str='model', quiet:bool=False):
|
||||
try:
|
||||
remove_group_offload(sd_model)
|
||||
log.quiet(quiet, f'Setting {op}: offload={shared.opts.diffusers_offload_mode} limit={shared.opts.cuda_mem_fraction}')
|
||||
if shared.opts.diffusers_move_base or shared.opts.diffusers_move_unet or shared.opts.diffusers_move_refiner:
|
||||
shared.opts.diffusers_move_base = False
|
||||
@@ -117,6 +346,7 @@ def apply_model_offload(sd_model, op:str='model', quiet:bool=False):
|
||||
|
||||
def apply_sequential_offload(sd_model, op:str='model', quiet:bool=False):
|
||||
try:
|
||||
remove_group_offload(sd_model)
|
||||
log.quiet(quiet, f'Setting {op}: offload={shared.opts.diffusers_offload_mode} limit={shared.opts.cuda_mem_fraction}')
|
||||
if shared.opts.diffusers_move_base or shared.opts.diffusers_move_unet or shared.opts.diffusers_move_refiner:
|
||||
shared.opts.diffusers_move_base = False
|
||||
@@ -144,6 +374,7 @@ def apply_none_offload(sd_model, op:str='model', quiet:bool=False):
|
||||
log.quiet(quiet, f'Setting {op}: offload={shared.opts.diffusers_offload_mode} limit={shared.opts.cuda_mem_fraction}')
|
||||
try:
|
||||
sd_model.has_accelerate = False
|
||||
remove_group_offload(sd_model)
|
||||
if hasattr(sd_model, 'maybe_free_model_hooks'):
|
||||
sd_model.maybe_free_model_hooks()
|
||||
sd_model = accelerate.hooks.remove_hook_from_module(sd_model, recurse=True)
|
||||
@@ -224,6 +455,14 @@ class OffloadHook(accelerate.hooks.ModelHook):
|
||||
def model_size(self):
|
||||
return sum(self.offload_map.values())
|
||||
|
||||
def matches(self, module, names: list, module_name: str | None = None) -> bool:
|
||||
"""Match against an always/never list by class name or by pipeline component name.
|
||||
Component entries such as `text_encoder` cover every architecture without listing each encoder class."""
|
||||
if module.__class__.__name__ in names:
|
||||
return True
|
||||
module_name = module_name or getattr(module, 'module_name', None)
|
||||
return module_name is not None and module_name in names
|
||||
|
||||
def init_hook(self, module):
|
||||
return module
|
||||
|
||||
@@ -249,8 +488,7 @@ class OffloadHook(accelerate.hooks.ModelHook):
|
||||
for pipe in get_pipe_variants():
|
||||
for module_name in get_module_names(pipe):
|
||||
module_instance = getattr(pipe, module_name, None)
|
||||
module_cls = module_instance.__class__.__name__
|
||||
if (module_instance is not None) and (_id != id(module_instance)) and (module_cls not in self.offload_never) and (not devices.same_device(getattr(module_instance, "device", devices.cpu), devices.cpu)):
|
||||
if (module_instance is not None) and (_id != id(module_instance)) and (not self.matches(module_instance, self.offload_never, module_name)) and (not devices.same_device(getattr(module_instance, "device", devices.cpu), devices.cpu)):
|
||||
apply_balanced_offload_to_module(module_instance, op='pre')
|
||||
self.last_cls = module.__class__.__name__
|
||||
process_timer.add('offload', time.time() - t0)
|
||||
@@ -440,9 +678,9 @@ def move_module_to_cpu(module, op='unk', force:bool=False):
|
||||
op = f'{op}:force'
|
||||
module = do_move(module)
|
||||
used_gpu -= module_size
|
||||
elif module_cls in offload_hook_instance.offload_never:
|
||||
elif offload_hook_instance.matches(module, offload_hook_instance.offload_never, module_name):
|
||||
op = f'{op}:never'
|
||||
elif module_cls in offload_hook_instance.offload_always:
|
||||
elif offload_hook_instance.matches(module, offload_hook_instance.offload_always, module_name):
|
||||
op = f'{op}:always'
|
||||
module = do_move(module)
|
||||
used_gpu -= module_size
|
||||
@@ -505,7 +743,9 @@ def get_logical_param_count(module: torch.nn.Module) -> int:
|
||||
|
||||
def report_model_stats(module_name, module):
|
||||
try:
|
||||
size = offload_hook_instance.offload_map.get(module_name, 0)
|
||||
size = offload_hook_instance.offload_map.get(module_name, 0) if offload_hook_instance is not None else 0
|
||||
if size == 0:
|
||||
size, _params = get_module_size(module)
|
||||
quant = getattr(module, "quantization_method", None)
|
||||
params = sum(p.numel() for p in module.parameters(recurse=True))
|
||||
logical = get_logical_param_count(module)
|
||||
@@ -528,6 +768,7 @@ def apply_balanced_offload(sd_model=None, exclude: list[str] | None = None, forc
|
||||
exclude = []
|
||||
if sd_model.__class__.__name__ in balanced_offload_exclude:
|
||||
return sd_model
|
||||
remove_group_offload(sd_model)
|
||||
|
||||
t0 = time.time()
|
||||
cached = True
|
||||
|
||||
@@ -153,6 +153,7 @@ def create_settings(cmd_opts):
|
||||
"group_offload_type": OptionInfo("leaf_level", "Group offload type", gr.Radio, {"choices": ['leaf_level', 'block_level']}),
|
||||
"group_offload_stream": OptionInfo(False, "Use torch streams", gr.Checkbox),
|
||||
'group_offload_record': OptionInfo(False, "Record torch streams", gr.Checkbox),
|
||||
'group_offload_pin': OptionInfo(True, "Pin offload memory", gr.Checkbox),
|
||||
'group_offload_blocks': OptionInfo(1, "Offload blocks", gr.Number),
|
||||
}))
|
||||
|
||||
|
||||
@@ -620,7 +620,7 @@
|
||||
{"id":"","label":"Generic","localized":"","hint":"","ui":"video"},
|
||||
{"id":"","label":"Google GenAI","localized":"","hint":"","ui":"settings_model_options"},
|
||||
{"id":"","label":"Group Offload","localized":"","hint":"","ui":"settings_offload"},
|
||||
{"id":"","label":"Group offload type","localized":"","hint":"Granularity used by <b>group</b> offload.<br>- <b>leaf_level</b>: offloads at the smallest module level; maximum memory savings, slower<br>- <b>block_level</b>: offloads groups of transformer blocks (size set by <b><i>Offload blocks</i></b>); faster with less savings, and keeps the VAE resident<br><br>Applies only when <b><i>Model offload mode</i></b> is <b>group</b>.<br><br>Default is <b>leaf_level</b>.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Group offload type","localized":"","hint":"Granularity used by <b>group</b> offload.<br>- <b>leaf_level</b>: offloads at the smallest module level; maximum memory savings, slower<br>- <b>block_level</b>: offloads groups of transformer blocks (size set by <b><i>Offload blocks</i></b>); faster with less savings<br>Text encoders always offload at leaf level. Small VAEs stay resident on the GPU; VAEs above 1GB rest in system memory and load whole for each encode or decode.<br><br>Applies only when <b><i>Model offload mode</i></b> is <b>group</b>.<br><br>Default is <b>leaf_level</b>.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Grid Options","localized":"","hint":"","ui":"settings_saving-images"},
|
||||
{"id":"","label":"Grids","localized":"","hint":"","ui":"settings_saving-paths"},
|
||||
{"id":"","label":"Guider","localized":"","hint":"","ui":"txt2img"},
|
||||
@@ -943,8 +943,8 @@
|
||||
{"id":"","label":"Model load model direct to GPU","localized":"","hint":"","ui":"settings_sd"},
|
||||
{"id":"","label":"Model offload mode","localized":"","hint":"Controls how model components move between VRAM and system RAM to fit larger models on less VRAM.<br>- <b>none</b>: keeps everything on the GPU; fastest, but only works if the whole model fits in VRAM<br>- <b>balanced</b>: the recommended default; offloads only when VRAM use crosses a threshold, so it suits almost any GPU (tuned by the watermarks below)<br>- <b>group</b>: offloads groups of layers via diffusers group offloading; an alternative middle ground with optional stream prefetch<br>- <b>model</b>: offloads whole components such as the VAE or text encoder when idle; a more compatible fallback when balanced or group are unsupported, with smaller savings<br>- <b>sequential</b>: offloads layer by layer; the most memory saving but slowest, for when even balanced runs out of memory<br><br>Command-line flags override the automatic choice:<br>- <code>--lowvram</code>: forces <b>sequential</b><br>- <code>--medvram</code>: forces <b>balanced</b> with low watermark <b>0</b><br><br>With no flag, <b>balanced</b> is the automatic default on any GPU, with watermarks set by GPU memory (low / high):<br>- 12 GB or less: <b>0</b> / <b>0.6</b><br>- 12-24 GB: <b>0.2</b> / <b>0.6</b><br>- 24 GB or more: <b>0.2</b> / <b>0.8</b><br>(or <b>none</b> if no GPU is detected)","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Model types not to offload","localized":"","hint":"Model architectures to skip when offloading, separated by spaces or commas.<br>Useful for model types that misbehave when offloaded.<br><br>Applies only to <b>balanced</b> offload.<br><br>Default is empty.","ui":"settings_offload"},
|
||||
{"id":"","label":"Modules to always offload","localized":"","hint":"Module names that are always offloaded in <b>balanced</b> mode, separated by spaces, commas, or semicolons, regardless of the watermarks.<br><br>Applies only to <b>balanced</b> offload.<br><br>Default by GPU memory: the large text encoders (<i>T5</i>, <i>UMT5</i>) are added at roughly 4-12 GB and at 24 GB or more; empty otherwise.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Modules to never offload","localized":"","hint":"Module names that are never offloaded in <b>balanced</b> mode, separated by spaces, commas, or semicolons, keeping them resident in VRAM.<br><br>Applies only to <b>balanced</b> offload.<br><br>Default by GPU memory: the CLIP text encoders and the VAE are kept resident at 24 GB or more; empty otherwise.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Modules to always offload","localized":"","hint":"Modules that are always offloaded in <b>balanced</b> mode, separated by spaces, commas, or semicolons, regardless of the watermarks.<br>Entries match either a class name (<i>T5EncoderModel</i>) or a pipeline component name (<i>text_encoder</i>, <i>text_encoder_2</i>).<br>A component entry covers every model architecture without naming each encoder class.<br><br>Applies only to <b>balanced</b> offload.<br><br>Default by GPU memory: the large text encoders (<i>T5</i>, <i>UMT5</i>) are added at roughly 4-12 GB and at 24 GB or more; empty otherwise.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Modules to never offload","localized":"","hint":"Modules that are never offloaded in <b>balanced</b> mode, separated by spaces, commas, or semicolons, keeping them resident in VRAM.<br>Entries match either a class name (<i>CLIPTextModel</i>) or a pipeline component name (<i>vae</i>).<br>This list takes precedence, so a class entry here exempts one model from a component entry in <b><i>Modules to always offload</i></b>.<br><br>Applies only to <b>balanced</b> offload.<br><br>Default by GPU memory: the CLIP text encoders and the VAE are kept resident at 24 GB or more; empty otherwise.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Model types not to quantize","localized":"","hint":"Model families that quantization always skips, even when it is otherwise enabled. Space or comma separated list of model type codes; when the loaded model matches, none of its components are quantized.<br><br>Codes are the short family names shown in the load log, such as <code>sd</code>, <code>sdxl</code>, <code>sd3</code>, <code>f1</code>.<br><br>Example: <code>sd sdxl</code> leaves <i>SD</i> and <i>SDXL</i> checkpoints in full precision while other families are still quantized.<br><br>Applies to all quantization backends.<br><br>Default is empty.","reload":"model","ui":"settings_quantization"},
|
||||
{"id":"","label":"Modules to not convert","localized":"","hint":"Names of modules to leave unquantized (kept in original precision), separated by spaces, commas, or semicolons.<br>Useful for layers that are sensitive to quantization, such as gate or projection layers. Example: <code>proj_out, x_embedder</code>.<br><br>Some models already exclude sensitive modules by default; entries here extend that built-in list rather than replacing it.<br><br>Default is empty.","reload":"model","ui":"settings_quantization"},
|
||||
{"id":"","label":"Modules dtype dict","localized":"","hint":"Advanced: JSON mapping a quantization type to a list of module names, to quantize specific modules at a different type than the global <b><i>Quantization type</i></b>. Example: <code>{\"uint4\": [\"proj_out\"]}</code>.<br><br>Some models already assign certain modules a specific type by default; entries here merge with those built-in mappings rather than replacing them.<br><br>Default is empty.","reload":"model","ui":"settings_quantization"},
|
||||
@@ -1067,6 +1067,7 @@
|
||||
{"id":"","label":"OpenBody","localized":"","hint":"","ui":"control"}
|
||||
],
|
||||
"p": [
|
||||
{"id":"","label":"Pin offload memory","localized":"","hint":"Keeps the CPU copy of every stream-offloaded weight in pinned non-pageable memory for the fastest transfers, at a host memory cost equal to the full module size.<br>When disabled, memory is pinned one group at a time during transfer: slower, but the weights stay pageable and use no extra memory at rest.<br>Modules larger than half of system memory fall back to per-group pinning automatically.<br><br>Applies only to <b>group</b> offload with <b><i>Use torch streams</i></b> enabled.<br><br>Enabled by default.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"extras_nav","label":"Process","localized":"","hint":"Process existing image<br>Can be used to upscale images, remove backgrounds, obfuscate NSFW content, apply various filters and effects"},
|
||||
{"id":"txt2img_prompts","label":"Prompts","localized":"","hint":"Image prompt and negative prompt","ui":"txt2img"},
|
||||
{"id":"txt2img_pause","label":"Pause","localized":"","hint":"Pause processing","ui":"txt2img"},
|
||||
@@ -1237,7 +1238,7 @@
|
||||
{"id":"","label":"Rebase","localized":"","hint":"","ui":"tab_update"},
|
||||
{"id":"","label":"Repos","localized":"","hint":"","ui":"component-8779"},
|
||||
{"id":"","label":"Refiner model","localized":"","hint":"Refiner model used for second-pass operations","ui":"settings_sd"},
|
||||
{"id":"","label":"Record torch streams","localized":"","hint":"Records CUDA stream usage during <b>group</b> offload so reused buffers stay correct when <b><i>Use torch streams</i></b> is enabled.<br><br>Applies only to <b>group</b> offload with streams.<br><br>Disabled by default.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Record torch streams","localized":"","hint":"Skips a stream synchronization each time a group offloads, letting transfers and compute overlap more tightly.<br>Slightly faster at the cost of slightly higher VRAM use; correctness is maintained either way.<br>Has no effect unless <b><i>Use torch streams</i></b> is enabled.<br><br>Applies only to <b>group</b> offload with streams.<br><br>Disabled by default.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Remote VAE image type","localized":"","hint":"","ui":"settings_vae_encoder"},
|
||||
{"id":"","label":"Remote VAE for encode","localized":"","hint":"","ui":"settings_vae_encoder"},
|
||||
{"id":"","label":"RAS enabled","localized":"","hint":"","ui":"settings_advanced"},
|
||||
@@ -1562,7 +1563,7 @@
|
||||
{"id":"","label":"Username","localized":"","hint":"","ui":"component-8823"},
|
||||
{"id":"","label":"UNET model","localized":"","hint":"","ui":"settings_sd"},
|
||||
{"id":"","label":"UNET model secondary","localized":"","hint":"Override for the second transformer of dual-transformer architectures:<br>- <b>Ideogram 4</b>: the unconditional transformer<br>- <b>Wan</b> combined stage: the second expert (transformer_2)<br><br>Shown only when the loaded model has a second transformer.<br>Default loads the component from the base model.","ui":"settings_sd"},
|
||||
{"id":"","label":"Use torch streams","localized":"","hint":"In <b>group</b> offload, uses CUDA streams to prefetch the next group while the current one runs, hiding transfer latency.<br>Faster, but uses more VRAM.<br><br>Applies only to <b>group</b> offload.<br><br>Disabled by default.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Use torch streams","localized":"","hint":"In <b>group</b> offload, uses CUDA streams to prefetch the next group while the current one runs, hiding transfer latency.<br>Faster, but offloaded weights are then staged in pinned non-pageable host memory: the whole module when <b><i>Pin offload memory</i></b> is enabled, one group at a time otherwise.<br>Text encoders are exempt and always offload without streams.<br><br>Applies only to <b>group</b> offload.<br><br>Disabled by default.","reload":"model","ui":"settings_offload"},
|
||||
{"id":"","label":"Use SVD quantization","localized":"","hint":"Adds a low-rank (SVDQuant) correction on top of SDNQ to recover accuracy lost at low bit widths, at the cost of extra size and compute.<br>Tuned by <b><i>SVD rank size</i></b> and <b><i>SVD steps</i></b>.<br><br>Disabled by default.","reload":"model","ui":"settings_quantization"},
|
||||
{"id":"","label":"Use Dynamic quantization","localized":"","hint":"Picks a per-layer weight type automatically instead of one type everywhere. Each layer starts at the <b><i>Quantization type</i></b> (the minimum) and steps up to higher precision until its error meets the <b><i>Dynamic loss threshold</i></b>.<br>Protects error-sensitive layers at the cost of a larger model and slower load.<br><br>Disabled by default.","reload":"model","ui":"settings_quantization"},
|
||||
{"id":"","label":"Use Hadamard rotations","localized":"","hint":"Applies a Hadamard rotation before quantizing to spread out weight outliers, which can improve accuracy at low bit widths.<br>Group size is set by <b><i>Hadamard group size</i></b>.<br><br>Disabled by default.","reload":"model","ui":"settings_quantization"},
|
||||
|
||||
Reference in New Issue
Block a user