mirror of
https://github.com/vladmandic/automatic
synced 2026-08-30 00:50:59 +02:00
259e15fafe
Two-network subject+style sets select a winner per layer instead of summing: scores are top-K magnitude sums (klora) or Frobenius energies (estlora), and a timestep ramp shifts layers from the subject network toward the style network across sampling, reduced to at most one precomputed flip per layer per pass. On sub-8-bit SDNQ the pair rides the side-channel as separate segments flipped in place; other layers recompute the winner from the pristine backup, so select modes force backup mode. Selection resets per pass from the callback setup and is gated off under model compile. estlora's measured style-discrepancy term is exposed as an option. Adds XYZ axes for the stack settings.
337 lines
18 KiB
Python
337 lines
18 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
import time
|
|
from typing import TYPE_CHECKING
|
|
import torch
|
|
from modules.lora import lora_common as l
|
|
from modules.lora import lora_stack
|
|
from modules import shared, devices, errors
|
|
from modules.logger import log
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
import diffusers.models.lora
|
|
|
|
|
|
re_network_name = re.compile(r"(.*)\s*\([0-9a-fA-F]+\)")
|
|
|
|
|
|
def network_backup_weights(self: torch.nn.Conv2d | torch.nn.Linear | torch.nn.GroupNorm | torch.nn.LayerNorm | diffusers.models.lora.LoRACompatibleLinear | diffusers.models.lora.LoRACompatibleConv, network_layer_name: str, wanted_names: tuple, fuse: bool):
|
|
backup_size = 0
|
|
if len(l.loaded_networks) > 0 and network_layer_name is not None and any([net.modules.get(network_layer_name, None) for net in l.loaded_networks]): # noqa: C419 # pylint: disable=R1729
|
|
t0 = time.time()
|
|
|
|
weights_backup = getattr(self, "network_weights_backup", None)
|
|
bias_backup = getattr(self, "network_bias_backup", None)
|
|
if weights_backup is not None or bias_backup is not None:
|
|
if (fuse and not isinstance(weights_backup, bool)) or (not fuse and isinstance(weights_backup, bool)): # invalidate so we can change direct/backup on-the-fly
|
|
weights_backup = None
|
|
bias_backup = None
|
|
self.network_weights_backup = weights_backup
|
|
self.network_bias_backup = bias_backup
|
|
|
|
if weights_backup is None and wanted_names != (): # pylint: disable=C1803
|
|
weight = getattr(self, 'weight', None)
|
|
self.network_weights_backup = None
|
|
if fuse:
|
|
self.network_weights_backup = True
|
|
else:
|
|
self.network_weights_backup = weight.clone().to(devices.cpu)
|
|
if hasattr(self, "sdnq_dequantizer"):
|
|
self.sdnq_dequantizer_backup = self.sdnq_dequantizer
|
|
self.sdnq_scale_backup = self.scale.clone().to(devices.cpu)
|
|
if self.zero_point is not None:
|
|
self.sdnq_zero_point_backup = self.zero_point.clone().to(devices.cpu)
|
|
else:
|
|
self.sdnq_zero_point_backup = None
|
|
if self.svd_up is not None:
|
|
self.sdnq_svd_up_backup = self.svd_up.clone().to(devices.cpu)
|
|
self.sdnq_svd_down_backup = self.svd_down.clone().to(devices.cpu)
|
|
else:
|
|
self.sdnq_svd_up_backup = None
|
|
self.sdnq_svd_down_backup = None
|
|
|
|
if bias_backup is None:
|
|
if getattr(self, 'bias', None) is not None:
|
|
if fuse:
|
|
self.network_bias_backup = True
|
|
else:
|
|
bias_backup = self.bias.clone()
|
|
bias_backup = bias_backup.to(devices.cpu)
|
|
|
|
if getattr(self, 'network_weights_backup', None) is not None:
|
|
backup_size += self.network_weights_backup.numel() * self.network_weights_backup.element_size() if isinstance(self.network_weights_backup, torch.Tensor) else 0
|
|
if getattr(self, 'network_bias_backup', None) is not None:
|
|
backup_size += self.network_bias_backup.numel() * self.network_bias_backup.element_size() if isinstance(self.network_bias_backup, torch.Tensor) else 0
|
|
l.timer.backup += time.time() - t0
|
|
return backup_size
|
|
|
|
|
|
def network_calc_weights(self: torch.nn.Conv2d | torch.nn.Linear | torch.nn.GroupNorm | torch.nn.LayerNorm | diffusers.models.lora.LoRACompatibleLinear | diffusers.models.lora.LoRACompatibleConv, network_layer_name: str, use_previous: bool = False, *, elimit: Callable[[], None] | None = None, per_net: bool = False):
|
|
if shared.opts.diffusers_offload_mode == "none":
|
|
try:
|
|
self.to(devices.device)
|
|
except Exception:
|
|
pass
|
|
batch_updown = None
|
|
batch_ex_bias = None
|
|
stack_deltas = None
|
|
if per_net or (lora_stack.mode() in lora_stack.DENSE_MODES and network_layer_name is not None and not network_layer_name.startswith('lora_te')):
|
|
stack_deltas = [] # collect per-net deltas; combined after the loop unless the caller wants them separate (bias deltas stay summed)
|
|
loaded = l.loaded_networks if not use_previous else l.previously_loaded_networks
|
|
for net in loaded:
|
|
module = net.modules.get(network_layer_name, None)
|
|
if module is None:
|
|
continue
|
|
try:
|
|
t0 = time.time()
|
|
if hasattr(self, "sdnq_dequantizer_backup"):
|
|
weight = self.sdnq_dequantizer_backup(
|
|
self.weight.to(devices.device),
|
|
self.sdnq_scale_backup.to(devices.device),
|
|
self.sdnq_zero_point_backup.to(devices.device) if self.sdnq_zero_point_backup is not None else None,
|
|
self.sdnq_svd_up_backup.to(devices.device) if self.sdnq_svd_up_backup is not None else None,
|
|
self.sdnq_svd_down_backup.to(devices.device) if self.sdnq_svd_down_backup is not None else None,
|
|
skip_quantized_matmul=self.sdnq_dequantizer_backup.use_quantized_matmul
|
|
)
|
|
elif hasattr(self, "sdnq_dequantizer"):
|
|
weight = self.sdnq_dequantizer(
|
|
self.weight.to(devices.device),
|
|
self.scale.to(devices.device),
|
|
self.zero_point.to(devices.device) if self.zero_point is not None else None,
|
|
self.svd_up.to(devices.device) if self.svd_up is not None else None,
|
|
self.svd_down.to(devices.device) if self.svd_down is not None else None,
|
|
skip_quantized_matmul=self.sdnq_dequantizer.use_quantized_matmul
|
|
)
|
|
else:
|
|
weight = self.weight.to(devices.device) # must perform calc on gpu due to performance
|
|
updown, ex_bias = module.calc_updown(weight)
|
|
weight = None
|
|
del weight
|
|
|
|
if updown is not None:
|
|
if stack_deltas is not None:
|
|
stack_deltas.append((net.name, updown.to(devices.device)))
|
|
elif batch_updown is not None:
|
|
batch_updown += updown.to(batch_updown.device)
|
|
else:
|
|
batch_updown = updown.to(devices.device)
|
|
if ex_bias is not None:
|
|
if batch_ex_bias is not None:
|
|
batch_ex_bias += ex_bias.to(batch_ex_bias.device)
|
|
else:
|
|
batch_ex_bias = ex_bias.to(devices.device)
|
|
l.timer.calc += time.time() - t0
|
|
|
|
if shared.opts.diffusers_offload_mode == "sequential":
|
|
t0 = time.time()
|
|
if batch_updown is not None:
|
|
batch_updown = batch_updown.to(devices.cpu)
|
|
if batch_ex_bias is not None:
|
|
batch_ex_bias = batch_ex_bias.to(devices.cpu)
|
|
t1 = time.time()
|
|
l.timer.move += t1 - t0
|
|
except RuntimeError as e:
|
|
l.extra_network_lora.errors[net.name] = l.extra_network_lora.errors.get(net.name, 0) + 1
|
|
module_name = net.modules.get(network_layer_name, None)
|
|
log.error(f'Network: type=LoRA name="{net.name}" module="{module_name}" layer="{network_layer_name}" apply weight: {e}')
|
|
if l.debug:
|
|
errors.display(e, 'LoRA')
|
|
raise RuntimeError('LoRA apply weight') from e
|
|
if elimit is not None:
|
|
elimit()
|
|
continue
|
|
if per_net:
|
|
return stack_deltas, batch_ex_bias
|
|
if stack_deltas is not None and stack_deltas:
|
|
if len(stack_deltas) >= 2:
|
|
t0 = time.time()
|
|
batch_updown = lora_stack.combine(stack_deltas, network_layer_name)
|
|
l.timer.calc += time.time() - t0
|
|
else:
|
|
batch_updown = stack_deltas[0][1]
|
|
if shared.opts.diffusers_offload_mode == "sequential":
|
|
batch_updown = batch_updown.to(devices.cpu)
|
|
return batch_updown, batch_ex_bias
|
|
|
|
|
|
def assign_weight(self: torch.nn.Module, new_weight: torch.Tensor, device: torch.device, bias: bool = False):
|
|
"""Install new values on a module, writing into the existing parameter when it can take them.
|
|
|
|
Replacing a parameter puts the tensor on a fresh allocation, and matmul kernel
|
|
selection is sensitive to operand placement, so a swap shifts otherwise
|
|
deterministic outputs bitwise; copying in place keeps the module on its
|
|
load-time allocation across apply and restore cycles.
|
|
"""
|
|
target = self.bias if bias else self.weight
|
|
new_weight = new_weight.to(device)
|
|
if isinstance(target, torch.nn.Parameter) and target.shape == new_weight.shape and target.dtype == new_weight.dtype and target.device == new_weight.device:
|
|
target.data.copy_(new_weight)
|
|
elif bias:
|
|
self.bias = torch.nn.Parameter(new_weight, requires_grad=False)
|
|
else:
|
|
self.weight = torch.nn.Parameter(new_weight, requires_grad=False)
|
|
|
|
|
|
def network_add_weights(self: torch.nn.Conv2d | torch.nn.Linear | torch.nn.GroupNorm | torch.nn.LayerNorm | diffusers.models.lora.LoRACompatibleLinear | diffusers.models.lora.LoRACompatibleConv, model_weights: torch.Tensor | None = None, lora_weights: torch.Tensor = None, deactivate: bool = False, device: torch.device = None, bias: bool = False) -> bool:
|
|
"""Add a delta onto the module's weight or bias; False when nothing was written."""
|
|
if lora_weights is None:
|
|
return False
|
|
if deactivate:
|
|
lora_weights *= -1
|
|
if model_weights is None: # weights are used if provided-from-backup else use the live tensor the delta targets
|
|
model_weights = self.bias if bias else self.weight
|
|
weight, new_weight = None, None
|
|
written = True
|
|
if not bias and hasattr(self, "sdnq_dequantizer"):
|
|
try:
|
|
from sdnq import SDNQConfig, sdnq_quantize_layer
|
|
if hasattr(self, "sdnq_dequantizer_backup"):
|
|
use_svd = bool(self.sdnq_svd_up_backup is not None)
|
|
dequantize_fp32 = bool(self.sdnq_scale_backup.dtype == torch.float32)
|
|
sdnq_dequantizer = self.sdnq_dequantizer_backup
|
|
dequant_weight = self.sdnq_dequantizer_backup(
|
|
model_weights.to(devices.device),
|
|
self.sdnq_scale_backup.to(devices.device),
|
|
self.sdnq_zero_point_backup.to(devices.device) if self.sdnq_zero_point_backup is not None else None,
|
|
self.sdnq_svd_up_backup.to(devices.device) if use_svd else None,
|
|
self.sdnq_svd_down_backup.to(devices.device) if use_svd else None,
|
|
skip_quantized_matmul=self.sdnq_dequantizer_backup.use_quantized_matmul,
|
|
dtype=torch.float32,
|
|
)
|
|
else:
|
|
use_svd = bool(self.svd_up is not None)
|
|
dequantize_fp32 = bool(self.scale.dtype == torch.float32)
|
|
sdnq_dequantizer = self.sdnq_dequantizer
|
|
dequant_weight = self.sdnq_dequantizer(
|
|
model_weights.to(devices.device),
|
|
self.scale.to(devices.device),
|
|
self.zero_point.to(devices.device) if self.zero_point is not None else None,
|
|
self.svd_up.to(devices.device) if use_svd else None,
|
|
self.svd_down.to(devices.device) if use_svd else None,
|
|
skip_quantized_matmul=self.sdnq_dequantizer.use_quantized_matmul,
|
|
dtype=torch.float32,
|
|
)
|
|
|
|
new_weight = dequant_weight.to(devices.device, dtype=torch.float32) + lora_weights.to(devices.device, dtype=torch.float32)
|
|
self.weight = torch.nn.Parameter(new_weight, requires_grad=False)
|
|
self.sdnq_dequantizer = self.scale = self.zero_point = self.svd_up = self.svd_down = None
|
|
self = sdnq_quantize_layer(
|
|
self,
|
|
SDNQConfig(
|
|
weights_dtype=sdnq_dequantizer.weights_dtype,
|
|
quantized_matmul_dtype=sdnq_dequantizer.quantized_matmul_dtype,
|
|
group_size=sdnq_dequantizer.group_size,
|
|
hadamard_group_size=sdnq_dequantizer.hadamard_group_size,
|
|
svd_rank=sdnq_dequantizer.svd_rank,
|
|
use_quantized_matmul=sdnq_dequantizer.use_quantized_matmul,
|
|
use_quantized_matmul_conv=sdnq_dequantizer.use_quantized_matmul,
|
|
use_svd=use_svd,
|
|
use_hadamard=sdnq_dequantizer.use_hadamard,
|
|
use_codebook=sdnq_dequantizer.use_codebook,
|
|
dequantize_fp32=dequantize_fp32,
|
|
svd_steps=shared.opts.sdnq_svd_steps,
|
|
codebook_steps=shared.opts.sdnq_codebook_steps,
|
|
quant_conv=True, # quant_conv is True if conv layers ends up here
|
|
non_blocking=False,
|
|
quantization_device=devices.device,
|
|
return_device=device,
|
|
),
|
|
torch_dtype=sdnq_dequantizer.result_dtype,
|
|
param_name=getattr(self, 'network_layer_name', None),
|
|
)[0].to(device)
|
|
weight = None
|
|
del dequant_weight
|
|
except Exception as e:
|
|
log.error(f'Network load: type=LoRA quant=sdnq cls={self.__class__.__name__} weight={self.weight} lora_weights={lora_weights} {e}')
|
|
written = False
|
|
else:
|
|
try:
|
|
new_weight = model_weights.to(devices.device) + lora_weights.to(devices.device)
|
|
except Exception as e:
|
|
log.warning(f'Network load: {e}')
|
|
if 'The size of tensor' in str(e):
|
|
target = 'bias' if bias else 'weight'
|
|
log.error(f'Network load: type=LoRA model={shared.sd_model.__class__.__name__} layer="{getattr(self, "network_layer_name", None)}" target={target} shape={tuple(model_weights.shape)} lora={tuple(lora_weights.shape)} incompatible lora shape')
|
|
new_weight = model_weights
|
|
written = False
|
|
else:
|
|
new_weight = model_weights + lora_weights # try without device cast
|
|
assign_weight(self, new_weight, device, bias=bias)
|
|
del model_weights, lora_weights, new_weight, weight # required to avoid memory leak
|
|
return written
|
|
|
|
|
|
def network_apply_direct(self: torch.nn.Conv2d | torch.nn.Linear | torch.nn.GroupNorm | torch.nn.LayerNorm | diffusers.models.lora.LoRACompatibleLinear | diffusers.models.lora.LoRACompatibleConv, updown: torch.Tensor, ex_bias: torch.Tensor, deactivate: bool = False, device: torch.device = devices.device) -> tuple[bool, bool]:
|
|
"""Add the deltas onto the live tensors; returns which of (weight, bias) was written."""
|
|
weights_backup = getattr(self, "network_weights_backup", False)
|
|
bias_backup = getattr(self, "network_bias_backup", False)
|
|
if not isinstance(weights_backup, bool): # remove previous backup if we switched settings
|
|
weights_backup = True
|
|
if not isinstance(bias_backup, bool):
|
|
bias_backup = True
|
|
if not weights_backup and not bias_backup:
|
|
return False, False
|
|
t0 = time.time()
|
|
weight_written, bias_written = False, False
|
|
|
|
if weights_backup:
|
|
if updown is not None and len(self.weight.shape) == 4 and self.weight.shape[1] == 9: # inpainting model so zero pad updown to make channel 4 to 9
|
|
updown = torch.nn.functional.pad(updown, (0, 0, 0, 0, 0, 5)) # pylint: disable=not-callable
|
|
if updown is not None:
|
|
weight_written = network_add_weights(self, lora_weights=updown, deactivate=deactivate, device=device, bias=False)
|
|
|
|
if bias_backup:
|
|
if ex_bias is not None:
|
|
bias_written = network_add_weights(self, lora_weights=ex_bias, deactivate=deactivate, device=device, bias=True)
|
|
|
|
if hasattr(self, "qweight") and hasattr(self, "freeze"):
|
|
self.freeze()
|
|
|
|
l.timer.apply += time.time() - t0
|
|
return weight_written, bias_written
|
|
|
|
|
|
def network_apply_weights(self: torch.nn.Conv2d | torch.nn.Linear | torch.nn.GroupNorm | torch.nn.LayerNorm | diffusers.models.lora.LoRACompatibleLinear | diffusers.models.lora.LoRACompatibleConv, updown: torch.Tensor, ex_bias: torch.Tensor, device: torch.device, deactivate: bool = False) -> tuple[bool, bool]:
|
|
"""Add the deltas onto the backup copies; returns which of (weight, bias) was written."""
|
|
weights_backup = getattr(self, "network_weights_backup", None)
|
|
bias_backup = getattr(self, "network_bias_backup", None)
|
|
if weights_backup is None and bias_backup is None:
|
|
return False, False
|
|
t0 = time.time()
|
|
weight_written, bias_written = False, False
|
|
|
|
if weights_backup is not None and not isinstance(weights_backup, bool):
|
|
if updown is not None and len(weights_backup.shape) == 4 and weights_backup.shape[1] == 9: # inpainting model. zero pad updown to make channel[1] 4 to 9
|
|
updown = torch.nn.functional.pad(updown, (0, 0, 0, 0, 0, 5)) # pylint: disable=not-callable
|
|
if updown is not None:
|
|
weight_written = network_add_weights(self, model_weights=weights_backup, lora_weights=updown, deactivate=deactivate, device=device, bias=False)
|
|
else:
|
|
assign_weight(self, weights_backup, device)
|
|
if hasattr(self, "sdnq_dequantizer_backup"):
|
|
self.sdnq_dequantizer = self.sdnq_dequantizer_backup
|
|
self.scale = torch.nn.Parameter(self.sdnq_scale_backup.to(device), requires_grad=False)
|
|
if self.sdnq_zero_point_backup is not None:
|
|
self.zero_point = torch.nn.Parameter(self.sdnq_zero_point_backup.to(device), requires_grad=False)
|
|
else:
|
|
self.zero_point = None
|
|
if self.sdnq_svd_up_backup is not None:
|
|
self.svd_up = torch.nn.Parameter(self.sdnq_svd_up_backup.to(device), requires_grad=False)
|
|
self.svd_down = torch.nn.Parameter(self.sdnq_svd_down_backup.to(device), requires_grad=False)
|
|
else:
|
|
self.svd_up, self.svd_down = None, None
|
|
# del self.sdnq_dequantizer_backup, self.sdnq_scale_backup, self.sdnq_zero_point_backup, self.sdnq_svd_up_backup, self.sdnq_svd_down_backup
|
|
|
|
if bias_backup is not None and not isinstance(bias_backup, bool):
|
|
if ex_bias is not None:
|
|
bias_written = network_add_weights(self, model_weights=bias_backup, lora_weights=ex_bias, deactivate=deactivate, device=device, bias=True)
|
|
else:
|
|
assign_weight(self, bias_backup, device, bias=True)
|
|
|
|
if hasattr(self, "qweight") and hasattr(self, "freeze"):
|
|
self.freeze()
|
|
|
|
l.timer.apply += time.time() - t0
|
|
return weight_written, bias_written
|