minimax ui

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-08-13 15:33:47 +02:00
parent 0ddd0f7091
commit ad6f3d3e11
17 changed files with 272 additions and 26 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
## TODO
- video API: video, text2image, image2image
- MiniMax-H3
- MiniMax-H3: geo-locked
- LTX-2.5
- Group offloading in 16gb
+2 -1
View File
@@ -39,5 +39,6 @@
"vladmandic--Qwen-Lightning-Edit": "Qwen-Lightning.jpg",
"vladmandic--Qwen-Lightning": "Qwen-Lightning.jpg",
"Wan-AI--Wan2.1-T2V-14B-Diffusers": "Wan-AI--Wan2.1-T2V-14B-Diffusers.jpg",
"Wan-AI--Wan2.2-T2V-A14B-Diffusers": "Wan-AI--Wan2.2-T2V-A14B-Diffusers.jpg"
"Wan-AI--Wan2.2-T2V-A14B-Diffusers": "Wan-AI--Wan2.2-T2V-A14B-Diffusers.jpg",
"OzzyGT--MiniMax_H3_sdnq_dynamic_4bit": "MiniMaxAI--MiniMax-H3.jpg"
}
+9
View File
@@ -87,6 +87,15 @@
"size": 47.33,
"date": "2026 August"
},
"MiniMaxAI MiniMax-H3 sdnq-uint4 Ref2VA": {
"path": "OzzyGT/MiniMax_H3_sdnq_dynamic_4bit",
"preview": "MiniMaxAI--MiniMax-H3.jpg",
"subfolder": "ref2va",
"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": 47.33,
"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",
+1 -1
View File
@@ -112,7 +112,7 @@ def create_ui(prompt, negative, styles, overrides, script_inputs, mp4_fps, mp4_i
audio_accordion = gr.Accordion(open=False, label="Audio", elem_id='ltx_audio_accordion', visible=False)
with audio_accordion:
with gr.Row():
audio_enable = gr.Checkbox(label='LTX save audio', value=True, elem_id="ltx_audio_enable")
audio_enable = gr.Checkbox(label='Save audio', value=True, elem_id="ltx_audio_enable")
with gr.Column(elem_id='ltx-output-column', scale=2) as _column_output:
with gr.Row():
+95
View File
@@ -0,0 +1,95 @@
import os
import gradio as gr
from modules import ui_sections, ui_symbols
from modules.ui_components import ToolButton
from modules.logger import log
from modules.video_models.models_def import models
from modules.minimax import minimax_video
debug = log.trace if os.environ.get('SD_VIDEO_DEBUG', None) is not None else lambda *args, **kwargs: None
def create_ui(prompt, _negative, styles, overrides, script_inputs, mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, mp4_thumb):
with gr.Row():
with gr.Column(variant='compact', elem_id="minimax_settings", elem_classes=['settings-column'], scale=1):
with gr.Row():
generate = gr.Button('Generate', elem_id="minimax_generate_btn", variant='primary', visible=False)
with gr.Row():
minimax_models = [m.name for m in models['MiniMax']] if 'MiniMax' in models else ['None']
model = gr.Dropdown(label='MiniMax model', choices=minimax_models, value=minimax_models[0], elem_id="minimax_model")
btn_load = ToolButton(ui_symbols.loading, elem_id="video_model_load_minimax")
workflow = gr.Textbox(value='', label='Workflow', elem_id='minimax_workflow', show_label=True, interactive=False)
with gr.Accordion(open=True, label='Parameters', elem_id='minimax_param_accordion') as _param_accordion:
with gr.Row():
width, height = ui_sections.create_resolution_inputs('minimax', default_width=832, default_height=480, step=32)
with gr.Row():
steps = gr.Slider(minimum=2, maximum=100, step=1, label="Steps", elem_id='minimax_steps', value=30)
frames = gr.Slider(label='Frames', minimum=22, maximum=345, step=17, value=107, elem_id='minimax_frames')
with gr.Row():
seed = gr.Number(label='Seed', value=-1, elem_id='minimax_seed', container=True)
random_seed = ToolButton(ui_symbols.random, elem_id='minimax_seed_random')
random_seed.click(fn=lambda: -1, show_progress='hidden', inputs=[], outputs=[seed])
audio_enable = gr.Checkbox(label='Save audio', value=True, elem_id="minimax_audio_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)
with gr.Row():
last_image = gr.Image(label='Last image', elem_id='minimax_last_image', type='pil', image_mode='RGB', width=256, height=256)
with gr.Accordion(open=False, label="Reference media", elem_id='minimax_reference_accordion', visible=True) as reference_accordion:
gr.HTML("""Upload up to 9 images, 3 videos, and 3 audio files<br>
The total number of files must not exceed 12<br><br>""", elem_id='minimax_reference_media_info', elem_classes=['smaller'])
reference_images = gr.Files(label="Reference media", interactive=True, elem_id="minimax_reference_media", type="file", file_count="multiple", visible=True)
with gr.Column(elem_id='minimax-output-column', scale=2) as _column_output:
with gr.Row():
video = gr.Video(label="Output", show_label=False, elem_id='minimax_output_video', elem_classes=['control-image'], height=512, autoplay=False)
with gr.Row():
text = gr.HTML('', elem_id='minimax_generation_info', show_label=False)
def on_change(model_name: str, init_image):
model_info = next((m for m in models['MiniMax'] if m.name == model_name), None)
if model_info is None or model_info.name is None or model_info.name == '' or model_info.name == 'None':
return gr.update(value='none'), gr.update(visible=False), gr.update(visible=False)
log.debug(f'Selected: name="{model_info.name}" repo="{model_info.repo}" cls={model_info.repo_cls}')
if model_info.workflow == 'fl2va':
workflow = 'fl2va' if init_image is not None else 't2va'
else:
workflow = model_info.workflow
return gr.update(value=workflow), gr.update(visible=workflow != 'ref2va'), gr.update(visible=workflow == 'ref2va')
def on_load(model_name: str):
model_info = next((m for m in models['MiniMax'] if m.name == model_name), None)
minimax_video.load_model(model_info.name if model_info is not None else None)
model.change(fn=on_change, inputs=[model, init_image], outputs=[workflow, input_accordion, reference_accordion], show_progress='hidden')
init_image.change(fn=on_change, inputs=[model, init_image], outputs=[workflow, input_accordion, reference_accordion], show_progress='hidden')
btn_load.click(fn=on_load, inputs=[model], outputs=[])
task_id = gr.Textbox(visible=False, value='')
ui_state = gr.Textbox(visible=False, value='')
state_inputs = [task_id, ui_state]
video_inputs = [
model, workflow,
prompt, styles,
width, height, frames,
steps, seed,
init_image, last_image, reference_images,
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, mp4_thumb,
audio_enable,
overrides,
]
video_outputs = [
video,
text,
]
video_dict = dict(
fn=minimax_video.generate,
_js="submit_minimax",
inputs=state_inputs + video_inputs + script_inputs,
outputs=video_outputs,
show_progress='hidden',
)
generate.click(**video_dict)
+89
View File
@@ -0,0 +1,89 @@
import time
from modules.logger import log
from modules import shared, processing, timer, progress, paths, sd_models, scripts_manager, call_queue, memstats
from modules.video_models import models_def
engine = 'MiniMax'
loaded = ''
def load_model(model: str):
global loaded # pylint: disable=global-statement
if model is None or model == '' or model == 'None':
shared.sd_model = None
return False
t0 = time.time()
from pipelines.model_minimax import load_minimax
selected: models_def.Model = [m for m in models_def.models[engine] if m.name == model][0]
requested = f'repo={selected.repo} workflow={selected.workflow}'
if (loaded == requested) and (shared.sd_model is not None) and (shared.sd_model.__class__.__name__ == 'MiniMaxH3ModularPipeline'):
log.info(f'Load video: engine="{engine}" selected="{model}" cached')
return selected.workflow
log.info(f'Load video: engine="{engine}" selected="{model}"')
t0 = time.time()
ckpt = sd_models.CheckpointInfo(filename=selected.repo)
shared.sd_model = load_minimax(ckpt, workflow=selected.workflow)
loaded = f'repo={selected.repo} workflow={selected.workflow}'
t1 = time.time()
timer.process.add('load', t1 - t0)
if shared.sd_model is not None:
return selected.workflow
return None
def generate(task_id, _ui_state,
model, workflow,
prompt, styles,
width, height, frames,
steps, seed,
init_image, last_image, reference_images,
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, mp4_thumb,
audio_enable,
_overrides,
*args,
**_kwargs,
):
from modules.video_models import video_minimax
progress.add_task_to_queue(task_id)
with call_queue.get_lock():
progress.start_task(task_id)
memstats.reset_stats()
timer.process.reset()
workflow = load_model(model) # override workflow based on loaded model
if not workflow:
progress.finish_task(task_id)
log.error('Video: model not loaded')
return None, 'Model not loaded'
p = processing.StableDiffusionProcessingVideo(
sd_model=shared.sd_model,
video_engine=engine,
video_model=model,
prompt=prompt,
styles=styles,
seed=int(seed) if seed is not None else -1,
steps=int(steps),
width=width,
height=height,
frames=frames,
do_not_save_grid=True,
do_not_save_samples=not mp4_frames,
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)
log.debug(f'Video: engine="{engine}" model="{model}" workflow={workflow} cls={shared.sd_model.__class__.__name__} kwargs={p.task_args}')
processing.fix_seed(p)
p.ops.append('video')
p.scripts = scripts_manager.scripts_video
p.script_args = args
processed: processing.Processed = scripts_manager.scripts_video.run(p, *args)
if workflow == 'fl2va':
# init images
if workflow == 'ref2va':
# init reference
return None, 'Whatever'
+12
View File
@@ -606,6 +606,9 @@ class ScriptRunner:
return inputs
def run(self, p: StableDiffusionProcessing, *args) -> Processed | None:
from modules import shared
if not shared.sd_loaded:
return None
s = ScriptSummary('run')
script_index = args[0] if len(args) > 0 else 0
if (script_index is None) or (script_index == 0):
@@ -661,6 +664,9 @@ class ScriptRunner:
s.report()
def process(self, p: StableDiffusionProcessing, **kwargs):
from modules import shared
if not shared.sd_loaded:
return None
s = ScriptSummary('process')
for script in self.alwayson_scripts:
try:
@@ -673,6 +679,9 @@ class ScriptRunner:
s.report()
def process_images(self, p: StableDiffusionProcessing, **kwargs):
from modules import shared
if not shared.sd_loaded:
return None
s = ScriptSummary('process_images')
processed = None
for script in self.alwayson_scripts:
@@ -701,6 +710,9 @@ class ScriptRunner:
s.report()
def process_batch(self, p: StableDiffusionProcessing, **kwargs):
from modules import shared
if not shared.sd_loaded:
return None
s = ScriptSummary('process-batch')
for script in self.alwayson_scripts:
try:
+1 -1
View File
@@ -173,7 +173,7 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage):
cls = record['info']['_class_name']
if isinstance(cls, list):
cls = cls[-1]
record['version'] = cls.replace('Pipeline', '').replace('Image', '')
record['version'] = cls.replace('Pipeline', '').replace('Image', '').replace('Modular', '')
else:
record['version'] = ''
record['version'] = version_map.get(record['version'], record['version'])
+3 -3
View File
@@ -70,9 +70,9 @@ def create_toprow(is_img2img: bool = False, id_part: str | None = None, generate
return prompt, styles, negative_prompt, submit, reprocess, button_paste, button_extra, token_counter, token_button, negative_token_counter, negative_token_button
def create_resolution_inputs(tab, default_width=1024, default_height=1024):
width = gr.Slider(minimum=64, maximum=4096, step=8, label="Width", value=default_width, elem_id=f"{tab}_width")
height = gr.Slider(minimum=64, maximum=4096, step=8, label="Height", value=default_height, elem_id=f"{tab}_height")
def create_resolution_inputs(tab, default_width=1024, default_height=1024, step=8):
width = gr.Slider(minimum=64, maximum=4096, step=step, label="Width", value=default_width, elem_id=f"{tab}_width")
height = gr.Slider(minimum=64, maximum=4096, step=step, label="Height", value=default_height, elem_id=f"{tab}_height")
ar_list = ['AR'] + [x.strip() for x in shared.opts.aspect_ratios.split(',') if x.strip() != '']
gr.Dropdown(show_label=False, interactive=True, choices=ar_list, value=ar_list[0], elem_id=f"{tab}_ar", elem_classes=["ar-dropdown"]) # aspect-ratio linking wired client-side in ui/resolutionLock.ts
res_switch_btn = ToolButton(value=ui_symbols.switch, elem_id=f"{tab}_res_btn_swap")
+18 -10
View File
@@ -37,13 +37,28 @@ def create_ui():
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, mp4_thumb = video_ui.create_ui_outputs()
with gr.Tab('Extras', id='video-extras-tab', elem_id='video_extras') as _video_extras_tab:
video_script_inputs = scripts_manager.scripts_video.setup_ui(parent='video', accordion=True)
with gr.Tab('Generic', id='video-core-tab') as video_core_tab:
with gr.Tab('Models', id='video-core-tab') as video_core_tab:
from modules.video_models import video_ui
engine, model, steps, sampler_index, width, height, frames, seed = video_ui.create_ui(
prompt, negative, styles,
overrides, video_script_inputs,
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, mp4_thumb,
)
with gr.Tab('MiniMax', id='minimax-tab') as minimax_tab:
from modules.minimax import minimax_ui
minimax_ui.create_ui(
prompt, negative, styles,
overrides, video_script_inputs,
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, mp4_thumb,
)
with gr.Tab('LTXVideo', id='ltx-tab') as ltx_tab:
from modules.ltx import ltx_ui
ltx_ui.create_ui(
prompt, negative, styles,
overrides, video_script_inputs,
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, mp4_thumb,
)
with gr.Tab('FramePack', id='framepack-tab') as framepack_tab:
from modules.framepack import framepack_ui
framepack_ui.create_ui(
@@ -51,14 +66,6 @@ def create_ui():
overrides, video_script_inputs,
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, mp4_thumb,
)
with gr.Tab('LTX', id='ltx-tab') as ltx_tab:
from modules.ltx import ltx_ui
ltx_ui.create_ui(
prompt, negative, styles,
overrides, video_script_inputs,
mp4_fps, mp4_interpolate, mp4_codec, mp4_ext, mp4_opt, mp4_video, mp4_frames, mp4_sf, mp4_thumb,
)
paste_fields = [
(prompt, "Prompt"), # cannot add more fields as they are not defined yet
@@ -81,7 +88,8 @@ def create_ui():
current_tab = gr.Textbox(visible=False, value='video')
video_core_tab.select(fn=lambda: 'video', inputs=[], outputs=[current_tab])
framepack_tab.select(fn=lambda: 'framepack', inputs=[], outputs=[current_tab])
minimax_tab.select(fn=lambda: 'minimax', inputs=[], outputs=[current_tab])
ltx_tab.select(fn=lambda: 'ltx', inputs=[], outputs=[current_tab])
framepack_tab.select(fn=lambda: 'framepack', inputs=[], outputs=[current_tab])
generate_btn.click(fn=None, _js='submit_video_wrapper', inputs=[current_tab], outputs=[])
+12
View File
@@ -646,6 +646,18 @@ try:
image_hijack=False,
vae_hijack=False,
vae_remote=False),
Model(name='MiniMax H3 SDNQ uint4 Ref2VA',
url='https://huggingface.co/MiniMaxAI/MiniMax-H3',
repo='OzzyGT/MiniMax_H3_sdnq_dynamic_4bit',
repo_cls='MiniMaxH3ModularPipeline',
workflow='ref2va',
base=True,
te_cls=None,
dit_cls=None,
te_hijack=False,
image_hijack=False,
vae_hijack=False,
vae_remote=False),
Model(name='MiniMax H3',
url='https://huggingface.co/MiniMaxAI/MiniMax-H3',
repo='MiniMaxAI/MiniMax-H3',
+2 -2
View File
@@ -3,7 +3,7 @@ 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
def load_minimax(checkpoint_info, diffusers_load_config = None, workflow: str | None = None):
from modules.video_models import video_load
from modules.modular_load import load_modular_pipe
repo_id = sd_models.path_to_repo(checkpoint_info)
@@ -11,7 +11,7 @@ def load_minimax(checkpoint_info, diffusers_load_config=None): # pylint: disable
if repo_id is None or repo_id.lower() == 'none':
return None
offline_args = {'local_files_only': True} if shared.opts.offline_mode else {}
workflow = (getattr(checkpoint_info, 'subfolder', None) or 'fl2va').lower() # one repo holds both checkpoint partitions; reference entries select ref2va via the subfolder tag
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}')
repo_cls = diffusers.MiniMaxH3ModularPipeline
+13 -4
View File
@@ -11022,9 +11022,9 @@ function checkPaused(state) {
if (v_el) v_el.innerText = lastState.paused ? "Resume" : "Pause";
}
function setProgress(res) {
const elements = ["txt2img_generate", "img2img_generate", "extras_generate", "control_generate", "video_generate", "framepack_generate"];
const elements = ["txt2img_generate", "img2img_generate", "extras_generate", "control_generate", "video_generate", "framepack_generate", "ltx_generate", "minimax_generate"];
const progress = res?.progress || 0;
const job = res?.job || "";
const job = res?.textinfo || res?.job || "";
let perc;
let eta = "";
if (job === "VAE") perc = "Decode";
@@ -11044,7 +11044,7 @@ function setProgress(res) {
const elPerf = document.getElementById("control-performance");
let hint = "";
if (elPerf && res) {
const jobTxt = res.job && res.job !== "" ? ` | Job ${res.job}` : "";
const jobTxt = res.job && res.job !== "" ? ` | Job ${res.job}${res.textinfo ? `: ${res.textinfo}` : ""}` : "";
const batchTxt = res.batch > 0 ? ` | Batch ${res.batch}/${res.batches}` : "";
const stateTxt = res.queued ? "Queued" : res.paused ? "Paused" : res.completed ? "Completed" : res.active ? "Active" : "Idle";
const stepsTxt = res.step > 0 ? ` | Step ${res.step}/${res.steps}` : "";
@@ -11074,7 +11074,7 @@ function setProgress(res) {
}
const el2 = document.getElementById("control-performance");
if (el2 && res) {
const jobTxt = res.job && res.job !== "" ? ` | Job ${res.job}` : "";
const jobTxt = res.job && res.job !== "" ? ` | Job ${res.job}${res.textinfo ? `: ${res.textinfo}` : ""}` : "";
const batchTxt = res.batch > 0 ? ` | Batch ${res.batch}/${res.batches}` : "";
const stateTxt = res.queued ? "Queued" : res.paused ? "Paused" : res.completed ? "Completed" : res.active ? "Active" : "Idle";
const stepsTxt = res.step > 0 ? ` | Step ${res.step}/${res.steps}` : "";
@@ -11533,6 +11533,14 @@ function submit_ltx(...args) {
args[0] = id;
return args;
}
function submit_minimax(...args) {
const id = randomId();
log("submitMiniMax", id);
requestProgress(id, null, null);
window.submit_state = "";
args[0] = id;
return args;
}
function submit_video_wrapper(...args) {
const modernEl = gradioApp().querySelector(".video_output.fade-in");
let id = modernEl ? modernEl.id : args[0];
@@ -11864,6 +11872,7 @@ window.submit_control = submit_control;
window.submit_framepack = submit_framepack;
window.submit_img2img = submit_img2img;
window.submit_ltx = submit_ltx;
window.submit_minimax = submit_minimax;
window.submit_postprocessing = submit_postprocessing;
window.submit = submit_txt2img;
window.submit_txt2img = submit_txt2img;
+2 -2
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -56,6 +56,7 @@ declare global {
submit_framepack?: (...args: unknown[]) => unknown[]; // ui/ui.ts
submit_img2img?: (...args: unknown[]) => unknown[]; // ui/ui.ts
submit_ltx?: (...args: unknown[]) => unknown[]; // ui/ui.ts
submit_minimax?: (...args: unknown[]) => unknown[]; // ui/ui.ts
submit_postprocessing?: (...args: unknown[]) => unknown[]; // ui/ui.ts
submit_txt2img?: (...args: unknown[]) => unknown[]; // ui/ui.ts
submit_video?: (...args: unknown[]) => unknown[]; // ui/ui.ts
+1 -1
View File
@@ -40,7 +40,7 @@ export function checkPaused(state) {
}
export function setProgress(res?: any) {
const elements = ['txt2img_generate', 'img2img_generate', 'extras_generate', 'control_generate', 'video_generate', 'framepack_generate'];
const elements = ['txt2img_generate', 'img2img_generate', 'extras_generate', 'control_generate', 'video_generate', 'framepack_generate', 'ltx_generate', 'minimax_generate'];
const progress = res?.progress || 0;
const job = res?.textinfo || res?.job || ''; // stage label when the backend reports one, job name otherwise
let perc: string;
+10
View File
@@ -347,6 +347,15 @@ function submit_ltx(...args) {
return args;
}
function submit_minimax(...args) {
const id = randomId();
log('submitMiniMax', id);
requestProgress(id, null, null);
window.submit_state = '';
args[0] = id;
return args;
}
function submit_video_wrapper(...args) {
const modernEl = gradioApp().querySelector('.video_output.fade-in');
let id = modernEl ? modernEl.id : args[0];
@@ -842,6 +851,7 @@ window.submit_control = submit_control;
window.submit_framepack = submit_framepack;
window.submit_img2img = submit_img2img;
window.submit_ltx = submit_ltx;
window.submit_minimax = submit_minimax;
window.submit_postprocessing = submit_postprocessing;
window.submit = submit_txt2img; // compatibility alias
window.submit_txt2img = submit_txt2img;