mirror of
https://github.com/vladmandic/automatic
synced 2026-09-20 01:31:13 +02:00
experimental minimax chunking
Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
@@ -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,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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user