mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 01:04:32 +02:00
Merge branch 'dev' into feat/ltx-2.5
This commit is contained in:
+5
-2
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
@@ -14,6 +14,7 @@
|
||||
*note*: LTX-2.5 is a [gated model](https://vladmandic.github.io/sdnext-docs/Gated/)
|
||||
*note*: image conditioning is now re-compressed to match what the models were trained on,
|
||||
at CRF 18 for LTX-2.5 and 33 for earlier LTX-2.x versions
|
||||
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
|
||||
@@ -38,8 +39,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
|
||||
@@ -57,6 +58,8 @@
|
||||
- ltx: reload the latent upsampler when the model or its repo changes
|
||||
- video: take the audio sample rate from the loaded vocoder
|
||||
- video: keep the shared text encoder out of the registry rows
|
||||
- processing stats reporting
|
||||
- image metadata handle correct image index
|
||||
|
||||
## Update for 2026-08-07
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
"preview": "MiniMaxAI--MiniMax-H3.jpg",
|
||||
"desc": "Quantization of MiniMaxAI/MiniMax-H3 using SDNQ: dynamic 4-bit uint. Video with synchronized audio; in image tabs the model runs in experimental still mode.",
|
||||
"extras": "sampler: Default",
|
||||
"size": 47.33,
|
||||
"size": 64.80,
|
||||
"date": "2026 August"
|
||||
},
|
||||
"MiniMaxAI MiniMax-H3 sdnq-uint4 Ref2VA": {
|
||||
@@ -93,24 +93,24 @@
|
||||
"subfolder": "ref2va",
|
||||
"desc": "Quantization of MiniMaxAI/MiniMax-H3 using SDNQ: dynamic 4-bit uint. Video with synchronized audio; in image tabs the model runs in experimental still mode.",
|
||||
"extras": "sampler: Default",
|
||||
"size": 47.33,
|
||||
"size": 64.80,
|
||||
"date": "2026 August"
|
||||
},
|
||||
"MiniMaxAI MiniMax-H3 Pruned sdnq-uint4": {
|
||||
"path": "OzzyGT/MiniMax_H3_sdnq_4bit_pruned",
|
||||
"preview": "MiniMaxAI--MiniMax-H3.jpg",
|
||||
"preview": "OzzyGT--MiniMax_H3_sdnq_4bit_pruned.jpg",
|
||||
"desc": "Quantization of MiniMaxAI/MiniMax-H3 using SDNQ: dynamic 4-bit uint. Video with synchronized audio; in image tabs the model runs in experimental still mode.",
|
||||
"extras": "sampler: Default",
|
||||
"size": 47.33,
|
||||
"size": 23.70,
|
||||
"date": "2026 August"
|
||||
},
|
||||
"MiniMaxAI MiniMax-H3 Pruned sdnq-uint4 Ref2VA": {
|
||||
"path": "OzzyGT/MiniMax_H3_sdnq_4bit_pruned",
|
||||
"preview": "MiniMaxAI--MiniMax-H3.jpg",
|
||||
"preview": "OzzyGT--MiniMax_H3_sdnq_4bit_pruned.jpg",
|
||||
"subfolder": "ref2va",
|
||||
"desc": "Quantization of MiniMaxAI/MiniMax-H3 using SDNQ: dynamic 4-bit uint. Video with synchronized audio; in image tabs the model runs in experimental still mode.",
|
||||
"extras": "sampler: Default",
|
||||
"size": 47.33,
|
||||
"size": 23.70,
|
||||
"date": "2026 August"
|
||||
},
|
||||
"Z-Image-Turbo sdnq-svd-uint4": {
|
||||
|
||||
+1
-1
Submodule extensions-builtin/sdnq updated: 46fe58736a...d19656260d
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
+4
-1
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
+10
-5
@@ -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:
|
||||
|
||||
@@ -23,6 +23,7 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No
|
||||
if not hasattr(shared.sd_model, 'sd_checkpoint_info'):
|
||||
return ''
|
||||
|
||||
job_size = p.n_iter * p.batch_size
|
||||
if index is None:
|
||||
index = position_in_batch + iteration * p.batch_size
|
||||
if all_prompts is None:
|
||||
@@ -209,7 +210,13 @@ def create_infotext(p: StableDiffusionProcessing, all_prompts=None, all_seeds=No
|
||||
if shared.sd_model_type == 'h1':
|
||||
args['LLM'] = None if shared.opts.model_h1_llama_repo == 'Default' else shared.opts.model_h1_llama_repo
|
||||
|
||||
args.update(p.extra_generation_params)
|
||||
# args.update(p.extra_generation_params)
|
||||
for k, v in p.extra_generation_params.items():
|
||||
if isinstance(v, (list, tuple)) and (job_size > index) and (len(v) > 1) and (len(v) == job_size): # likely a per-job param
|
||||
args[k] = v[index]
|
||||
else:
|
||||
args[k] = v
|
||||
|
||||
for k, v in args.copy().items():
|
||||
if v is None:
|
||||
del args[k]
|
||||
|
||||
@@ -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'
|
||||
|
||||
+10
-2
@@ -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"')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -60,7 +60,7 @@ def get_default_modes(cmd_opts, mem_stat):
|
||||
default_sdp_override_options = ['Dynamic attention']
|
||||
|
||||
if devices.get_optimal_device_name() != "cpu":
|
||||
os.environ.setdefault('SDNQ_USE_OPENVINO_MM', '0')
|
||||
os.environ.setdefault('SDNQ_USE_OPENVINO_MM', '0') # TODO sdnq openvino: this is too late as sdnq already initialized it
|
||||
|
||||
return (
|
||||
default_offload_mode,
|
||||
|
||||
+3
-2
@@ -123,6 +123,7 @@ def apply_file_wildcards(prompt, replaced = None, not_found = None, recursion=0,
|
||||
not_found = []
|
||||
if replaced is None:
|
||||
replaced = []
|
||||
|
||||
def check_wildcard_files(prompt, wildcard, files, file_only=True):
|
||||
trimmed = wildcard.replace('\\', os.path.sep).replace('/', os.path.sep).strip().lower()
|
||||
for file in files:
|
||||
@@ -144,8 +145,6 @@ def apply_file_wildcards(prompt, replaced = None, not_found = None, recursion=0,
|
||||
prompt = prompt.replace(f"__{wildcard}__", choice, 1)
|
||||
log.debug(f'Apply wildcard: select="{wildcard}" choice="{choice}" file="{file}" choices={len(lines)}')
|
||||
replaced.append(wildcard)
|
||||
if p is not None:
|
||||
p.extra_generation_params['Wildcards'] = p.extra_generation_params.get('Wildcards', []) + [trimmed]
|
||||
return prompt, True
|
||||
except Exception as e:
|
||||
log.error(f'Wildcards: wildcard={wildcard} file={file} {e}')
|
||||
@@ -206,6 +205,8 @@ def apply_wildcards_to_prompt(prompt, all_wildcards, seed=-1, silent=False, p: S
|
||||
log.debug(f'Apply wildcards: {replaced} path="{shared.opts.wildcards_dir}" type=style time={t1-t0:.2f}')
|
||||
if (len(replaced_file) > 0 or len(not_found) > 0) and not silent:
|
||||
log.debug(f'Apply wildcards: found={replaced_file} missing={not_found} path="{shared.opts.wildcards_dir}" type=file seed={seed} time={t2-t2:.2f}')
|
||||
if p is not None:
|
||||
p.extra_generation_params['Wildcards'] = p.extra_generation_params.get('Wildcards', []) + [replaced_file]
|
||||
if old_state is not None:
|
||||
random.setstate(old_state)
|
||||
return prompt
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -138,8 +138,6 @@ def create_settings(cmd_opts):
|
||||
"offload_sep": OptionInfo("<h2>Model Offloading</h2>", "", 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("<h2>Balanced Offload</h2>", "", 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("<h2>Caption Model Offloading</h2>", "", gr.HTML),
|
||||
"caption_offload": OptionInfo(True, "Offload caption models"),
|
||||
"caption_to_gpu": OptionInfo(True, "Load caption models direct to GPU"),
|
||||
}))
|
||||
|
||||
# --- Model Quantization ---
|
||||
|
||||
@@ -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,
|
||||
|
||||
+1
-1
Submodule wiki updated: 4114a2e8c2...5bca12bb6c
Reference in New Issue
Block a user