Files
2024-01-29 14:36:21 -06:00

239 lines
9.4 KiB
Python

# ddare/tensor.py
import torch
from typing import Optional, Literal
from .const import EPSILON, CHUNK_SIZE
def get_ties_mask(delta: torch.Tensor, method: Literal["sum", "count"] = "sum", mask_dtype: Optional[torch.dtype] = None, **kwargs) -> torch.Tensor:
"""
TIES-merging https://arxiv.org/abs/2306.01708 uses sign agreement, protecting from
major perturbations in the opposite direction of the base model
Returns a mask determining which delta vectors should be merged
into the final model.
For the methodology described in the paper use 'sum'. For a
simpler naive count of signs, use 'count'.
"""
if mask_dtype is None:
mask_dtype = delta.dtype
sign = delta.sign().to(mask_dtype)
if method == "sum":
sign_weight = (sign * delta.abs()).sum(dim=0)
majority_sign = (sign_weight >= 0).to(mask_dtype) * 2 - 1
del sign_weight
elif method == "count":
majority_sign = (sign.sum(dim=0) >= 0).to(mask_dtype) * 2 - 1
else:
raise RuntimeError(f'Unimplemented mask method "{method}"')
return sign == majority_sign
def dare_ties_sparsification(model_a_param: torch.Tensor, model_b_param: torch.Tensor,
drop_rate: float, ties : str, rescale : str, device : torch.device,
**kwargs) -> torch.Tensor:
"""
DARE-TIES sparsification uses a stochastic mask to determine which deltas to apply
and then sign-agreement to determine which deltas to merge into the final model.
Args:
model_a_param (torch.Tensor): The base model parameter tensor.
model_b_param (torch.Tensor): The model parameter tensor to merge into the base model.
drop_rate (float): The drop rate for the stochastic mask.
ties (str): Whether to use the TIES-merging method.
rescale (str): Whether to rescale the remaining deltas.
device (torch.device): The device to use for the merge.
Returns:
torch.Tensor: The updated parameter tensor.
"""
model_a_flat = model_a_param.view(-1).float().to(device)
model_b_flat = model_b_param.view(-1).float().to(device)
delta_flat = model_b_flat - model_a_flat
dare_mask = torch.bernoulli(torch.full(delta_flat.shape, 1 - drop_rate, device=device)).bool()
# The paper says we should rescale, but it yields terrible results for SD.
if rescale == "on":
# Rescale the remaining deltas
delta_flat = delta_flat / (1 - drop_rate)
if ties != "off":
ties_mask = get_ties_mask(delta_flat, ties)
dare_mask = dare_mask & ties_mask
del ties_mask
sparsified_flat = torch.where(dare_mask, model_a_flat + delta_flat, model_a_flat)
del delta_flat, model_a_flat, model_b_flat, dare_mask
return sparsified_flat.view_as(model_a_param).to(dtype=model_a_param.dtype)
def relative_norm(tensor_a: torch.Tensor, tensor_b: torch.Tensor, eps : float = EPSILON) -> float:
"""
Calculate the relative norm of two tensor tensors.
Args:
tensor_a (torch.Tensor): tensor tensor of this instance.
tensor_b (torch.Tensor): tensor tensor of the other instance.
eps (float): Epsilon value to avoid division by zero.
Returns:
float: Scaling factor.
"""
norm_a = torch.norm(tensor_a)
norm_b = torch.norm(tensor_b)
return norm_b / (norm_a + eps) # Adding epsilon to avoid division by zero
def l2_norm(tensor: torch.Tensor, eps : float = EPSILON) -> torch.Tensor:
"""
Calculate the L2 norm of a tensor.
Args:
tensor (torch.Tensor): The tensor to calculate the L2 norm for.
eps (float): Epsilon value to avoid division by zero.
Returns:
float: The L2 norm of the tensor.
"""
return tensor / (torch.norm(tensor) + eps)
def spectral_norm(matrix: torch.Tensor) -> torch.Tensor:
"""
Calculate the spectral norm of a matrix.
The spectral norm of a matrix is the largest singular value of the matrix. It's a measure
of the matrix's ability to stretch or compress vectors when applied to them.
Args:
matrix (torch.Tensor): A 2D tensor representing the matrix for which the spectral norm
is to be computed. The tensor must have exactly two dimensions.
Returns:
torch.Tensor: A tensor containing a single scalar value, which is the spectral norm
of the input matrix.
Raises:
ValueError: If the input is not a 2D tensor.
"""
if matrix.ndim != 2:
raise ValueError("Input must be a 2D tensor.")
# Compute the spectral norm (2-norm) using torch.linalg.norm
norm = torch.linalg.norm(matrix, ord=2)
return norm
def get_threshold_mask(model_a_param: torch.Tensor, model_b_param: torch.Tensor, device : torch.device, threshold: float, select: str, **kwargs) -> torch.Tensor:
"""
Gets a mask of the delta parameter based on the specified sparsity level.
Args:
model_a_param (torch.Tensor): The parameter from model_a.
model_b_param (torch.Tensor): The parameter from model_b.
device (torch.device): The device to use for the mask.
threshold (float): The sparsity level to use.
invert (str): Whether to invert the mask or not.
**kwargs: Additional arguments specifying the merge ratios for different layers and sparsity.
Returns:
torch.Tensor: The mask of the delta parameter.
"""
model_a_flat = model_a_param.view(-1).float().to(device)
model_b_flat = model_b_param.view(-1).float().to(device)
delta_flat = model_b_flat - model_a_flat
invertion = 0 if select == 'below' else 1
if threshold == 1.0:
mask = torch.zeros_like(delta_flat) == invertion
#print(f"select: {select} threshold: {threshold} Selected: ({mask.sum()}) Excluded: ({(mask == False).sum()})")
elif threshold == 0.0:
mask = torch.ones_like(delta_flat) == invertion
#print(f"select: {select} threshold: {threshold} Selected: ({mask.sum()}) Excluded: ({(mask == False).sum()})")
else:
absolute_delta = torch.abs(delta_flat)
# We can easily overrun memory with large tensors, so we chunk the tensor
delta_threshold = _threshold_in_chunks(tensor=absolute_delta, threshold=threshold, **kwargs)
# Create a mask for values to keep or preserve (above the threshold)
mask = absolute_delta < delta_threshold if select == 'below' else absolute_delta >= delta_threshold
#print(f"select: {select} threshold: {threshold} Selected: ({mask.sum()}) Excluded: ({(mask == False).sum()}) Delta threshold: {delta_threshold} Mask: {absolute_delta.sum()} / {absolute_delta.numel()}")
return mask.view_as(model_a_param)
def _threshold_in_chunks(tensor: torch.Tensor, threshold: float, threshold_type : str, **kwargs) -> float:
"""
Processes the tensor in chunks and calculates the quantile thresholds for each chunk to determine our layer threshold.
Args:
tensor (torch.Tensor): The tensor to process.
threshold (float): The quantile threshold to use.
threshold_type (str): The type of threshold to use, either "median" or "quantile".
Returns:
float: The layer threshold.
"""
thresholds = []
for i in range(0, tensor.numel(), CHUNK_SIZE):
chunk = tensor[i:i + CHUNK_SIZE]
if chunk.numel() == 0:
continue
athreshold = torch.quantile(torch.abs(chunk), threshold).item()
thresholds.append(athreshold)
if threshold_type == "median":
global_threshold = torch.median(torch.tensor(thresholds))
else:
sorted_thresholds = sorted(thresholds)
index = int(threshold * len(sorted_thresholds))
index = max(0, min(index, len(sorted_thresholds) - 1))
global_threshold = sorted_thresholds[index]
return global_threshold
def bernoulli_noise(layer : torch.Tensor, threshold : float) -> torch.Tensor:
"""
Create a random mask of the same shape as the given layer.
Args:
layer (torch.Tensor): The layer to create a random mask for.
threshold (float): Percentage of the layer to be true.
Returns:
torch.Tensor: A random mask of the same shape as the given layer.
"""
return torch.rand(layer.shape) > threshold
def gaussian_noise(layer : torch.Tensor, mean : float = 0.5, std : float = 0.15):
"""
Create a gaussian noise mask of the same shape as the given layer.
Args:
layer (torch.Tensor): The layer to create a gaussian noise mask for.
mean (float): Mean of the gaussian distribution.
std (float): Standard deviation of the gaussian distribution.
Returns:
torch.Tensor: A gaussian noise mask of the same shape as the given layer.
"""
return torch.normal(mean, std, size=layer.shape) > mean
def divide_tensor_into_sets(tensor : torch.Tensor, n_sets : int):
"""
Randomly divide a tensor into n different sets.
Args:
tensor (torch.Tensor): The tensor to be divided.
n_sets (int): The number of sets to divide the tensor into.
Returns:
torch.Tensor: A tensor of the same shape as the input, where each element
indicates the set number (ranging from 0 to n_sets-1) to which
it has been assigned.
"""
# Randomly assign each element to one of the n sets
random_set_assignments = torch.randint(0, n_sets, tensor.shape, dtype=torch.int)
return random_set_assignments