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