experimental minimax chunking

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-08-24 18:51:25 +02:00
parent 1714657cf7
commit 5159e8b1a5
7 changed files with 104 additions and 9 deletions
+1
View File
@@ -119,6 +119,7 @@ Plus quite a lot more, see full [changelog](https://github.com/vladmandic/automa
- torchsde: handle obsolete dependency
- upscaler: avoid unnecessary multi-pass
- ideogram4: fix callback
- xyzgrid: fix seed/prompt/negative handling, thanks @QuintusAntonius
## Update for 2026-08-07
+3 -4
View File
@@ -3,7 +3,8 @@
## Short-term
- Update LTX wiki, @CalamitousFelicitousness
- Lora: new handler, @CalamitousFelicitousness
- LoRA: new handler, @CalamitousFelicitousness
- LoRA: native loader for MiniMax-H3
- Productize benchmark tool, @CalamitousFelicitousness
- Inpaint: https://discord.com/channels/1101998836328697867/1130536562422186044/1506850651035144322, @vladmandic
- Control tab verify overrides handling, @vladmandic
@@ -33,9 +34,7 @@
- Video models: use Networks/Reference instead of custom
- UI Lite vs Expert mode
- Expand custom VAE support
- Refactor: remove obsolete code:
- Remove `olive-ai`
- Remove obsolete code: `olive-ai`
### OnHold
+2 -2
View File
@@ -226,7 +226,7 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork):
if debug:
import sys
fn = f'{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access
debug_log(f'Network load: type=LoRA include={include} exclude={exclude} method={load_method} reason={load_reason} requested={requested} fn={fn}')
debug_log(f'Network load: type=LoRA include={include} exclude={exclude} method={load_method} reason="{load_reason}" requested={requested} fn={fn}')
if load_method == 'diffusers':
has_changed, reason = self.changed(requested)
@@ -267,7 +267,7 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork):
prompt(p)
if has_changed and len(include) == 0: # print only once
actual_method = 'native' if any(len(n.modules) > 0 for n in l.loaded_networks) else load_method
log.info(f'Network load: type=LoRA networks={[n.name for n in l.loaded_networks]} load={load_method}({load_reason}) method={actual_method} mode={"fuse" if shared.opts.lora_fuse_native else "backup"} te={te_multipliers} unet={unet_multipliers} time={l.timer.summary} reason={reason}')
log.info(f'Network load: type=LoRA networks={[n.name for n in l.loaded_networks]} load={load_method}({load_reason}) method={actual_method} mode={"fuse" if shared.opts.lora_fuse_native else "backup"} te={te_multipliers} unet={unet_multipliers} time={l.timer.summary} reason="{reason}"')
def deactivate(self, p, force=False):
if len(lora_diffusers.diffuser_loaded) > 0 and (shared.opts.lora_force_reload or force):
+78
View File
@@ -0,0 +1,78 @@
from contextlib import contextmanager
import torch
import torch.nn.functional as F
orig_sdpa = F.scaled_dot_product_attention
def safe_slice_mask(mask, start, end):
if mask is None:
return None
ndim = mask.ndim
if ndim == 2:
return mask[start:end, :]
elif ndim == 3:
return mask[:, start:end, :]
elif ndim >= 4:
return mask[..., start:end, :]
return mask
def chunked_sdpa(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None, chunk_size=512, **kwargs):
"""
Drop-in replacement for F.scaled_dot_product_attention
"""
N_q = query.shape[2]
if (chunk_size == 0) or (N_q <= chunk_size): # fallback if disabled or sequence length is already smaller than chunk size
return orig_sdpa(
query, key, value,
attn_mask=attn_mask,
dropout_p=dropout_p,
is_causal=is_causal,
scale=scale,
**kwargs,
)
out_chunks = []
for start in range(0, N_q, chunk_size): # process query sequence in chunks against all Keys/Values
end = min(start + chunk_size, N_q)
q_chunk = query[:, :, start:end, :]
chunk_mask = safe_slice_mask(attn_mask, start, end)
out_chunk = orig_sdpa(
q_chunk, key, value,
attn_mask=chunk_mask,
dropout_p=dropout_p,
is_causal=is_causal,
scale=scale,
**kwargs,
)
out_chunks.append(out_chunk)
return torch.cat(out_chunks, dim=2)
@contextmanager
def minimax_attention(chunk_size=0):
"""
Context manager to safely monkey-patch PyTorch's SDPA function
Value 0 bypasses the patch entirely and uses native SDPA
Values are 64-2048 aligned to multiples of 64. Lower values reduce VRAM usage at the cost of speed
"""
global orig_sdpa # pylint: disable=global-statement
orig_sdpa = F.scaled_dot_product_attention
chunk_size = max(64, (chunk_size // 64) * 64) # sanitize and align chunk_size to a multiple of 64 (minimum 64)
def patched_sdpa(query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False, scale=None, **kwargs):
return chunked_sdpa(
query, key, value,
attn_mask=attn_mask,
dropout_p=dropout_p,
is_causal=is_causal,
scale=scale,
chunk_size=chunk_size,
**kwargs,
)
F.scaled_dot_product_attention = patched_sdpa
try:
yield
finally:
F.scaled_dot_product_attention = orig_sdpa
+10 -1
View File
@@ -1,3 +1,4 @@
import os
import time
from PIL import Image
import numpy as np
@@ -137,7 +138,15 @@ def generate(task_id, _ui_state,
p.task_args.update(task_args)
_processed: processing.Processed = scripts_manager.scripts_video.run(p, *args)
processed = processing.process_images(p)
if os.environ.get("SD_MINIMAX_CHUNK", None) is not None:
from modules.minimax.minimax_chunking import minimax_attention
chunk_size = int(os.environ.get("SD_MINIMAX_CHUNK", 0))
log.debug(f'Video: engine="{engine}" model="{model}" chunking=True size={chunk_size}')
with minimax_attention(chunk_size=chunk_size):
processed = processing.process_images(p)
else:
processed = processing.process_images(p)
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
+9 -1
View File
@@ -40,12 +40,20 @@ def apply_task_args(p, x, xs):
def apply_processing(p, x, xs):
for section in x.split(';'):
k, v = section.split('=')
k, v = section.split('=', 1)
k, v = k.strip(), v.strip()
if v.replace('.','',1).isdigit():
v = float(v) if '.' in v else int(v)
found = 'existing' if hasattr(p, k) else 'new'
setattr(p, k, v)
if k == 'prompt':
p.all_prompts = None
elif k == 'negative_prompt':
p.all_negative_prompts = None
elif k == 'seed':
p.all_seeds = None
elif k == 'subseed':
p.all_subseeds = None
log.debug(f'XYZ grid apply processing-arg: type={found} {k}={type(v)}:{v} ')
+1 -1
Submodule wiki updated: b974e50847...eff72c57fe