mirror of
https://github.com/vladmandic/automatic
synced 2026-09-20 01:31:13 +02:00
+20
-10
@@ -199,7 +199,7 @@ def discover_components(model_index: dict[str, Any] | None, files_map: dict[str,
|
||||
components: dict[str, Any] = {
|
||||
"mains": [],
|
||||
"text_encoders": [],
|
||||
"ae": None,
|
||||
"ae": [],
|
||||
}
|
||||
|
||||
if isinstance(model_index, dict):
|
||||
@@ -210,8 +210,8 @@ def discover_components(model_index: dict[str, Any] | None, files_map: dict[str,
|
||||
text_keys = sorted([k for k in keys if re.fullmatch(r"text_encoder(_\d+)?", k or "")])
|
||||
components["text_encoders"] = text_keys
|
||||
|
||||
if "vae" in keys:
|
||||
components["ae"] = "vae"
|
||||
vae_keys = sorted([k for k in keys if re.fullmatch(r".*vae?", k or "")])
|
||||
components["ae"] = vae_keys
|
||||
|
||||
top_dirs = {f.split("/", 1)[0] for f in files_map if "/" in f}
|
||||
|
||||
@@ -221,8 +221,8 @@ def discover_components(model_index: dict[str, Any] | None, files_map: dict[str,
|
||||
if not components["text_encoders"]:
|
||||
components["text_encoders"] = sorted([d for d in top_dirs if re.fullmatch(r"text_encoder|mllm(_\d+)?", d or "")])
|
||||
|
||||
if components["ae"] is None and "vae" in top_dirs:
|
||||
components["ae"] = "vae"
|
||||
if not components["ae"]:
|
||||
components["ae"] = sorted([d for d in top_dirs if re.fullmatch(r".*vae?", d or "")])
|
||||
|
||||
return components
|
||||
|
||||
@@ -635,7 +635,7 @@ def search(repo_id: str) -> int:
|
||||
|
||||
main_components = components["mains"]
|
||||
text_components = components["text_encoders"]
|
||||
ae_component = components["ae"]
|
||||
ae_components = components["ae"]
|
||||
|
||||
main_files: list[str] = []
|
||||
for main_component in main_components:
|
||||
@@ -643,7 +643,9 @@ def search(repo_id: str) -> int:
|
||||
te_files: list[str] = []
|
||||
for te_component in text_components:
|
||||
te_files.extend(component_weight_files(te_component, files_map))
|
||||
ae_files = component_weight_files(ae_component, files_map)
|
||||
ae_files: list[str] = []
|
||||
for ae_component in ae_components:
|
||||
ae_files.extend(component_weight_files(ae_component, files_map))
|
||||
|
||||
fs = hf.HfFileSystem(token=token)
|
||||
|
||||
@@ -674,8 +676,12 @@ def search(repo_id: str) -> int:
|
||||
arch = arch_from_config(cfg, component_type="te")
|
||||
te_arches.append(arch if arch is not None else te_component)
|
||||
|
||||
ae_cfg = component_config(ae_component, repo_id, token)
|
||||
ae_arch = arch_from_config(ae_cfg, component_type="ae")
|
||||
ae_arches: list[str] = []
|
||||
for ae_component in ae_components:
|
||||
cfg = component_config(ae_component, repo_id, token)
|
||||
arch = arch_from_config(cfg, component_type="ae")
|
||||
ae_arches.append(arch if arch is not None else ae_component)
|
||||
|
||||
model_class = class_from_model_index(model_index)
|
||||
if model_class is None:
|
||||
first_main_class = next((c for c in main_component_classes if isinstance(c, str) and c.strip()), None)
|
||||
@@ -707,10 +713,14 @@ def search(repo_id: str) -> int:
|
||||
"dit": ", ".join(main_dit_entries) if len(main_dit_entries) > 0 else None,
|
||||
"dit_params": model_params_raw,
|
||||
"dit_size": model_size_raw,
|
||||
"dit_size_gb": round(model_size_raw / (1024**3), 2) if isinstance(model_size_raw, int) else None,
|
||||
"te": ", ".join(te_arches) if len(te_arches) > 0 else None,
|
||||
"te_params": te_params_raw,
|
||||
"te_size": te_size_raw,
|
||||
"ae": ae_arch,
|
||||
"te_size_gb": round(te_size_raw / (1024**3), 2) if isinstance(te_size_raw, int) else None,
|
||||
"ae": ", ".join(ae_arches) if len(ae_arches) > 0 else None,
|
||||
"ae_size": ae_size_raw,
|
||||
"ae_size_gb": round(ae_size_raw / (1024**3), 2) if isinstance(ae_size_raw, int) else None,
|
||||
"downloads": downloads_int,
|
||||
"tags": tags,
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@
|
||||
"preview": "MiniMaxAI--MiniMax-H3.jpg",
|
||||
"desc": "MiniMax-H3 generates video with synchronized stereo audio in a single denoising pass through a 33B single-stream transformer with a Qwen3-VL conditioner. In image tabs the model runs in experimental still mode, keeping the first frame of a minimal generation.",
|
||||
"extras": "sampler: Default",
|
||||
"size": 134,
|
||||
"size": 134.12,
|
||||
"date": "2026 August"
|
||||
},
|
||||
"MiniMaxAI MiniMax-H3 Ref2VA": {
|
||||
@@ -388,7 +388,7 @@
|
||||
"preview": "MiniMaxAI--MiniMax-H3.jpg",
|
||||
"desc": "The omni-reference variant of MiniMax-H3, sharing one repository with the base model as a separate checkpoint partition. Video with synchronized stereo audio is conditioned on reference images for identity and appearance, with reference rows held clean while video rows denoise.",
|
||||
"extras": "sampler: Default",
|
||||
"size": 134,
|
||||
"size": 134.12,
|
||||
"date": "2026 August"
|
||||
},
|
||||
"Freepik F-Lite": {
|
||||
|
||||
@@ -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": 51,
|
||||
"size": 47.33,
|
||||
"date": "2026 August"
|
||||
},
|
||||
"Z-Image-Turbo sdnq-svd-uint4": {
|
||||
|
||||
+1
-1
@@ -584,7 +584,7 @@ def check_diffusers():
|
||||
t_start = time.time()
|
||||
if args.skip_all:
|
||||
return
|
||||
target_commit = "9f169d98d0bce392a889c3b6524d0d97734dfc0e" # diffusers commit hash == 0.40.0.dev0 == 08-05-2026
|
||||
target_commit = "90c0ffdc045902a3667d473d2fbfc03e8716dba9" # diffusers commit hash == 0.40.0.dev0 == 08-11-2026
|
||||
# if args.use_rocm or args.use_zluda:
|
||||
# sha = '043ab2520f6a19fce78e6e060a68dbc947edb9f9' # lock diffusers versions for now
|
||||
pkg = package_spec('diffusers')
|
||||
|
||||
+8
-4
@@ -230,9 +230,12 @@ def torch_gc(force: bool = False, fast: bool = False, reason: str | None = None)
|
||||
collected = gc.collect() if not fast else 0 # python gc
|
||||
try:
|
||||
if hasattr(torch, "accelerator") and torch.accelerator.is_available(): # torch >= 2.6
|
||||
torch.accelerator.synchronize()
|
||||
torch.accelerator.empty_cache()
|
||||
torch.accelerator.empty_host_cache()
|
||||
if hasattr(torch.accelerator, "synchronize"):
|
||||
torch.accelerator.synchronize()
|
||||
if hasattr(torch.accelerator, "empty_cache"):
|
||||
torch.accelerator.empty_cache()
|
||||
if hasattr(torch.accelerator, "empty_host_cache"):
|
||||
torch.accelerator.empty_host_cache()
|
||||
if torch.cuda.is_available() and hasattr(torch.cuda, "ipc_collect"):
|
||||
torch.cuda.ipc_collect()
|
||||
elif hasattr(torch, "xpu") and hasattr(torch.xpu, "ipc_collect"):
|
||||
@@ -240,7 +243,8 @@ def torch_gc(force: bool = False, fast: bool = False, reason: str | None = None)
|
||||
elif torch.cuda.is_available(): # Fallback for older PyTorch versions
|
||||
torch.cuda.synchronize()
|
||||
torch.cuda.empty_cache()
|
||||
torch.cuda.ipc_collect()
|
||||
if hasattr(torch.cuda, "ipc_collect"):
|
||||
torch.cuda.ipc_collect()
|
||||
elif hasattr(torch, "xpu") and torch.xpu.is_available():
|
||||
torch.xpu.synchronize()
|
||||
torch.xpu.empty_cache()
|
||||
|
||||
@@ -282,6 +282,7 @@ def setup_logging(debug=None, trace=None, filename=None):
|
||||
logging.getLogger("ControlNet").handlers = log.handlers
|
||||
|
||||
logging.getLogger("diffusers").setLevel(logging.ERROR)
|
||||
logging.getLogger("diffusers.modular_pipelines").setLevel(logging.ERROR)
|
||||
logging.getLogger("transformers").setLevel(logging.ERROR)
|
||||
logging.getLogger("torch").setLevel(logging.ERROR)
|
||||
logging.getLogger("urllib3").setLevel(logging.ERROR)
|
||||
|
||||
@@ -54,6 +54,9 @@ def xet_get_hijack(*args, **kwargs):
|
||||
if fn and not fn.endswith(".json"):
|
||||
log.debug(f'Download: type=xet mode="{opts.hf_transfer_mode}" fn="{fn}" size={size}')
|
||||
debug(f'Download start: type=xet args={args} kwargs={kwargs}')
|
||||
# import tqdm # TODO xet_download: hijack progress bar
|
||||
# bar_format = 'Download {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m' + '{desc}' + '\x1b[0m'
|
||||
# kwargs['_tqdm_bar'] = tqdm.tqdm(*args, bar_format=bar_format, ncols=80, colour='#327fba', **kwargs)
|
||||
res = orig_xet_get(*args, **kwargs)
|
||||
debug(f'Download end: type=xet res={res}')
|
||||
state.end(jobid)
|
||||
|
||||
@@ -37,11 +37,11 @@ def load_modular_pipe(repo_cls, repo: str, workflow: str | None = None, revision
|
||||
cache_dir=cache_dir,
|
||||
**offline_args,
|
||||
)
|
||||
# workflow selection stays out of from_pretrained: pruning the blocks tree to one task
|
||||
# would disable runtime auto-dispatch between them; only the component fetch is restricted
|
||||
# workflow selection stays out of from_pretrained: pruning the blocks tree to one task would disable runtime auto-dispatch between them; only the component fetch is restricted
|
||||
load_kwargs = {}
|
||||
quant_config = {}
|
||||
quant_args = model_quant.create_config(module='Model')
|
||||
# TODO load_modular: need to handle component names dynamically
|
||||
if 'quantization_config' in quant_args:
|
||||
quant_config['transformer'] = quant_args['quantization_config']
|
||||
quant_config['transformer_ref'] = quant_args['quantization_config']
|
||||
@@ -49,8 +49,7 @@ def load_modular_pipe(repo_cls, repo: str, workflow: str | None = None, revision
|
||||
if 'quantization_config' in te_args:
|
||||
quant_config['text_encoder'] = te_args['quantization_config']
|
||||
if quant_config:
|
||||
# per-component dict without a default entry: only the listed components quantize while
|
||||
# loading, everything else loads unquantized
|
||||
# per-component dict without a default entry: only the listed components quantize while loading, everything else loads unquantized
|
||||
load_kwargs['quantization_config'] = quant_config
|
||||
log.debug(f'Load modular: quant={next(iter(quant_config.values())).__class__.__name__} modules={list(quant_config)}')
|
||||
pipe.load_components(
|
||||
@@ -61,10 +60,11 @@ def load_modular_pipe(repo_cls, repo: str, workflow: str | None = None, revision
|
||||
**offline_args,
|
||||
)
|
||||
loaded = [name for name, component in pipe.components.items() if component is not None]
|
||||
empty = [name for name, component in pipe.components.items() if component is None]
|
||||
pipe.sdnext_video_workflow = workflow # lets a pipe loaded outside the video registry report its own workflow
|
||||
if hasattr(pipe, 'min_duration') and hasattr(pipe, 'fps'):
|
||||
pipe.sdnext_supported_min_frames = int(pipe.min_duration * pipe.fps) # fresh pipes report the true floor; still mode gates per instance
|
||||
log.debug(f'Load modular: cls={pipe.__class__.__name__} workflow={workflow} components={loaded} time={time.time()-t0:.2f}')
|
||||
log.info(f'Load modular: cls={pipe.__class__.__name__} workflow={workflow} components={loaded} empty={empty} time={time.time()-t0:.2f}')
|
||||
return pipe
|
||||
except Exception as e:
|
||||
log.error(f'Load modular: repo="{repo}" workflow={workflow} {e}')
|
||||
@@ -87,7 +87,7 @@ def apply_minimax_overrides(p, pipe, still: bool = False, audio: bool = True):
|
||||
set_still(pipe, still)
|
||||
if still:
|
||||
frames = 5 # two latent frames; decode pads to the decoder floor and only the first frame is kept
|
||||
log.info(f'Video modular: cls={pipe.__class__.__name__} mode=still experimental')
|
||||
log.info(f'Pipeline: cls={pipe.__class__.__name__} mode=still')
|
||||
else:
|
||||
frames = max(getattr(p, 'frames', 1), getattr(pipe, 'sdnext_supported_min_frames', 120))
|
||||
while frames % pipe.vae_frames_per_chunk != pipe.vae_latents_per_chunk: # frame counts align to 17n+5
|
||||
@@ -96,14 +96,14 @@ def apply_minimax_overrides(p, pipe, still: bool = False, audio: bool = True):
|
||||
while frames > max_frames:
|
||||
frames -= pipe.vae_frames_per_chunk
|
||||
if frames != getattr(p, 'frames', None):
|
||||
log.debug(f'Video modular: cls={pipe.__class__.__name__} frames={getattr(p, "frames", None)} aligned={frames}')
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} frames={getattr(p, "frames", None)} aligned={frames}')
|
||||
p.frames = frames
|
||||
p.task_args['num_frames'] = frames
|
||||
p.steps = max(2, p.steps)
|
||||
p.task_args['num_inference_steps'] = p.steps
|
||||
pipe.num_timesteps = p.steps - 1 # sigma grid includes the terminal point; feeds the progress total
|
||||
if p.sampler_name not in ('None', 'Default'):
|
||||
log.warning(f'Video modular: cls={pipe.__class__.__name__} sampler={p.sampler_name} unsupported: using model default')
|
||||
log.warning(f'Pipeline: cls={pipe.__class__.__name__} sampler={p.sampler_name} unsupported: using model default')
|
||||
p.sampler_name = 'Default' # the model default is the bespoke scheduler pair, which discrete samplers must not replace
|
||||
pipe.vae.enable_tiling() # model always tiles; the shared vae params path may have disabled it
|
||||
set_audio(pipe, audio)
|
||||
@@ -149,10 +149,10 @@ def set_audio(pipe, enabled: bool):
|
||||
stashed = getattr(pipe, 'sdnext_audio_decode_block', None)
|
||||
if stashed is not None:
|
||||
sub.insert('audio', stashed, len(sub))
|
||||
log.debug(f'Video modular: cls={pipe.__class__.__name__} audio=enabled')
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} audio=enabled')
|
||||
elif not enabled and 'audio' in sub:
|
||||
pipe.sdnext_audio_decode_block = sub.pop('audio')
|
||||
log.debug(f'Video modular: cls={pipe.__class__.__name__} audio=disabled')
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} audio=disabled')
|
||||
|
||||
|
||||
class InterruptLogFilter(logging.Filter):
|
||||
@@ -172,7 +172,7 @@ def install_state_hook(pipe):
|
||||
if getattr(pipe, 'sdnext_phase', None) != phase:
|
||||
pipe.sdnext_phase = phase
|
||||
shared.state.textinfo = phase
|
||||
log.debug(f'Video modular: cls={pipe.__class__.__name__} phase="{phase}"')
|
||||
log.debug(f'Pipeline: cls={pipe.__class__.__name__} phase={phase}')
|
||||
|
||||
def state_hook(module, args): # pylint: disable=unused-argument
|
||||
set_phase('Generate')
|
||||
@@ -189,7 +189,7 @@ def install_state_hook(pipe):
|
||||
raise AssertionError('Interrupted...')
|
||||
|
||||
def encode_hook(module, args): # pylint: disable=unused-argument
|
||||
set_phase('Text encode')
|
||||
set_phase('TextEncode')
|
||||
if shared.state.interrupted or shared.state.skipped:
|
||||
raise AssertionError('Interrupted...')
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ def load_minimax(checkpoint_info, diffusers_load_config=None): # pylint: disable
|
||||
workflow = (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}')
|
||||
|
||||
repo_cls = diffusers.MiniMaxH3ModularPipeline
|
||||
pipe = video_modular.load_modular_pipe(
|
||||
getattr(diffusers, 'MiniMaxH3ModularPipeline', None),
|
||||
repo_cls,
|
||||
repo_id,
|
||||
workflow=workflow,
|
||||
offline_args=offline_args,
|
||||
@@ -22,6 +23,12 @@ def load_minimax(checkpoint_info, diffusers_load_config=None): # pylint: disable
|
||||
)
|
||||
if pipe is None:
|
||||
return None
|
||||
if pipe.text_encoder is None:
|
||||
# TODO minimax missing te: we should never be here
|
||||
import transformers
|
||||
from pipelines import generic
|
||||
text_encoder = generic.load_text_encoder(repo_id, cls_name=transformers.Qwen3VLForConditionalGeneration, load_config=diffusers_load_config, allow_shared=False)
|
||||
pipe.update_components(text_encoder=text_encoder)
|
||||
|
||||
video_modular.install_state_hook(pipe)
|
||||
video_load.loaded_model = None # image-path load invalidates the video tab's name cache
|
||||
|
||||
Reference in New Issue
Block a user