major refactoring of modules

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2025-07-03 09:18:38 -04:00
parent 772a5c9ad3
commit c4d9338d2e
214 changed files with 1154 additions and 1153 deletions
+118
View File
@@ -0,0 +1,118 @@
# Copyright (C) 2024 NVIDIA Corporation. All rights reserved.
#
# This work is licensed under the LICENSE file
# located at the root directory.
import torch
import torch.nn.functional as F
import numpy as np
## Attention Utils
def get_dynamic_threshold(tensor):
from skimage import filters
return filters.threshold_otsu(tensor.float().cpu().numpy())
def attn_map_to_binary(attention_map, scaler=1.):
from skimage import filters
attention_map_np = attention_map.float().cpu().numpy()
threshold_value = filters.threshold_otsu(attention_map_np) * scaler
binary_mask = (attention_map_np > threshold_value).astype(np.uint8)
return binary_mask
## Features
def gaussian_smooth(input_tensor, kernel_size=3, sigma=1):
"""
Function to apply Gaussian smoothing on each 2D slice of a 3D tensor.
"""
kernel = np.fromfunction(
lambda x, y: (1/ (2 * np.pi * sigma ** 2)) *
np.exp(-((x - (kernel_size - 1) / 2) ** 2 + (y - (kernel_size - 1) / 2) ** 2) / (2 * sigma ** 2)),
(kernel_size, kernel_size)
)
kernel = torch.Tensor(kernel / kernel.sum()).to(input_tensor.dtype).to(input_tensor.device)
# Add batch and channel dimensions to the kernel
kernel = kernel.unsqueeze(0).unsqueeze(0)
# Iterate over each 2D slice and apply convolution
smoothed_slices = []
for i in range(input_tensor.size(0)):
slice_tensor = input_tensor[i, :, :]
slice_tensor = F.conv2d(slice_tensor.unsqueeze(0).unsqueeze(0), kernel, padding=kernel_size // 2)[0, 0]
smoothed_slices.append(slice_tensor)
# Stack the smoothed slices to get the final tensor
smoothed_tensor = torch.stack(smoothed_slices, dim=0)
return smoothed_tensor
## Dense correspondence utils
def cos_dist(a, b):
a_norm = F.normalize(a, dim=-1)
b_norm = F.normalize(b, dim=-1)
res = a_norm @ b_norm.T
return 1 - res
def gen_nn_map(src_features, src_mask, tgt_features, tgt_mask, device, batch_size=100, tgt_size=768):
resized_src_features = F.interpolate(src_features.unsqueeze(0), size=tgt_size, mode='bilinear', align_corners=False).squeeze(0)
resized_src_features = resized_src_features.permute(1,2,0).view(tgt_size**2, -1)
resized_tgt_features = F.interpolate(tgt_features.unsqueeze(0), size=tgt_size, mode='bilinear', align_corners=False).squeeze(0)
resized_tgt_features = resized_tgt_features.permute(1,2,0).view(tgt_size**2, -1)
nearest_neighbor_indices = torch.zeros(tgt_size**2, dtype=torch.long, device=device)
nearest_neighbor_distances = torch.zeros(tgt_size**2, dtype=src_features.dtype, device=device)
if not batch_size:
batch_size = tgt_size**2
for i in range(0, tgt_size**2, batch_size):
distances = cos_dist(resized_src_features, resized_tgt_features[i:i+batch_size])
distances[~src_mask] = 2.
min_distances, min_indices = torch.min(distances, dim=0)
nearest_neighbor_indices[i:i+batch_size] = min_indices
nearest_neighbor_distances[i:i+batch_size] = min_distances
return nearest_neighbor_indices, nearest_neighbor_distances
def cyclic_nn_map(features, masks, latent_resolutions, device):
bsz = features.shape[0]
nn_map_dict = {}
nn_distances_dict = {}
for tgt_size in latent_resolutions:
nn_map = torch.empty(bsz, bsz, tgt_size**2, dtype=torch.long, device=device)
nn_distances = torch.full((bsz, bsz, tgt_size**2), float('inf'), dtype=features.dtype, device=device)
for i in range(bsz):
for j in range(bsz):
if i != j:
nearest_neighbor_indices, nearest_neighbor_distances = gen_nn_map(features[j], masks[tgt_size][j], features[i], masks[tgt_size][i], device, batch_size=None, tgt_size=tgt_size)
nn_map[i,j] = nearest_neighbor_indices
nn_distances[i,j] = nearest_neighbor_distances
nn_map_dict[tgt_size] = nn_map
nn_distances_dict[tgt_size] = nn_distances
return nn_map_dict, nn_distances_dict
def anchor_nn_map(features, anchor_features, masks, anchor_masks, latent_resolutions, device):
bsz = features.shape[0]
anchor_bsz = anchor_features.shape[0]
nn_map_dict = {}
nn_distances_dict = {}
for tgt_size in latent_resolutions:
nn_map = torch.empty(bsz, anchor_bsz, tgt_size**2, dtype=torch.long, device=device)
nn_distances = torch.full((bsz, anchor_bsz, tgt_size**2), float('inf'), dtype=features.dtype, device=device)
for i in range(bsz):
for j in range(anchor_bsz):
nearest_neighbor_indices, nearest_neighbor_distances = gen_nn_map(anchor_features[j], anchor_masks[tgt_size][j], features[i], masks[tgt_size][i], device, batch_size=None, tgt_size=tgt_size)
nn_map[i,j] = nearest_neighbor_indices
nn_distances[i,j] = nearest_neighbor_distances
nn_map_dict[tgt_size] = nn_map
nn_distances_dict[tgt_size] = nn_distances
return nn_map_dict, nn_distances_dict
+194
View File
@@ -0,0 +1,194 @@
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# MIT License
#
# Copyright (c) 2023 AttendAndExcite
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Not a contribution
# Changes made by NVIDIA CORPORATION & AFFILIATES enabling ConsiStory or otherwise documented as NVIDIA-proprietary
# are not a contribution and subject to the license under the LICENSE file located at the root directory.
import torch
from collections import defaultdict
import numpy as np
from typing import Union, List
from PIL import Image
from modules.consistory.utils.general_utils import attn_map_to_binary
import torch.nn.functional as F
class AttentionStore:
def __init__(self, attention_store_kwargs):
"""
Initialize an empty AttentionStore :param step_index: used to visualize only a specific step in the diffusion
process
"""
self.attn_res = attention_store_kwargs.get('attn_res', (32,32))
self.token_indices = attention_store_kwargs['token_indices']
bsz = self.token_indices.size(1)
self.mask_background_query = attention_store_kwargs.get('mask_background_query', False)
self.original_attn_masks = attention_store_kwargs.get('original_attn_masks', None)
self.extended_mapping = attention_store_kwargs.get('extended_mapping', torch.ones(bsz, bsz).bool())
self.mask_dropout = attention_store_kwargs.get('mask_dropout', 0.0)
torch.manual_seed(0) # For dropout mask reproducibility
self.curr_iter = 0
self.ALL_RES = [32, 64]
self.step_store = defaultdict(list)
self.attn_masks = {res: None for res in self.ALL_RES}
self.last_mask = {res: None for res in self.ALL_RES}
self.last_mask_dropout = {res: None for res in self.ALL_RES}
def __call__(self, attn, is_cross: bool, place_in_unet: str, attn_heads: int):
if is_cross and attn.shape[1] == np.prod(self.attn_res):
guidance_attention = attn[attn.size(0)//2:]
batched_guidance_attention = guidance_attention.reshape([guidance_attention.shape[0]//attn_heads, attn_heads, *guidance_attention.shape[1:]])
batched_guidance_attention = batched_guidance_attention.mean(dim=1)
self.step_store[place_in_unet].append(batched_guidance_attention)
def reset(self):
self.step_store = defaultdict(list)
self.attn_masks = {res: None for res in self.ALL_RES}
self.last_mask = {res: None for res in self.ALL_RES}
self.last_mask_dropout = {res: None for res in self.ALL_RES}
torch.cuda.empty_cache()
def aggregate_last_steps_attention(self) -> torch.Tensor:
"""Aggregates the attention across the different layers and heads at the specified resolution."""
attention_maps = torch.cat([torch.stack(x[-20:]) for x in self.step_store.values()]).mean(dim=0)
bsz, wh, _ = attention_maps.shape
# Create attention maps for each concept token, for each batch item
agg_attn_maps = []
for i in range(bsz):
curr_prompt_indices = []
for concept_token_indices in self.token_indices:
if concept_token_indices[i] != -1:
curr_prompt_indices.append(attention_maps[i, :, concept_token_indices[i]].view(*self.attn_res))
agg_attn_maps.append(torch.stack(curr_prompt_indices))
# Upsample the attention maps to the target resolution
# and create the attention masks, unifying masks across the different concepts
for tgt_size in self.ALL_RES:
pixels = tgt_size ** 2
tgt_agg_attn_maps = [F.interpolate(x.unsqueeze(1), size=tgt_size, mode='bilinear').squeeze(1) for x in agg_attn_maps]
attn_masks = []
for batch_item_map in tgt_agg_attn_maps:
concept_attn_masks = []
for concept_maps in batch_item_map:
concept_attn_masks.append(torch.from_numpy(attn_map_to_binary(concept_maps, 1.)).to(attention_maps.device).bool().view(-1))
concept_attn_masks = torch.stack(concept_attn_masks, dim=0).max(dim=0).values
attn_masks.append(concept_attn_masks)
attn_masks = torch.stack(attn_masks)
self.last_mask[tgt_size] = attn_masks.clone()
# Add mask dropout
if self.curr_iter < 1000:
rand_mask = (torch.rand_like(attn_masks.float()) < self.mask_dropout)
attn_masks[rand_mask] = False
self.last_mask_dropout[tgt_size] = attn_masks.clone()
# # Create subject driven extended self attention masks
# output_attn_mask = torch.zeros((bsz, tgt_size**2, attn_masks.view(-1).size(0)), device=attn_masks.device).bool()
# for i in range(bsz):
# for j in range(bsz):
# if i==j:
# output_attn_mask[i, :, j*pixels:(j+1)*pixels] = 1
# else:
# if self.extended_mapping[i,j]:
# if not self.mask_background_query:
# output_attn_mask[i, :, j*pixels:(j+1)*pixels] = attn_masks[j].unsqueeze(0).expand(pixels, -1)
# else:
# output_attn_mask[i, attn_masks[i], j*pixels:(j+1)*pixels] = attn_masks[j].unsqueeze(0).expand(attn_masks[i].sum(), -1)
# self.attn_masks[tgt_size] = output_attn_mask
def get_attn_mask_bias(self, tgt_size, bsz=None):
attn_mask = self.attn_masks[tgt_size] if self.original_attn_masks is None else self.original_attn_masks[tgt_size]
if attn_mask is None:
return None
attn_bias = torch.zeros_like(attn_mask, dtype=torch.float16)
attn_bias[~attn_mask] = float('-inf')
if bsz and bsz != attn_bias.shape[0]:
attn_bias = attn_bias.repeat(bsz // attn_bias.shape[0], 1, 1)
return attn_bias
def get_extended_attn_mask_instance(self, width, i):
attn_mask = self.last_mask_dropout[width]
if attn_mask is None:
return None
n_patches = width**2
output_attn_mask = torch.zeros((attn_mask.shape[0] * attn_mask.shape[1],), device=attn_mask.device, dtype=torch.bool)
for j in range(attn_mask.shape[0]):
if i==j:
output_attn_mask[j*n_patches:(j+1)*n_patches] = 1
else:
if self.extended_mapping[i,j]:
if not self.mask_background_query:
output_attn_mask[j*n_patches:(j+1)*n_patches] = attn_mask[j].unsqueeze(0) #.expand(n_patches, -1)
else:
raise NotImplementedError('mask_background_query is not supported anymore')
output_attn_mask[0, attn_mask[i], k*n_patches:(k+1)*n_patches] = attn_mask[j].unsqueeze(0).expand(attn_mask[i].sum(), -1)
return output_attn_mask