Files
stable-diffusion-webui-forge/backend/attention.py
T
2025-07-29 15:52:30 +08:00

483 lines
16 KiB
Python

import math
import einops
import torch
from backend import memory_management
from backend.args import args
from modules.errors import display_once
if memory_management.xformers_enabled():
import xformers
import xformers.ops
try:
x_vers = xformers.__version__
except Exception:
BROKEN_XFORMERS = True
else:
BROKEN_XFORMERS = x_vers.startswith("0.0.2") and not x_vers.startswith("0.0.20")
IS_SAGE_2 = False
"""SageAttention 2 has looser restrictions, allowing it to work on more models (e.g. SD1)"""
if memory_management.sage_enabled():
import importlib.metadata
from sageattention import sageattn
IS_SAGE_2 = importlib.metadata.version("sageattention").startswith("2")
if memory_management.flash_enabled():
from flash_attn import flash_attn_func
@torch.library.custom_op("flash_attention::flash_attn", mutates_args=())
def flash_attn_wrapper(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, dropout_p: float = 0.0, causal: bool = False) -> torch.Tensor:
return flash_attn_func(q, k, v, dropout_p=dropout_p, causal=causal)
@flash_attn_wrapper.register_fake
def flash_attn_fake(q, k, v, dropout_p=0.0, causal=False):
return q.new_empty(q.shape)
FORCE_UPCAST_ATTENTION_DTYPE = memory_management.force_upcast_attention_dtype()
def get_xformers_flash_attention_op(q, k, v):
try:
flash_attention_op = xformers.ops.MemoryEfficientAttentionFlashAttentionOp
fw, bw = flash_attention_op
if fw.supports(xformers.ops.fmha.Inputs(query=q, key=k, value=v, attn_bias=None)):
return flash_attention_op
except Exception as e:
display_once(e, "enabling flash attention")
return None
def get_attn_precision(attn_precision=torch.float32):
if args.disable_attention_upcast:
return None
if FORCE_UPCAST_ATTENTION_DTYPE is not None:
return FORCE_UPCAST_ATTENTION_DTYPE
return attn_precision
def exists(val):
return val is not None
# ========== Diffusion ========== #
def attention_basic(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False):
attn_precision = get_attn_precision(attn_precision)
if skip_reshape:
b, _, _, dim_head = q.shape
else:
b, _, dim_head = q.shape
dim_head //= heads
scale = dim_head**-0.5
h = heads
if skip_reshape:
q, k, v = map(
lambda t: t.reshape(b * heads, -1, dim_head),
(q, k, v),
)
else:
q, k, v = map(
lambda t: t.unsqueeze(3).reshape(b, -1, heads, dim_head).permute(0, 2, 1, 3).reshape(b * heads, -1, dim_head).contiguous(),
(q, k, v),
)
if attn_precision == torch.float32:
sim = torch.einsum("b i d, b j d -> b i j", q.float(), k.float()) * scale
else:
sim = torch.einsum("b i d, b j d -> b i j", q, k) * scale
del q, k
if exists(mask):
if mask.dtype == torch.bool:
mask = einops.rearrange(mask, "b ... -> b (...)")
max_neg_value = -torch.finfo(sim.dtype).max
mask = einops.repeat(mask, "b j -> (b h) () j", h=h)
sim.masked_fill_(~mask, max_neg_value)
else:
if len(mask.shape) == 2:
bs = 1
else:
bs = mask.shape[0]
mask = mask.reshape(bs, -1, mask.shape[-2], mask.shape[-1]).expand(b, heads, -1, -1).reshape(-1, mask.shape[-2], mask.shape[-1])
sim.add_(mask)
sim = sim.softmax(dim=-1)
out = torch.einsum("b i j, b j d -> b i d", sim.to(v.dtype), v)
out = out.unsqueeze(0).reshape(b, heads, -1, dim_head).permute(0, 2, 1, 3).reshape(b, -1, heads * dim_head)
return out
def attention_split(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False):
attn_precision = get_attn_precision(attn_precision)
if skip_reshape:
b, _, _, dim_head = q.shape
else:
b, _, dim_head = q.shape
dim_head //= heads
scale = dim_head**-0.5
h = heads
if skip_reshape:
q, k, v = map(
lambda t: t.reshape(b * heads, -1, dim_head),
(q, k, v),
)
else:
q, k, v = map(
lambda t: t.unsqueeze(3).reshape(b, -1, heads, dim_head).permute(0, 2, 1, 3).reshape(b * heads, -1, dim_head).contiguous(),
(q, k, v),
)
r1 = torch.zeros(q.shape[0], q.shape[1], v.shape[2], device=q.device, dtype=q.dtype)
mem_free_total = memory_management.get_free_memory(q.device)
if attn_precision == torch.float32:
element_size = 4
upcast = True
else:
element_size = q.element_size()
upcast = False
gb = 1024**3
tensor_size = q.shape[0] * q.shape[1] * k.shape[1] * element_size
modifier = 3
mem_required = tensor_size * modifier
steps = 1
if mem_required > mem_free_total:
steps = 2 ** (math.ceil(math.log(mem_required / mem_free_total, 2)))
# print(f"Expected tensor size:{tensor_size/gb:0.1f}GB, cuda free:{mem_free_cuda/gb:0.1f}GB "
# f"torch free:{mem_free_torch/gb:0.1f} total:{mem_free_total/gb:0.1f} steps:{steps}")
if steps > 64:
max_res = math.floor(math.sqrt(math.sqrt(mem_free_total / 2.5)) / 8) * 64
raise RuntimeError(f"Not enough memory, use lower resolution (max approx. {max_res}x{max_res}). " f"Need: {mem_required / 64 / gb:0.1f}GB free, Have:{mem_free_total / gb:0.1f}GB free")
if mask is not None:
if len(mask.shape) == 2:
bs = 1
else:
bs = mask.shape[0]
mask = mask.reshape(bs, -1, mask.shape[-2], mask.shape[-1]).expand(b, heads, -1, -1).reshape(-1, mask.shape[-2], mask.shape[-1])
# print("steps", steps, mem_required, mem_free_total, modifier, q.element_size(), tensor_size)
first_op_done = False
cleared_cache = False
while True:
try:
slice_size = q.shape[1] // steps if (q.shape[1] % steps) == 0 else q.shape[1]
for i in range(0, q.shape[1], slice_size):
end = i + slice_size
if upcast:
with torch.autocast(enabled=False, device_type="cuda"):
s1 = torch.einsum("b i d, b j d -> b i j", q[:, i:end].float(), k.float()) * scale
else:
s1 = torch.einsum("b i d, b j d -> b i j", q[:, i:end], k) * scale
if mask is not None:
if len(mask.shape) == 2:
s1 += mask[i:end]
else:
s1 += mask[:, i:end]
s2 = s1.softmax(dim=-1).to(v.dtype)
del s1
first_op_done = True
r1[:, i:end] = torch.einsum("b i j, b j d -> b i d", s2, v)
del s2
break
except memory_management.OOM_EXCEPTION as e:
if first_op_done == False:
memory_management.soft_empty_cache(True)
if cleared_cache == False:
cleared_cache = True
print("out of memory error, emptying cache and trying again")
continue
steps *= 2
if steps > 64:
raise e
print("out of memory error, increasing steps and trying again {}".format(steps))
else:
raise e
del q, k, v
r1 = r1.unsqueeze(0).reshape(b, heads, -1, dim_head).permute(0, 2, 1, 3).reshape(b, -1, heads * dim_head)
return r1
def attention_xformers(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False):
if skip_reshape:
b, _, _, dim_head = q.shape
else:
b, _, dim_head = q.shape
dim_head //= heads
if BROKEN_XFORMERS and b * heads > 65535:
return attention_pytorch(q, k, v, heads, mask, skip_reshape=skip_reshape)
if skip_reshape:
q, k, v = map(
lambda t: t.reshape(b * heads, -1, dim_head),
(q, k, v),
)
else:
q, k, v = map(
lambda t: t.reshape(b, -1, heads, dim_head),
(q, k, v),
)
if mask is not None:
pad = 8 - q.shape[1] % 8
mask_out = torch.empty([q.shape[0], q.shape[1], q.shape[1] + pad], dtype=q.dtype, device=q.device)
mask_out[:, :, : mask.shape[-1]] = mask
mask = mask_out[:, :, : mask.shape[-1]]
out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=mask, op=get_xformers_flash_attention_op(q, k, v))
if skip_reshape:
out = out.unsqueeze(0).reshape(b, heads, -1, dim_head).permute(0, 2, 1, 3).reshape(b, -1, heads * dim_head)
else:
out = out.reshape(b, -1, heads * dim_head)
return out
def attention_pytorch(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False):
if skip_reshape:
b, _, _, dim_head = q.shape
else:
b, _, dim_head = q.shape
dim_head //= heads
q, k, v = map(
lambda t: t.view(b, -1, heads, dim_head).transpose(1, 2),
(q, k, v),
)
out = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.0, is_causal=False)
out = out.transpose(1, 2).reshape(b, -1, heads * dim_head)
return out
def attention_sage(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False):
if skip_reshape:
b, _, _, dim_head = q.shape
tensor_layout = "HND"
else:
b, _, dim_head = q.shape
dim_head //= heads
tensor_layout = "NHD"
if (IS_SAGE_2 and dim_head > 128) or ((not IS_SAGE_2) and (dim_head not in (64, 96, 128))):
if memory_management.xformers_enabled():
return attention_xformers(q, k, v, heads, mask, attn_precision, skip_reshape)
else:
return attention_pytorch(q, k, v, heads, mask, attn_precision, skip_reshape)
if not skip_reshape:
q, k, v = map(
lambda t: t.view(b, -1, heads, dim_head),
(q, k, v),
)
if mask is not None:
# add a batch dimension if there isn't already one
if mask.ndim == 2:
mask = mask.unsqueeze(0)
# add a heads dimension if there isn't already one
if mask.ndim == 3:
mask = mask.unsqueeze(1)
try:
out = sageattn(q, k, v, attn_mask=mask, is_causal=False, tensor_layout=tensor_layout)
except Exception as e:
display_once(e)
if tensor_layout == "NHD":
q, k, v = map(
lambda t: t.transpose(1, 2),
(q, k, v),
)
if memory_management.xformers_enabled():
return attention_xformers(q, k, v, heads, mask=mask, skip_reshape=True)
else:
return attention_pytorch(q, k, v, heads, mask=mask, skip_reshape=True)
if tensor_layout == "HND":
out = out.transpose(1, 2).reshape(b, -1, heads * dim_head)
else:
out = out.reshape(b, -1, heads * dim_head)
return out
def attention_flash(q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False):
if skip_reshape:
b, _, _, dim_head = q.shape
else:
b, _, dim_head = q.shape
dim_head //= heads
q, k, v = map(
lambda t: t.view(b, -1, heads, dim_head).transpose(1, 2),
(q, k, v),
)
if mask is not None:
# add a batch dimension if there isn't already one
if mask.ndim == 2:
mask = mask.unsqueeze(0)
# add a heads dimension if there isn't already one
if mask.ndim == 3:
mask = mask.unsqueeze(1)
try:
assert mask is None
out = flash_attn_wrapper(
q.transpose(1, 2),
k.transpose(1, 2),
v.transpose(1, 2),
dropout_p=0.0,
causal=False,
).transpose(1, 2)
except Exception as e:
display_once(e)
out = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.0, is_causal=False)
out = out.transpose(1, 2).reshape(b, -1, heads * dim_head)
return out
if memory_management.sage_enabled():
print(f"Using SageAttention {'2' if IS_SAGE_2 else ''}")
attention_function = attention_sage
elif memory_management.flash_enabled():
print("Using FlashAttention")
attention_function = attention_flash
elif memory_management.xformers_enabled():
print("Using xformers Cross Attention")
attention_function = attention_xformers
elif memory_management.pytorch_attention_enabled():
print("Using PyTorch Cross Attention")
attention_function = attention_pytorch
elif args.attention_split:
print("Using Split Optimization for Cross Attention")
attention_function = attention_split
else:
print("Using Basic Cross Attention")
attention_function = attention_basic
# ========== VAE ========== #
def slice_attention_single_head_spatial(q, k, v):
r1 = torch.zeros_like(k, device=q.device)
scale = int(q.shape[-1]) ** (-0.5)
mem_free_total = memory_management.get_free_memory(q.device)
tensor_size = q.shape[0] * q.shape[1] * k.shape[2] * q.element_size()
modifier = 3 if q.element_size() == 2 else 2.5
mem_required = tensor_size * modifier
steps = 1
if mem_required > mem_free_total:
steps = 2 ** (math.ceil(math.log(mem_required / mem_free_total, 2)))
while True:
try:
slice_size = q.shape[1] // steps if (q.shape[1] % steps) == 0 else q.shape[1]
for i in range(0, q.shape[1], slice_size):
end = i + slice_size
s1 = torch.bmm(q[:, i:end], k) * scale
s2 = torch.nn.functional.softmax(s1, dim=2).permute(0, 2, 1)
del s1
r1[:, :, i:end] = torch.bmm(v, s2)
del s2
break
except memory_management.OOM_EXCEPTION as e:
memory_management.soft_empty_cache(True)
steps *= 2
if steps > 128:
raise e
print("out of memory error, increasing steps and trying again {}".format(steps))
return r1
def normal_attention_single_head_spatial(q, k, v):
# compute attention
b, c, h, w = q.shape
q = q.reshape(b, c, h * w)
q = q.permute(0, 2, 1) # b,hw,c
k = k.reshape(b, c, h * w) # b,c,hw
v = v.reshape(b, c, h * w)
r1 = slice_attention_single_head_spatial(q, k, v)
h_ = r1.reshape(b, c, h, w)
del r1
return h_
def xformers_attention_single_head_spatial(q, k, v):
# compute attention
B, C, H, W = q.shape
q, k, v = map(
lambda t: t.view(B, C, -1).transpose(1, 2).contiguous(),
(q, k, v),
)
try:
out = xformers.ops.memory_efficient_attention(q, k, v, attn_bias=None, op=get_xformers_flash_attention_op(q, k, v))
out = out.transpose(1, 2).reshape(B, C, H, W)
except NotImplementedError:
out = slice_attention_single_head_spatial(q.view(B, -1, C), k.view(B, -1, C).transpose(1, 2), v.view(B, -1, C).transpose(1, 2)).reshape(B, C, H, W)
return out
def pytorch_attention_single_head_spatial(q, k, v):
# compute attention
B, C, H, W = q.shape
q, k, v = map(
lambda t: t.view(B, 1, C, -1).transpose(2, 3).contiguous(),
(q, k, v),
)
try:
out = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=None, dropout_p=0.0, is_causal=False)
out = out.transpose(2, 3).reshape(B, C, H, W)
except memory_management.OOM_EXCEPTION as e:
display_once(e)
out = slice_attention_single_head_spatial(q.view(B, -1, C), k.view(B, -1, C).transpose(1, 2), v.view(B, -1, C).transpose(1, 2)).reshape(B, C, H, W)
return out
if memory_management.xformers_enabled_vae():
print("Using xformers Attention for VAE")
attention_function_single_head_spatial = xformers_attention_single_head_spatial
elif memory_management.pytorch_attention_enabled():
print("Using PyTorch Attention for VAE")
attention_function_single_head_spatial = pytorch_attention_single_head_spatial
else:
print("Using Split Attention for VAE")
attention_function_single_head_spatial = normal_attention_single_head_spatial