add minimax-nunchaku-lite

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-09-13 12:49:57 +02:00
parent 03bc3159ec
commit 70beb1ee7c
14 changed files with 114 additions and 45 deletions
+14 -8
View File
@@ -6,12 +6,12 @@
*What's New*? Well, code-wise, this is a big one...
First, a-lot-of-optimizations:
- updated core packages
- improved **LoRA** performance and quality, especially with quantized models
- newly structured **attention** mechanisms
- modular pipelines with new **guidance** methods
- support for different **caching** stacks
- compute updates across the board
- Updated core packages
- Improved **LoRA** performance and quality, especially with quantized models
- Newly structured **attention** mechanisms
- Modular pipelines with new **guidance** methods
- Support for different **caching** stacks
- Compute updates across the board
And some cool new stuff and models:
- **DLSSv5** integration
@@ -32,7 +32,10 @@ Plus inevitable bug-fixes...
LLaDA-Image is a 6.5B transformer with massive 16.3B fully-custom MoE text-encoder and optional 1.3B SigVQ conditioning model
with support for text-to-image, vq-conditioned text-to-image and image-editing workflows
*note* model is extremely quantization sensitive so minimum allowed quant type is `uint8`
- [MiniMax-H3](https://huggingface.co/MiniMaxAI/MiniMax-H3) update with pre-quantized `sdnq-uint8` *pruned* variants
- [MiniMax-H3](https://huggingface.co/MiniMaxAI/MiniMax-H3) updates
new [SDNQ-uint8](https://huggingface.co/OzzyGT/MiniMax_H3_sdnq_8bit_pruned) pre-quantized *pruned* variants
new [Nunchaku-Lite](https://huggingface.co/rootonchair/MiniMax-H3-nunchaku-lite-int4) variant
new [VDN](https://huggingface.co/OpenVDN/vdn-minimax-h3) *video-delta-net* variant
- **LoRA**
- see [LoRA docs](https://vladmandic.github.io/sdnext-docs/LoRA) for all of the improvements and usage instructions
*note*: lora now has its own settings section in *settings -> lora*
@@ -108,7 +111,10 @@ Plus inevitable bug-fixes...
*note*: requires grok api key
- remove `/redocs` as `/docs` are primary api docs
- rebuild docs site index
- **ui**: ability to filter samplers and upscalers, thanks @emecii
- **UI**
- some (light) re-styling of the *Default* theme
- add new *Tillerz-CleanDark* theme, thanks @Tillerz
- ability to filter samplers and upscalers, thanks @emecii
- **Wiki/Docs**:
- new articles: *Attention, Modular-Pipelines*
- updated: *LoRA, MiniMax*
+8 -7
View File
@@ -25,6 +25,14 @@
"size": 19.38,
"date": "2023 November"
},
"SDXL Flash Mini": {
"path": "SDXL-Flash_Mini.safetensors@https://huggingface.co/sd-community/sdxl-flash-mini/resolve/main/SDXL-Flash_Mini.safetensors?download=true",
"preview": "SDXL-Flash_Mini.jpg",
"desc": "Introducing the new fast model SDXL Flash (Mini), we learned that all fast XL models work fast, but the quality decreases, and we also made a fast model, but it is not as fast as LCM, Turbo, Lightning and Hyper, but the quality is higher.",
"extras": "sampler: DEIS, steps: 40, cfg_scale: 6.0",
"experimental": true,
"date": "2024 May"
},
"Tencent HunyuanDiT 1.1 Distilled": {
"path": "Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled",
"desc": "Hunyuan-DiT : A Powerful Multi-Resolution Diffusion Transformer with Fine-Grained Chinese Understanding.",
@@ -258,12 +266,5 @@
"extras": "steps: 4, cfg_scale: 1.0",
"size": 37.15,
"date": "2026 September"
},
"SDXL Flash Mini": {
"path": "SDXL-Flash_Mini.safetensors@https://huggingface.co/sd-community/sdxl-flash-mini/resolve/main/SDXL-Flash_Mini.safetensors?download=true",
"preview": "SDXL-Flash_Mini.jpg",
"desc": "Introducing the new fast model SDXL Flash (Mini), we learned that all fast XL models work fast, but the quality decreases, and we also made a fast model, but it is not as fast as LCM, Turbo, Lightning and Hyper, but the quality is higher.",
"extras": "sampler: DEIS, steps: 40, cfg_scale: 6.0",
"experimental": true
}
}
+7
View File
@@ -295,5 +295,12 @@
"desc": "Nunchaku-Lite quantization using precompiled Kernels",
"size": 10.92,
"date": "2026 August"
},
"MiniMaxAI MiniMax-H3 Nunchaku-Lite": {
"path": "rootonchair/MiniMax-H3-nunchaku-lite-int4",
"preview": "OzzyGT/MiniMax_H3_sdnq_dynamic_4bit.jpg",
"desc": "Nunchaku-Lite quantization using precompiled Kernels",
"size": 23.70,
"date": "2026 September"
}
}
+1 -1
View File
@@ -72,7 +72,7 @@ def get_hf_api_hijack(user_agent = None): # pylint: disable=unused-argument
def hijack_kernels():
global orig_get_kernel # pylint: disable=global-statement
try:
install('kernels==0.16.0')
install('kernels==0.16.1')
import kernels
import kernels.utils
log.debug(f'Attention dispatcher: kernels={kernels.__version__}')
+6 -11
View File
@@ -644,21 +644,16 @@ def try_load_lora(name, network_on_disk, lora_scale, *,
continue
if not shapes_match(sd_module, target_w["lora_down.weight"], target_w["lora_up.weight"]):
log.warning(
f'Network load: type=LoRA name="{name}" arch={arch_name} key={network_key}'
f' lora={target_w["lora_down.weight"].shape[1]}x{target_w["lora_up.weight"].shape[0]}'
f' module={getattr(sd_module, "weight", None).shape if hasattr(sd_module, "weight") else "?"}'
f' shape mismatch'
)
if l.debug:
_module = f'{getattr(sd_module, "weight", None).shape if hasattr(sd_module, "weight") else "?"}'
log.warning(f'Network load: type=LoRA name="{name}" arch={arch_name} key={network_key} lora={target_w["lora_down.weight"].shape[1]}x{target_w["lora_up.weight"].shape[0]} module={_module} shape mismatch')
mismatch += 1
continue
if "diff_b" in target_w and not bias_delta_fits(sd_module, target_w["diff_b"]):
log.warning(
f'Network load: type=LoRA name="{name}" arch={arch_name} key={network_key}'
f' bias={tuple(target_w["diff_b"].shape)} module={tuple(sd_module.bias.shape)}'
f' bias shape mismatch'
)
if l.debug:
_bias = f'bias={tuple(target_w["diff_b"].shape)} module={tuple(sd_module.bias.shape)}'
log.warning(f'Network load: type=LoRA name="{name}" arch={arch_name} key={network_key} {_bias} bias shape mismatch')
mismatch += 1
continue
+4 -2
View File
@@ -36,7 +36,8 @@ def create_ui(prompt, _negative, styles, overrides, script_inputs, mp4_fps, mp4_
random_seed = ToolButton(ui_symbols.random, elem_id='minimax_seed_random')
random_seed.click(fn=lambda: -1, show_progress='hidden', inputs=[], outputs=[seed])
with gr.Row():
audio_enable = gr.Checkbox(label='Enable audio', value=True, elem_id="minimax_audio_enable")
enable_audio = gr.Checkbox(label='Enable audio', value=True, elem_id="minimax_audio_enable")
enable_preview = gr.Checkbox(label='Enable preview', value=True, elem_id="minimax_preview_enable")
with gr.Accordion(open=False, label="Input media", elem_id='minimax_input_media_accordion', visible=True) as input_accordion:
with gr.Row():
init_image = gr.Image(label='Image', elem_id='minimax_init_image', type='pil', image_mode='RGB', width=256, height=256)
@@ -97,7 +98,8 @@ def create_ui(prompt, _negative, styles, overrides, script_inputs, mp4_fps, mp4_
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt,
mp4_video, mp4_frames, mp4_sf, mp4_thumb,
mp4_scale, mp4_upscaler,
audio_enable,
enable_audio,
enable_preview,
overrides,
]
video_outputs = [
+3 -3
View File
@@ -88,7 +88,7 @@ def generate(task_id, _ui_state,
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt,
mp4_video, mp4_frames, mp4_sf, mp4_thumb,
mp4_scale, mp4_upscaler,
audio_enable,
enable_audio, enable_preview,
_overrides,
*args,
**_kwargs,
@@ -144,7 +144,7 @@ def generate(task_id, _ui_state,
outpath_samples=paths.resolve_output_path(shared.opts.outdir_samples, shared.opts.outdir_video),
ops=['video'],
)
video_minimax.apply_overrides(p, shared.sd_model, still=False, audio=audio_enable)
video_minimax.apply_overrides(p, shared.sd_model, still=False, audio=enable_audio, preview=enable_preview)
video_minimax.set_sampler_shift(shared.sd_model, video_shift=video_shift, audio_shift=audio_shift)
log.debug(f'Video: engine="{engine}" model="{model}" workflow={workflow} cls={shared.sd_model.__class__.__name__} shift={video_shift}:{audio_shift} kwargs={p.task_args}')
processing.fix_seed(p)
@@ -168,7 +168,7 @@ def generate(task_id, _ui_state,
sd_models.offload_ondemand(shared.sd_model, reason='finish', force=True) # force offload all loaded modules to cpu
devices.torch_gc(force=True) # free gpu memory before saving video
audio = getattr(processed, 'audio', None) if audio_enable else None
audio = getattr(processed, 'audio', None) if enable_audio else None
if audio is not None:
audio = audio[0].float().cpu() if audio.ndim == 3 else audio.float().cpu()
aac_sample_rate = getattr(shared.sd_model, 'audio_sampling_rate', 32000)
+15 -5
View File
@@ -38,7 +38,7 @@ def is_modular(obj) -> bool:
return 'Modular' in cls.__name__
def preload_components(pipe, workflow: str | None, load_config: dict | None = None) -> dict:
def preload_components(pipe, workflow: str | None, load_config: dict | None = None, loaded: dict | None = None) -> dict:
"""Load the denoiser and text encoder through the shared loaders rather than the pipeline's own.
`load_components` fetches every component into the pipeline's cache directory with no
@@ -53,8 +53,10 @@ def preload_components(pipe, workflow: str | None, load_config: dict | None = No
"""
from pipelines import generic
specs = getattr(pipe, '_component_specs', {}) # pylint: disable=protected-access
loaded = {}
loaded = loaded or {}
for name in missing_components(pipe, workflow):
if name in loaded:
continue
spec = specs.get(name)
if spec is None or getattr(spec, 'default_creation_method', None) != 'from_pretrained':
continue
@@ -71,7 +73,7 @@ def preload_components(pipe, workflow: str | None, load_config: dict | None = No
elif origin.startswith('transformers') and ('text_encoder' in name):
# shared substitution is on: the map matches class plus a substring of the repo name, so its entries have to run narrow before broad
component = generic.load_text_encoder(repo, cls_name=cls, load_config=load_config, subfolder=subfolder)
if 'transformer' in name:
if ('transformer' in name) and (component is None):
# fallback for component with remote-code as it does not have resolvable cls
component = generic.load_transformer(repo, cls_name=None, load_config=load_config, subfolder=subfolder, trust_remote_code=True)
if component is not None:
@@ -96,7 +98,15 @@ def missing_components(pipe, workflow: str | None) -> list:
return [name for name in names if getattr(pipe, name, None) is None]
def load_modular_pipe(repo_cls, repo: str, workflow: str | None = None, revision: str | None = None, offline_args: dict | None = None, base: bool = False, load_config: dict | None = None):
def load_modular_pipe(repo_cls,
repo: str,
workflow: str | None = None,
revision: str | None = None,
offline_args: dict | None = None,
base: bool = False,
load_config: dict | None = None,
loaded: dict | None = None,
):
if repo_cls is None or isinstance(repo_cls, str):
log.error(f'Load modular: repo="{repo}" cls="{repo_cls}" pipeline class not found: diffusers too old')
return None
@@ -112,7 +122,7 @@ def load_modular_pipe(repo_cls, repo: str, workflow: str | None = None, revision
**offline_args,
)
# the workflow restricts the component fetch only: passing it to from_pretrained instead would prune the blocks tree to one task and disable runtime dispatch between them
preloaded = preload_components(pipe, workflow, load_config=load_config)
preloaded = preload_components(pipe, workflow, load_config=load_config, loaded=loaded)
if preloaded:
pipe.update_components(**preloaded) # registered before the rest, which load_components then skips
log.debug(f'Load modular: cls={pipe.__class__.__name__} preloaded={list(preloaded)}')
+12
View File
@@ -771,6 +771,18 @@ try:
image_hijack=False,
vae_hijack=False,
vae_remote=False),
Model(name='MiniMax H3 Nunchaku-Lite',
url='https://huggingface.co/rootonchair/MiniMax-H3-nunchaku-lite-int4',
repo='rootonchair/MiniMax-H3-nunchaku-lite-int4',
repo_cls='MiniMaxH3ModularPipeline',
workflow='fl2va',
base=True,
te_cls=None,
dit_cls=None,
te_hijack=False,
image_hijack=False,
vae_hijack=False,
vae_remote=False),
Model(name='MiniMax H3 VDN',
url='https://huggingface.co/OpenVDN/vdn-minimax-h3',
repo='OpenVDN/vdn-minimax-h3',
+8 -1
View File
@@ -5,7 +5,7 @@ from modules.logger import log
MIN_LATENT_FRAMES = 7 # decoder floor: fewer latent frames leave the chunked decode with nothing to emit
def apply_overrides(p, pipe, still: bool = False, audio: bool = True):
def apply_overrides(p, pipe, still: bool = False, audio: bool = True, preview: bool = False):
"""Per-generation constraints shared by the video tab and the image path: canvas and frame
alignment, the bespoke scheduler guard, tiling, and the audio/still toggles."""
if still:
@@ -40,6 +40,13 @@ def apply_overrides(p, pipe, still: bool = False, audio: bool = True):
p.task_args['output_type'] = 'pil' if still else 'np'
p.video_still = still
if preview:
from pipelines.minimax.minimax_latents import unpack_latents
pipe.custom_unpack_latents = unpack_latents # add a helper to unpack the video latents from the block state
else:
if hasattr(pipe, 'custom_unpack_latents'):
del pipe.custom_unpack_latents
def set_still(pipe, enabled: bool = True):
"""Toggle sub-floor generation for single-frame output. The duration floor is lifted only
+3 -4
View File
@@ -24,7 +24,6 @@ def load_transformer(
override_slot='primary',
trust_remote_code=False,
**kwargs):
"""Load a DiT transformer from the base repo, or from a user-selected
single file when the slot's UNET override dropdown is set.
@@ -164,19 +163,19 @@ def load_transformer(
**load_kwargs,
)
# 4. default loading from local file (also the fallback when an incompatible override is dropped above) # 5. default loading from diffusers repo (also the fallback when an incompatible override is dropped above)
# 4. default loading from diffusers repo (also the fallback when an incompatible override is dropped above)
else:
transformer = load_from_repo()
# mark the dropdown selection as loaded so the slot's onchange callback, does not force a redundant full reload for an already-consumed override
if transformer is not None and override_name is not None and getattr(shared.opts, override_opt, None) == override_name:
if (transformer is not None) and (override_name is not None) and getattr(shared.opts, override_opt, None) == override_name:
setattr(sd_unet, tracker_attr, override_name)
sd_models.allow_post_quant = False # we already handled it
if shared.opts.diffusers_offload_mode != 'none' and transformer is not None:
sd_models.move_model(transformer, devices.cpu)
if transformer is not None and not hasattr(transformer, 'quantization_config'): # attach quantization_config
if (transformer is not None) and not hasattr(transformer, 'quantization_config'): # attach quantization_config
if hasattr(transformer, 'config') and hasattr(transformer.config, 'quantization_config'):
transformer.quantization_config = transformer.config.quantization_config
elif (quant_type is not None) and (quant_args.get('quantization_config', None) is not None):
+20
View File
@@ -0,0 +1,20 @@
import diffusers
from modules import shared
from modules.logger import log
def load_nunchaku(repo_id, load_config=None):
load_config = load_config or {}
from modules.attention import hijack_kernels
hijack_kernels()
cls_name = diffusers.MiniMaxH3Transformer3DModel
log.debug(f'Load model: transformer="{repo_id}" subfolder="calibrated-8x20" cls={cls_name.__name__} loader="nunchaku-lite" args={load_config}')
transformer = cls_name.from_pretrained(
repo_id,
subfolder="calibrated-8x20",
cache_dir=shared.opts.hfcache_dir,
**load_config,
)
return transformer
+12 -2
View File
@@ -15,6 +15,14 @@ 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}')
loaded = {}
if 'nunchaku-lite' in repo_id.lower():
from pipelines.minimax.minimax_nunchaku import load_nunchaku
transformer = load_nunchaku(repo_id, load_config=diffusers_load_config)
if transformer is not None:
loaded['transformer'] = transformer
repo_id = 'OzzyGT/MiniMax_H3_sdnq_dynamic_4bit' # nunchaku repo does not contain non-transformer modules
sd_models.warn_group_offload(min_vram=20)
repo_cls = diffusers.MiniMaxH3ModularPipeline
pipe = load_modular_pipe(
@@ -24,6 +32,7 @@ def load_minimax(checkpoint_info, diffusers_load_config = None, workflow: str |
offline_args=offline_args,
base=True,
load_config=diffusers_load_config,
loaded=loaded,
)
if pipe is None:
return None
@@ -34,8 +43,9 @@ def load_minimax(checkpoint_info, diffusers_load_config = None, workflow: str |
pipe.sdnext_supported_min_frames = int(pipe.min_duration * pipe.fps) # fresh pipes report the true floor; still mode gates per instance
video_load.loaded_model = None # image-path load invalidates the video tab's name cache
# if hasattr(pipe, 'vae'):
# pipe.vae = pipe.vae.to(torch.float16) # minimax loads vae in float32
if hasattr(pipe, 'vae'):
import torch
pipe.vae = pipe.vae.to(torch.float16) # minimax loads vae in float32
if hasattr(pipe, 'vae') and hasattr(pipe.vae, 'enable_tiling'):
pipe.vae.enable_tiling()