From 6375b42ff72fef9ce4da3b36a822848da75eb26b Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Fri, 7 Aug 2026 02:08:06 +0100 Subject: [PATCH] feat(model): register minimax h3 as a text2image base model Reference entries for the bf16 repo and the sdnq uint4 quant load the modular pipeline through the standard dispatch. Image tabs run the model in still mode with audio off; the video tab keeps its own overrides through the shared per-generation hook. Detailer is not supported and is disabled with a warning. --- data/reference-base.json | 8 ++++++++ data/reference-quantized.json | 8 ++++++++ modules/modeldata.py | 2 ++ modules/processing_diffusers.py | 9 ++++++++- modules/sd_detect.py | 2 ++ modules/sd_models.py | 4 ++++ modules/shared_items.py | 1 + pipelines/model_minimax.py | 30 ++++++++++++++++++++++++++++++ 8 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 pipelines/model_minimax.py diff --git a/data/reference-base.json b/data/reference-base.json index 96b18d90d..2efaa7ae4 100644 --- a/data/reference-base.json +++ b/data/reference-base.json @@ -374,6 +374,14 @@ "size": 75.64, "date": "2025 September" }, + "MiniMaxAI MiniMax-H3": { + "path": "MiniMaxAI/MiniMax-H3", + "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: None", + "size": 134, + "date": "2026 August" + }, "Freepik F-Lite": { "path": "Freepik/F-Lite", "preview": "Freepik--F-Lite.jpg", diff --git a/data/reference-quantized.json b/data/reference-quantized.json index 132fdfe6e..a617e6ac5 100644 --- a/data/reference-quantized.json +++ b/data/reference-quantized.json @@ -79,6 +79,14 @@ "date": "2025 October", "size": 23.53 }, + "MiniMaxAI MiniMax-H3 sdnq-uint4": { + "path": "OzzyGT/MiniMax_H3_sdnq_dynamic_4bit", + "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: None", + "size": 51, + "date": "2026 August" + }, "Z-Image-Turbo sdnq-svd-uint4": { "path": "Disty0/Z-Image-Turbo-SDNQ-uint4-svd-r32", "preview": "Disty0--Z-Image-Turbo-SDNQ-uint4-svd-r32.jpg", diff --git a/modules/modeldata.py b/modules/modeldata.py index 439123a05..ee8122b15 100644 --- a/modules/modeldata.py +++ b/modules/modeldata.py @@ -156,6 +156,8 @@ def get_model_type(pipe): model_type = 'mochivideo' elif "Allegro" in name: model_type = 'allegrovideo' + elif 'MiniMaxH3' in name: + model_type = 'minimaxh3' # cloud models elif 'GoogleVeo' in name: model_type = 'veo3' diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index bde951ae4..1b84ba775 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -540,6 +540,13 @@ def update_pipeline(sd_model, p: processing.StableDiffusionProcessing): log.warning('Processing: op=update model not loaded') return None updated_model = sd_model + if 'MiniMaxH3' in sd_model.__class__.__name__ and not isinstance(p, processing.StableDiffusionProcessingVideo): + # image tabs run the model in still mode; the video tab applies its own overrides + from modules.video_models import video_modular + video_modular.apply_minimax_overrides(p, sd_model, still=True, audio=False) + if getattr(p, 'detailer_enabled', False): + log.warning(f'Processing: cls={sd_model.__class__.__name__} detailer not supported') + p.detailer_enabled = False if sd_models.get_diffusers_task(sd_model) == sd_models.DiffusersTaskType.INPAINTING and getattr(p, 'image_mask', None) is None and p.task_args.get('image_mask', None) is None and getattr(p, 'mask', None) is None: log.warning('Processing: mode=inpaint mask=None') updated_model = sd_models.set_diffuser_pipe(sd_model, sd_models.DiffusersTaskType.IMAGE_2_IMAGE) @@ -569,7 +576,7 @@ def validate_pipeline(p: processing.StableDiffusionProcessing): if m.custom is not None: models_cls.append(m.custom) is_video_model = shared.sd_model.__class__.__name__ in models_cls - override_video_pipelines = ['WanPipeline', 'WanImageToVideoPipeline', 'WanVACEPipeline'] + override_video_pipelines = ['WanPipeline', 'WanImageToVideoPipeline', 'WanVACEPipeline', 'MiniMaxH3ModularPipeline'] is_video_pipeline = ('video' in p.__class__.__name__.lower()) or (shared.sd_model.__class__.__name__ in override_video_pipelines) if is_video_model and not is_video_pipeline: log.error(f'Mismatch: type={shared.sd_model_type} cls={shared.sd_model.__class__.__name__} request={p.__class__.__name__} video model with non-video pipeline') diff --git a/modules/sd_detect.py b/modules/sd_detect.py index b6b1e72fb..c57cdaec3 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -115,6 +115,8 @@ def guess_by_name(fn, current_guess): new_guess = 'Cosmos' elif 'f-lite' in fn.lower(): new_guess = 'FLite' + elif 'minimax' in fn.lower(): + new_guess = 'MiniMaxH3' elif 'wan' in fn.lower(): new_guess = 'WanAI' if 'chronoedit' in fn.lower(): diff --git a/modules/sd_models.py b/modules/sd_models.py index 448fc30c8..f5f38f1ea 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -493,6 +493,10 @@ def load_diffuser_force(detected_model_type: str, checkpoint_info: CheckpointInf from pipelines.model_wanai import load_wan sd_model = load_wan(checkpoint_info, diffusers_load_config) allow_post_quant = False + elif model_type in ['MiniMaxH3']: + from pipelines.model_minimax import load_minimax + sd_model = load_minimax(checkpoint_info, diffusers_load_config) + allow_post_quant = False elif model_type in ['ChronoEdit']: from pipelines.model_chrono import load_chrono sd_model = load_chrono(checkpoint_info, diffusers_load_config) diff --git a/modules/shared_items.py b/modules/shared_items.py index a69edfeba..3ebfb5baf 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -55,6 +55,7 @@ pipelines = { 'PixArtAlpha': getattr(diffusers, 'PixArtAlphaPipeline', None), 'PixArtSigma': getattr(diffusers, 'PixArtSigmaPipeline', None), 'PRXPixel': getattr(diffusers, 'PRXPixelPipeline', None), + 'MiniMaxH3': getattr(diffusers, 'MiniMaxH3ModularPipeline', None), 'Qwen': getattr(diffusers, 'QwenImagePipeline', None), 'Sana': getattr(diffusers, 'SanaPipeline', None), 'WanAI': getattr(diffusers, 'WanPipeline', None), diff --git a/pipelines/model_minimax.py b/pipelines/model_minimax.py new file mode 100644 index 000000000..79985c8b7 --- /dev/null +++ b/pipelines/model_minimax.py @@ -0,0 +1,30 @@ +import diffusers +from modules import shared, devices, sd_models +from modules.logger import log + + +def load_minimax(checkpoint_info, diffusers_load_config=None): # pylint: disable=unused-argument + from modules.video_models import video_modular, video_load + repo_id = sd_models.path_to_repo(checkpoint_info) + sd_models.hf_auth_check(checkpoint_info) + if repo_id is None or repo_id.lower() == 'none': + return None + offline_args = {'local_files_only': True} if shared.opts.offline_mode else {} + log.debug(f'Load model: type=MiniMaxH3 repo="{repo_id}" offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype}') + + pipe = video_modular.load_modular_pipe( + getattr(diffusers, 'MiniMaxH3ModularPipeline', None), + repo_id, + workflow='fl2va', + offline_args=offline_args, + ) + if pipe is None: + return None + + video_modular.install_state_hook(pipe) + video_load.loaded_model = None # image-path load invalidates the video tab's name cache + if hasattr(pipe, 'vae') and hasattr(pipe.vae, 'enable_tiling'): + pipe.vae.enable_tiling() + + devices.torch_gc() + return pipe