diff --git a/CHANGELOG.md b/CHANGELOG.md index 681cc1331..76a3ee0d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Change Log for SD.Next -## Update for 2026-08-13 +## Update for 2026-08-14 - **Models** - [MiniMax H3](https://huggingface.co/MiniMaxAI/MiniMax-H3) in *base* and *ref* variants MiniMax-H3 is an amazing, but absolutely massive at 32B text-encoder and 33B transformer video model - for details, see [MiniMax wiki page](wiki/MiniMax) + see [MiniMax wiki page](wiki/MiniMax) for details and usage instructions - **Detailer**: Pretty much *detailer.next* :) Detailer detection models were traditionally *YOLO* models, but now we can also use: - [Facebook-SAM3](https://huggingface.co/facebook/sam3) hybrid promptable concept segmentation and detection network @@ -30,8 +30,8 @@ - add `SD_DIFFUSERS_DEBUG` and `SD_TRANSFORMERS_DEBUG` env variables to trace diffusers and transformers internal operations - **API** - full support for video generation using api - *note*: video api uses async workflow where you submit request and then later download the result new endpoints: `/sdapi/v1/video`, `/sdapi/v1/video/models`, `/sdapi/v1/video/file` + *note*: video api uses async workflow where you submit request and then later download the result - **Removed** - remove DirectML support latest release was over 2 years ago and is not compatible with modern frameworks @@ -43,6 +43,7 @@ - improve pipeline detection for non-cached models - cleanup alt offload codepaths - hf progress bars + - processing stats reporting ## Update for 2026-08-07 diff --git a/extensions-builtin/sdnq b/extensions-builtin/sdnq index 46fe58736..d19656260 160000 --- a/extensions-builtin/sdnq +++ b/extensions-builtin/sdnq @@ -1 +1 @@ -Subproject commit 46fe58736ad454362b35d45d9f34103c09ce7d09 +Subproject commit d19656260d32be0636301665e4204ea9dbbd2f92 diff --git a/modules/devices.py b/modules/devices.py index c6e792145..114b1623a 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -143,7 +143,10 @@ def get_gpu_info(): } elif backend == 'cuda' or backend == 'zluda': return { - 'device': f'{torch.cuda.get_device_name(torch.cuda.current_device())} n={torch.cuda.device_count()} arch={torch.cuda.get_arch_list()[-1]} capability={torch.cuda.get_device_capability(device)}', + 'device': f'"{torch.cuda.get_device_name(torch.cuda.current_device())}"', + 'count': torch.cuda.device_count(), + 'arch': torch.cuda.get_arch_list()[-1], + 'capability': torch.cuda.get_device_capability(torch.cuda.current_device()), 'cuda': torch.version.cuda, 'cudnn': torch.backends.cudnn.version(), 'driver': get_driver(), diff --git a/modules/loader.py b/modules/loader.py index 275afadae..14aea1c11 100644 --- a/modules/loader.py +++ b/modules/loader.py @@ -11,6 +11,7 @@ from modules import timer, errors from modules.logger import log +log.info('Initializing: packages') initialized = False errors.install() logging.getLogger("DeepSpeed").disabled = True diff --git a/modules/processing.py b/modules/processing.py index b5cd149e7..59982495c 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -417,7 +417,7 @@ def process_samples(p: StableDiffusionProcessing, samples): def print_stats(): - log.debug(f'Processed: timers={timer.process.dct()}') + log.debug(f'Processed: timers={timer.process.dct(no_total=True)}') log.debug(f'Processed: memory={memstats.memory_stats()}') if devices.triton_ok: @@ -428,13 +428,14 @@ def print_stats(): from modules.sd_models_compile import update_compile_times update_compile_times() - dynamo_dct = timer.dynamo.dct(min_time=0.5, no_total=True) + dynamo_dct = timer.dynamo.dct(min_time=1.0, no_total=True) timer.dynamo.reset() if dynamo_dct: log.debug(f'Processed: dynamo={dynamo_dct}') def process_images_inner(p: StableDiffusionProcessing) -> Processed: + t0 = time.time() if type(p.prompt) == list: assert len(p.prompt) > 0 else: @@ -453,7 +454,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: jobid = shared.state.begin('Process') shared.state.batch_count = p.n_iter with devices.inference_context(): - t0 = time.time() + t1 = time.time() if not hasattr(p, 'skip_init'): p.init(p.all_prompts, p.all_seeds, p.all_subseeds) debug(f'Processing inner: args={vars(p)}') @@ -553,7 +554,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: shared.sd_model.restore_pipeline() shared.sd_model = sd_models.set_diffuser_pipe(shared.sd_model, sd_models.DiffusersTaskType.TEXT_2_IMAGE) - t1 = time.time() + t2 = time.time() + timer.process.add('process', t2 - t1) p.color_corrections = None index_of_first_image = 0 @@ -588,9 +590,12 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.scripts.postprocess(p, results) timer.process.record('post') p.ops = list(set(p.ops)) + t3 = time.time() + timer.process.add('wall', t3 - t0) if not p.disable_extra_networks: - log.info(f'Processed: images={len(output_images)} its={(p.steps * len(output_images)) / (t1 - t0):.3f} ops={p.ops}') + its = (p.steps * len(output_images)) / (t2 - t1) + log.info(f'Processed: images={len(output_images)} its={its:.3f} ops={p.ops}') print_stats() if shared.cmd_opts.lowvram or shared.cmd_opts.medvram: diff --git a/modules/processing_vae.py b/modules/processing_vae.py index 0d18e875d..03c1161d2 100644 --- a/modules/processing_vae.py +++ b/modules/processing_vae.py @@ -90,11 +90,10 @@ def full_vae_decode(latents, model): if debug: devices.torch_gc(force=True) shared.mem_mon.reset() - if shared.opts.diffusers_offload_mode != "sequential": sd_models.move_model(model.vae, devices.device) - sd_models.set_vae_options(model, vae=None, op='decode') + # sd_models.set_vae_options(model, vae=None, op='decode') # set during model load upcast = (model.vae.dtype == torch.float16) and (getattr(model.vae.config, 'force_upcast', False) or shared.opts.no_half_vae) if upcast: if hasattr(model, 'upcast_vae'): # this is done by diffusers automatically if output_type != 'latent' diff --git a/modules/sd_models.py b/modules/sd_models.py index a393652fd..6df77d0d4 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -12,7 +12,7 @@ import torch import huggingface_hub as hf from modules.logger import log from modules import timer, paths, shared, modelloader, devices, script_callbacks, sd_vae, sd_unet, errors, sd_models_compile, sd_detect, model_quant, sd_hijack_te, sd_hijack_vae, sd_hijack_accelerate, sd_hijack_safetensors, sd_hijack_transformers, sd_hijack_hfhub, attention -from modules.memstats import memory_stats +from modules.memstats import memory_stats, gpu_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 @@ -149,7 +149,8 @@ def set_vae_options(sd_model, vae=None, op:str='model', quiet:bool=False): ops['upcast'] = True sd_model.vqvae.to(torch.float32) # vqvae is producing nans in fp16 if not quiet and len(ops) > 0: - log.quiet(quiet, f'Setting {op}: component=vae {ops}') + fn = f'{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access + log.quiet(quiet, f'Setting {op}: component=vae {ops} fn={fn}') def set_diffuser_options(sd_model, vae=None, op:str='model', offload:bool=True, quiet:bool=False): @@ -1685,3 +1686,10 @@ def list_hfcache(): checkpoint = CheckpointInfo(filename=f.path, name=path_to_repo(f.name), model_type='hfcache') checkpoints.append(checkpoint) return checkpoints + + +def warn_group_offload(min_vram: int = 0): + vram = gpu_stats() + vram = round(vram['total'] if "total" in vram else 0) + if (0 < vram < min_vram) and (shared.opts.diffusers_offload_mode in ['none', 'balanced', 'model']): + log.warning(f'Load model: vram={vram} min={min_vram} offload={shared.opts.diffusers_offload_mode} recommended=group reason="insufficient vram"') diff --git a/modules/sd_offload.py b/modules/sd_offload.py index 0b8089a64..8bdb6fd48 100644 --- a/modules/sd_offload.py +++ b/modules/sd_offload.py @@ -796,10 +796,10 @@ def apply_balanced_offload(sd_model=None, exclude: list[str] | None = None, forc report_model_stats(module_name, module) set_accelerate(sd_model) - t = time.time() - t0 - process_timer.add('offload', t) + t1 = time.time() + process_timer.add('offload', t1 - t0) fn = f'{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access - debug_move(f'Apply offload: time={t:.2f} type=balanced fn={fn}') + debug_move(f'Apply offload: time={t1 - t0:.2f} type=balanced fn={fn}') if not cached: log.info(f'Model class={sd_model.__class__.__name__} modules={len(offload_hook_instance.offload_map)} size={offload_hook_instance.model_size():.3f}') return sd_model diff --git a/modules/timer.py b/modules/timer.py index dcb2b642b..cdd443188 100644 --- a/modules/timer.py +++ b/modules/timer.py @@ -64,6 +64,11 @@ class Timer: res += " ".join([f"{category}={time_taken:.2f}" for category, time_taken in additions]) return res + def max(self): + if not self.records: + return 0 + return max(self.records.values()) + def get_total(self): return sum(self.records.values()) diff --git a/modules/ui_control.py b/modules/ui_control.py index dd07aaa03..f31edb989 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -35,11 +35,12 @@ def return_stats(t: float | None = None): ooms = mem_mon_read.pop("oom") retries = mem_mon_read.pop("retries") vram = {k: v // 1048576 for k, v in mem_mon_read.items()} - peak = max(vram.get('active_peak', 0), vram.get('reserved_peak', 0), vram.get('used', 0)) - used = round(100.0 * peak / vram.get('total', 0)) if vram.get('total', 0) > 0 else 0 - if peak > 0: - gpu += f"| 🕮 GPU {peak} MB" - gpu += f" {used}%" if used > 0 else '' + peak_mb = max(vram.get('active_peak', 0), vram.get('reserved_peak', 0), vram.get('used', 0)) + peak_gb = round(100.0 * peak_mb / 1024) / 100.0 + used_perc = round(100.0 * peak_mb / vram.get('total', 0)) if vram.get('total', 0) > 0 else 0 + if peak_mb > 0: + gpu += f"| 🕮 GPU {peak_gb} GB" + gpu += f" {used_perc}%" if used_perc > 0 else '' gpu += f" | Retries {retries} OOM {ooms}" if retries > 0 or ooms > 0 else '' ram = ram_stats() if ram['used'] > 0: diff --git a/modules/ui_definitions.py b/modules/ui_definitions.py index 4aa269a11..dc356cbfb 100644 --- a/modules/ui_definitions.py +++ b/modules/ui_definitions.py @@ -138,8 +138,6 @@ def create_settings(cmd_opts): "offload_sep": OptionInfo("

Model Offloading

", "", gr.HTML), "diffusers_offload_mode": OptionInfo(startup_offload_mode, "Model offload mode", gr.Radio, {"choices": ['none', 'balanced', 'group', 'model', 'sequential']}), "diffusers_offload_nonblocking": OptionInfo(False, "Non-blocking move operations"), - "caption_offload": OptionInfo(True, "Offload caption models"), - "caption_to_gpu": OptionInfo(True, "Load caption models direct to GPU"), "offload_balanced_sep": OptionInfo("

Balanced Offload

", "", gr.HTML), "diffusers_offload_pre": OptionInfo(True, "Offload during pre-forward", gr.Checkbox, {"visible": False}), "diffusers_offload_streams": OptionInfo(False, "Offload using streams"), @@ -155,6 +153,9 @@ def create_settings(cmd_opts): '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), + "caption_offload_sep": OptionInfo("

Caption Model Offloading

", "", gr.HTML), + "caption_offload": OptionInfo(True, "Offload caption models"), + "caption_to_gpu": OptionInfo(True, "Load caption models direct to GPU"), })) # --- Model Quantization --- diff --git a/pipelines/model_minimax.py b/pipelines/model_minimax.py index f0df0d227..a29bc3907 100644 --- a/pipelines/model_minimax.py +++ b/pipelines/model_minimax.py @@ -14,6 +14,7 @@ def load_minimax(checkpoint_info, diffusers_load_config = None, workflow: str | workflow = (workflow or getattr(checkpoint_info, 'subfolder', None) or 'fl2va').lower() # one repo holds both checkpoint partitions; reference entries select ref2va via the subfolder tag log.debug(f'Load model: type=MiniMaxH3 repo="{repo_id}" workflow={workflow} offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype}') + sd_models.warn_group_offload(min_vram=20) repo_cls = diffusers.MiniMaxH3ModularPipeline pipe = load_modular_pipe( repo_cls, diff --git a/wiki b/wiki index 4114a2e8c..5bca12bb6 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 4114a2e8c236e93678ab3d5abbe1b261d2b841ad +Subproject commit 5bca12bb6cdf8c89633a647d6778a4f796d80e43