cleanup minimax

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-08-11 15:42:29 +02:00
parent 1ca694aa26
commit 5123bfd96b
9 changed files with 56 additions and 31 deletions
+20 -10
View File
@@ -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,
}
+2 -2
View File
@@ -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": {
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+1
View File
@@ -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)
+3
View File
@@ -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)
+12 -12
View File
@@ -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...')
+8 -1
View File
@@ -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