Integrate CharacterGen & Improve its workflow with Unique3D
@@ -0,0 +1,38 @@
|
||||
[
|
||||
[
|
||||
[
|
||||
0, 0, -1, 0,
|
||||
0, 1, 0, 0,
|
||||
1, 0, 0, 0,
|
||||
1.5, 0, 0, 1
|
||||
],
|
||||
"pose0.png"
|
||||
],
|
||||
[
|
||||
[
|
||||
0, 0, 1, 0,
|
||||
0, 1, 0, 0,
|
||||
-1, 0, 0, 0,
|
||||
-1.5, 0, 0, 1
|
||||
],
|
||||
"pose1.png"
|
||||
],
|
||||
[
|
||||
[
|
||||
0, 0, 1, 0,
|
||||
0, 1, 0, 0,
|
||||
-1, 0, 0, 0,
|
||||
-1.5, 0, 0, 1
|
||||
],
|
||||
"pose2.png"
|
||||
],
|
||||
[
|
||||
[
|
||||
-1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, -1, 0,
|
||||
0, 0, -1.5, 1
|
||||
],
|
||||
"pose3.png"
|
||||
]
|
||||
]
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
@@ -0,0 +1,59 @@
|
||||
import os
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.init as init
|
||||
from einops import rearrange
|
||||
|
||||
class PoseGuider(nn.Module):
|
||||
def __init__(self, noise_latent_channels=4):
|
||||
super(PoseGuider, self).__init__()
|
||||
|
||||
self.conv_layers = nn.Sequential(
|
||||
nn.Conv2d(in_channels=3, out_channels=16, kernel_size=4, stride=2, padding=1),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(in_channels=16, out_channels=32, kernel_size=4, stride=2, padding=1),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=4, stride=2, padding=1),
|
||||
nn.ReLU(),
|
||||
nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
|
||||
nn.ReLU()
|
||||
)
|
||||
|
||||
# Final projection layer
|
||||
self.final_proj = nn.Conv2d(in_channels=128, out_channels=noise_latent_channels, kernel_size=1)
|
||||
|
||||
# Initialize layers
|
||||
self._initialize_weights()
|
||||
|
||||
def _initialize_weights(self):
|
||||
# Initialize weights with Gaussian distribution and zero out the final layer
|
||||
for m in self.conv_layers:
|
||||
if isinstance(m, nn.Conv2d):
|
||||
init.normal_(m.weight, mean=0.0, std=0.02)
|
||||
if m.bias is not None:
|
||||
init.zeros_(m.bias)
|
||||
|
||||
init.zeros_(self.final_proj.weight)
|
||||
if self.final_proj.bias is not None:
|
||||
init.zeros_(self.final_proj.bias)
|
||||
|
||||
def forward(self, pose_image):
|
||||
x = self.conv_layers(pose_image)
|
||||
x = self.final_proj(x)
|
||||
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(pretrained_model_path):
|
||||
if not os.path.exists(pretrained_model_path):
|
||||
print(f"There is no model file in {pretrained_model_path}")
|
||||
print(f"loaded PoseGuider's pretrained weights from {pretrained_model_path} ...")
|
||||
|
||||
state_dict = torch.load(pretrained_model_path, map_location="cpu")
|
||||
model = PoseGuider(noise_latent_channels=4)
|
||||
m, u = model.load_state_dict(state_dict, strict=False)
|
||||
print(f"### missing keys: {len(m)}; \n### unexpected keys: {len(u)};")
|
||||
params = [p.numel() if "temporal" in n else 0 for n, p in model.named_parameters()]
|
||||
print(f"### PoseGuider's Parameters: {sum(params) / 1e6} M")
|
||||
|
||||
return model
|
||||
@@ -0,0 +1,344 @@
|
||||
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention.py
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
|
||||
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
||||
from diffusers import ModelMixin
|
||||
from diffusers.utils import BaseOutput
|
||||
from diffusers.utils.import_utils import is_xformers_available
|
||||
from diffusers.models.attention import CrossAttention, FeedForward, AdaLayerNorm
|
||||
|
||||
from einops import rearrange, repeat
|
||||
|
||||
|
||||
@dataclass
|
||||
class Transformer3DModelOutput(BaseOutput):
|
||||
sample: torch.FloatTensor
|
||||
|
||||
|
||||
if is_xformers_available():
|
||||
import xformers
|
||||
import xformers.ops
|
||||
else:
|
||||
xformers = None
|
||||
|
||||
|
||||
class Transformer3DModel(ModelMixin, ConfigMixin):
|
||||
@register_to_config
|
||||
def __init__(
|
||||
self,
|
||||
num_attention_heads: int = 16,
|
||||
attention_head_dim: int = 88,
|
||||
in_channels: Optional[int] = None,
|
||||
num_layers: int = 1,
|
||||
dropout: float = 0.0,
|
||||
norm_num_groups: int = 32,
|
||||
cross_attention_dim: Optional[int] = None,
|
||||
attention_bias: bool = False,
|
||||
activation_fn: str = "geglu",
|
||||
num_embeds_ada_norm: Optional[int] = None,
|
||||
use_linear_projection: bool = False,
|
||||
only_cross_attention: bool = False,
|
||||
upcast_attention: bool = False,
|
||||
use_attn_temp: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.use_linear_projection = use_linear_projection
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.attention_head_dim = attention_head_dim
|
||||
inner_dim = num_attention_heads * attention_head_dim
|
||||
|
||||
# Define input layers
|
||||
self.in_channels = in_channels
|
||||
|
||||
self.norm = torch.nn.GroupNorm(num_groups=norm_num_groups, num_channels=in_channels, eps=1e-6, affine=True)
|
||||
if use_linear_projection:
|
||||
self.proj_in = nn.Linear(in_channels, inner_dim)
|
||||
else:
|
||||
self.proj_in = nn.Conv2d(in_channels, inner_dim, kernel_size=1, stride=1, padding=0)
|
||||
|
||||
# Define transformers blocks
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
inner_dim,
|
||||
num_attention_heads,
|
||||
attention_head_dim,
|
||||
dropout=dropout,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
activation_fn=activation_fn,
|
||||
num_embeds_ada_norm=num_embeds_ada_norm,
|
||||
attention_bias=attention_bias,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
use_attn_temp = use_attn_temp,
|
||||
)
|
||||
for d in range(num_layers)
|
||||
]
|
||||
)
|
||||
|
||||
# 4. Define output layers
|
||||
if use_linear_projection:
|
||||
self.proj_out = nn.Linear(in_channels, inner_dim)
|
||||
else:
|
||||
self.proj_out = nn.Conv2d(inner_dim, in_channels, kernel_size=1, stride=1, padding=0)
|
||||
|
||||
def forward(self, hidden_states, encoder_hidden_states=None, timestep=None, return_dict: bool = True):
|
||||
# Input
|
||||
assert hidden_states.dim() == 5, f"Expected hidden_states to have ndim=5, but got ndim={hidden_states.dim()}."
|
||||
video_length = hidden_states.shape[2]
|
||||
hidden_states = rearrange(hidden_states, "b c f h w -> (b f) c h w")
|
||||
encoder_hidden_states = repeat(encoder_hidden_states, 'b n c -> (b f) n c', f=video_length)
|
||||
|
||||
batch, channel, height, weight = hidden_states.shape
|
||||
residual = hidden_states
|
||||
|
||||
hidden_states = self.norm(hidden_states)
|
||||
if not self.use_linear_projection:
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * weight, inner_dim)
|
||||
else:
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(batch, height * weight, inner_dim)
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
|
||||
# Blocks
|
||||
for block in self.transformer_blocks:
|
||||
hidden_states = block(
|
||||
hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
timestep=timestep,
|
||||
video_length=video_length
|
||||
)
|
||||
|
||||
# Output
|
||||
if not self.use_linear_projection:
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, height, weight, inner_dim).permute(0, 3, 1, 2).contiguous()
|
||||
)
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
else:
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, height, weight, inner_dim).permute(0, 3, 1, 2).contiguous()
|
||||
)
|
||||
|
||||
output = hidden_states + residual
|
||||
|
||||
output = rearrange(output, "(b f) c h w -> b c f h w", f=video_length)
|
||||
if not return_dict:
|
||||
return (output,)
|
||||
|
||||
return Transformer3DModelOutput(sample=output)
|
||||
|
||||
|
||||
class BasicTransformerBlock(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
num_attention_heads: int,
|
||||
attention_head_dim: int,
|
||||
dropout=0.0,
|
||||
cross_attention_dim: Optional[int] = None,
|
||||
activation_fn: str = "geglu",
|
||||
num_embeds_ada_norm: Optional[int] = None,
|
||||
attention_bias: bool = False,
|
||||
only_cross_attention: bool = False,
|
||||
upcast_attention: bool = False,
|
||||
use_attn_temp: bool = False
|
||||
):
|
||||
super().__init__()
|
||||
self.only_cross_attention = only_cross_attention
|
||||
self.use_ada_layer_norm = num_embeds_ada_norm is not None
|
||||
self.use_attn_temp = use_attn_temp
|
||||
# SC-Attn
|
||||
self.attn1 = SparseCausalAttention(
|
||||
query_dim=dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
cross_attention_dim=cross_attention_dim if only_cross_attention else None,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
self.norm1 = AdaLayerNorm(dim, num_embeds_ada_norm) if self.use_ada_layer_norm else nn.LayerNorm(dim)
|
||||
|
||||
# Cross-Attn
|
||||
if cross_attention_dim is not None:
|
||||
self.attn2 = CrossAttention(
|
||||
query_dim=dim,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
else:
|
||||
self.attn2 = None
|
||||
|
||||
if cross_attention_dim is not None:
|
||||
self.norm2 = AdaLayerNorm(dim, num_embeds_ada_norm) if self.use_ada_layer_norm else nn.LayerNorm(dim)
|
||||
else:
|
||||
self.norm2 = None
|
||||
|
||||
# Feed-forward
|
||||
self.ff = FeedForward(dim, dropout=dropout, activation_fn=activation_fn)
|
||||
self.norm3 = nn.LayerNorm(dim)
|
||||
|
||||
# Temp-Attn
|
||||
if self.use_attn_temp:
|
||||
self.attn_temp = CrossAttention(
|
||||
query_dim=dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
nn.init.zeros_(self.attn_temp.to_out[0].weight.data)
|
||||
self.norm_temp = AdaLayerNorm(dim, num_embeds_ada_norm) if self.use_ada_layer_norm else nn.LayerNorm(dim)
|
||||
|
||||
def set_use_memory_efficient_attention_xformers(self, use_memory_efficient_attention_xformers: bool):
|
||||
if not is_xformers_available():
|
||||
print("Here is how to install it")
|
||||
raise ModuleNotFoundError(
|
||||
"Refer to https://github.com/facebookresearch/xformers for more information on how to install"
|
||||
" xformers",
|
||||
name="xformers",
|
||||
)
|
||||
elif not torch.cuda.is_available():
|
||||
raise ValueError(
|
||||
"torch.cuda.is_available() should be True but is False. xformers' memory efficient attention is only"
|
||||
" available for GPU "
|
||||
)
|
||||
else:
|
||||
try:
|
||||
# Make sure we can run the memory efficient attention
|
||||
_ = xformers.ops.memory_efficient_attention(
|
||||
torch.randn((1, 2, 40), device="cuda"),
|
||||
torch.randn((1, 2, 40), device="cuda"),
|
||||
torch.randn((1, 2, 40), device="cuda"),
|
||||
)
|
||||
except Exception as e:
|
||||
raise e
|
||||
self.attn1._use_memory_efficient_attention_xformers = use_memory_efficient_attention_xformers
|
||||
if self.attn2 is not None:
|
||||
self.attn2._use_memory_efficient_attention_xformers = use_memory_efficient_attention_xformers
|
||||
#self.attn_temp._use_memory_efficient_attention_xformers = use_memory_efficient_attention_xformers
|
||||
|
||||
def forward(self, hidden_states, encoder_hidden_states=None, timestep=None, attention_mask=None, video_length=None):
|
||||
# SparseCausal-Attention
|
||||
norm_hidden_states = (
|
||||
self.norm1(hidden_states, timestep) if self.use_ada_layer_norm else self.norm1(hidden_states)
|
||||
)
|
||||
|
||||
if self.only_cross_attention:
|
||||
hidden_states = (
|
||||
self.attn1(norm_hidden_states, encoder_hidden_states, attention_mask=attention_mask) + hidden_states
|
||||
)
|
||||
else:
|
||||
hidden_states = self.attn1(norm_hidden_states, attention_mask=attention_mask, video_length=video_length) + hidden_states
|
||||
|
||||
if self.attn2 is not None:
|
||||
# Cross-Attention
|
||||
norm_hidden_states = (
|
||||
self.norm2(hidden_states, timestep) if self.use_ada_layer_norm else self.norm2(hidden_states)
|
||||
)
|
||||
hidden_states = (
|
||||
self.attn2(
|
||||
norm_hidden_states, encoder_hidden_states=encoder_hidden_states, attention_mask=attention_mask
|
||||
)
|
||||
+ hidden_states
|
||||
)
|
||||
|
||||
# Feed-forward
|
||||
hidden_states = self.ff(self.norm3(hidden_states)) + hidden_states
|
||||
|
||||
# Temporal-Attention
|
||||
if self.use_attn_temp:
|
||||
d = hidden_states.shape[1]
|
||||
hidden_states = rearrange(hidden_states, "(b f) d c -> (b d) f c", f=video_length)
|
||||
norm_hidden_states = (
|
||||
self.norm_temp(hidden_states, timestep) if self.use_ada_layer_norm else self.norm_temp(hidden_states)
|
||||
)
|
||||
hidden_states = self.attn_temp(norm_hidden_states) + hidden_states
|
||||
hidden_states = rearrange(hidden_states, "(b d) f c -> (b f) d c", d=d)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class SparseCausalAttention(CrossAttention):
|
||||
def forward(self, hidden_states, encoder_hidden_states=None, attention_mask=None, video_length=None, use_full_attn=True):
|
||||
batch_size, sequence_length, _ = hidden_states.shape
|
||||
|
||||
encoder_hidden_states = encoder_hidden_states
|
||||
|
||||
if self.group_norm is not None:
|
||||
hidden_states = self.group_norm(hidden_states.transpose(1, 2)).transpose(1, 2)
|
||||
|
||||
query = self.to_q(hidden_states)
|
||||
# query = rearrange(query, "(b f) d c -> b (f d) c", f=video_length)
|
||||
dim = query.shape[-1]
|
||||
query = self.reshape_heads_to_batch_dim(query)
|
||||
|
||||
if self.added_kv_proj_dim is not None:
|
||||
raise NotImplementedError
|
||||
|
||||
encoder_hidden_states = encoder_hidden_states if encoder_hidden_states is not None else hidden_states
|
||||
key = self.to_k(encoder_hidden_states)
|
||||
value = self.to_v(encoder_hidden_states)
|
||||
|
||||
former_frame_index = torch.arange(video_length) - 1
|
||||
former_frame_index[0] = 0
|
||||
|
||||
key = rearrange(key, "(b f) d c -> b f d c", f=video_length)
|
||||
if not use_full_attn:
|
||||
key = torch.cat([key[:, [0] * video_length], key[:, former_frame_index]], dim=2)
|
||||
else:
|
||||
# key = torch.cat([key[:, [0] * video_length], key[:, [1] * video_length], key[:, [2] * video_length], key[:, [3] * video_length]], dim=2)
|
||||
key_video_length = [key[:, [i] * video_length] for i in range(video_length)]
|
||||
key = torch.cat(key_video_length, dim=2)
|
||||
key = rearrange(key, "b f d c -> (b f) d c")
|
||||
|
||||
value = rearrange(value, "(b f) d c -> b f d c", f=video_length)
|
||||
if not use_full_attn:
|
||||
value = torch.cat([value[:, [0] * video_length], value[:, former_frame_index]], dim=2)
|
||||
else:
|
||||
# value = torch.cat([value[:, [0] * video_length], value[:, [1] * video_length], value[:, [2] * video_length], value[:, [3] * video_length]], dim=2)
|
||||
value_video_length = [value[:, [i] * video_length] for i in range(video_length)]
|
||||
value = torch.cat(value_video_length, dim=2)
|
||||
value = rearrange(value, "b f d c -> (b f) d c")
|
||||
|
||||
key = self.reshape_heads_to_batch_dim(key)
|
||||
value = self.reshape_heads_to_batch_dim(value)
|
||||
|
||||
if attention_mask is not None:
|
||||
if attention_mask.shape[-1] != query.shape[1]:
|
||||
target_length = query.shape[1]
|
||||
attention_mask = F.pad(attention_mask, (0, target_length), value=0.0)
|
||||
attention_mask = attention_mask.repeat_interleave(self.heads, dim=0)
|
||||
|
||||
# attention, what we cannot get enough of
|
||||
if self._use_memory_efficient_attention_xformers:
|
||||
hidden_states = self._memory_efficient_attention_xformers(query, key, value, attention_mask)
|
||||
# Some versions of xformers return output in fp32, cast it back to the dtype of the input
|
||||
hidden_states = hidden_states.to(query.dtype)
|
||||
else:
|
||||
if self._slice_size is None or query.shape[0] // self._slice_size == 1:
|
||||
hidden_states = self._attention(query, key, value, attention_mask)
|
||||
else:
|
||||
hidden_states = self._sliced_attention(query, key, value, sequence_length, dim, attention_mask)
|
||||
|
||||
# linear proj
|
||||
hidden_states = self.to_out[0](hidden_states)
|
||||
|
||||
# dropout
|
||||
hidden_states = self.to_out[1](hidden_states)
|
||||
return hidden_states
|
||||
@@ -0,0 +1,118 @@
|
||||
# modified from https://github.com/mlfoundations/open_flamingo/blob/main/open_flamingo/src/helpers.py
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
# FFN
|
||||
def FeedForward(dim, mult=4):
|
||||
inner_dim = int(dim * mult)
|
||||
return nn.Sequential(
|
||||
nn.LayerNorm(dim),
|
||||
nn.Linear(dim, inner_dim, bias=False),
|
||||
nn.GELU(),
|
||||
nn.Linear(inner_dim, dim, bias=False),
|
||||
)
|
||||
|
||||
def reshape_tensor(x, heads):
|
||||
bs, length, width = x.shape
|
||||
#(bs, length, width) --> (bs, length, n_heads, dim_per_head)
|
||||
x = x.view(bs, length, heads, -1)
|
||||
# (bs, length, n_heads, dim_per_head) --> (bs, n_heads, length, dim_per_head)
|
||||
x = x.transpose(1, 2)
|
||||
# (bs, n_heads, length, dim_per_head) --> (bs*n_heads, length, dim_per_head)
|
||||
x = x.reshape(bs, heads, length, -1)
|
||||
return x
|
||||
|
||||
|
||||
class PerceiverAttention(nn.Module):
|
||||
def __init__(self, *, dim, dim_head=64, heads=8):
|
||||
super().__init__()
|
||||
self.scale = dim_head**-0.5
|
||||
self.dim_head = dim_head
|
||||
self.heads = heads
|
||||
inner_dim = dim_head * heads
|
||||
|
||||
self.norm1 = nn.LayerNorm(dim)
|
||||
self.norm2 = nn.LayerNorm(dim)
|
||||
|
||||
self.to_q = nn.Linear(dim, inner_dim, bias=False)
|
||||
self.to_kv = nn.Linear(dim, inner_dim * 2, bias=False)
|
||||
self.to_out = nn.Linear(inner_dim, dim, bias=False)
|
||||
|
||||
|
||||
def forward(self, x, latents):
|
||||
"""
|
||||
Args:
|
||||
x (torch.Tensor): image features
|
||||
shape (b, n1, D)
|
||||
latent (torch.Tensor): latent features
|
||||
shape (b, n2, D)
|
||||
"""
|
||||
x = self.norm1(x)
|
||||
latents = self.norm2(latents)
|
||||
|
||||
b, l, _ = latents.shape
|
||||
|
||||
q = self.to_q(latents)
|
||||
kv_input = torch.cat((x, latents), dim=-2)
|
||||
k, v = self.to_kv(kv_input).chunk(2, dim=-1)
|
||||
|
||||
q = reshape_tensor(q, self.heads)
|
||||
k = reshape_tensor(k, self.heads)
|
||||
v = reshape_tensor(v, self.heads)
|
||||
|
||||
# attention
|
||||
scale = 1 / math.sqrt(math.sqrt(self.dim_head))
|
||||
weight = (q * scale) @ (k * scale).transpose(-2, -1) # More stable with f16 than dividing afterwards
|
||||
weight = torch.softmax(weight.float(), dim=-1).type(weight.dtype)
|
||||
out = weight @ v
|
||||
|
||||
out = out.permute(0, 2, 1, 3).reshape(b, l, -1)
|
||||
|
||||
return self.to_out(out)
|
||||
|
||||
class Resampler(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim=1024,
|
||||
depth=8,
|
||||
dim_head=64,
|
||||
heads=16,
|
||||
num_queries=8,
|
||||
embedding_dim=768,
|
||||
output_dim=1024,
|
||||
ff_mult=4,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.latents = nn.Parameter(torch.randn(1, num_queries, dim) / dim**0.5)
|
||||
|
||||
self.proj_in = nn.Linear(embedding_dim, dim)
|
||||
|
||||
self.proj_out = nn.Linear(dim, output_dim)
|
||||
self.norm_out = nn.LayerNorm(output_dim)
|
||||
|
||||
self.layers = nn.ModuleList([])
|
||||
for _ in range(depth):
|
||||
self.layers.append(
|
||||
nn.ModuleList(
|
||||
[
|
||||
PerceiverAttention(dim=dim, dim_head=dim_head, heads=heads),
|
||||
FeedForward(dim=dim, mult=ff_mult),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
latents = self.latents.repeat(x.size(0), 1, 1)
|
||||
|
||||
x = self.proj_in(x)
|
||||
|
||||
for attn, ff in self.layers:
|
||||
latents = attn(x, latents) + latents
|
||||
latents = ff(latents) + latents
|
||||
|
||||
latents = self.proj_out(latents)
|
||||
return self.norm_out(latents)
|
||||
@@ -0,0 +1,125 @@
|
||||
import torch
|
||||
from einops import rearrange
|
||||
from typing import Any, Dict, Optional
|
||||
from diffusers.utils.import_utils import is_xformers_available
|
||||
from .transformer_mv2d import XFormersMVAttnProcessor, MVAttnProcessor
|
||||
class ReferenceOnlyAttnProc(torch.nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
chained_proc,
|
||||
enabled=False,
|
||||
name=None
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.enabled = enabled
|
||||
self.chained_proc = chained_proc
|
||||
self.name = name
|
||||
|
||||
def __call__(
|
||||
self, attn, hidden_states, encoder_hidden_states=None, attention_mask=None,
|
||||
mode="w", ref_dict: dict = None, is_cfg_guidance = False,num_views=4,
|
||||
multiview_attention=True,
|
||||
cross_domain_attention=False,
|
||||
) -> Any:
|
||||
if encoder_hidden_states is None:
|
||||
encoder_hidden_states = hidden_states
|
||||
# print(self.enabled)
|
||||
if self.enabled:
|
||||
if mode == 'w':
|
||||
ref_dict[self.name] = encoder_hidden_states
|
||||
res = self.chained_proc(attn, hidden_states, encoder_hidden_states, attention_mask, num_views=1,
|
||||
multiview_attention=False,
|
||||
cross_domain_attention=False,)
|
||||
elif mode == 'r':
|
||||
encoder_hidden_states = rearrange(encoder_hidden_states, '(b t) d c-> b (t d) c', t=num_views)
|
||||
if self.name in ref_dict:
|
||||
encoder_hidden_states = torch.cat([encoder_hidden_states, ref_dict.pop(self.name)], dim=1).unsqueeze(1).repeat(1,num_views,1,1).flatten(0,1)
|
||||
res = self.chained_proc(attn, hidden_states, encoder_hidden_states, attention_mask, num_views=num_views,
|
||||
multiview_attention=False,
|
||||
cross_domain_attention=False,)
|
||||
elif mode == 'm':
|
||||
encoder_hidden_states = torch.cat([encoder_hidden_states, ref_dict[self.name]], dim=1)
|
||||
elif mode == 'n':
|
||||
encoder_hidden_states = rearrange(encoder_hidden_states, '(b t) d c-> b (t d) c', t=num_views)
|
||||
encoder_hidden_states = torch.cat([encoder_hidden_states], dim=1).unsqueeze(1).repeat(1,num_views,1,1).flatten(0,1)
|
||||
res = self.chained_proc(attn, hidden_states, encoder_hidden_states, attention_mask, num_views=num_views,
|
||||
multiview_attention=False,
|
||||
cross_domain_attention=False,)
|
||||
else:
|
||||
assert False, mode
|
||||
else:
|
||||
res = self.chained_proc(attn, hidden_states, encoder_hidden_states, attention_mask)
|
||||
return res
|
||||
|
||||
class RefOnlyNoisedUNet(torch.nn.Module):
|
||||
def __init__(self, unet, train_sched, val_sched) -> None:
|
||||
super().__init__()
|
||||
self.unet = unet
|
||||
self.train_sched = train_sched
|
||||
self.val_sched = val_sched
|
||||
|
||||
unet_lora_attn_procs = dict()
|
||||
for name, _ in unet.attn_processors.items():
|
||||
if is_xformers_available():
|
||||
default_attn_proc = XFormersMVAttnProcessor()
|
||||
else:
|
||||
default_attn_proc = MVAttnProcessor()
|
||||
unet_lora_attn_procs[name] = ReferenceOnlyAttnProc(
|
||||
default_attn_proc, enabled=name.endswith("attn1.processor"), name=name)
|
||||
|
||||
self.unet.set_attn_processor(unet_lora_attn_procs)
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
try:
|
||||
return super().__getattr__(name)
|
||||
except AttributeError:
|
||||
return getattr(self.unet, name)
|
||||
|
||||
def forward_cond(self, noisy_cond_lat, timestep, encoder_hidden_states, class_labels, ref_dict, is_cfg_guidance, **kwargs):
|
||||
if is_cfg_guidance:
|
||||
encoder_hidden_states = encoder_hidden_states[1:]
|
||||
class_labels = class_labels[1:]
|
||||
self.unet(
|
||||
noisy_cond_lat, timestep,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
class_labels=class_labels,
|
||||
cross_attention_kwargs=dict(mode="w", ref_dict=ref_dict),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def forward(
|
||||
self, sample, timestep, encoder_hidden_states, class_labels=None,
|
||||
*args, cross_attention_kwargs,
|
||||
down_block_res_samples=None, mid_block_res_sample=None,
|
||||
**kwargs
|
||||
):
|
||||
cond_lat = cross_attention_kwargs['cond_lat']
|
||||
is_cfg_guidance = cross_attention_kwargs.get('is_cfg_guidance', False)
|
||||
noise = torch.randn_like(cond_lat)
|
||||
if self.training:
|
||||
noisy_cond_lat = self.train_sched.add_noise(cond_lat, noise, timestep)
|
||||
noisy_cond_lat = self.train_sched.scale_model_input(noisy_cond_lat, timestep)
|
||||
else:
|
||||
noisy_cond_lat = self.val_sched.add_noise(cond_lat, noise, timestep.reshape(-1))
|
||||
noisy_cond_lat = self.val_sched.scale_model_input(noisy_cond_lat, timestep.reshape(-1))
|
||||
ref_dict = {}
|
||||
self.forward_cond(
|
||||
noisy_cond_lat, timestep,
|
||||
encoder_hidden_states, class_labels,
|
||||
ref_dict, is_cfg_guidance, **kwargs
|
||||
)
|
||||
weight_dtype = self.unet.dtype
|
||||
return self.unet(
|
||||
sample, timestep,
|
||||
encoder_hidden_states, *args,
|
||||
class_labels=class_labels,
|
||||
cross_attention_kwargs=dict(mode="r", ref_dict=ref_dict, is_cfg_guidance=is_cfg_guidance),
|
||||
down_block_additional_residuals=[
|
||||
sample.to(dtype=weight_dtype) for sample in down_block_res_samples
|
||||
] if down_block_res_samples is not None else None,
|
||||
mid_block_additional_residual=(
|
||||
mid_block_res_sample.to(dtype=weight_dtype)
|
||||
if mid_block_res_sample is not None else None
|
||||
),
|
||||
**kwargs
|
||||
)
|
||||
@@ -0,0 +1,210 @@
|
||||
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/resnet.py
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from einops import rearrange
|
||||
|
||||
|
||||
class InflatedConv3d(nn.Conv2d):
|
||||
def forward(self, x):
|
||||
video_length = x.shape[2]
|
||||
|
||||
x = rearrange(x, "b c f h w -> (b f) c h w")
|
||||
x = super().forward(x)
|
||||
x = rearrange(x, "(b f) c h w -> b c f h w", f=video_length)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class Upsample3D(nn.Module):
|
||||
def __init__(self, channels, use_conv=False, use_conv_transpose=False, out_channels=None, name="conv"):
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.out_channels = out_channels or channels
|
||||
self.use_conv = use_conv
|
||||
self.use_conv_transpose = use_conv_transpose
|
||||
self.name = name
|
||||
|
||||
conv = None
|
||||
if use_conv_transpose:
|
||||
raise NotImplementedError
|
||||
elif use_conv:
|
||||
conv = InflatedConv3d(self.channels, self.out_channels, 3, padding=1)
|
||||
|
||||
if name == "conv":
|
||||
self.conv = conv
|
||||
else:
|
||||
self.Conv2d_0 = conv
|
||||
|
||||
def forward(self, hidden_states, output_size=None):
|
||||
assert hidden_states.shape[1] == self.channels
|
||||
|
||||
if self.use_conv_transpose:
|
||||
raise NotImplementedError
|
||||
|
||||
# Cast to float32 to as 'upsample_nearest2d_out_frame' op does not support bfloat16
|
||||
dtype = hidden_states.dtype
|
||||
if dtype == torch.bfloat16:
|
||||
hidden_states = hidden_states.to(torch.float32)
|
||||
|
||||
# upsample_nearest_nhwc fails with large batch sizes. see https://github.com/huggingface/diffusers/issues/984
|
||||
if hidden_states.shape[0] >= 64:
|
||||
hidden_states = hidden_states.contiguous()
|
||||
|
||||
# if `output_size` is passed we force the interpolation output
|
||||
# size and do not make use of `scale_factor=2`
|
||||
if output_size is None:
|
||||
hidden_states = F.interpolate(hidden_states, scale_factor=[1.0, 2.0, 2.0], mode="nearest")
|
||||
else:
|
||||
hidden_states = F.interpolate(hidden_states, size=output_size, mode="nearest")
|
||||
|
||||
# If the input is bfloat16, we cast back to bfloat16
|
||||
if dtype == torch.bfloat16:
|
||||
hidden_states = hidden_states.to(dtype)
|
||||
|
||||
if self.use_conv:
|
||||
if self.name == "conv":
|
||||
hidden_states = self.conv(hidden_states)
|
||||
else:
|
||||
hidden_states = self.Conv2d_0(hidden_states)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class Downsample3D(nn.Module):
|
||||
def __init__(self, channels, use_conv=False, out_channels=None, padding=1, name="conv"):
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.out_channels = out_channels or channels
|
||||
self.use_conv = use_conv
|
||||
self.padding = padding
|
||||
stride = 2
|
||||
self.name = name
|
||||
|
||||
if use_conv:
|
||||
conv = InflatedConv3d(self.channels, self.out_channels, 3, stride=stride, padding=padding)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if name == "conv":
|
||||
self.Conv2d_0 = conv
|
||||
self.conv = conv
|
||||
elif name == "Conv2d_0":
|
||||
self.conv = conv
|
||||
else:
|
||||
self.conv = conv
|
||||
|
||||
def forward(self, hidden_states):
|
||||
assert hidden_states.shape[1] == self.channels
|
||||
if self.use_conv and self.padding == 0:
|
||||
raise NotImplementedError
|
||||
|
||||
assert hidden_states.shape[1] == self.channels
|
||||
hidden_states = self.conv(hidden_states)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class ResnetBlock3D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
in_channels,
|
||||
out_channels=None,
|
||||
conv_shortcut=False,
|
||||
dropout=0.0,
|
||||
temb_channels=512,
|
||||
groups=32,
|
||||
groups_out=None,
|
||||
pre_norm=True,
|
||||
eps=1e-6,
|
||||
non_linearity="swish",
|
||||
time_embedding_norm="default",
|
||||
output_scale_factor=1.0,
|
||||
use_in_shortcut=None,
|
||||
):
|
||||
super().__init__()
|
||||
self.pre_norm = pre_norm
|
||||
self.pre_norm = True
|
||||
self.in_channels = in_channels
|
||||
out_channels = in_channels if out_channels is None else out_channels
|
||||
self.out_channels = out_channels
|
||||
self.use_conv_shortcut = conv_shortcut
|
||||
self.time_embedding_norm = time_embedding_norm
|
||||
self.output_scale_factor = output_scale_factor
|
||||
|
||||
if groups_out is None:
|
||||
groups_out = groups
|
||||
|
||||
self.norm1 = torch.nn.GroupNorm(num_groups=groups, num_channels=in_channels, eps=eps, affine=True)
|
||||
|
||||
self.conv1 = InflatedConv3d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
||||
|
||||
if temb_channels is not None:
|
||||
if self.time_embedding_norm == "default":
|
||||
time_emb_proj_out_channels = out_channels
|
||||
elif self.time_embedding_norm == "scale_shift":
|
||||
time_emb_proj_out_channels = out_channels * 2
|
||||
else:
|
||||
raise ValueError(f"unknown time_embedding_norm : {self.time_embedding_norm} ")
|
||||
|
||||
self.time_emb_proj = torch.nn.Linear(temb_channels, time_emb_proj_out_channels)
|
||||
else:
|
||||
self.time_emb_proj = None
|
||||
|
||||
self.norm2 = torch.nn.GroupNorm(num_groups=groups_out, num_channels=out_channels, eps=eps, affine=True)
|
||||
self.dropout = torch.nn.Dropout(dropout)
|
||||
self.conv2 = InflatedConv3d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
||||
|
||||
if non_linearity == "swish":
|
||||
self.nonlinearity = lambda x: F.silu(x)
|
||||
elif non_linearity == "mish":
|
||||
self.nonlinearity = Mish()
|
||||
elif non_linearity == "silu":
|
||||
self.nonlinearity = nn.SiLU()
|
||||
|
||||
self.use_in_shortcut = self.in_channels != self.out_channels if use_in_shortcut is None else use_in_shortcut
|
||||
|
||||
self.conv_shortcut = None
|
||||
if self.use_in_shortcut:
|
||||
self.conv_shortcut = InflatedConv3d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
|
||||
|
||||
def forward(self, input_tensor, temb):
|
||||
hidden_states = input_tensor
|
||||
|
||||
hidden_states = self.norm1(hidden_states)
|
||||
hidden_states = self.nonlinearity(hidden_states)
|
||||
|
||||
hidden_states = self.conv1(hidden_states)
|
||||
|
||||
if temb is not None:
|
||||
# temb = self.time_emb_proj(self.nonlinearity(temb))[:, :, None, None, None]
|
||||
temb = self.time_emb_proj(self.nonlinearity(temb))[:, :, :, None, None].permute(0,2,1,3,4)
|
||||
|
||||
if temb is not None and self.time_embedding_norm == "default":
|
||||
hidden_states = hidden_states + temb
|
||||
|
||||
hidden_states = self.norm2(hidden_states)
|
||||
|
||||
if temb is not None and self.time_embedding_norm == "scale_shift":
|
||||
scale, shift = torch.chunk(temb, 2, dim=1)
|
||||
hidden_states = hidden_states * (1 + scale) + shift
|
||||
|
||||
hidden_states = self.nonlinearity(hidden_states)
|
||||
|
||||
hidden_states = self.dropout(hidden_states)
|
||||
hidden_states = self.conv2(hidden_states)
|
||||
|
||||
if self.conv_shortcut is not None:
|
||||
input_tensor = self.conv_shortcut(input_tensor)
|
||||
|
||||
output_tensor = (input_tensor + hidden_states) / self.output_scale_factor
|
||||
|
||||
return output_tensor
|
||||
|
||||
|
||||
class Mish(torch.nn.Module):
|
||||
def forward(self, hidden_states):
|
||||
return hidden_states * torch.tanh(torch.nn.functional.softplus(hidden_states))
|
||||
@@ -0,0 +1,497 @@
|
||||
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/unet_2d_condition.py
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.utils.checkpoint
|
||||
|
||||
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
||||
from diffusers import ModelMixin
|
||||
from diffusers.utils import BaseOutput, logging
|
||||
from diffusers.models.embeddings import TimestepEmbedding, Timesteps
|
||||
from .unet_blocks import (
|
||||
CrossAttnDownBlock3D,
|
||||
CrossAttnUpBlock3D,
|
||||
DownBlock3D,
|
||||
UNetMidBlock3DCrossAttn,
|
||||
UpBlock3D,
|
||||
get_down_block,
|
||||
get_up_block,
|
||||
)
|
||||
from .resnet import InflatedConv3d
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@dataclass
|
||||
class UNet3DConditionOutput(BaseOutput):
|
||||
sample: torch.FloatTensor
|
||||
|
||||
|
||||
class UNet3DConditionModel(ModelMixin, ConfigMixin):
|
||||
_supports_gradient_checkpointing = True
|
||||
|
||||
@register_to_config
|
||||
def __init__(
|
||||
self,
|
||||
sample_size: Optional[int] = None,
|
||||
in_channels: int = 4,
|
||||
out_channels: int = 4,
|
||||
center_input_sample: bool = False,
|
||||
flip_sin_to_cos: bool = True,
|
||||
freq_shift: int = 0,
|
||||
down_block_types: Tuple[str] = (
|
||||
"CrossAttnDownBlock3D",
|
||||
"CrossAttnDownBlock3D",
|
||||
"CrossAttnDownBlock3D",
|
||||
"DownBlock3D",
|
||||
),
|
||||
mid_block_type: str = "UNetMidBlock3DCrossAttn",
|
||||
up_block_types: Tuple[str] = (
|
||||
"UpBlock3D",
|
||||
"CrossAttnUpBlock3D",
|
||||
"CrossAttnUpBlock3D",
|
||||
"CrossAttnUpBlock3D"
|
||||
),
|
||||
only_cross_attention: Union[bool, Tuple[bool]] = False,
|
||||
block_out_channels: Tuple[int] = (320, 640, 1280, 1280),
|
||||
layers_per_block: int = 2,
|
||||
downsample_padding: int = 1,
|
||||
mid_block_scale_factor: float = 1,
|
||||
act_fn: str = "silu",
|
||||
norm_num_groups: int = 32,
|
||||
norm_eps: float = 1e-5,
|
||||
cross_attention_dim: int = 1280,
|
||||
attention_head_dim: Union[int, Tuple[int]] = 8,
|
||||
dual_cross_attention: bool = False,
|
||||
use_linear_projection: bool = False,
|
||||
class_embed_type: Optional[str] = None,
|
||||
num_class_embeds: Optional[int] = None,
|
||||
upcast_attention: bool = False,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
use_attn_temp: bool = False,
|
||||
camera_input_dim: int = 12,
|
||||
camera_hidden_dim: int = 320,
|
||||
camera_output_dim: int = 1280,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.sample_size = sample_size
|
||||
time_embed_dim = block_out_channels[0] * 4
|
||||
|
||||
# input
|
||||
self.conv_in = InflatedConv3d(in_channels, block_out_channels[0], kernel_size=3, padding=(1, 1))
|
||||
|
||||
# time
|
||||
self.time_proj = Timesteps(block_out_channels[0], flip_sin_to_cos, freq_shift)
|
||||
timestep_input_dim = block_out_channels[0]
|
||||
|
||||
self.time_embedding = TimestepEmbedding(timestep_input_dim, time_embed_dim)
|
||||
|
||||
# class embedding
|
||||
if class_embed_type is None and num_class_embeds is not None:
|
||||
self.class_embedding = nn.Embedding(num_class_embeds, time_embed_dim)
|
||||
elif class_embed_type == "timestep":
|
||||
self.class_embedding = TimestepEmbedding(timestep_input_dim, time_embed_dim)
|
||||
elif class_embed_type == "identity":
|
||||
self.class_embedding = nn.Identity(time_embed_dim, time_embed_dim)
|
||||
else:
|
||||
self.class_embedding = None
|
||||
|
||||
# camera metrix
|
||||
# def init_linear(l, stddev):
|
||||
# nn.init.normal_(l.weight, std=stddev)
|
||||
# if l.bias is not None:
|
||||
# nn.init.constant_(l.bias, 0.0)
|
||||
# self.camera_embedding_1 = nn.Linear(camera_input_dim, camera_hidden_dim)
|
||||
# self.camera_embedding_2 = nn.Linear(camera_hidden_dim, camera_output_dim)
|
||||
# init_linear(self.camera_embedding_1, 0.25)
|
||||
# init_linear(self.camera_embedding_2, 0.25)
|
||||
|
||||
self.camera_embedding = nn.Sequential(
|
||||
nn.Linear(camera_input_dim, time_embed_dim),
|
||||
nn.SiLU(),
|
||||
nn.Linear(time_embed_dim, time_embed_dim),
|
||||
)
|
||||
|
||||
self.down_blocks = nn.ModuleList([])
|
||||
self.mid_block = None
|
||||
self.up_blocks = nn.ModuleList([])
|
||||
|
||||
if isinstance(only_cross_attention, bool):
|
||||
only_cross_attention = [only_cross_attention] * len(down_block_types)
|
||||
|
||||
if isinstance(attention_head_dim, int):
|
||||
attention_head_dim = (attention_head_dim,) * len(down_block_types)
|
||||
|
||||
# down
|
||||
output_channel = block_out_channels[0]
|
||||
for i, down_block_type in enumerate(down_block_types):
|
||||
input_channel = output_channel
|
||||
output_channel = block_out_channels[i]
|
||||
is_final_block = i == len(block_out_channels) - 1
|
||||
|
||||
down_block = get_down_block(
|
||||
down_block_type,
|
||||
num_layers=layers_per_block,
|
||||
in_channels=input_channel,
|
||||
out_channels=output_channel,
|
||||
temb_channels=time_embed_dim,
|
||||
add_downsample=not is_final_block,
|
||||
resnet_eps=norm_eps,
|
||||
resnet_act_fn=act_fn,
|
||||
resnet_groups=norm_num_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attn_num_head_channels=attention_head_dim[i],
|
||||
downsample_padding=downsample_padding,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention[i],
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
use_attn_temp=use_attn_temp
|
||||
)
|
||||
self.down_blocks.append(down_block)
|
||||
|
||||
# mid
|
||||
if mid_block_type == "UNetMidBlock3DCrossAttn":
|
||||
self.mid_block = UNetMidBlock3DCrossAttn(
|
||||
in_channels=block_out_channels[-1],
|
||||
temb_channels=time_embed_dim,
|
||||
resnet_eps=norm_eps,
|
||||
resnet_act_fn=act_fn,
|
||||
output_scale_factor=mid_block_scale_factor,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attn_num_head_channels=attention_head_dim[-1],
|
||||
resnet_groups=norm_num_groups,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"unknown mid_block_type : {mid_block_type}")
|
||||
|
||||
# count how many layers upsample the videos
|
||||
self.num_upsamplers = 0
|
||||
|
||||
# up
|
||||
reversed_block_out_channels = list(reversed(block_out_channels))
|
||||
reversed_attention_head_dim = list(reversed(attention_head_dim))
|
||||
only_cross_attention = list(reversed(only_cross_attention))
|
||||
output_channel = reversed_block_out_channels[0]
|
||||
for i, up_block_type in enumerate(up_block_types):
|
||||
is_final_block = i == len(block_out_channels) - 1
|
||||
|
||||
prev_output_channel = output_channel
|
||||
output_channel = reversed_block_out_channels[i]
|
||||
input_channel = reversed_block_out_channels[min(i + 1, len(block_out_channels) - 1)]
|
||||
|
||||
# add upsample block for all BUT final layer
|
||||
if not is_final_block:
|
||||
add_upsample = True
|
||||
self.num_upsamplers += 1
|
||||
else:
|
||||
add_upsample = False
|
||||
|
||||
up_block = get_up_block(
|
||||
up_block_type,
|
||||
num_layers=layers_per_block + 1,
|
||||
in_channels=input_channel,
|
||||
out_channels=output_channel,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=time_embed_dim,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=norm_eps,
|
||||
resnet_act_fn=act_fn,
|
||||
resnet_groups=norm_num_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attn_num_head_channels=reversed_attention_head_dim[i],
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention[i],
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
use_attn_temp=use_attn_temp,
|
||||
)
|
||||
self.up_blocks.append(up_block)
|
||||
prev_output_channel = output_channel
|
||||
|
||||
# out
|
||||
self.conv_norm_out = nn.GroupNorm(num_channels=block_out_channels[0], num_groups=norm_num_groups, eps=norm_eps)
|
||||
self.conv_act = nn.SiLU()
|
||||
self.conv_out = InflatedConv3d(block_out_channels[0], out_channels, kernel_size=3, padding=1)
|
||||
|
||||
def set_attention_slice(self, slice_size):
|
||||
r"""
|
||||
Enable sliced attention computation.
|
||||
|
||||
When this option is enabled, the attention module will split the input tensor in slices, to compute attention
|
||||
in several steps. This is useful to save some memory in exchange for a small speed decrease.
|
||||
|
||||
Args:
|
||||
slice_size (`str` or `int` or `list(int)`, *optional*, defaults to `"auto"`):
|
||||
When `"auto"`, halves the input to the attention heads, so attention will be computed in two steps. If
|
||||
`"max"`, maxium amount of memory will be saved by running only one slice at a time. If a number is
|
||||
provided, uses as many slices as `attention_head_dim // slice_size`. In this case, `attention_head_dim`
|
||||
must be a multiple of `slice_size`.
|
||||
"""
|
||||
sliceable_head_dims = []
|
||||
|
||||
def fn_recursive_retrieve_slicable_dims(module: torch.nn.Module):
|
||||
if hasattr(module, "set_attention_slice"):
|
||||
sliceable_head_dims.append(module.sliceable_head_dim)
|
||||
|
||||
for child in module.children():
|
||||
fn_recursive_retrieve_slicable_dims(child)
|
||||
|
||||
# retrieve number of attention layers
|
||||
for module in self.children():
|
||||
fn_recursive_retrieve_slicable_dims(module)
|
||||
|
||||
num_slicable_layers = len(sliceable_head_dims)
|
||||
|
||||
if slice_size == "auto":
|
||||
# half the attention head size is usually a good trade-off between
|
||||
# speed and memory
|
||||
slice_size = [dim // 2 for dim in sliceable_head_dims]
|
||||
elif slice_size == "max":
|
||||
# make smallest slice possible
|
||||
slice_size = num_slicable_layers * [1]
|
||||
|
||||
slice_size = num_slicable_layers * [slice_size] if not isinstance(slice_size, list) else slice_size
|
||||
|
||||
if len(slice_size) != len(sliceable_head_dims):
|
||||
raise ValueError(
|
||||
f"You have provided {len(slice_size)}, but {self.config} has {len(sliceable_head_dims)} different"
|
||||
f" attention layers. Make sure to match `len(slice_size)` to be {len(sliceable_head_dims)}."
|
||||
)
|
||||
|
||||
for i in range(len(slice_size)):
|
||||
size = slice_size[i]
|
||||
dim = sliceable_head_dims[i]
|
||||
if size is not None and size > dim:
|
||||
raise ValueError(f"size {size} has to be smaller or equal to {dim}.")
|
||||
|
||||
# Recursively walk through all the children.
|
||||
# Any children which exposes the set_attention_slice method
|
||||
# gets the message
|
||||
def fn_recursive_set_attention_slice(module: torch.nn.Module, slice_size: List[int]):
|
||||
if hasattr(module, "set_attention_slice"):
|
||||
module.set_attention_slice(slice_size.pop())
|
||||
|
||||
for child in module.children():
|
||||
fn_recursive_set_attention_slice(child, slice_size)
|
||||
|
||||
reversed_slice_size = list(reversed(slice_size))
|
||||
for module in self.children():
|
||||
fn_recursive_set_attention_slice(module, reversed_slice_size)
|
||||
|
||||
def _set_gradient_checkpointing(self, module, value=False):
|
||||
if isinstance(module, (CrossAttnDownBlock3D, DownBlock3D, CrossAttnUpBlock3D, UpBlock3D)):
|
||||
module.gradient_checkpointing = value
|
||||
|
||||
def forward(
|
||||
self,
|
||||
sample: torch.FloatTensor,
|
||||
timestep: Union[torch.Tensor, float, int],
|
||||
encoder_hidden_states: torch.Tensor,
|
||||
camera_matrixs: Optional[torch.Tensor] = None,
|
||||
class_labels: Optional[torch.Tensor] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
return_dict: bool = True,
|
||||
) -> Union[UNet3DConditionOutput, Tuple]:
|
||||
r"""
|
||||
Args:
|
||||
sample (`torch.FloatTensor`): (batch, channel, height, width) noisy inputs tensor
|
||||
timestep (`torch.FloatTensor` or `float` or `int`): (batch) timesteps
|
||||
encoder_hidden_states (`torch.FloatTensor`): (batch, sequence_length, feature_dim) encoder hidden states
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain tuple.
|
||||
|
||||
Returns:
|
||||
[`~models.unet_2d_condition.UNet2DConditionOutput`] or `tuple`:
|
||||
[`~models.unet_2d_condition.UNet2DConditionOutput`] if `return_dict` is True, otherwise a `tuple`. When
|
||||
returning a tuple, the first element is the sample tensor.
|
||||
"""
|
||||
# By default samples have to be AT least a multiple of the overall upsampling factor.
|
||||
# The overall upsampling factor is equal to 2 ** (# num of upsampling layears).
|
||||
# However, the upsampling interpolation output size can be forced to fit any upsampling size
|
||||
# on the fly if necessary.
|
||||
default_overall_up_factor = 2**self.num_upsamplers
|
||||
|
||||
# upsample size should be forwarded when sample is not a multiple of `default_overall_up_factor`
|
||||
forward_upsample_size = False
|
||||
upsample_size = None
|
||||
|
||||
if any(s % default_overall_up_factor != 0 for s in sample.shape[-2:]):
|
||||
logger.info("Forward upsample size to force interpolation output size.")
|
||||
forward_upsample_size = True
|
||||
|
||||
# prepare attention_mask
|
||||
if attention_mask is not None:
|
||||
attention_mask = (1 - attention_mask.to(sample.dtype)) * -10000.0
|
||||
attention_mask = attention_mask.unsqueeze(1)
|
||||
|
||||
# center input if necessary
|
||||
if self.config.center_input_sample:
|
||||
sample = 2 * sample - 1.0
|
||||
# time
|
||||
timesteps = timestep
|
||||
if not torch.is_tensor(timesteps):
|
||||
# This would be a good case for the `match` statement (Python 3.10+)
|
||||
is_mps = sample.device.type == "mps"
|
||||
if isinstance(timestep, float):
|
||||
dtype = torch.float32 if is_mps else torch.float64
|
||||
else:
|
||||
dtype = torch.int32 if is_mps else torch.int64
|
||||
timesteps = torch.tensor([timesteps], dtype=dtype, device=sample.device)
|
||||
elif len(timesteps.shape) == 0:
|
||||
timesteps = timesteps[None].to(sample.device)
|
||||
|
||||
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML
|
||||
timesteps = timesteps.expand(sample.shape[0])
|
||||
|
||||
t_emb = self.time_proj(timesteps)
|
||||
|
||||
# timesteps does not contain any weights and will always return f32 tensors
|
||||
# but time_embedding might actually be running in fp16. so we need to cast here.
|
||||
# there might be better ways to encapsulate this.
|
||||
t_emb = t_emb.to(dtype=self.dtype)
|
||||
emb = self.time_embedding(t_emb) #torch.Size([32, 1280])
|
||||
emb = torch.unsqueeze(emb, 1)
|
||||
if camera_matrixs is not None:
|
||||
# came emb
|
||||
cam_emb = self.camera_embedding(camera_matrixs)
|
||||
# cam_emb = self.camera_embedding_2(cam_emb)
|
||||
emb = emb.repeat(1,cam_emb.shape[1],1) #torch.Size([32, 4, 1280])
|
||||
emb = emb + cam_emb
|
||||
|
||||
# import pdb;pdb.set_trace()
|
||||
if self.class_embedding is not None:
|
||||
# if class_labels is None:
|
||||
# raise ValueError("class_labels should be provided when num_class_embeds > 0")
|
||||
if class_labels is not None:
|
||||
|
||||
if self.config.class_embed_type == "timestep":
|
||||
class_labels = self.time_proj(class_labels)
|
||||
|
||||
class_emb = self.class_embedding(class_labels)
|
||||
emb = emb + class_emb
|
||||
|
||||
# pre-process
|
||||
sample = self.conv_in(sample)
|
||||
|
||||
# down
|
||||
down_block_res_samples = (sample,)
|
||||
for downsample_block in self.down_blocks:
|
||||
if hasattr(downsample_block, "has_cross_attention") and downsample_block.has_cross_attention:
|
||||
sample, res_samples = downsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
attention_mask=attention_mask,
|
||||
)
|
||||
else:
|
||||
sample, res_samples = downsample_block(hidden_states=sample, temb=emb)
|
||||
|
||||
down_block_res_samples += res_samples
|
||||
|
||||
# mid
|
||||
sample = self.mid_block(
|
||||
sample, emb, encoder_hidden_states=encoder_hidden_states, attention_mask=attention_mask
|
||||
)
|
||||
|
||||
# up
|
||||
for i, upsample_block in enumerate(self.up_blocks):
|
||||
is_final_block = i == len(self.up_blocks) - 1
|
||||
|
||||
res_samples = down_block_res_samples[-len(upsample_block.resnets) :]
|
||||
down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)]
|
||||
|
||||
# if we have not reached the final block and need to forward the
|
||||
# upsample size, we do it here
|
||||
if not is_final_block and forward_upsample_size:
|
||||
upsample_size = down_block_res_samples[-1].shape[2:]
|
||||
|
||||
if hasattr(upsample_block, "has_cross_attention") and upsample_block.has_cross_attention:
|
||||
sample = upsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
res_hidden_states_tuple=res_samples,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
upsample_size=upsample_size,
|
||||
attention_mask=attention_mask,
|
||||
)
|
||||
else:
|
||||
sample = upsample_block(
|
||||
hidden_states=sample, temb=emb, res_hidden_states_tuple=res_samples, upsample_size=upsample_size
|
||||
)
|
||||
# post-process
|
||||
sample = self.conv_norm_out(sample)
|
||||
sample = self.conv_act(sample)
|
||||
sample = self.conv_out(sample)
|
||||
|
||||
if not return_dict:
|
||||
return (sample,)
|
||||
|
||||
return UNet3DConditionOutput(sample=sample)
|
||||
|
||||
@classmethod
|
||||
def from_pretrained_2d(cls, pretrained_model_path, subfolder=None):
|
||||
if subfolder is not None:
|
||||
pretrained_model_path = os.path.join(pretrained_model_path, subfolder)
|
||||
|
||||
config_file = os.path.join(pretrained_model_path, 'config.json')
|
||||
if not os.path.isfile(config_file):
|
||||
raise RuntimeError(f"{config_file} does not exist")
|
||||
with open(config_file, "r") as f:
|
||||
config = json.load(f)
|
||||
config["_class_name"] = cls.__name__
|
||||
config["down_block_types"] = [
|
||||
"CrossAttnDownBlock3D",
|
||||
"CrossAttnDownBlock3D",
|
||||
"CrossAttnDownBlock3D",
|
||||
"DownBlock3D"
|
||||
]
|
||||
config["up_block_types"] = [
|
||||
"UpBlock3D",
|
||||
"CrossAttnUpBlock3D",
|
||||
"CrossAttnUpBlock3D",
|
||||
"CrossAttnUpBlock3D"
|
||||
]
|
||||
|
||||
from diffusers.utils import WEIGHTS_NAME, SAFETENSORS_WEIGHTS_NAME
|
||||
# model = cls.from_config(config)
|
||||
# model_file = os.path.join(pretrained_model_path, WEIGHTS_NAME)
|
||||
# if not os.path.isfile(model_file):
|
||||
# raise RuntimeError(f"{model_file} does not exist")
|
||||
# state_dict = torch.load(model_file, map_location="cpu")
|
||||
|
||||
import safetensors
|
||||
model = cls.from_config(config)
|
||||
model_file = os.path.join(pretrained_model_path, WEIGHTS_NAME)
|
||||
if not os.path.isfile(model_file):
|
||||
model_file = os.path.join(pretrained_model_path, SAFETENSORS_WEIGHTS_NAME)
|
||||
if not os.path.isfile(model_file):
|
||||
raise RuntimeError(f"{model_file} does not exist")
|
||||
else:
|
||||
state_dict = safetensors.torch.load_file(model_file, device="cpu")
|
||||
else:
|
||||
state_dict = torch.load(model_file, map_location="cpu")
|
||||
|
||||
for k, v in model.state_dict().items():
|
||||
if '_temp.' in k or 'camera_embedding' in k or 'class_embedding' in k:
|
||||
state_dict.update({k: v})
|
||||
for k in list(state_dict.keys()):
|
||||
if 'camera_embedding_' in k:
|
||||
v = state_dict.pop(k)
|
||||
model.load_state_dict(state_dict)
|
||||
|
||||
return model
|
||||
@@ -0,0 +1,596 @@
|
||||
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/unet_2d_blocks.py
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
# from .attention import Transformer3DModel
|
||||
from .resnet import Downsample3D, ResnetBlock3D, Upsample3D
|
||||
|
||||
|
||||
def get_down_block(
|
||||
down_block_type,
|
||||
num_layers,
|
||||
in_channels,
|
||||
out_channels,
|
||||
temb_channels,
|
||||
add_downsample,
|
||||
resnet_eps,
|
||||
resnet_act_fn,
|
||||
attn_num_head_channels,
|
||||
resnet_groups=None,
|
||||
cross_attention_dim=None,
|
||||
downsample_padding=None,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
resnet_time_scale_shift="default",
|
||||
use_attn_temp=False,
|
||||
):
|
||||
down_block_type = down_block_type[7:] if down_block_type.startswith("UNetRes") else down_block_type
|
||||
if down_block_type == "DownBlock3D":
|
||||
return DownBlock3D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "CrossAttnDownBlock3D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnDownBlock3D")
|
||||
return CrossAttnDownBlock3D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attn_num_head_channels=attn_num_head_channels,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
use_attn_temp=use_attn_temp,
|
||||
)
|
||||
raise ValueError(f"{down_block_type} does not exist.")
|
||||
|
||||
|
||||
def get_up_block(
|
||||
up_block_type,
|
||||
num_layers,
|
||||
in_channels,
|
||||
out_channels,
|
||||
prev_output_channel,
|
||||
temb_channels,
|
||||
add_upsample,
|
||||
resnet_eps,
|
||||
resnet_act_fn,
|
||||
attn_num_head_channels,
|
||||
resnet_groups=None,
|
||||
cross_attention_dim=None,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
resnet_time_scale_shift="default",
|
||||
use_attn_temp=False,
|
||||
):
|
||||
up_block_type = up_block_type[7:] if up_block_type.startswith("UNetRes") else up_block_type
|
||||
if up_block_type == "UpBlock3D":
|
||||
return UpBlock3D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif up_block_type == "CrossAttnUpBlock3D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnUpBlock3D")
|
||||
return CrossAttnUpBlock3D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attn_num_head_channels=attn_num_head_channels,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
use_attn_temp=use_attn_temp,
|
||||
)
|
||||
raise ValueError(f"{up_block_type} does not exist.")
|
||||
|
||||
|
||||
class UNetMidBlock3DCrossAttn(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
attn_num_head_channels=1,
|
||||
output_scale_factor=1.0,
|
||||
cross_attention_dim=1280,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
upcast_attention=False,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.attn_num_head_channels = attn_num_head_channels
|
||||
resnet_groups = resnet_groups if resnet_groups is not None else min(in_channels // 4, 32)
|
||||
|
||||
# there is always at least one resnet
|
||||
resnets = [
|
||||
ResnetBlock3D(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
]
|
||||
attentions = []
|
||||
|
||||
for _ in range(num_layers):
|
||||
if dual_cross_attention:
|
||||
raise NotImplementedError
|
||||
attentions.append(
|
||||
Transformer3DModel(
|
||||
attn_num_head_channels,
|
||||
in_channels // attn_num_head_channels,
|
||||
in_channels=in_channels,
|
||||
num_layers=1,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
)
|
||||
resnets.append(
|
||||
ResnetBlock3D(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
def forward(self, hidden_states, temb=None, encoder_hidden_states=None, attention_mask=None):
|
||||
hidden_states = self.resnets[0](hidden_states, temb)
|
||||
for attn, resnet in zip(self.attentions, self.resnets[1:]):
|
||||
hidden_states = attn(hidden_states, encoder_hidden_states=encoder_hidden_states).sample
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class CrossAttnDownBlock3D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
attn_num_head_channels=1,
|
||||
cross_attention_dim=1280,
|
||||
output_scale_factor=1.0,
|
||||
downsample_padding=1,
|
||||
add_downsample=True,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
use_attn_temp=False,
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
attentions = []
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.attn_num_head_channels = attn_num_head_channels
|
||||
|
||||
for i in range(num_layers):
|
||||
in_channels = in_channels if i == 0 else out_channels
|
||||
resnets.append(
|
||||
ResnetBlock3D(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
if dual_cross_attention:
|
||||
raise NotImplementedError
|
||||
attentions.append(
|
||||
Transformer3DModel(
|
||||
attn_num_head_channels,
|
||||
out_channels // attn_num_head_channels,
|
||||
in_channels=out_channels,
|
||||
num_layers=1,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
use_attn_temp=use_attn_temp,
|
||||
)
|
||||
)
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_downsample:
|
||||
self.downsamplers = nn.ModuleList(
|
||||
[
|
||||
Downsample3D(
|
||||
out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op"
|
||||
)
|
||||
]
|
||||
)
|
||||
else:
|
||||
self.downsamplers = None
|
||||
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(self, hidden_states, temb=None, encoder_hidden_states=None, attention_mask=None):
|
||||
output_states = ()
|
||||
|
||||
for resnet, attn in zip(self.resnets, self.attentions):
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module, return_dict=None):
|
||||
def custom_forward(*inputs):
|
||||
if return_dict is not None:
|
||||
return module(*inputs, return_dict=return_dict)
|
||||
else:
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(create_custom_forward(resnet), hidden_states, temb)
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(attn, return_dict=False),
|
||||
hidden_states,
|
||||
encoder_hidden_states,
|
||||
)[0]
|
||||
else:
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
hidden_states = attn(hidden_states, encoder_hidden_states=encoder_hidden_states).sample
|
||||
|
||||
output_states += (hidden_states,)
|
||||
|
||||
if self.downsamplers is not None:
|
||||
for downsampler in self.downsamplers:
|
||||
hidden_states = downsampler(hidden_states)
|
||||
|
||||
output_states += (hidden_states,)
|
||||
|
||||
return hidden_states, output_states
|
||||
|
||||
|
||||
class DownBlock3D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
output_scale_factor=1.0,
|
||||
add_downsample=True,
|
||||
downsample_padding=1,
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
|
||||
for i in range(num_layers):
|
||||
in_channels = in_channels if i == 0 else out_channels
|
||||
resnets.append(
|
||||
ResnetBlock3D(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_downsample:
|
||||
self.downsamplers = nn.ModuleList(
|
||||
[
|
||||
Downsample3D(
|
||||
out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op"
|
||||
)
|
||||
]
|
||||
)
|
||||
else:
|
||||
self.downsamplers = None
|
||||
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(self, hidden_states, temb=None):
|
||||
output_states = ()
|
||||
|
||||
for resnet in self.resnets:
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module):
|
||||
def custom_forward(*inputs):
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(create_custom_forward(resnet), hidden_states, temb)
|
||||
else:
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
|
||||
output_states += (hidden_states,)
|
||||
|
||||
if self.downsamplers is not None:
|
||||
for downsampler in self.downsamplers:
|
||||
hidden_states = downsampler(hidden_states)
|
||||
|
||||
output_states += (hidden_states,)
|
||||
|
||||
return hidden_states, output_states
|
||||
|
||||
|
||||
class CrossAttnUpBlock3D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
prev_output_channel: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
attn_num_head_channels=1,
|
||||
cross_attention_dim=1280,
|
||||
output_scale_factor=1.0,
|
||||
add_upsample=True,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
use_attn_temp=False,
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
attentions = []
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.attn_num_head_channels = attn_num_head_channels
|
||||
|
||||
for i in range(num_layers):
|
||||
res_skip_channels = in_channels if (i == num_layers - 1) else out_channels
|
||||
resnet_in_channels = prev_output_channel if i == 0 else out_channels
|
||||
|
||||
resnets.append(
|
||||
ResnetBlock3D(
|
||||
in_channels=resnet_in_channels + res_skip_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
if dual_cross_attention:
|
||||
raise NotImplementedError
|
||||
attentions.append(
|
||||
Transformer3DModel(
|
||||
attn_num_head_channels,
|
||||
out_channels // attn_num_head_channels,
|
||||
in_channels=out_channels,
|
||||
num_layers=1,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
use_attn_temp=use_attn_temp,
|
||||
)
|
||||
)
|
||||
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_upsample:
|
||||
self.upsamplers = nn.ModuleList([Upsample3D(out_channels, use_conv=True, out_channels=out_channels)])
|
||||
else:
|
||||
self.upsamplers = None
|
||||
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states,
|
||||
res_hidden_states_tuple,
|
||||
temb=None,
|
||||
encoder_hidden_states=None,
|
||||
upsample_size=None,
|
||||
attention_mask=None,
|
||||
):
|
||||
for resnet, attn in zip(self.resnets, self.attentions):
|
||||
# pop res hidden states
|
||||
res_hidden_states = res_hidden_states_tuple[-1]
|
||||
res_hidden_states_tuple = res_hidden_states_tuple[:-1]
|
||||
hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1)
|
||||
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module, return_dict=None):
|
||||
def custom_forward(*inputs):
|
||||
if return_dict is not None:
|
||||
return module(*inputs, return_dict=return_dict)
|
||||
else:
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(create_custom_forward(resnet), hidden_states, temb)
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(attn, return_dict=False),
|
||||
hidden_states,
|
||||
encoder_hidden_states,
|
||||
)[0]
|
||||
else:
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
hidden_states = attn(hidden_states, encoder_hidden_states=encoder_hidden_states).sample
|
||||
|
||||
if self.upsamplers is not None:
|
||||
for upsampler in self.upsamplers:
|
||||
hidden_states = upsampler(hidden_states, upsample_size)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class UpBlock3D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
prev_output_channel: int,
|
||||
out_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
output_scale_factor=1.0,
|
||||
add_upsample=True,
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
|
||||
for i in range(num_layers):
|
||||
res_skip_channels = in_channels if (i == num_layers - 1) else out_channels
|
||||
resnet_in_channels = prev_output_channel if i == 0 else out_channels
|
||||
|
||||
resnets.append(
|
||||
ResnetBlock3D(
|
||||
in_channels=resnet_in_channels + res_skip_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_upsample:
|
||||
self.upsamplers = nn.ModuleList([Upsample3D(out_channels, use_conv=True, out_channels=out_channels)])
|
||||
else:
|
||||
self.upsamplers = None
|
||||
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(self, hidden_states, res_hidden_states_tuple, temb=None, upsample_size=None):
|
||||
for resnet in self.resnets:
|
||||
# pop res hidden states
|
||||
res_hidden_states = res_hidden_states_tuple[-1]
|
||||
res_hidden_states_tuple = res_hidden_states_tuple[:-1]
|
||||
hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1)
|
||||
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module):
|
||||
def custom_forward(*inputs):
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(create_custom_forward(resnet), hidden_states, temb)
|
||||
else:
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
|
||||
if self.upsamplers is not None:
|
||||
for upsampler in self.upsamplers:
|
||||
hidden_states = upsampler(hidden_states, upsample_size)
|
||||
|
||||
return hidden_states
|
||||
@@ -0,0 +1,926 @@
|
||||
# Copyright 2023 The HuggingFace Team. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
|
||||
from diffusers.utils import is_torch_version, logging
|
||||
# from diffusers.models.attention import AdaGroupNorm
|
||||
from diffusers.models.attention_processor import Attention, AttnAddedKVProcessor, AttnAddedKVProcessor2_0
|
||||
from diffusers.models.transformers.dual_transformer_2d import DualTransformer2DModel
|
||||
from diffusers.models.resnet import Downsample2D, FirDownsample2D, FirUpsample2D, KDownsample2D, KUpsample2D, ResnetBlock2D, Upsample2D
|
||||
from .transformer_mv2d import TransformerMV2DModel
|
||||
|
||||
from diffusers.models.unets.unet_2d_blocks import DownBlock2D, ResnetDownsampleBlock2D, AttnDownBlock2D, CrossAttnDownBlock2D, SimpleCrossAttnDownBlock2D, SkipDownBlock2D, AttnSkipDownBlock2D, DownEncoderBlock2D, AttnDownEncoderBlock2D, KDownBlock2D, KCrossAttnDownBlock2D
|
||||
from diffusers.models.unets.unet_2d_blocks import UpBlock2D, ResnetUpsampleBlock2D, CrossAttnUpBlock2D, SimpleCrossAttnUpBlock2D, AttnUpBlock2D, SkipUpBlock2D, AttnSkipUpBlock2D, UpDecoderBlock2D, AttnUpDecoderBlock2D, KUpBlock2D, KCrossAttnUpBlock2D
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def get_down_block(
|
||||
down_block_type,
|
||||
num_layers,
|
||||
in_channels,
|
||||
out_channels,
|
||||
temb_channels,
|
||||
add_downsample,
|
||||
resnet_eps,
|
||||
resnet_act_fn,
|
||||
transformer_layers_per_block=1,
|
||||
num_attention_heads=None,
|
||||
resnet_groups=None,
|
||||
cross_attention_dim=None,
|
||||
downsample_padding=None,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
resnet_time_scale_shift="default",
|
||||
resnet_skip_time_act=False,
|
||||
resnet_out_scale_factor=1.0,
|
||||
cross_attention_norm=None,
|
||||
attention_head_dim=None,
|
||||
downsample_type=None,
|
||||
num_views=1,
|
||||
joint_attention: bool = False,
|
||||
joint_attention_twice: bool = False,
|
||||
multiview_attention: bool = True,
|
||||
cross_domain_attention: bool=False
|
||||
):
|
||||
# If attn head dim is not defined, we default it to the number of heads
|
||||
if attention_head_dim is None:
|
||||
logger.warn(
|
||||
f"It is recommended to provide `attention_head_dim` when calling `get_down_block`. Defaulting `attention_head_dim` to {num_attention_heads}."
|
||||
)
|
||||
attention_head_dim = num_attention_heads
|
||||
|
||||
down_block_type = down_block_type[7:] if down_block_type.startswith("UNetRes") else down_block_type
|
||||
if down_block_type == "DownBlock2D":
|
||||
return DownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "ResnetDownsampleBlock2D":
|
||||
return ResnetDownsampleBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
skip_time_act=resnet_skip_time_act,
|
||||
output_scale_factor=resnet_out_scale_factor,
|
||||
)
|
||||
elif down_block_type == "AttnDownBlock2D":
|
||||
if add_downsample is False:
|
||||
downsample_type = None
|
||||
else:
|
||||
downsample_type = downsample_type or "conv" # default to 'conv'
|
||||
return AttnDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
downsample_type=downsample_type,
|
||||
)
|
||||
elif down_block_type == "CrossAttnDownBlock2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnDownBlock2D")
|
||||
return CrossAttnDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
transformer_layers_per_block=transformer_layers_per_block,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
# custom MV2D attention block
|
||||
elif down_block_type == "CrossAttnDownBlockMV2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnDownBlockMV2D")
|
||||
return CrossAttnDownBlockMV2D(
|
||||
num_layers=num_layers,
|
||||
transformer_layers_per_block=transformer_layers_per_block,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
num_views=num_views,
|
||||
joint_attention=joint_attention,
|
||||
joint_attention_twice=joint_attention_twice,
|
||||
multiview_attention=multiview_attention,
|
||||
cross_domain_attention=cross_domain_attention
|
||||
)
|
||||
elif down_block_type == "SimpleCrossAttnDownBlock2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for SimpleCrossAttnDownBlock2D")
|
||||
return SimpleCrossAttnDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
skip_time_act=resnet_skip_time_act,
|
||||
output_scale_factor=resnet_out_scale_factor,
|
||||
only_cross_attention=only_cross_attention,
|
||||
cross_attention_norm=cross_attention_norm,
|
||||
)
|
||||
elif down_block_type == "SkipDownBlock2D":
|
||||
return SkipDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
downsample_padding=downsample_padding,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "AttnSkipDownBlock2D":
|
||||
return AttnSkipDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "DownEncoderBlock2D":
|
||||
return DownEncoderBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "AttnDownEncoderBlock2D":
|
||||
return AttnDownEncoderBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
downsample_padding=downsample_padding,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif down_block_type == "KDownBlock2D":
|
||||
return KDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
)
|
||||
elif down_block_type == "KCrossAttnDownBlock2D":
|
||||
return KCrossAttnDownBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_downsample=add_downsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attention_head_dim=attention_head_dim,
|
||||
add_self_attention=True if not add_downsample else False,
|
||||
)
|
||||
raise ValueError(f"{down_block_type} does not exist.")
|
||||
|
||||
|
||||
def get_up_block(
|
||||
up_block_type,
|
||||
num_layers,
|
||||
in_channels,
|
||||
out_channels,
|
||||
prev_output_channel,
|
||||
temb_channels,
|
||||
add_upsample,
|
||||
resnet_eps,
|
||||
resnet_act_fn,
|
||||
transformer_layers_per_block=1,
|
||||
num_attention_heads=None,
|
||||
resnet_groups=None,
|
||||
cross_attention_dim=None,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
resnet_time_scale_shift="default",
|
||||
resnet_skip_time_act=False,
|
||||
resnet_out_scale_factor=1.0,
|
||||
cross_attention_norm=None,
|
||||
attention_head_dim=None,
|
||||
upsample_type=None,
|
||||
num_views=1,
|
||||
joint_attention: bool = False,
|
||||
joint_attention_twice: bool = False,
|
||||
multiview_attention: bool = True,
|
||||
cross_domain_attention: bool=False
|
||||
):
|
||||
# If attn head dim is not defined, we default it to the number of heads
|
||||
if attention_head_dim is None:
|
||||
logger.warn(
|
||||
f"It is recommended to provide `attention_head_dim` when calling `get_up_block`. Defaulting `attention_head_dim` to {num_attention_heads}."
|
||||
)
|
||||
attention_head_dim = num_attention_heads
|
||||
|
||||
up_block_type = up_block_type[7:] if up_block_type.startswith("UNetRes") else up_block_type
|
||||
if up_block_type == "UpBlock2D":
|
||||
return UpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif up_block_type == "ResnetUpsampleBlock2D":
|
||||
return ResnetUpsampleBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
skip_time_act=resnet_skip_time_act,
|
||||
output_scale_factor=resnet_out_scale_factor,
|
||||
)
|
||||
elif up_block_type == "CrossAttnUpBlock2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnUpBlock2D")
|
||||
return CrossAttnUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
transformer_layers_per_block=transformer_layers_per_block,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
# custom MV2D attention block
|
||||
elif up_block_type == "CrossAttnUpBlockMV2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for CrossAttnUpBlockMV2D")
|
||||
return CrossAttnUpBlockMV2D(
|
||||
num_layers=num_layers,
|
||||
transformer_layers_per_block=transformer_layers_per_block,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dual_cross_attention=dual_cross_attention,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
num_views=num_views,
|
||||
joint_attention=joint_attention,
|
||||
joint_attention_twice=joint_attention_twice,
|
||||
multiview_attention=multiview_attention,
|
||||
cross_domain_attention=cross_domain_attention
|
||||
)
|
||||
elif up_block_type == "SimpleCrossAttnUpBlock2D":
|
||||
if cross_attention_dim is None:
|
||||
raise ValueError("cross_attention_dim must be specified for SimpleCrossAttnUpBlock2D")
|
||||
return SimpleCrossAttnUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
skip_time_act=resnet_skip_time_act,
|
||||
output_scale_factor=resnet_out_scale_factor,
|
||||
only_cross_attention=only_cross_attention,
|
||||
cross_attention_norm=cross_attention_norm,
|
||||
)
|
||||
elif up_block_type == "AttnUpBlock2D":
|
||||
if add_upsample is False:
|
||||
upsample_type = None
|
||||
else:
|
||||
upsample_type = upsample_type or "conv" # default to 'conv'
|
||||
|
||||
return AttnUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
upsample_type=upsample_type,
|
||||
)
|
||||
elif up_block_type == "SkipUpBlock2D":
|
||||
return SkipUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif up_block_type == "AttnSkipUpBlock2D":
|
||||
return AttnSkipUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
)
|
||||
elif up_block_type == "UpDecoderBlock2D":
|
||||
return UpDecoderBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
temb_channels=temb_channels,
|
||||
)
|
||||
elif up_block_type == "AttnUpDecoderBlock2D":
|
||||
return AttnUpDecoderBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
resnet_groups=resnet_groups,
|
||||
attention_head_dim=attention_head_dim,
|
||||
resnet_time_scale_shift=resnet_time_scale_shift,
|
||||
temb_channels=temb_channels,
|
||||
)
|
||||
elif up_block_type == "KUpBlock2D":
|
||||
return KUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
)
|
||||
elif up_block_type == "KCrossAttnUpBlock2D":
|
||||
return KCrossAttnUpBlock2D(
|
||||
num_layers=num_layers,
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=resnet_eps,
|
||||
resnet_act_fn=resnet_act_fn,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
attention_head_dim=attention_head_dim,
|
||||
)
|
||||
|
||||
raise ValueError(f"{up_block_type} does not exist.")
|
||||
|
||||
|
||||
class UNetMidBlockMV2DCrossAttn(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
transformer_layers_per_block: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
num_attention_heads=1,
|
||||
output_scale_factor=1.0,
|
||||
cross_attention_dim=1280,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
upcast_attention=False,
|
||||
num_views: int = 1,
|
||||
joint_attention: bool = False,
|
||||
joint_attention_twice: bool = False,
|
||||
multiview_attention: bool = True,
|
||||
cross_domain_attention: bool=False
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.num_attention_heads = num_attention_heads
|
||||
resnet_groups = resnet_groups if resnet_groups is not None else min(in_channels // 4, 32)
|
||||
|
||||
# there is always at least one resnet
|
||||
resnets = [
|
||||
ResnetBlock2D(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
]
|
||||
attentions = []
|
||||
|
||||
for _ in range(num_layers):
|
||||
if not dual_cross_attention:
|
||||
attentions.append(
|
||||
TransformerMV2DModel(
|
||||
num_attention_heads,
|
||||
in_channels // num_attention_heads,
|
||||
in_channels=in_channels,
|
||||
num_layers=transformer_layers_per_block,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
upcast_attention=upcast_attention,
|
||||
num_views=num_views,
|
||||
joint_attention=joint_attention,
|
||||
joint_attention_twice=joint_attention_twice,
|
||||
multiview_attention=multiview_attention,
|
||||
cross_domain_attention=cross_domain_attention
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
resnets.append(
|
||||
ResnetBlock2D(
|
||||
in_channels=in_channels,
|
||||
out_channels=in_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.FloatTensor,
|
||||
temb: Optional[torch.FloatTensor] = None,
|
||||
encoder_hidden_states: Optional[torch.FloatTensor] = None,
|
||||
attention_mask: Optional[torch.FloatTensor] = None,
|
||||
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
|
||||
encoder_attention_mask: Optional[torch.FloatTensor] = None,
|
||||
) -> torch.FloatTensor:
|
||||
hidden_states = self.resnets[0](hidden_states, temb)
|
||||
for attn, resnet in zip(self.attentions, self.resnets[1:]):
|
||||
hidden_states = attn(
|
||||
hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
attention_mask=attention_mask,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
return_dict=False,
|
||||
)[0]
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class CrossAttnUpBlockMV2D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
prev_output_channel: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
transformer_layers_per_block: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
num_attention_heads=1,
|
||||
cross_attention_dim=1280,
|
||||
output_scale_factor=1.0,
|
||||
add_upsample=True,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
num_views: int = 1,
|
||||
joint_attention: bool = False,
|
||||
joint_attention_twice: bool = False,
|
||||
multiview_attention: bool = True,
|
||||
cross_domain_attention: bool=False
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
attentions = []
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.num_attention_heads = num_attention_heads
|
||||
|
||||
for i in range(num_layers):
|
||||
res_skip_channels = in_channels if (i == num_layers - 1) else out_channels
|
||||
resnet_in_channels = prev_output_channel if i == 0 else out_channels
|
||||
|
||||
resnets.append(
|
||||
ResnetBlock2D(
|
||||
in_channels=resnet_in_channels + res_skip_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
if not dual_cross_attention:
|
||||
attentions.append(
|
||||
TransformerMV2DModel(
|
||||
num_attention_heads,
|
||||
out_channels // num_attention_heads,
|
||||
in_channels=out_channels,
|
||||
num_layers=transformer_layers_per_block,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
num_views=num_views,
|
||||
joint_attention=joint_attention,
|
||||
joint_attention_twice=joint_attention_twice,
|
||||
multiview_attention=multiview_attention,
|
||||
cross_domain_attention=cross_domain_attention
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_upsample:
|
||||
self.upsamplers = nn.ModuleList([Upsample2D(out_channels, use_conv=True, out_channels=out_channels)])
|
||||
else:
|
||||
self.upsamplers = None
|
||||
if num_views == 4:
|
||||
self.gradient_checkpointing = False
|
||||
else:
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.FloatTensor,
|
||||
res_hidden_states_tuple: Tuple[torch.FloatTensor, ...],
|
||||
temb: Optional[torch.FloatTensor] = None,
|
||||
encoder_hidden_states: Optional[torch.FloatTensor] = None,
|
||||
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
|
||||
upsample_size: Optional[int] = None,
|
||||
attention_mask: Optional[torch.FloatTensor] = None,
|
||||
encoder_attention_mask: Optional[torch.FloatTensor] = None,
|
||||
):
|
||||
for resnet, attn in zip(self.resnets, self.attentions):
|
||||
# pop res hidden states
|
||||
res_hidden_states = res_hidden_states_tuple[-1]
|
||||
res_hidden_states_tuple = res_hidden_states_tuple[:-1]
|
||||
hidden_states = torch.cat([hidden_states, res_hidden_states], dim=1)
|
||||
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module, return_dict=None):
|
||||
def custom_forward(*inputs):
|
||||
if return_dict is not None:
|
||||
return module(*inputs, return_dict=return_dict)
|
||||
else:
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {}
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(resnet),
|
||||
hidden_states,
|
||||
temb,
|
||||
**ckpt_kwargs,
|
||||
)
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(attn, return_dict=False),
|
||||
hidden_states,
|
||||
encoder_hidden_states,
|
||||
None, # timestep
|
||||
None, # class_labels
|
||||
cross_attention_kwargs,
|
||||
attention_mask,
|
||||
encoder_attention_mask,
|
||||
**ckpt_kwargs,
|
||||
)[0]
|
||||
# hidden_states = attn(
|
||||
# hidden_states,
|
||||
# encoder_hidden_states=encoder_hidden_states,
|
||||
# cross_attention_kwargs=cross_attention_kwargs,
|
||||
# attention_mask=attention_mask,
|
||||
# encoder_attention_mask=encoder_attention_mask,
|
||||
# return_dict=False,
|
||||
# )[0]
|
||||
else:
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
hidden_states = attn(
|
||||
hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
attention_mask=attention_mask,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
return_dict=False,
|
||||
)[0]
|
||||
|
||||
if self.upsamplers is not None:
|
||||
for upsampler in self.upsamplers:
|
||||
hidden_states = upsampler(hidden_states, upsample_size)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class CrossAttnDownBlockMV2D(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
temb_channels: int,
|
||||
dropout: float = 0.0,
|
||||
num_layers: int = 1,
|
||||
transformer_layers_per_block: int = 1,
|
||||
resnet_eps: float = 1e-6,
|
||||
resnet_time_scale_shift: str = "default",
|
||||
resnet_act_fn: str = "swish",
|
||||
resnet_groups: int = 32,
|
||||
resnet_pre_norm: bool = True,
|
||||
num_attention_heads=1,
|
||||
cross_attention_dim=1280,
|
||||
output_scale_factor=1.0,
|
||||
downsample_padding=1,
|
||||
add_downsample=True,
|
||||
dual_cross_attention=False,
|
||||
use_linear_projection=False,
|
||||
only_cross_attention=False,
|
||||
upcast_attention=False,
|
||||
num_views: int = 1,
|
||||
joint_attention: bool = False,
|
||||
joint_attention_twice: bool = False,
|
||||
multiview_attention: bool = True,
|
||||
cross_domain_attention: bool=False
|
||||
):
|
||||
super().__init__()
|
||||
resnets = []
|
||||
attentions = []
|
||||
|
||||
self.has_cross_attention = True
|
||||
self.num_attention_heads = num_attention_heads
|
||||
|
||||
for i in range(num_layers):
|
||||
in_channels = in_channels if i == 0 else out_channels
|
||||
resnets.append(
|
||||
ResnetBlock2D(
|
||||
in_channels=in_channels,
|
||||
out_channels=out_channels,
|
||||
temb_channels=temb_channels,
|
||||
eps=resnet_eps,
|
||||
groups=resnet_groups,
|
||||
dropout=dropout,
|
||||
time_embedding_norm=resnet_time_scale_shift,
|
||||
non_linearity=resnet_act_fn,
|
||||
output_scale_factor=output_scale_factor,
|
||||
pre_norm=resnet_pre_norm,
|
||||
)
|
||||
)
|
||||
if not dual_cross_attention:
|
||||
attentions.append(
|
||||
TransformerMV2DModel(
|
||||
num_attention_heads,
|
||||
out_channels // num_attention_heads,
|
||||
in_channels=out_channels,
|
||||
num_layers=transformer_layers_per_block,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
norm_num_groups=resnet_groups,
|
||||
use_linear_projection=use_linear_projection,
|
||||
only_cross_attention=only_cross_attention,
|
||||
upcast_attention=upcast_attention,
|
||||
num_views=num_views,
|
||||
joint_attention=joint_attention,
|
||||
joint_attention_twice=joint_attention_twice,
|
||||
multiview_attention=multiview_attention,
|
||||
cross_domain_attention=cross_domain_attention
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
self.attentions = nn.ModuleList(attentions)
|
||||
self.resnets = nn.ModuleList(resnets)
|
||||
|
||||
if add_downsample:
|
||||
self.downsamplers = nn.ModuleList(
|
||||
[
|
||||
Downsample2D(
|
||||
out_channels, use_conv=True, out_channels=out_channels, padding=downsample_padding, name="op"
|
||||
)
|
||||
]
|
||||
)
|
||||
else:
|
||||
self.downsamplers = None
|
||||
if num_views == 4:
|
||||
self.gradient_checkpointing = False
|
||||
else:
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.FloatTensor,
|
||||
temb: Optional[torch.FloatTensor] = None,
|
||||
encoder_hidden_states: Optional[torch.FloatTensor] = None,
|
||||
attention_mask: Optional[torch.FloatTensor] = None,
|
||||
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
|
||||
encoder_attention_mask: Optional[torch.FloatTensor] = None,
|
||||
additional_residuals=None,
|
||||
):
|
||||
output_states = ()
|
||||
|
||||
blocks = list(zip(self.resnets, self.attentions))
|
||||
|
||||
for i, (resnet, attn) in enumerate(blocks):
|
||||
if self.training and self.gradient_checkpointing:
|
||||
|
||||
def create_custom_forward(module, return_dict=None):
|
||||
def custom_forward(*inputs):
|
||||
if return_dict is not None:
|
||||
return module(*inputs, return_dict=return_dict)
|
||||
else:
|
||||
return module(*inputs)
|
||||
|
||||
return custom_forward
|
||||
|
||||
ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {}
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(resnet),
|
||||
hidden_states,
|
||||
temb,
|
||||
**ckpt_kwargs,
|
||||
)
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(attn, return_dict=False),
|
||||
hidden_states,
|
||||
encoder_hidden_states,
|
||||
None, # timestep
|
||||
None, # class_labels
|
||||
cross_attention_kwargs,
|
||||
attention_mask,
|
||||
encoder_attention_mask,
|
||||
**ckpt_kwargs,
|
||||
)[0]
|
||||
else:
|
||||
# import ipdb
|
||||
# ipdb.set_trace()
|
||||
hidden_states = resnet(hidden_states, temb)
|
||||
hidden_states = attn(
|
||||
hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
attention_mask=attention_mask,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
return_dict=False,
|
||||
)[0]
|
||||
|
||||
# apply additional residuals to the output of the last pair of resnet and attention blocks
|
||||
if i == len(blocks) - 1 and additional_residuals is not None:
|
||||
hidden_states = hidden_states + additional_residuals
|
||||
|
||||
output_states = output_states + (hidden_states,)
|
||||
|
||||
if self.downsamplers is not None:
|
||||
for downsampler in self.downsamplers:
|
||||
hidden_states = downsampler(hidden_states)
|
||||
|
||||
output_states = output_states + (hidden_states,)
|
||||
|
||||
return hidden_states, output_states
|
||||
|
||||
@@ -0,0 +1,589 @@
|
||||
# Adapted from https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion.py
|
||||
|
||||
import tqdm
|
||||
|
||||
import inspect
|
||||
from typing import Callable, List, Optional, Union
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from diffusers.utils import is_accelerate_available
|
||||
from packaging import version
|
||||
from transformers import CLIPTextModel, CLIPTokenizer
|
||||
import torchvision.transforms.functional as TF
|
||||
|
||||
from diffusers.configuration_utils import FrozenDict
|
||||
from diffusers.models import AutoencoderKL
|
||||
from diffusers import DiffusionPipeline
|
||||
from diffusers.schedulers import (
|
||||
DDIMScheduler,
|
||||
DPMSolverMultistepScheduler,
|
||||
EulerAncestralDiscreteScheduler,
|
||||
EulerDiscreteScheduler,
|
||||
LMSDiscreteScheduler,
|
||||
PNDMScheduler,
|
||||
)
|
||||
from diffusers.utils import deprecate, logging, BaseOutput
|
||||
|
||||
from einops import rearrange
|
||||
|
||||
from ..models.unet import UNet3DConditionModel
|
||||
from torchvision.transforms import InterpolationMode
|
||||
|
||||
import comfy.utils
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@dataclass
|
||||
class TuneAVideoPipelineOutput(BaseOutput):
|
||||
videos: Union[torch.Tensor, np.ndarray]
|
||||
|
||||
|
||||
class TuneAVideoPipeline(DiffusionPipeline):
|
||||
_optional_components = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vae: AutoencoderKL,
|
||||
text_encoder: CLIPTextModel,
|
||||
tokenizer: CLIPTokenizer,
|
||||
unet: UNet3DConditionModel,
|
||||
|
||||
scheduler: Union[
|
||||
DDIMScheduler,
|
||||
PNDMScheduler,
|
||||
LMSDiscreteScheduler,
|
||||
EulerDiscreteScheduler,
|
||||
EulerAncestralDiscreteScheduler,
|
||||
DPMSolverMultistepScheduler,
|
||||
],
|
||||
ref_unet = None,
|
||||
feature_extractor=None,
|
||||
image_encoder=None
|
||||
):
|
||||
super().__init__()
|
||||
self.ref_unet = ref_unet
|
||||
self.feature_extractor = feature_extractor
|
||||
self.image_encoder = image_encoder
|
||||
|
||||
if hasattr(scheduler.config, "steps_offset") and scheduler.config.steps_offset != 1:
|
||||
deprecation_message = (
|
||||
f"The configuration file of this scheduler: {scheduler} is outdated. `steps_offset`"
|
||||
f" should be set to 1 instead of {scheduler.config.steps_offset}. Please make sure "
|
||||
"to update the config accordingly as leaving `steps_offset` might led to incorrect results"
|
||||
" in future versions. If you have downloaded this checkpoint from the Hugging Face Hub,"
|
||||
" it would be very nice if you could open a Pull request for the `scheduler/scheduler_config.json`"
|
||||
" file"
|
||||
)
|
||||
deprecate("steps_offset!=1", "1.0.0", deprecation_message, standard_warn=False)
|
||||
new_config = dict(scheduler.config)
|
||||
new_config["steps_offset"] = 1
|
||||
scheduler._internal_dict = FrozenDict(new_config)
|
||||
|
||||
if hasattr(scheduler.config, "clip_sample") and scheduler.config.clip_sample is True:
|
||||
deprecation_message = (
|
||||
f"The configuration file of this scheduler: {scheduler} has not set the configuration `clip_sample`."
|
||||
" `clip_sample` should be set to False in the configuration file. Please make sure to update the"
|
||||
" config accordingly as not setting `clip_sample` in the config might lead to incorrect results in"
|
||||
" future versions. If you have downloaded this checkpoint from the Hugging Face Hub, it would be very"
|
||||
" nice if you could open a Pull request for the `scheduler/scheduler_config.json` file"
|
||||
)
|
||||
deprecate("clip_sample not set", "1.0.0", deprecation_message, standard_warn=False)
|
||||
new_config = dict(scheduler.config)
|
||||
new_config["clip_sample"] = False
|
||||
scheduler._internal_dict = FrozenDict(new_config)
|
||||
|
||||
is_unet_version_less_0_9_0 = hasattr(unet.config, "_diffusers_version") and version.parse(
|
||||
version.parse(unet.config._diffusers_version).base_version
|
||||
) < version.parse("0.9.0.dev0")
|
||||
is_unet_sample_size_less_64 = hasattr(unet.config, "sample_size") and unet.config.sample_size < 64
|
||||
if is_unet_version_less_0_9_0 and is_unet_sample_size_less_64:
|
||||
deprecation_message = (
|
||||
"The configuration file of the unet has set the default `sample_size` to smaller than"
|
||||
" 64 which seems highly unlikely. If your checkpoint is a fine-tuned version of any of the"
|
||||
" following: \n- CompVis/stable-diffusion-v1-4 \n- CompVis/stable-diffusion-v1-3 \n-"
|
||||
" CompVis/stable-diffusion-v1-2 \n- CompVis/stable-diffusion-v1-1 \n- runwayml/stable-diffusion-v1-5"
|
||||
" \n- runwayml/stable-diffusion-inpainting \n you should change 'sample_size' to 64 in the"
|
||||
" configuration file. Please make sure to update the config accordingly as leaving `sample_size=32`"
|
||||
" in the config might lead to incorrect results in future versions. If you have downloaded this"
|
||||
" checkpoint from the Hugging Face Hub, it would be very nice if you could open a Pull request for"
|
||||
" the `unet/config.json` file"
|
||||
)
|
||||
deprecate("sample_size<64", "1.0.0", deprecation_message, standard_warn=False)
|
||||
new_config = dict(unet.config)
|
||||
new_config["sample_size"] = 64
|
||||
unet._internal_dict = FrozenDict(new_config)
|
||||
|
||||
self.register_modules(
|
||||
vae=vae,
|
||||
text_encoder=text_encoder,
|
||||
tokenizer=tokenizer,
|
||||
unet=unet,
|
||||
scheduler=scheduler,
|
||||
)
|
||||
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
|
||||
|
||||
def enable_vae_slicing(self):
|
||||
self.vae.enable_slicing()
|
||||
|
||||
def disable_vae_slicing(self):
|
||||
self.vae.disable_slicing()
|
||||
|
||||
def enable_sequential_cpu_offload(self, gpu_id=0):
|
||||
if is_accelerate_available():
|
||||
from accelerate import cpu_offload
|
||||
else:
|
||||
raise ImportError("Please install accelerate via `pip install accelerate`")
|
||||
|
||||
device = torch.device(f"cuda:{gpu_id}")
|
||||
|
||||
for cpu_offloaded_model in [self.unet, self.text_encoder, self.vae]:
|
||||
if cpu_offloaded_model is not None:
|
||||
cpu_offload(cpu_offloaded_model, device)
|
||||
|
||||
|
||||
@property
|
||||
def _execution_device(self):
|
||||
if self.device != torch.device("meta") or not hasattr(self.unet, "_hf_hook"):
|
||||
return self.device
|
||||
for module in self.unet.modules():
|
||||
if (
|
||||
hasattr(module, "_hf_hook")
|
||||
and hasattr(module._hf_hook, "execution_device")
|
||||
and module._hf_hook.execution_device is not None
|
||||
):
|
||||
return torch.device(module._hf_hook.execution_device)
|
||||
return self.device
|
||||
|
||||
def _encode_image(self, image_pil, device, num_images_per_prompt, do_classifier_free_guidance, img_proj=None):
|
||||
dtype = next(self.image_encoder.parameters()).dtype
|
||||
|
||||
# image_pt = self.feature_extractor(images=image_pil, return_tensors="pt").pixel_values
|
||||
# image_pt = image_pt.to(device=device, dtype=dtype)
|
||||
# image_embeddings = self.image_encoder(image_pt).image_embeds
|
||||
# image_embeddings = image_embeddings.unsqueeze(1)
|
||||
|
||||
# # image encoding
|
||||
clip_image_mean = torch.as_tensor(self.feature_extractor.image_mean)[:,None,None].to(device, dtype=torch.float32)
|
||||
clip_image_std = torch.as_tensor(self.feature_extractor.image_std)[:,None,None].to(device, dtype=torch.float32)
|
||||
imgs_in_proc = TF.resize(image_pil, (self.feature_extractor.crop_size['height'], self.feature_extractor.crop_size['width']), interpolation=InterpolationMode.BICUBIC)
|
||||
# do the normalization in float32 to preserve precision
|
||||
imgs_in_proc = ((imgs_in_proc.float() - clip_image_mean) / clip_image_std).to(dtype)
|
||||
if img_proj is None:
|
||||
# (B*Nv, 1, 768)
|
||||
image_embeddings = self.image_encoder(imgs_in_proc).image_embeds.unsqueeze(1)
|
||||
# duplicate image embeddings for each generation per prompt, using mps friendly method
|
||||
# Note: repeat differently from official pipelines
|
||||
# B1B2B3B4 -> B1B2B3B4B1B2B3B4
|
||||
bs_embed, seq_len, _ = image_embeddings.shape
|
||||
image_embeddings = image_embeddings.repeat(num_images_per_prompt, 1, 1)
|
||||
if do_classifier_free_guidance:
|
||||
negative_prompt_embeds = torch.zeros_like(image_embeddings)
|
||||
|
||||
# For classifier free guidance, we need to do two forward passes.
|
||||
# Here we concatenate the unconditional and text embeddings into a single batch
|
||||
# to avoid doing two forward passes
|
||||
image_embeddings = torch.cat([negative_prompt_embeds, image_embeddings])
|
||||
else:
|
||||
if do_classifier_free_guidance:
|
||||
negative_image_proc = torch.zeros_like(imgs_in_proc)
|
||||
|
||||
# For classifier free guidance, we need to do two forward passes.
|
||||
# Here we concatenate the unconditional and text embeddings into a single batch
|
||||
# to avoid doing two forward passes
|
||||
imgs_in_proc = torch.cat([negative_image_proc, imgs_in_proc])
|
||||
|
||||
image_embeds = self.image_encoder(imgs_in_proc, output_hidden_states=True).hidden_states[-2]
|
||||
image_embeddings = img_proj(image_embeds)
|
||||
|
||||
# image_embeddings_unet = rearrange(image_embeddings_unet, 'B Nv d c -> (B Nv) d c')
|
||||
|
||||
# image_pt = torch.stack([TF.to_tensor(img) for img in image_pil], dim=0).to(device)
|
||||
# image_pil = image_pil * 2.0 - 1.0
|
||||
image_latents = self.vae.encode(image_pil* 2.0 - 1.0).latent_dist.mode() * self.vae.config.scaling_factor
|
||||
|
||||
# Note: repeat differently from official pipelines
|
||||
# B1B2B3B4 -> B1B2B3B4B1B2B3B4
|
||||
image_latents = image_latents.repeat(num_images_per_prompt, 1, 1, 1)
|
||||
|
||||
# if do_classifier_free_guidance:
|
||||
# image_latents = torch.cat([torch.zeros_like(image_latents), image_latents])
|
||||
|
||||
return image_embeddings, image_latents
|
||||
|
||||
def _encode_prompt(self, prompt, device, num_videos_per_prompt, do_classifier_free_guidance, negative_prompt):
|
||||
batch_size = len(prompt) if isinstance(prompt, list) else 1
|
||||
|
||||
text_inputs = self.tokenizer(
|
||||
prompt,
|
||||
padding="max_length",
|
||||
max_length=self.tokenizer.model_max_length,
|
||||
truncation=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
text_input_ids = text_inputs.input_ids
|
||||
untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
|
||||
|
||||
if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
|
||||
removed_text = self.tokenizer.batch_decode(untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1])
|
||||
logger.warning(
|
||||
"The following part of your input was truncated because CLIP can only handle sequences up to"
|
||||
f" {self.tokenizer.model_max_length} tokens: {removed_text}"
|
||||
)
|
||||
|
||||
if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask:
|
||||
attention_mask = text_inputs.attention_mask.to(device)
|
||||
else:
|
||||
attention_mask = None
|
||||
|
||||
text_embeddings = self.text_encoder(
|
||||
text_input_ids.to(device),
|
||||
attention_mask=attention_mask,
|
||||
)
|
||||
text_embeddings = text_embeddings[0]
|
||||
|
||||
# duplicate text embeddings for each generation per prompt, using mps friendly method
|
||||
bs_embed, seq_len, _ = text_embeddings.shape
|
||||
text_embeddings = text_embeddings.repeat(1, num_videos_per_prompt, 1)
|
||||
text_embeddings = text_embeddings.view(bs_embed * num_videos_per_prompt, seq_len, -1)
|
||||
|
||||
# get unconditional embeddings for classifier free guidance
|
||||
if do_classifier_free_guidance:
|
||||
uncond_tokens: List[str]
|
||||
if negative_prompt is None:
|
||||
uncond_tokens = [""] * batch_size
|
||||
elif type(prompt) is not type(negative_prompt):
|
||||
raise TypeError(
|
||||
f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !="
|
||||
f" {type(prompt)}."
|
||||
)
|
||||
elif isinstance(negative_prompt, str):
|
||||
uncond_tokens = [negative_prompt]
|
||||
elif batch_size != len(negative_prompt):
|
||||
raise ValueError(
|
||||
f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"
|
||||
f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"
|
||||
" the batch size of `prompt`."
|
||||
)
|
||||
else:
|
||||
uncond_tokens = negative_prompt
|
||||
|
||||
max_length = text_input_ids.shape[-1]
|
||||
uncond_input = self.tokenizer(
|
||||
uncond_tokens,
|
||||
padding="max_length",
|
||||
max_length=max_length,
|
||||
truncation=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
|
||||
if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask:
|
||||
attention_mask = uncond_input.attention_mask.to(device)
|
||||
else:
|
||||
attention_mask = None
|
||||
|
||||
uncond_embeddings = self.text_encoder(
|
||||
uncond_input.input_ids.to(device),
|
||||
attention_mask=attention_mask,
|
||||
)
|
||||
uncond_embeddings = uncond_embeddings[0]
|
||||
|
||||
# duplicate unconditional embeddings for each generation per prompt, using mps friendly method
|
||||
seq_len = uncond_embeddings.shape[1]
|
||||
uncond_embeddings = uncond_embeddings.repeat(1, num_videos_per_prompt, 1)
|
||||
uncond_embeddings = uncond_embeddings.view(batch_size * num_videos_per_prompt, seq_len, -1)
|
||||
|
||||
# For classifier free guidance, we need to do two forward passes.
|
||||
# Here we concatenate the unconditional and text embeddings into a single batch
|
||||
# to avoid doing two forward passes
|
||||
text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
|
||||
|
||||
return text_embeddings
|
||||
|
||||
def decode_latents(self, latents):
|
||||
video_length = latents.shape[2]
|
||||
latents = 1 / 0.18215 * latents
|
||||
latents = rearrange(latents, "b c f h w -> (b f) c h w")
|
||||
video = self.vae.decode(latents).sample
|
||||
video = rearrange(video, "(b f) c h w -> b c f h w", f=video_length)
|
||||
video = (video / 2 + 0.5).clamp(0, 1)
|
||||
# we always cast to float32 as this does not cause significant overhead and is compatible with bfloa16
|
||||
video = video.cpu().float().numpy()
|
||||
return video
|
||||
|
||||
def prepare_extra_step_kwargs(self, generator, eta):
|
||||
# prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
|
||||
# eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
|
||||
# eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
|
||||
# and should be between [0, 1]
|
||||
|
||||
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
|
||||
extra_step_kwargs = {}
|
||||
if accepts_eta:
|
||||
extra_step_kwargs["eta"] = eta
|
||||
|
||||
# check if the scheduler accepts generator
|
||||
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
|
||||
if accepts_generator:
|
||||
extra_step_kwargs["generator"] = generator
|
||||
return extra_step_kwargs
|
||||
|
||||
def check_inputs(self, prompt, height, width, callback_steps):
|
||||
if not isinstance(prompt, str) and not isinstance(prompt, list):
|
||||
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
|
||||
|
||||
if height % 8 != 0 or width % 8 != 0:
|
||||
raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
|
||||
|
||||
if (callback_steps is None) or (
|
||||
callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)
|
||||
):
|
||||
raise ValueError(
|
||||
f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
|
||||
f" {type(callback_steps)}."
|
||||
)
|
||||
|
||||
def prepare_latents(self, batch_size, num_channels_latents, video_length, height, width, dtype, device, generator, latents=None):
|
||||
shape = (batch_size, num_channels_latents, video_length, height // self.vae_scale_factor, width // self.vae_scale_factor)
|
||||
if isinstance(generator, list) and len(generator) != batch_size:
|
||||
raise ValueError(
|
||||
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
|
||||
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
||||
)
|
||||
|
||||
if latents is None:
|
||||
rand_device = "cpu" if device.type == "mps" else device
|
||||
|
||||
if isinstance(generator, list):
|
||||
shape = (1,) + shape[1:]
|
||||
latents = [
|
||||
torch.randn(shape, generator=generator[i], device=rand_device, dtype=dtype)
|
||||
for i in range(batch_size)
|
||||
]
|
||||
latents = torch.cat(latents, dim=0).to(device)
|
||||
else:
|
||||
latents = torch.randn(shape, generator=generator, device=rand_device, dtype=dtype).to(device)
|
||||
else:
|
||||
if latents.shape != shape:
|
||||
raise ValueError(f"Unexpected latents shape, got {latents.shape}, expected {shape}")
|
||||
latents = latents.to(device)
|
||||
|
||||
# scale the initial noise by the standard deviation required by the scheduler
|
||||
latents = latents * self.scheduler.init_noise_sigma
|
||||
return latents
|
||||
|
||||
@torch.no_grad()
|
||||
def __call__(
|
||||
self,
|
||||
prompt: Union[str, List[str]],
|
||||
image: Union[str, List[str]],
|
||||
video_length: Optional[int],
|
||||
height: Optional[int] = None,
|
||||
width: Optional[int] = None,
|
||||
num_inference_steps: int = 50,
|
||||
guidance_scale: float = 7.5,
|
||||
negative_prompt: Optional[Union[str, List[str]]] = None,
|
||||
num_videos_per_prompt: Optional[int] = 1,
|
||||
eta: float = 0.0,
|
||||
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
|
||||
latents: Optional[torch.FloatTensor] = None,
|
||||
output_type: Optional[str] = "tensor",
|
||||
return_dict: bool = True,
|
||||
callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,
|
||||
callback_steps: Optional[int] = 1,
|
||||
camera_matrixs = None,
|
||||
class_labels = None,
|
||||
prompt_ids = None,
|
||||
unet_condition_type = None,
|
||||
pose_guider = None,
|
||||
pose_image = None,
|
||||
img_proj=None,
|
||||
use_noise=True,
|
||||
use_shifted_noise=False,
|
||||
rescale = 0.7,
|
||||
**kwargs,
|
||||
):
|
||||
# Default height and width to unet
|
||||
height = height or self.unet.config.sample_size * self.vae_scale_factor
|
||||
width = width or self.unet.config.sample_size * self.vae_scale_factor
|
||||
|
||||
# Check inputs. Raise error if not correct
|
||||
self.check_inputs(prompt, height, width, callback_steps)
|
||||
if isinstance(image, list):
|
||||
batch_size = len(image)
|
||||
else:
|
||||
batch_size = image.shape[0]
|
||||
# assert batch_size >= video_length and batch_size % video_length == 0
|
||||
# Define call parameters
|
||||
batch_size = 1 if isinstance(prompt, str) else len(prompt)
|
||||
device = self._execution_device
|
||||
# here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
|
||||
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
|
||||
# corresponds to doing no classifier free guidance.
|
||||
do_classifier_free_guidance = guidance_scale > 1.0
|
||||
|
||||
# 3. Encode input image
|
||||
# if isinstance(image, list):
|
||||
# image_pil = image
|
||||
# elif isinstance(image, torch.Tensor):
|
||||
# image_pil = [TF.to_pil_image(image[i]) for i in range(image.shape[0])]
|
||||
# encode input reference image
|
||||
image_embeddings, image_latents = self._encode_image(image, device, num_videos_per_prompt, do_classifier_free_guidance, img_proj=img_proj) #torch.Size([64, 1, 768]) torch.Size([64, 4, 32, 32])
|
||||
image_latents = rearrange(image_latents, "(b f) c h w -> b c f h w", f=1) #torch.Size([64, 4, 1, 32, 32])
|
||||
|
||||
# Encode input prompt_id
|
||||
# encoder_hidden_states = self.text_encoder(prompt_ids)[0] #torch.Size([32, 77, 768])
|
||||
|
||||
# Encode input prompt
|
||||
text_embeddings = self._encode_prompt( #torch.Size([64, 77, 768])
|
||||
prompt, device, num_videos_per_prompt, do_classifier_free_guidance, negative_prompt
|
||||
)
|
||||
|
||||
# Prepare timesteps
|
||||
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
||||
timesteps = self.scheduler.timesteps
|
||||
|
||||
# Prepare latent variables
|
||||
num_channels_latents = self.unet.in_channels
|
||||
latents = self.prepare_latents( #torch.Size([32, 4, 4, 32, 32])
|
||||
batch_size * num_videos_per_prompt,
|
||||
num_channels_latents,
|
||||
video_length,
|
||||
height,
|
||||
width,
|
||||
text_embeddings.dtype,
|
||||
device,
|
||||
generator,
|
||||
latents,
|
||||
)
|
||||
latents_dtype = latents.dtype
|
||||
# import ipdb
|
||||
# ipdb.set_trace()
|
||||
# Prepare extra step kwargs.
|
||||
extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
|
||||
# prepare camera_matrix
|
||||
if camera_matrixs is not None:
|
||||
camera_matrixs = torch.cat([camera_matrixs] * 2) if do_classifier_free_guidance else camera_matrixs #(64, 4, 12)
|
||||
# Denoising loop
|
||||
comfy_pbar = comfy.utils.ProgressBar(num_inference_steps)
|
||||
|
||||
num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
|
||||
if pose_guider is not None:
|
||||
if len(pose_image.shape) == 5:
|
||||
pose_embeds = pose_guider(rearrange(pose_image, "b f c h w -> (b f) c h w"))
|
||||
pose_embeds = rearrange(pose_embeds, "(b f) c h w-> b c f h w ", f=video_length)
|
||||
else:
|
||||
pose_embeds = pose_guider(pose_image).unsqueeze(0)
|
||||
pose_embeds = torch.cat([pose_embeds]*2, dim=0)
|
||||
# import ipdb
|
||||
# ipdb.set_trace()
|
||||
with self.progress_bar(total=num_inference_steps) as progress_bar:
|
||||
for i, t in enumerate(tqdm.tqdm(timesteps)):
|
||||
# expand the latents if we are doing classifier free guidance
|
||||
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
|
||||
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
|
||||
if pose_guider is not None:
|
||||
latent_model_input = latent_model_input + pose_embeds
|
||||
|
||||
noise_cond = torch.randn_like(image_latents)
|
||||
if use_noise:
|
||||
cond_latents = self.scheduler.add_noise(image_latents, noise_cond, t)
|
||||
else:
|
||||
cond_latents = image_latents
|
||||
cond_latent_model_input = torch.cat([cond_latents] * 2) if do_classifier_free_guidance else cond_latents
|
||||
cond_latent_model_input = self.scheduler.scale_model_input(cond_latent_model_input, t)
|
||||
|
||||
# predict the noise residual
|
||||
# ref text condition
|
||||
ref_dict = {}
|
||||
if self.ref_unet is not None:
|
||||
noise_pred_cond = self.ref_unet(
|
||||
cond_latent_model_input, #torch.Size([64, 4, 1, 32, 32])
|
||||
t, #torch.Size([32])
|
||||
encoder_hidden_states=text_embeddings.to(torch.float32), #torch.Size([64, 77, 768])
|
||||
cross_attention_kwargs=dict(mode="w", ref_dict=ref_dict)
|
||||
).sample.to(dtype=latents_dtype)
|
||||
|
||||
# if torch.isnan(noise_pred_cond).any():
|
||||
# ipdb.set_trace()
|
||||
# Predict the noise residual and compute loss
|
||||
# model_pred = unet(noisy_latents, timesteps, encoder_hidden_states, camera_matrixs).sample
|
||||
# unet
|
||||
#text condition for unet
|
||||
text_embeddings_unet = text_embeddings.unsqueeze(1).repeat(1,latents.shape[2],1,1)
|
||||
text_embeddings_unet = rearrange(text_embeddings_unet, 'B Nv d c -> (B Nv) d c')
|
||||
#image condition for unet
|
||||
image_embeddings_unet = image_embeddings.unsqueeze(1).repeat(1,latents.shape[2],1, 1)
|
||||
image_embeddings_unet = rearrange(image_embeddings_unet, 'B Nv d c -> (B Nv) d c')
|
||||
|
||||
if unet_condition_type == 'text':
|
||||
encoder_hidden_states_unet_cond = text_embeddings_unet
|
||||
elif unet_condition_type == 'image':
|
||||
encoder_hidden_states_unet_cond = image_embeddings_unet
|
||||
else:
|
||||
raise('need unet_condition_type')
|
||||
|
||||
if self.ref_unet is not None:
|
||||
noise_pred = self.unet(
|
||||
latent_model_input.to(torch.float32), #torch.Size([64, 4, 4, 32, 32])
|
||||
t,
|
||||
encoder_hidden_states=encoder_hidden_states_unet_cond.to(torch.float32),
|
||||
camera_matrixs=camera_matrixs.to(torch.float32), #torch.Size([64, 4, 12])
|
||||
cross_attention_kwargs=dict(mode="r", ref_dict=ref_dict, is_cfg_guidance=do_classifier_free_guidance)
|
||||
# cross_attention_kwargs=dict(mode="n", ref_dict=ref_dict, is_cfg_guidance=do_classifier_free_guidance)
|
||||
).sample.to(dtype=latents_dtype)
|
||||
else:
|
||||
noise_pred = self.unet(
|
||||
latent_model_input.to(torch.float32), #torch.Size([64, 4, 4, 32, 32])
|
||||
t,
|
||||
encoder_hidden_states=encoder_hidden_states_unet_cond.to(torch.float32),
|
||||
camera_matrixs=camera_matrixs.to(torch.float32), #torch.Size([64, 4, 12])
|
||||
# cross_attention_kwargs=dict(mode="r", ref_dict=ref_dict, is_cfg_guidance=do_classifier_free_guidance)
|
||||
cross_attention_kwargs=dict(mode="n", ref_dict=ref_dict, is_cfg_guidance=do_classifier_free_guidance)
|
||||
).sample.to(dtype=latents_dtype)
|
||||
# perform guidance
|
||||
if do_classifier_free_guidance:
|
||||
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
|
||||
if use_shifted_noise:
|
||||
# Apply regular classifier-free guidance.
|
||||
cfg = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
|
||||
# Calculate standard deviations.
|
||||
std_pos = noise_pred_text.std([1,2,3], keepdim=True)
|
||||
std_cfg = cfg.std([1,2,3], keepdim=True)
|
||||
# Apply guidance rescale with fused operations.
|
||||
factor = std_pos / std_cfg
|
||||
factor = rescale * factor + (1 - rescale)
|
||||
noise_pred = cfg * factor
|
||||
else:
|
||||
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
|
||||
# noise_pred_uncond_, noise_pred_text_ = noise_pred_cond.chunk(2)
|
||||
# noise_pred_cond = noise_pred_uncond_ + guidance_scale * (noise_pred_text_ - noise_pred_uncond_)
|
||||
|
||||
# compute the previous noisy sample x_t -> x_t-1
|
||||
noise_pred = rearrange(noise_pred, "(b f) c h w -> b c f h w", f=video_length)
|
||||
latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample
|
||||
# noise_pred_cond = rearrange(noise_pred_cond, "(b f) c h w -> b c f h w", f=1)
|
||||
# cond_latents = self.scheduler.step(noise_pred_cond, t, cond_latents, **extra_step_kwargs).prev_sample
|
||||
|
||||
# call the callback, if provided
|
||||
if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
|
||||
progress_bar.update()
|
||||
if callback is not None and i % callback_steps == 0:
|
||||
callback(i, t, latents)
|
||||
|
||||
comfy_pbar.update_absolute(i + 1)
|
||||
|
||||
# Post-processing
|
||||
video = self.decode_latents(latents)
|
||||
|
||||
# Convert to tensor
|
||||
if output_type == "tensor":
|
||||
video = torch.from_numpy(video)
|
||||
|
||||
if not return_dict:
|
||||
return video
|
||||
|
||||
return TuneAVideoPipelineOutput(videos=video)
|
||||
@@ -0,0 +1,128 @@
|
||||
import os
|
||||
import imageio
|
||||
import numpy as np
|
||||
from typing import Union
|
||||
import cv2
|
||||
import torch
|
||||
import torchvision
|
||||
|
||||
from tqdm import tqdm
|
||||
from einops import rearrange
|
||||
|
||||
def shifted_noise(betas, image_d=512, noise_d=256, shifted_noise=True):
|
||||
alphas = 1 - betas
|
||||
alphas_bar = torch.cumprod(alphas, dim=0)
|
||||
d = (image_d / noise_d) ** 2
|
||||
if shifted_noise:
|
||||
alphas_bar = alphas_bar / (d - (d - 1) * alphas_bar)
|
||||
alphas_bar_sqrt = torch.sqrt(alphas_bar)
|
||||
alphas_bar_sqrt_0 = alphas_bar_sqrt[0].clone()
|
||||
alphas_bar_sqrt_T = alphas_bar_sqrt[-1].clone()
|
||||
# Shift so last timestep is zero.
|
||||
alphas_bar_sqrt -= alphas_bar_sqrt_T
|
||||
# Scale so first timestep is back to old value.
|
||||
alphas_bar_sqrt *= alphas_bar_sqrt_0 / (
|
||||
alphas_bar_sqrt_0 - alphas_bar_sqrt_T)
|
||||
|
||||
# Convert alphas_bar_sqrt to betas
|
||||
alphas_bar = alphas_bar_sqrt ** 2
|
||||
alphas = alphas_bar[1:] / alphas_bar[:-1]
|
||||
alphas = torch.cat([alphas_bar[0:1], alphas])
|
||||
betas = 1 - alphas
|
||||
return betas
|
||||
|
||||
def save_videos_grid(videos: torch.Tensor, path: str, rescale=False, n_rows=4, fps=8):
|
||||
videos = rearrange(videos, "b c t h w -> t b c h w")
|
||||
outputs = []
|
||||
for x in videos:
|
||||
x = torchvision.utils.make_grid(x, nrow=n_rows)
|
||||
x = x.transpose(0, 1).transpose(1, 2).squeeze(-1)
|
||||
if rescale:
|
||||
x = (x + 1.0) / 2.0 # -1,1 -> 0,1
|
||||
x = (x * 255).numpy().astype(np.uint8)
|
||||
outputs.append(x)
|
||||
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
imageio.mimsave(path, outputs, duration=1000/fps)
|
||||
|
||||
def save_imgs_grid(videos: torch.Tensor, path: str, rescale=False, n_rows=4, fps=8):
|
||||
videos = rearrange(videos, "b c t h w -> t b c h w")
|
||||
for i, x in enumerate(videos):
|
||||
x = torchvision.utils.make_grid(x, nrow=n_rows)
|
||||
x = x.transpose(0, 1).transpose(1, 2).squeeze(-1)
|
||||
if rescale:
|
||||
x = (x + 1.0) / 2.0 # -1,1 -> 0,1
|
||||
x = (x * 255).numpy().astype(np.uint8)
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
cv2.imwrite(os.path.join(path, f'view_{i}.png'), x[:,:,::-1])
|
||||
|
||||
def imgs_grid(videos: torch.Tensor, rescale=False, n_rows=4, fps=8):
|
||||
videos = rearrange(videos, "b c t h w -> t b c h w")
|
||||
image_list = []
|
||||
for i, x in enumerate(videos):
|
||||
x = torchvision.utils.make_grid(x, nrow=n_rows)
|
||||
x = x.transpose(0, 1).transpose(1, 2).squeeze(-1)
|
||||
if rescale:
|
||||
x = (x + 1.0) / 2.0 # -1,1 -> 0,1
|
||||
x = (x * 255).numpy().astype(np.uint8)
|
||||
# image_list.append(x[:,:,::-1])
|
||||
image_list.append(x)
|
||||
return image_list
|
||||
|
||||
# DDIM Inversion
|
||||
@torch.no_grad()
|
||||
def init_prompt(prompt, pipeline):
|
||||
uncond_input = pipeline.tokenizer(
|
||||
[""], padding="max_length", max_length=pipeline.tokenizer.model_max_length,
|
||||
return_tensors="pt"
|
||||
)
|
||||
uncond_embeddings = pipeline.text_encoder(uncond_input.input_ids.to(pipeline.device))[0]
|
||||
text_input = pipeline.tokenizer(
|
||||
[prompt],
|
||||
padding="max_length",
|
||||
max_length=pipeline.tokenizer.model_max_length,
|
||||
truncation=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
text_embeddings = pipeline.text_encoder(text_input.input_ids.to(pipeline.device))[0]
|
||||
context = torch.cat([uncond_embeddings, text_embeddings])
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def next_step(model_output: Union[torch.FloatTensor, np.ndarray], timestep: int,
|
||||
sample: Union[torch.FloatTensor, np.ndarray], ddim_scheduler):
|
||||
timestep, next_timestep = min(
|
||||
timestep - ddim_scheduler.config.num_train_timesteps // ddim_scheduler.num_inference_steps, 999), timestep
|
||||
alpha_prod_t = ddim_scheduler.alphas_cumprod[timestep] if timestep >= 0 else ddim_scheduler.final_alpha_cumprod
|
||||
alpha_prod_t_next = ddim_scheduler.alphas_cumprod[next_timestep]
|
||||
beta_prod_t = 1 - alpha_prod_t
|
||||
next_original_sample = (sample - beta_prod_t ** 0.5 * model_output) / alpha_prod_t ** 0.5
|
||||
next_sample_direction = (1 - alpha_prod_t_next) ** 0.5 * model_output
|
||||
next_sample = alpha_prod_t_next ** 0.5 * next_original_sample + next_sample_direction
|
||||
return next_sample
|
||||
|
||||
|
||||
def get_noise_pred_single(latents, t, context, unet):
|
||||
noise_pred = unet(latents, t, encoder_hidden_states=context)["sample"]
|
||||
return noise_pred
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def ddim_loop(pipeline, ddim_scheduler, latent, num_inv_steps, prompt):
|
||||
context = init_prompt(prompt, pipeline)
|
||||
uncond_embeddings, cond_embeddings = context.chunk(2)
|
||||
all_latent = [latent]
|
||||
latent = latent.clone().detach()
|
||||
for i in tqdm(range(num_inv_steps)):
|
||||
t = ddim_scheduler.timesteps[len(ddim_scheduler.timesteps) - i - 1]
|
||||
noise_pred = get_noise_pred_single(latent.to(torch.float32), t, cond_embeddings.to(torch.float32), pipeline.unet)
|
||||
latent = next_step(noise_pred, t, latent, ddim_scheduler)
|
||||
all_latent.append(latent)
|
||||
return all_latent
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def ddim_inversion(pipeline, ddim_scheduler, video_latent, num_inv_steps, prompt=""):
|
||||
ddim_latents = ddim_loop(pipeline, ddim_scheduler, video_latent, num_inv_steps, prompt)
|
||||
return ddim_latents
|
||||
@@ -0,0 +1,185 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import torch
|
||||
import nvdiffrast.torch as dr
|
||||
import json
|
||||
import torch.nn.functional as F
|
||||
from PIL import Image
|
||||
import pymeshlab
|
||||
import cv2
|
||||
|
||||
def back_to_texture(glctx, look_at, pos, tri, tex, uv, uv_idx, idx, vn):
|
||||
rast_out, rast_out_db = dr.rasterize(glctx, pos, tri, resolution=[tex.shape[0],tex.shape[1]])
|
||||
gb_normal, _ = dr.interpolate(vn[None], rast_out, tri)
|
||||
gb_normal = F.normalize(gb_normal, dim=-1)
|
||||
if idx == 2 or idx == 0:
|
||||
filter_camera = [torch.tensor([[[[1,0.,0.]]]]).cuda(), torch.tensor([[[[-1,0.,0.]]]]).cuda()]
|
||||
else:
|
||||
filter_camera = [torch.tensor([[[[0,-1.,0.]]]]).cuda(), torch.tensor([[[[0,1.,0.]]]]).cuda()]
|
||||
nmasks = []
|
||||
for fc in filter_camera:
|
||||
nmasks.append(((gb_normal * fc) > 0.75).int().sum(keepdim=True, dim=-1))
|
||||
gb_normal_mask = 1 - (nmasks[0] | nmasks[1])
|
||||
#Image.fromarray(np.clip(gb_normal_mask[0,...,0].detach().cpu().numpy() * 255, 0, 255).astype(np.uint8)).save(f"mask_normal_{idx}.png")
|
||||
gb_mask = rast_out[...,3:4] > 0
|
||||
tri_list = torch.unique(rast_out[...,3:4].reshape(-1))
|
||||
tri_list = (tri_list[1:] - 1).to(torch.int32)
|
||||
pos = pos[0]
|
||||
|
||||
depth_map = rast_out[...,3:4].clone()
|
||||
depth_map[depth_map > 0] = 1
|
||||
depth_map = depth_map.to(torch.float32)
|
||||
dmax = (rast_out[...,2:3] * gb_mask).max()
|
||||
uv = torch.cat([uv * 2 - 1, torch.zeros(uv.shape[0], 1).cuda(), torch.ones(uv.shape[0], 1).cuda()], dim=1).unsqueeze(0)
|
||||
uv_idx = uv_idx[tri_list.to(torch.long)]
|
||||
rast_uv, rast_uv_db = dr.rasterize(glctx, uv, uv_idx, resolution=(1024, 1024))
|
||||
pos_clip = torch.cat([pos[...,:2], pos[...,3:]], -1)
|
||||
pos_2d, _ = dr.interpolate(pos_clip, rast_uv, tri[tri_list.to(torch.long)]) # pos (x, y, z, w)
|
||||
pos_coord = (pos_2d[...,:2] / (pos_2d[...,2:3] + 1e-6) + 1) / 2.
|
||||
texture_mask = (rast_uv[...,3:4] > 0).int()
|
||||
color = dr.texture(tex[None, ...] * gb_normal_mask, pos_coord, filter_mode='linear')
|
||||
color_mask = dr.texture(gb_normal_mask.to(torch.float32), pos_coord, filter_mode='linear')
|
||||
color_mask[color_mask > 0.82] = 1
|
||||
color_mask[color_mask <= 0.82] = 0
|
||||
color_mask = color_mask.to(torch.int32)
|
||||
#Image.fromarray(np.clip(color_mask[0].repeat(1,1,3).detach().cpu().numpy() * 255, 0, 255).astype(np.uint8)).save(f"depth_{idx}.png")
|
||||
texture_mask = texture_mask * color_mask
|
||||
#Image.fromarray(np.clip(color[0].detach().cpu().numpy() * 255, 0, 255).astype(np.uint8)).save(f"{idx}.png")
|
||||
#Image.fromarray(np.clip(texture_mask[0].repeat(1,1,3).detach().cpu().numpy() * 255, 0, 255).astype(np.uint8)).convert("RGB").save(f"mask-{idx}.png")
|
||||
return color, texture_mask, rast_uv
|
||||
|
||||
def perspective(fovy=0.6913, aspect=1.0, n=0.1, f=1000.0, device=None):
|
||||
y = np.tan(fovy / 2)
|
||||
return torch.tensor([[1/(y*aspect), 0, 0, 0],
|
||||
[ 0, 1/-y, 0, 0],
|
||||
[ 0, 0, -(f+n)/(f-n), -(2*f*n)/(f-n)],
|
||||
[ 0, 0, -1, 0]]).to(torch.float32).cuda()
|
||||
|
||||
def rec_mvp(trans, h, w):
|
||||
mv = trans
|
||||
fov = 40. / 180. * np.pi
|
||||
proj = perspective(fov, h / w, n=0.1, f=1000)
|
||||
mvp = proj @ mv
|
||||
return mvp
|
||||
|
||||
def aggregate_texture(kd_map, textures, texture_masks, rast_uvs):
|
||||
texture = torch.zeros_like(textures[0])
|
||||
texture_mask = torch.zeros_like(texture_masks[0])
|
||||
ctex = []
|
||||
for idx in range(len(textures)):
|
||||
ctex.append(textures[idx] * texture_masks[idx] + 10 * (1 - texture_masks[idx]))
|
||||
cat_textures = torch.stack(ctex, dim=-2)
|
||||
dis_measure = (cat_textures - kd_map.unsqueeze(-2)).abs().sum(-1)
|
||||
_, choose_idx = dis_measure.min(-1)
|
||||
|
||||
choose_idx = choose_idx.unsqueeze(-1).unsqueeze(-1).repeat(1, 1, 1, 1, 3)
|
||||
final_texture_map = torch.gather(cat_textures, 3, choose_idx).squeeze(-2)
|
||||
#cv2.imwrite("final_texture_map.png", cv2.cvtColor((final_texture_map[0].cpu().numpy() * 255).astype(np.uint8), cv2.COLOR_BGR2RGB))
|
||||
#cv2.imwrite("final_texture_mask.png", (texture_mask[0].cpu().numpy() * 255).astype(np.uint8))
|
||||
zero_mask = (final_texture_map.max(dim=-1, keepdim=True)[0] > 0.1)
|
||||
close_mask = ((final_texture_map[0] - kd_map).abs().sum(dim=-1, keepdim=True) < 1.0).int()
|
||||
for idx in range(len(textures)):
|
||||
texture += textures[idx] * texture_masks[idx]
|
||||
texture_mask |= texture_masks[idx]
|
||||
texture_mask = texture_mask * zero_mask * close_mask[None]
|
||||
optimize_mask = (texture_mask == 0).int()
|
||||
|
||||
#import pdb; pdb.set_trace()
|
||||
#mask = (texture_mask[0].cpu().numpy() * 255).astype(np.uint8)
|
||||
#cv2.imwrite("mask.png", mask)
|
||||
#kernel = np.ones((5,5), np.uint8)
|
||||
#dilated = cv2.dilate(mask, kernel, iterations=1)
|
||||
#cv2.imwrite("di_mask.png", dilated)
|
||||
#texture_mask[0] = torch.from_numpy(dilated).unsqueeze(-1).to(torch.float32) / 255.
|
||||
|
||||
final_texture_map = final_texture_map[0] * texture_mask[0]
|
||||
Image.fromarray(np.rint(final_texture_map.cpu().numpy() * 255).astype(np.uint8)).save(f"final_texture.png")
|
||||
|
||||
#cv2.imwrite("kd_map.png", cv2.cvtColor((kd_map.cpu().numpy() * 255).astype(np.uint8), cv2.COLOR_BGR2RGB))
|
||||
#cv2.imwrite("texture_map.png", cv2.cvtColor((final_texture_map.cpu().numpy() * 255).astype(np.uint8), cv2.COLOR_BGR2RGB))
|
||||
#result = cv2.seamlessClone((final_texture_map.cpu().numpy() * 255).astype(np.uint8), (kd_map.cpu().numpy() * 255).astype(np.uint8), mask, (mask.shape[1]//2, mask.shape[0]//2), cv2.NORMAL_CLONE)
|
||||
#cv2.imwrite("result.png", cv2.cvtColor(result * 255, cv2.COLOR_BGR2RGB))
|
||||
|
||||
kd_map = kd_map * (1 - texture_mask[0]) + final_texture_map
|
||||
return kd_map, optimize_mask
|
||||
|
||||
def refine(save_path, front_image, back_image, left_image, right_image):
|
||||
ms = pymeshlab.MeshSet()
|
||||
mesh_path = f"{save_path}/model-00.obj"
|
||||
ms.load_new_mesh(mesh_path)
|
||||
ms.apply_coord_laplacian_smoothing(stepsmoothnum=10)
|
||||
tl = open(mesh_path, "r").readlines()
|
||||
tex_uv = []
|
||||
uv_idx = []
|
||||
for line in tl:
|
||||
if line.startswith("vt"):
|
||||
uvs = line.split(" ")[1:3]
|
||||
tex_uv += [float(uvs[0]), 1.0-float(uvs[1])]
|
||||
tex_uv = torch.from_numpy(np.array(tex_uv)).to(torch.float32).cuda().reshape(-1, 2)
|
||||
m = ms.current_mesh()
|
||||
v_matrix = m.vertex_matrix()
|
||||
f_matrix = m.face_matrix()
|
||||
vn = m.vertex_normal_matrix()
|
||||
uv_idx = torch.arange(f_matrix.shape[0] * 3).reshape(-1, 3).to(torch.int32).cuda()
|
||||
vn = torch.tensor(vn).contiguous().cuda().to(torch.float32)
|
||||
|
||||
frames = []
|
||||
front_camera = torch.tensor([[
|
||||
1,0,0,0,
|
||||
0,0,1,0,
|
||||
0,-1,0,-1.5,
|
||||
0,0,0,1,
|
||||
]]).to(torch.float32).reshape(4,4).cuda()
|
||||
back_camera = torch.tensor([[
|
||||
1,0,0,0,
|
||||
0,0,1,0,
|
||||
0,1,0,-1.5,
|
||||
0,0,0,1,
|
||||
]]).to(torch.float32).reshape(4,4).cuda()
|
||||
right_camera = torch.tensor([[
|
||||
0,-1,0,0,
|
||||
0,0,1,0,
|
||||
1,0,0,-1.5,
|
||||
0,0,0,1,
|
||||
]]).to(torch.float32).reshape(4,4).cuda()
|
||||
left_camera = torch.tensor([[
|
||||
0,1,0,0,
|
||||
0,0,1,0,
|
||||
-1,0,0,-1.5,
|
||||
0,0,0,1,
|
||||
]]).to(torch.float32).reshape(4,4).cuda()
|
||||
frames = [front_camera, left_camera, back_camera, right_camera]
|
||||
|
||||
target_images = []
|
||||
for target_image in [front_image, left_image, back_image, right_image]:
|
||||
target_images.append(torch.from_numpy(np.asarray(target_image.convert("RGB"))).to(torch.float32).cuda() / 255.)
|
||||
|
||||
pos = torch.tensor(v_matrix, dtype=torch.float32).contiguous().cuda()
|
||||
tri = torch.tensor(f_matrix, dtype=torch.int32).contiguous().cuda()
|
||||
|
||||
kd_map = (torch.tensor(np.asarray(Image.open(f"{save_path}/texture_kd.jpg"))) / 255.).cuda()
|
||||
translate_tensor = torch.zeros((1,1,3)).cuda()
|
||||
pos = torch.cat([pos, torch.ones([pos.shape[0], 1]).cuda()],-1).unsqueeze(0)
|
||||
glctx = dr.RasterizeCudaContext()
|
||||
target_texture = []
|
||||
target_mask = []
|
||||
rast_uvs = []
|
||||
with torch.no_grad():
|
||||
for idx, trans in enumerate(frames):
|
||||
target_image = target_images[idx]
|
||||
look_at = -torch.linalg.inv(trans)[:3,2]
|
||||
mvp = rec_mvp(trans, h=target_images[0].shape[0], w=target_images[0].shape[1])
|
||||
trans_pos = pos.clone()
|
||||
trans_pos[...,:3] += translate_tensor
|
||||
view_pos = torch.matmul(mvp, trans_pos.unsqueeze(-1)).squeeze(-1)
|
||||
texture, mask, rast_uv = back_to_texture(glctx, look_at, view_pos, tri, target_image, tex_uv, uv_idx, idx, vn)
|
||||
target_texture.append(texture)
|
||||
target_mask.append(mask)
|
||||
rast_uvs.append(rast_uv)
|
||||
kd_map, opt_mask = aggregate_texture(kd_map, target_texture, target_mask, rast_uvs)
|
||||
opt_mask = opt_mask[0]
|
||||
Image.fromarray((np.clip(kd_map.detach().cpu().numpy() * 255, 0, 255)).astype(np.uint8)).save(f"{save_path}/refined_texture_kd.jpg")
|
||||
|
||||
#ms.save_current_mesh(f"{save_path}/model-00.obj")
|
||||
with open(f"{save_path}/model-00.mtl", "w") as f:
|
||||
f.write(f"newmtl default\nKa 0.0 0.0 0.0\nmap_Kd refined_texture_kd.jpg\nKs 0.0 0.0 0.0")
|
||||
@@ -0,0 +1,29 @@
|
||||
import importlib
|
||||
|
||||
|
||||
def find(cls_string):
|
||||
module_string = ".".join(cls_string.split(".")[:-1])
|
||||
cls_name = cls_string.split(".")[-1]
|
||||
module = importlib.import_module(module_string, package=None)
|
||||
cls = getattr(module, cls_name)
|
||||
return cls
|
||||
|
||||
|
||||
### grammar sugar for logging utilities ###
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("pytorch_lightning")
|
||||
|
||||
from pytorch_lightning.utilities.rank_zero import (
|
||||
rank_zero_debug,
|
||||
rank_zero_info,
|
||||
rank_zero_only,
|
||||
)
|
||||
|
||||
debug = rank_zero_debug
|
||||
info = rank_zero_info
|
||||
|
||||
|
||||
@rank_zero_only
|
||||
def warn(*args, **kwargs):
|
||||
logger.warn(*args, **kwargs)
|
||||
@@ -0,0 +1,18 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ...utils.base import BaseModule
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class BaseBackground(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
pass
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self):
|
||||
pass
|
||||
|
||||
def forward(self, dirs: Float[Tensor, "B H W 3"]) -> Float[Tensor, "B H W Nc"]:
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,56 @@
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .base import BaseBackground
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class SolidColorBackground(BaseBackground):
|
||||
@dataclass
|
||||
class Config(BaseBackground.Config):
|
||||
n_output_dims: int = 3
|
||||
color: Tuple = (1.0, 1.0, 1.0)
|
||||
learned: bool = False
|
||||
random_aug: bool = False
|
||||
random_aug_prob: float = 0.5
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
self.env_color: Float[Tensor, "Nc"]
|
||||
if self.cfg.learned:
|
||||
self.env_color = nn.Parameter(
|
||||
torch.as_tensor(self.cfg.color, dtype=torch.float32)
|
||||
)
|
||||
else:
|
||||
self.register_buffer(
|
||||
"env_color", torch.as_tensor(self.cfg.color, dtype=torch.float32)
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
dirs: Float[Tensor, "B H W Nc"],
|
||||
color_spec: Optional[Float[Tensor, "Nc"]] = None,
|
||||
) -> Float[Tensor, "B H W Nc"]:
|
||||
color = torch.ones(*dirs.shape[:-1], self.cfg.n_output_dims).to(dirs) * (
|
||||
color_spec if color_spec is not None else self.env_color
|
||||
)
|
||||
if (
|
||||
self.training
|
||||
and self.cfg.random_aug
|
||||
and random.random() < self.cfg.random_aug_prob
|
||||
):
|
||||
# use random background color with probability random_aug_prob
|
||||
# color = color * 0 + (
|
||||
# torch.ones(*dirs.shape[:-1], self.cfg.n_output_dims).to(dirs) *
|
||||
# torch.rand(self.cfg.n_output_dims).to(dirs)
|
||||
# )
|
||||
color = color * 0 + ( # prevent checking for unused parameters in DDP
|
||||
torch.rand(dirs.shape[0], 1, 1, self.cfg.n_output_dims)
|
||||
.to(dirs)
|
||||
.expand(*dirs.shape[:-1], -1)
|
||||
)
|
||||
return color
|
||||
@@ -0,0 +1,33 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from ..utils.base import BaseModule
|
||||
from ..utils.typing import *
|
||||
|
||||
|
||||
class LinearCameraEmbedder(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
in_channels: int = 0
|
||||
out_channels: int = 0
|
||||
conditions: List[str] = field(default_factory=list)
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.linear = nn.Linear(self.cfg.in_channels, self.cfg.out_channels)
|
||||
|
||||
def forward(self, **kwargs):
|
||||
cond_tensors = []
|
||||
for cond_name in self.cfg.conditions:
|
||||
assert cond_name in kwargs
|
||||
cond = kwargs[cond_name]
|
||||
# cond in shape (B, Nv, ...)
|
||||
cond_tensors.append(cond.view(*cond.shape[:2], -1))
|
||||
cond_tensor = torch.cat(cond_tensors, dim=-1)
|
||||
assert cond_tensor.shape[-1] == self.cfg.in_channels
|
||||
embedding = self.linear(cond_tensor)
|
||||
return embedding
|
||||
@@ -0,0 +1,32 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ..renderers.base import BaseRenderer
|
||||
from ...utils.base import BaseObject
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExporterOutput:
|
||||
save_name: str
|
||||
save_type: str
|
||||
params: Dict[str, Any]
|
||||
|
||||
|
||||
class Exporter(BaseObject):
|
||||
@dataclass
|
||||
class Config(BaseObject.Config):
|
||||
save_video: bool = False
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self, renderer: BaseRenderer) -> None:
|
||||
self.renderer = renderer
|
||||
|
||||
def __call__(self, *args, **kwargs) -> List[ExporterOutput]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DummyExporter(Exporter):
|
||||
def __call__(self, *args, **kwargs) -> List[ExporterOutput]:
|
||||
# DummyExporter does not export anything
|
||||
return []
|
||||
@@ -0,0 +1,262 @@
|
||||
from dataclasses import dataclass, field
|
||||
import os
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ..renderers.base import BaseRenderer
|
||||
from .base import Exporter, ExporterOutput
|
||||
from ..mesh import Mesh
|
||||
from ...utils.rasterize import NVDiffRasterizerContext
|
||||
from ...utils.typing import *
|
||||
from ...utils.misc import time_recorder as tr, time_recorder_enabled
|
||||
|
||||
|
||||
def uv_padding_cpu(image, hole_mask, padding):
|
||||
uv_padding_size = padding
|
||||
inpaint_image = (
|
||||
cv2.inpaint(
|
||||
(image.detach().cpu().numpy() * 255).astype(np.uint8),
|
||||
(hole_mask.detach().cpu().numpy() * 255).astype(np.uint8),
|
||||
uv_padding_size,
|
||||
cv2.INPAINT_TELEA,
|
||||
)
|
||||
/ 255.0
|
||||
)
|
||||
return torch.from_numpy(inpaint_image).to(image)
|
||||
|
||||
|
||||
def uv_padding_cvc(image, hole_mask, padding):
|
||||
import cvcuda
|
||||
|
||||
torch_to_cvc = lambda x, layout: cvcuda.as_tensor(x, layout)
|
||||
cvc_to_torch = lambda x: torch.as_tensor(x.cuda())
|
||||
|
||||
uv_padding_size = padding
|
||||
image_cvc = torch_to_cvc((image.detach() * 255).to(torch.uint8), "HWC")
|
||||
hole_mask_cvc = torch_to_cvc((hole_mask.detach() * 255).to(torch.uint8), "HW")
|
||||
inpaint_image = cvcuda.inpaint(image_cvc, hole_mask_cvc, uv_padding_size)
|
||||
inpaint_image = cvc_to_torch(inpaint_image) / 255.0
|
||||
return inpaint_image.to(image)
|
||||
|
||||
|
||||
def uv_padding(image, hole_mask, padding):
|
||||
try:
|
||||
inpaint_image = uv_padding_cvc(image, hole_mask, padding)
|
||||
except:
|
||||
lrm.info(f"CVCUDA not available, fallback to CPU UV padding.")
|
||||
inpaint_image = uv_padding_cpu(image, hole_mask, padding)
|
||||
return inpaint_image
|
||||
|
||||
|
||||
class MeshExporter(Exporter):
|
||||
@dataclass
|
||||
class Config(Exporter.Config):
|
||||
fmt: str = "obj" # in ['obj', 'glb']
|
||||
visual: str = "uv" # in ['uv', 'vertex']
|
||||
save_name: str = "model"
|
||||
save_normal: bool = False
|
||||
save_uv: bool = True
|
||||
save_texture: bool = True
|
||||
texture_size: int = 1024
|
||||
texture_format: str = "jpg"
|
||||
uv_unwrap_method: str = "xatlas"
|
||||
xatlas_chart_options: dict = field(default_factory=dict)
|
||||
xatlas_pack_options: dict = field(default_factory=dict)
|
||||
smartuv_options: dict = field(default_factory=dict)
|
||||
uv_padding_size: int = 2
|
||||
subdivide: bool = False
|
||||
post_process: bool = False
|
||||
post_process_options: dict = field(default_factory=dict)
|
||||
context_type: str = "gl"
|
||||
output_path: str = "outputs"
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self, renderer: BaseRenderer) -> None:
|
||||
super().configure(renderer)
|
||||
self.ctx = NVDiffRasterizerContext(self.cfg.context_type, self.device)
|
||||
if self.cfg.fmt == "obj-mtl":
|
||||
lrm.warn(
|
||||
f"fmt=obj-mtl is deprecated, please us fmt=obj and visual=uv instead."
|
||||
)
|
||||
self.cfg.fmt = "obj"
|
||||
self.cfg.visual = "uv"
|
||||
|
||||
if self.cfg.fmt == "glb":
|
||||
assert self.cfg.visual in [
|
||||
"vertex",
|
||||
"uv-blender",
|
||||
], "GLB format only supports visual=vertex and visual=uv-blender!"
|
||||
|
||||
def get_geometry(self, scene_code: torch.Tensor) -> Mesh:
|
||||
tr.start("Surface extraction")
|
||||
mesh: Mesh = self.renderer.isosurface(scene_code)
|
||||
tr.end("Surface extraction")
|
||||
return mesh
|
||||
|
||||
def get_texture_maps(
|
||||
self, scene_code: torch.Tensor, mesh: Mesh
|
||||
) -> Dict[str, torch.Tensor]:
|
||||
assert mesh.has_uv
|
||||
# clip space transform
|
||||
uv_clip = mesh.v_tex * 2.0 - 1.0
|
||||
# pad to four component coordinate
|
||||
uv_clip4 = torch.cat(
|
||||
(
|
||||
uv_clip,
|
||||
torch.zeros_like(uv_clip[..., 0:1]),
|
||||
torch.ones_like(uv_clip[..., 0:1]),
|
||||
),
|
||||
dim=-1,
|
||||
)
|
||||
# rasterize
|
||||
rast, _ = self.ctx.rasterize_one(
|
||||
uv_clip4,
|
||||
mesh.t_tex_idx,
|
||||
(self.cfg.texture_size, self.cfg.texture_size),
|
||||
)
|
||||
|
||||
hole_mask = ~(rast[:, :, 3] > 0)
|
||||
|
||||
# Interpolate world space position
|
||||
gb_pos, _ = self.ctx.interpolate_one(
|
||||
mesh.v_pos, rast[None, ...], mesh.t_pos_idx
|
||||
)
|
||||
gb_pos = gb_pos[0]
|
||||
|
||||
# Sample out textures from MLP
|
||||
tr.start("Query color")
|
||||
geo_out = self.renderer.query(scene_code, points=gb_pos)
|
||||
tr.end("Query color")
|
||||
mat_out = self.renderer.material.export(points=gb_pos, **geo_out)
|
||||
|
||||
textures = {}
|
||||
tr.start("UV padding")
|
||||
if "albedo" in mat_out:
|
||||
textures["map_Kd"] = uv_padding(
|
||||
mat_out["albedo"], hole_mask, self.cfg.uv_padding_size
|
||||
)
|
||||
else:
|
||||
lrm.warn(
|
||||
"save_texture is True but no albedo texture found, using default white texture"
|
||||
)
|
||||
if "metallic" in mat_out:
|
||||
textures["map_Pm"] = uv_padding(
|
||||
mat_out["metallic"], hole_mask, self.cfg.uv_padding_size
|
||||
)
|
||||
if "roughness" in mat_out:
|
||||
textures["map_Pr"] = uv_padding(
|
||||
mat_out["roughness"], hole_mask, self.cfg.uv_padding_size
|
||||
)
|
||||
if "bump" in mat_out:
|
||||
textures["map_Bump"] = uv_padding(
|
||||
mat_out["bump"], hole_mask, self.cfg.uv_padding_size
|
||||
)
|
||||
tr.end("UV padding")
|
||||
return textures
|
||||
|
||||
def __call__(self, names, scene_codes) -> List[ExporterOutput]:
|
||||
outputs = []
|
||||
for name, scene_code in zip(names, scene_codes):
|
||||
mesh = self.get_geometry(scene_code)
|
||||
if self.cfg.post_process:
|
||||
tr.start("Mesh post-processing")
|
||||
mesh = mesh.post_process(self.cfg.post_process_options)
|
||||
tr.end("Mesh post-processing")
|
||||
if self.cfg.visual == "uv":
|
||||
output = self.export_model_with_mtl(
|
||||
name, self.cfg.fmt, scene_code, mesh
|
||||
)
|
||||
elif self.cfg.visual == "vertex":
|
||||
output = self.export_model(name, self.cfg.fmt, scene_code, mesh)
|
||||
elif self.cfg.visual == "uv-blender":
|
||||
output = self.export_model_blender(name, self.cfg.fmt, scene_code, mesh)
|
||||
else:
|
||||
raise ValueError(f"Unsupported visual format: {self.cfg.visual}")
|
||||
outputs.append(output)
|
||||
return outputs
|
||||
|
||||
def export_model_with_mtl(
|
||||
self, name: str, fmt: str, scene_code: torch.Tensor, mesh: Mesh
|
||||
) -> ExporterOutput:
|
||||
params = {
|
||||
"mesh": mesh,
|
||||
"save_mat": True,
|
||||
"save_normal": self.cfg.save_normal,
|
||||
"save_uv": self.cfg.save_uv,
|
||||
"save_vertex_color": False,
|
||||
"map_Kd": None, # Base Color
|
||||
"map_Ks": None, # Specular
|
||||
"map_Bump": None, # Normal
|
||||
# ref: https://en.wikipedia.org/wiki/Wavefront_.obj_file#Physically-based_Rendering
|
||||
"map_Pm": None, # Metallic
|
||||
"map_Pr": None, # Roughness
|
||||
"map_format": self.cfg.texture_format,
|
||||
}
|
||||
|
||||
if self.cfg.save_uv:
|
||||
mesh.unwrap_uv(
|
||||
self.cfg.uv_unwrap_method,
|
||||
self.cfg.xatlas_chart_options,
|
||||
self.cfg.xatlas_pack_options,
|
||||
self.cfg.smartuv_options,
|
||||
)
|
||||
|
||||
if self.cfg.save_texture:
|
||||
lrm.info("Exporting textures ...")
|
||||
assert self.cfg.save_uv, "save_uv must be True when save_texture is True"
|
||||
|
||||
with time_recorder_enabled():
|
||||
textures = self.get_texture_maps(scene_code, mesh)
|
||||
params.update(textures)
|
||||
os.makedirs(self.cfg.output_path, exist_ok=True)
|
||||
np.savez(f"{self.cfg.output_path}/tex_info.npz", v_tex=mesh.v_tex.cpu().numpy(), t_tex_idx=mesh.t_tex_idx.cpu().numpy())
|
||||
return ExporterOutput(
|
||||
save_name=f"{self.cfg.save_name}-{name}.{fmt}", save_type=fmt, params=params
|
||||
)
|
||||
|
||||
def export_model(
|
||||
self, name: str, fmt: str, scene_code, mesh: Mesh
|
||||
) -> ExporterOutput:
|
||||
params = {
|
||||
"mesh": mesh,
|
||||
"save_mat": False,
|
||||
"save_normal": self.cfg.save_normal,
|
||||
"save_uv": self.cfg.save_uv,
|
||||
"save_vertex_color": False,
|
||||
"map_Kd": None, # Base Color
|
||||
"map_Ks": None, # Specular
|
||||
"map_Bump": None, # Normal
|
||||
# ref: https://en.wikipedia.org/wiki/Wavefront_.obj_file#Physically-based_Rendering
|
||||
"map_Pm": None, # Metallic
|
||||
"map_Pr": None, # Roughness
|
||||
"map_format": self.cfg.texture_format,
|
||||
}
|
||||
|
||||
if self.cfg.save_uv:
|
||||
mesh.unwrap_uv(
|
||||
self.cfg.uv_unwrap_method,
|
||||
self.cfg.xatlas_chart_options,
|
||||
self.cfg.xatlas_pack_options,
|
||||
self.cfg.smartuv_options,
|
||||
)
|
||||
|
||||
if self.cfg.save_texture:
|
||||
lrm.info("Exporting textures ...")
|
||||
geo_out = self.renderer.query(scene_code, points=mesh.v_pos)
|
||||
mat_out = self.renderer.material.export(points=mesh.v_pos, **geo_out)
|
||||
|
||||
if "albedo" in mat_out:
|
||||
mesh.set_vertex_color(mat_out["albedo"])
|
||||
params["save_vertex_color"] = True
|
||||
else:
|
||||
lrm.warn(
|
||||
"save_texture is True but no albedo texture found, not saving vertex color"
|
||||
)
|
||||
|
||||
return ExporterOutput(
|
||||
save_name=f"{self.cfg.save_name}-{name}.{fmt}", save_type=fmt, params=params
|
||||
)
|
||||
@@ -0,0 +1,271 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ..models.mesh import Mesh
|
||||
from ..utils.typing import *
|
||||
from ..utils.ops import scale_tensor
|
||||
|
||||
|
||||
class IsosurfaceHelper(nn.Module):
|
||||
points_range: Tuple[float, float] = (0, 1)
|
||||
|
||||
@property
|
||||
def grid_vertices(self) -> Float[Tensor, "N 3"]:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class MarchingCubeCPUHelper(IsosurfaceHelper):
|
||||
def __init__(self, resolution: int) -> None:
|
||||
super().__init__()
|
||||
self.resolution = resolution
|
||||
import mcubes
|
||||
|
||||
self.mc_func: Callable = mcubes.marching_cubes
|
||||
self._grid_vertices: Optional[Float[Tensor, "N3 3"]] = None
|
||||
self._dummy: Float[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"_dummy", torch.zeros(0, dtype=torch.float32), persistent=False
|
||||
)
|
||||
|
||||
@property
|
||||
def grid_vertices(self) -> Float[Tensor, "N3 3"]:
|
||||
if self._grid_vertices is None:
|
||||
# keep the vertices on CPU so that we can support very large resolution
|
||||
x, y, z = (
|
||||
torch.linspace(*self.points_range, self.resolution),
|
||||
torch.linspace(*self.points_range, self.resolution),
|
||||
torch.linspace(*self.points_range, self.resolution),
|
||||
)
|
||||
x, y, z = torch.meshgrid(x, y, z, indexing="ij")
|
||||
verts = torch.cat(
|
||||
[x.reshape(-1, 1), y.reshape(-1, 1), z.reshape(-1, 1)], dim=-1
|
||||
).reshape(-1, 3)
|
||||
self._grid_vertices = verts
|
||||
return self._grid_vertices
|
||||
|
||||
def forward(
|
||||
self,
|
||||
level: Float[Tensor, "N3 1"],
|
||||
deformation: Optional[Float[Tensor, "N3 3"]] = None,
|
||||
) -> Mesh:
|
||||
if deformation is not None:
|
||||
lrm.warn(
|
||||
f"{self.__class__.__name__} does not support deformation. Ignoring."
|
||||
)
|
||||
level = -level.view(self.resolution, self.resolution, self.resolution)
|
||||
print(level.shape, level.min(), level.max())
|
||||
v_pos, t_pos_idx = self.mc_func(
|
||||
level.detach().cpu().numpy(), 0.0
|
||||
) # transform to numpy
|
||||
# test
|
||||
v_pos, t_pos_idx = (
|
||||
torch.from_numpy(v_pos).float().to(self._dummy.device),
|
||||
torch.from_numpy(t_pos_idx.astype(np.int64)).long().to(self._dummy.device),
|
||||
) # transform back to torch tensor on CUDA
|
||||
v_pos = v_pos / (self.resolution - 1.0)
|
||||
return Mesh(v_pos=v_pos, t_pos_idx=t_pos_idx)
|
||||
|
||||
|
||||
def get_center_boundary_index(verts):
|
||||
# Assuming the verts are in range [-1.0, 1.0]
|
||||
length_ = torch.linalg.norm(verts**2, ord=2, dim=-1, keepdim=False)
|
||||
center_idx = torch.argmin(length_)
|
||||
# center_idx = torch.where(length_ < 0.1)[0]
|
||||
boundary_neg = verts == verts.max()
|
||||
boundary_pos = verts == verts.min()
|
||||
boundary = torch.bitwise_or(boundary_pos, boundary_neg)
|
||||
boundary = torch.sum(boundary.float(), dim=-1)
|
||||
boundary_idx = torch.nonzero(boundary)
|
||||
return center_idx.unsqueeze(0), boundary_idx.squeeze(dim=-1)
|
||||
|
||||
|
||||
class MarchingTetrahedraHelper(IsosurfaceHelper):
|
||||
def __init__(self, resolution: int, tets_path: str):
|
||||
super().__init__()
|
||||
self.resolution = resolution
|
||||
self.tets_path = tets_path
|
||||
|
||||
self.triangle_table: Float[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"triangle_table",
|
||||
torch.as_tensor(
|
||||
[
|
||||
[-1, -1, -1, -1, -1, -1],
|
||||
[1, 0, 2, -1, -1, -1],
|
||||
[4, 0, 3, -1, -1, -1],
|
||||
[1, 4, 2, 1, 3, 4],
|
||||
[3, 1, 5, -1, -1, -1],
|
||||
[2, 3, 0, 2, 5, 3],
|
||||
[1, 4, 0, 1, 5, 4],
|
||||
[4, 2, 5, -1, -1, -1],
|
||||
[4, 5, 2, -1, -1, -1],
|
||||
[4, 1, 0, 4, 5, 1],
|
||||
[3, 2, 0, 3, 5, 2],
|
||||
[1, 3, 5, -1, -1, -1],
|
||||
[4, 1, 2, 4, 3, 1],
|
||||
[3, 0, 4, -1, -1, -1],
|
||||
[2, 0, 1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1],
|
||||
],
|
||||
dtype=torch.long,
|
||||
),
|
||||
persistent=False,
|
||||
)
|
||||
self.num_triangles_table: Integer[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"num_triangles_table",
|
||||
torch.as_tensor(
|
||||
[0, 1, 1, 2, 1, 2, 2, 1, 1, 2, 2, 1, 2, 1, 1, 0], dtype=torch.long
|
||||
),
|
||||
persistent=False,
|
||||
)
|
||||
self.base_tet_edges: Integer[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"base_tet_edges",
|
||||
torch.as_tensor([0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3], dtype=torch.long),
|
||||
persistent=False,
|
||||
)
|
||||
|
||||
tets = np.load(self.tets_path)
|
||||
self._grid_vertices: Float[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"_grid_vertices",
|
||||
torch.from_numpy(tets["vertices"]).float(),
|
||||
persistent=False,
|
||||
)
|
||||
self.indices: Integer[Tensor, "..."]
|
||||
self.register_buffer(
|
||||
"indices", torch.from_numpy(tets["indices"]).long(), persistent=False
|
||||
)
|
||||
|
||||
self._all_edges: Optional[Integer[Tensor, "Ne 2"]] = None
|
||||
self.center_indices, self.boundary_indices = get_center_boundary_index(
|
||||
scale_tensor(self.grid_vertices, self.points_range, (-1.0, 1.0))
|
||||
)
|
||||
|
||||
def normalize_grid_deformation(
|
||||
self, grid_vertex_offsets: Float[Tensor, "Nv 3"]
|
||||
) -> Float[Tensor, "Nv 3"]:
|
||||
return (
|
||||
(self.points_range[1] - self.points_range[0])
|
||||
/ (self.resolution) # half tet size is approximately 1 / self.resolution
|
||||
* torch.tanh(grid_vertex_offsets)
|
||||
) # FIXME: hard-coded activation
|
||||
|
||||
@property
|
||||
def grid_vertices(self) -> Float[Tensor, "Nv 3"]:
|
||||
return self._grid_vertices
|
||||
|
||||
@property
|
||||
def all_edges(self) -> Integer[Tensor, "Ne 2"]:
|
||||
if self._all_edges is None:
|
||||
# compute edges on GPU, or it would be VERY SLOW (basically due to the unique operation)
|
||||
edges = torch.tensor(
|
||||
[0, 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3],
|
||||
dtype=torch.long,
|
||||
device=self.indices.device,
|
||||
)
|
||||
_all_edges = self.indices[:, edges].reshape(-1, 2)
|
||||
_all_edges_sorted = torch.sort(_all_edges, dim=1)[0]
|
||||
_all_edges = torch.unique(_all_edges_sorted, dim=0)
|
||||
self._all_edges = _all_edges
|
||||
return self._all_edges
|
||||
|
||||
def sort_edges(self, edges_ex2):
|
||||
with torch.no_grad():
|
||||
order = (edges_ex2[:, 0] > edges_ex2[:, 1]).long()
|
||||
order = order.unsqueeze(dim=1)
|
||||
|
||||
a = torch.gather(input=edges_ex2, index=order, dim=1)
|
||||
b = torch.gather(input=edges_ex2, index=1 - order, dim=1)
|
||||
|
||||
return torch.stack([a, b], -1)
|
||||
|
||||
def _forward(self, pos_nx3, sdf_n, tet_fx4):
|
||||
with torch.no_grad():
|
||||
occ_n = sdf_n > 0
|
||||
occ_fx4 = occ_n[tet_fx4.reshape(-1)].reshape(-1, 4)
|
||||
occ_sum = torch.sum(occ_fx4, -1)
|
||||
valid_tets = (occ_sum > 0) & (occ_sum < 4)
|
||||
occ_sum = occ_sum[valid_tets]
|
||||
|
||||
# find all vertices
|
||||
all_edges = tet_fx4[valid_tets][:, self.base_tet_edges].reshape(-1, 2)
|
||||
all_edges = self.sort_edges(all_edges)
|
||||
unique_edges, idx_map = torch.unique(all_edges, dim=0, return_inverse=True)
|
||||
|
||||
unique_edges = unique_edges.long()
|
||||
mask_edges = occ_n[unique_edges.reshape(-1)].reshape(-1, 2).sum(-1) == 1
|
||||
mapping = (
|
||||
torch.ones(
|
||||
(unique_edges.shape[0]), dtype=torch.long, device=pos_nx3.device
|
||||
)
|
||||
* -1
|
||||
)
|
||||
mapping[mask_edges] = torch.arange(
|
||||
mask_edges.sum(), dtype=torch.long, device=pos_nx3.device
|
||||
)
|
||||
idx_map = mapping[idx_map] # map edges to verts
|
||||
|
||||
interp_v = unique_edges[mask_edges]
|
||||
edges_to_interp = pos_nx3[interp_v.reshape(-1)].reshape(-1, 2, 3)
|
||||
edges_to_interp_sdf = sdf_n[interp_v.reshape(-1)].reshape(-1, 2, 1)
|
||||
edges_to_interp_sdf[:, -1] *= -1
|
||||
|
||||
denominator = edges_to_interp_sdf.sum(1, keepdim=True)
|
||||
|
||||
edges_to_interp_sdf = torch.flip(edges_to_interp_sdf, [1]) / denominator
|
||||
verts = (edges_to_interp * edges_to_interp_sdf).sum(1)
|
||||
|
||||
idx_map = idx_map.reshape(-1, 6)
|
||||
|
||||
v_id = torch.pow(2, torch.arange(4, dtype=torch.long, device=pos_nx3.device))
|
||||
tetindex = (occ_fx4[valid_tets] * v_id.unsqueeze(0)).sum(-1)
|
||||
num_triangles = self.num_triangles_table[tetindex]
|
||||
|
||||
# Generate triangle indices
|
||||
faces = torch.cat(
|
||||
(
|
||||
torch.gather(
|
||||
input=idx_map[num_triangles == 1],
|
||||
dim=1,
|
||||
index=self.triangle_table[tetindex[num_triangles == 1]][:, :3],
|
||||
).reshape(-1, 3),
|
||||
torch.gather(
|
||||
input=idx_map[num_triangles == 2],
|
||||
dim=1,
|
||||
index=self.triangle_table[tetindex[num_triangles == 2]][:, :6],
|
||||
).reshape(-1, 3),
|
||||
),
|
||||
dim=0,
|
||||
)
|
||||
|
||||
return verts, faces
|
||||
|
||||
def forward(
|
||||
self,
|
||||
level: Float[Tensor, "N3 1"],
|
||||
deformation: Optional[Float[Tensor, "N3 3"]] = None,
|
||||
) -> Mesh:
|
||||
if deformation is not None:
|
||||
grid_vertices = self.grid_vertices + self.normalize_grid_deformation(
|
||||
deformation
|
||||
)
|
||||
else:
|
||||
grid_vertices = self.grid_vertices
|
||||
|
||||
v_pos, t_pos_idx = self._forward(grid_vertices, level, self.indices)
|
||||
|
||||
mesh = Mesh(
|
||||
v_pos=v_pos,
|
||||
t_pos_idx=t_pos_idx,
|
||||
# extras
|
||||
grid_vertices=grid_vertices,
|
||||
tet_edges=self.all_edges,
|
||||
grid_level=level,
|
||||
grid_deformation=deformation,
|
||||
)
|
||||
|
||||
return mesh
|
||||
@@ -0,0 +1,17 @@
|
||||
from ..utils.ops import scale_tensor
|
||||
from ..utils.misc import get_device
|
||||
|
||||
|
||||
class LPIPS:
|
||||
def __init__(self):
|
||||
import lpips
|
||||
self.model = lpips.LPIPS(net="vgg").to(get_device())
|
||||
self.model.eval()
|
||||
for params in self.model.parameters():
|
||||
params.requires_grad = False
|
||||
self.model_input_range = (-1, 1)
|
||||
|
||||
def __call__(self, x1, x2, return_layers=False, input_range=(0, 1)):
|
||||
x1 = scale_tensor(x1, input_range, self.model_input_range)
|
||||
x2 = scale_tensor(x2, input_range, self.model_input_range)
|
||||
return self.model(x1, x2, retPerLayer=return_layers, normalize=False)
|
||||
@@ -0,0 +1,23 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ...utils.base import BaseModule
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class BaseMaterial(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
pass
|
||||
|
||||
cfg: Config
|
||||
requires_normal: bool = False
|
||||
requires_tangent: bool = False
|
||||
|
||||
def configure(self):
|
||||
pass
|
||||
|
||||
def forward(self, *args, **kwargs) -> Float[Tensor, "*B 3"]:
|
||||
raise NotImplementedError
|
||||
|
||||
def export(self, *args, **kwargs) -> Dict[str, Any]:
|
||||
return {}
|
||||
@@ -0,0 +1,60 @@
|
||||
import random
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from .base import BaseMaterial
|
||||
from ..networks import get_encoding, get_mlp
|
||||
from ...utils.ops import dot, get_activation
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class NoMaterial(BaseMaterial):
|
||||
@dataclass
|
||||
class Config(BaseMaterial.Config):
|
||||
n_output_dims: int = 3
|
||||
color_activation: str = "sigmoid"
|
||||
input_feature_dims: Optional[int] = None
|
||||
mlp_network_config: Optional[dict] = None
|
||||
requires_normal: bool = False
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
self.use_network = False
|
||||
if (
|
||||
self.cfg.input_feature_dims is not None
|
||||
and self.cfg.mlp_network_config is not None
|
||||
):
|
||||
self.network = get_mlp(
|
||||
self.cfg.input_feature_dims,
|
||||
self.cfg.n_output_dims,
|
||||
self.cfg.mlp_network_config,
|
||||
)
|
||||
self.use_network = True
|
||||
self.requires_normal = self.cfg.requires_normal
|
||||
|
||||
def forward(
|
||||
self, features: Float[Tensor, "B ... Nf"], **kwargs
|
||||
) -> Float[Tensor, "B ... Nc"]:
|
||||
if not self.use_network:
|
||||
assert (
|
||||
features.shape[-1] == self.cfg.n_output_dims
|
||||
), f"Expected {self.cfg.n_output_dims} output dims, only got {features.shape[-1]} dims input."
|
||||
color = get_activation(self.cfg.color_activation)(features)
|
||||
else:
|
||||
color = self.network(features.view(-1, features.shape[-1])).view(
|
||||
*features.shape[:-1], self.cfg.n_output_dims
|
||||
)
|
||||
color = get_activation(self.cfg.color_activation)(color)
|
||||
return color
|
||||
|
||||
def export(self, features: Float[Tensor, "*N Nf"], **kwargs) -> Dict[str, Any]:
|
||||
color = self(features, **kwargs).clamp(0, 1)
|
||||
assert color.shape[-1] >= 3, "Output color must have at least 3 channels"
|
||||
if color.shape[-1] > 3:
|
||||
lrm.warn("Output color has >3 channels, treating the first 3 as RGB")
|
||||
return {"albedo": color[..., :3]}
|
||||
@@ -0,0 +1,470 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ..utils.ops import dot
|
||||
from ..utils.typing import *
|
||||
from ..utils.misc import time_recorder as tr, time_recorder_enabled
|
||||
|
||||
|
||||
class Mesh:
|
||||
def __init__(
|
||||
self, v_pos: Float[Tensor, "Nv 3"], t_pos_idx: Integer[Tensor, "Nf 3"], **kwargs
|
||||
) -> None:
|
||||
self.v_pos: Float[Tensor, "Nv 3"] = v_pos
|
||||
self.t_pos_idx: Integer[Tensor, "Nf 3"] = t_pos_idx
|
||||
self._v_nrm: Optional[Float[Tensor, "Nv 3"]] = None
|
||||
self._v_tng: Optional[Float[Tensor, "Nv 3"]] = None
|
||||
self._v_tex: Optional[Float[Tensor, "Nt 3"]] = None
|
||||
self._t_tex_idx: Optional[Float[Tensor, "Nf 3"]] = None
|
||||
self._v_rgb: Optional[Float[Tensor, "Nv 3"]] = None
|
||||
self._edges: Optional[Integer[Tensor, "Ne 2"]] = None
|
||||
self.extras: Dict[str, Any] = {}
|
||||
for k, v in kwargs.items():
|
||||
self.add_extra(k, v)
|
||||
|
||||
def add_extra(self, k, v) -> None:
|
||||
self.extras[k] = v
|
||||
|
||||
def remove_outlier(self, outlier_n_faces_threshold: Union[int, float]) -> Mesh:
|
||||
if self.requires_grad:
|
||||
lrm.debug("Mesh is differentiable, not removing outliers")
|
||||
return self
|
||||
|
||||
# use trimesh to first split the mesh into connected components
|
||||
# then remove the components with less than n_face_threshold faces
|
||||
import trimesh
|
||||
|
||||
# construct a trimesh object
|
||||
mesh = trimesh.Trimesh(
|
||||
vertices=self.v_pos.detach().cpu().numpy(),
|
||||
faces=self.t_pos_idx.detach().cpu().numpy(),
|
||||
)
|
||||
|
||||
# split the mesh into connected components
|
||||
components = mesh.split(only_watertight=False)
|
||||
# log the number of faces in each component
|
||||
lrm.debug(
|
||||
"Mesh has {} components, with faces: {}".format(
|
||||
len(components), [c.faces.shape[0] for c in components]
|
||||
)
|
||||
)
|
||||
|
||||
n_faces_threshold: int
|
||||
if isinstance(outlier_n_faces_threshold, float):
|
||||
# set the threshold to the number of faces in the largest component multiplied by outlier_n_faces_threshold
|
||||
n_faces_threshold = int(
|
||||
max([c.faces.shape[0] for c in components]) * outlier_n_faces_threshold
|
||||
)
|
||||
else:
|
||||
# set the threshold directly to outlier_n_faces_threshold
|
||||
n_faces_threshold = outlier_n_faces_threshold
|
||||
|
||||
# log the threshold
|
||||
lrm.debug(
|
||||
"Removing components with less than {} faces".format(n_faces_threshold)
|
||||
)
|
||||
|
||||
# remove the components with less than n_face_threshold faces
|
||||
components = [c for c in components if c.faces.shape[0] >= n_faces_threshold]
|
||||
|
||||
# log the number of faces in each component after removing outliers
|
||||
lrm.debug(
|
||||
"Mesh has {} components after removing outliers, with faces: {}".format(
|
||||
len(components), [c.faces.shape[0] for c in components]
|
||||
)
|
||||
)
|
||||
# merge the components
|
||||
mesh = trimesh.util.concatenate(components)
|
||||
|
||||
# convert back to our mesh format
|
||||
v_pos = torch.from_numpy(mesh.vertices).to(self.v_pos)
|
||||
t_pos_idx = torch.from_numpy(mesh.faces).to(self.t_pos_idx)
|
||||
|
||||
clean_mesh = Mesh(v_pos, t_pos_idx)
|
||||
# keep the extras unchanged
|
||||
|
||||
if len(self.extras) > 0:
|
||||
clean_mesh.extras = self.extras
|
||||
lrm.debug(
|
||||
f"The following extra attributes are inherited from the original mesh unchanged: {list(self.extras.keys())}"
|
||||
)
|
||||
return clean_mesh
|
||||
|
||||
def subdivide(self):
|
||||
if self.requires_grad:
|
||||
lrm.debug("Mesh is differentiable, not performing subdivision")
|
||||
return self
|
||||
|
||||
import trimesh
|
||||
|
||||
mesh = trimesh.Trimesh(
|
||||
vertices=self.v_pos.detach().cpu().numpy(),
|
||||
faces=self.t_pos_idx.detach().cpu().numpy(),
|
||||
)
|
||||
|
||||
mesh.subdivide_loop()
|
||||
|
||||
v_pos = torch.from_numpy(mesh.vertices).to(self.v_pos)
|
||||
t_pos_idx = torch.from_numpy(mesh.faces).to(self.t_pos_idx)
|
||||
|
||||
subdivided_mesh = Mesh(v_pos, t_pos_idx)
|
||||
|
||||
if len(self.extras) > 0:
|
||||
subdivided_mesh.extras = self.extras
|
||||
lrm.debug(
|
||||
f"The following extra attributes are inherited from the original mesh unchanged: {list(self.extras.keys())}"
|
||||
)
|
||||
|
||||
return subdivided_mesh
|
||||
|
||||
def post_process(self, options):
|
||||
if self.requires_grad:
|
||||
lrm.debug("Mesh is differentiable, not performing post processing")
|
||||
return self
|
||||
|
||||
from extern.mesh_process.MeshProcess import process_mesh
|
||||
|
||||
v_pos, t_pos_idx = process_mesh(
|
||||
vertices=self.v_pos.detach().cpu().numpy(),
|
||||
faces=self.t_pos_idx.detach().cpu().numpy(),
|
||||
**options,
|
||||
)
|
||||
|
||||
v_pos = torch.from_numpy(v_pos).to(self.v_pos).contiguous()
|
||||
t_pos_idx = torch.from_numpy(t_pos_idx).to(self.t_pos_idx).contiguous()
|
||||
|
||||
processed_mesh = Mesh(v_pos, t_pos_idx)
|
||||
|
||||
if len(self.extras) > 0:
|
||||
processed_mesh.extras = self.extras
|
||||
lrm.debug(
|
||||
f"The following extra attributes are inherited from the original mesh unchanged: {list(self.extras.keys())}"
|
||||
)
|
||||
|
||||
return processed_mesh
|
||||
|
||||
@property
|
||||
def requires_grad(self):
|
||||
return self.v_pos.requires_grad
|
||||
|
||||
@property
|
||||
def v_nrm(self):
|
||||
if self._v_nrm is None:
|
||||
self._v_nrm = self._compute_vertex_normal()
|
||||
return self._v_nrm
|
||||
|
||||
@property
|
||||
def v_tng(self):
|
||||
if self._v_tng is None:
|
||||
self._v_tng = self._compute_vertex_tangent()
|
||||
return self._v_tng
|
||||
|
||||
@property
|
||||
def v_tex(self):
|
||||
if self._v_tex is None:
|
||||
self._v_tex, self._t_tex_idx = self._unwrap_uv()
|
||||
return self._v_tex
|
||||
|
||||
@property
|
||||
def t_tex_idx(self):
|
||||
if self._t_tex_idx is None:
|
||||
self._v_tex, self._t_tex_idx = self._unwrap_uv()
|
||||
return self._t_tex_idx
|
||||
|
||||
@property
|
||||
def v_rgb(self):
|
||||
return self._v_rgb
|
||||
|
||||
@property
|
||||
def edges(self):
|
||||
if self._edges is None:
|
||||
self._edges = self._compute_edges()
|
||||
return self._edges
|
||||
|
||||
def _compute_vertex_normal(self):
|
||||
i0 = self.t_pos_idx[:, 0]
|
||||
i1 = self.t_pos_idx[:, 1]
|
||||
i2 = self.t_pos_idx[:, 2]
|
||||
|
||||
v0 = self.v_pos[i0, :]
|
||||
v1 = self.v_pos[i1, :]
|
||||
v2 = self.v_pos[i2, :]
|
||||
|
||||
face_normals = torch.cross(v1 - v0, v2 - v0)
|
||||
|
||||
# Splat face normals to vertices
|
||||
v_nrm = torch.zeros_like(self.v_pos)
|
||||
v_nrm.scatter_add_(0, i0[:, None].repeat(1, 3), face_normals)
|
||||
v_nrm.scatter_add_(0, i1[:, None].repeat(1, 3), face_normals)
|
||||
v_nrm.scatter_add_(0, i2[:, None].repeat(1, 3), face_normals)
|
||||
|
||||
# Normalize, replace zero (degenerated) normals with some default value
|
||||
v_nrm = torch.where(
|
||||
dot(v_nrm, v_nrm) > 1e-20, v_nrm, torch.as_tensor([0.0, 0.0, 1.0]).to(v_nrm)
|
||||
)
|
||||
v_nrm = F.normalize(v_nrm, dim=1)
|
||||
|
||||
if torch.is_anomaly_enabled():
|
||||
assert torch.all(torch.isfinite(v_nrm))
|
||||
|
||||
return v_nrm
|
||||
|
||||
def _compute_vertex_tangent(self):
|
||||
vn_idx = [None] * 3
|
||||
pos = [None] * 3
|
||||
tex = [None] * 3
|
||||
for i in range(0, 3):
|
||||
pos[i] = self.v_pos[self.t_pos_idx[:, i]]
|
||||
tex[i] = self.v_tex[self.t_tex_idx[:, i]]
|
||||
# t_nrm_idx is always the same as t_pos_idx
|
||||
vn_idx[i] = self.t_pos_idx[:, i]
|
||||
|
||||
tangents = torch.zeros_like(self.v_nrm)
|
||||
tansum = torch.zeros_like(self.v_nrm)
|
||||
|
||||
# Compute tangent space for each triangle
|
||||
uve1 = tex[1] - tex[0]
|
||||
uve2 = tex[2] - tex[0]
|
||||
pe1 = pos[1] - pos[0]
|
||||
pe2 = pos[2] - pos[0]
|
||||
|
||||
nom = pe1 * uve2[..., 1:2] - pe2 * uve1[..., 1:2]
|
||||
denom = uve1[..., 0:1] * uve2[..., 1:2] - uve1[..., 1:2] * uve2[..., 0:1]
|
||||
|
||||
# Avoid division by zero for degenerated texture coordinates
|
||||
tang = nom / torch.where(
|
||||
denom > 0.0, torch.clamp(denom, min=1e-6), torch.clamp(denom, max=-1e-6)
|
||||
)
|
||||
|
||||
# Update all 3 vertices
|
||||
for i in range(0, 3):
|
||||
idx = vn_idx[i][:, None].repeat(1, 3)
|
||||
tangents.scatter_add_(0, idx, tang) # tangents[n_i] = tangents[n_i] + tang
|
||||
tansum.scatter_add_(
|
||||
0, idx, torch.ones_like(tang)
|
||||
) # tansum[n_i] = tansum[n_i] + 1
|
||||
tangents = tangents / tansum
|
||||
|
||||
# Normalize and make sure tangent is perpendicular to normal
|
||||
tangents = F.normalize(tangents, dim=1)
|
||||
tangents = F.normalize(tangents - dot(tangents, self.v_nrm) * self.v_nrm)
|
||||
|
||||
if torch.is_anomaly_enabled():
|
||||
assert torch.all(torch.isfinite(tangents))
|
||||
|
||||
return tangents
|
||||
|
||||
def _unwrap_uv_open3d(
|
||||
self
|
||||
):
|
||||
import open3d as o3d
|
||||
mesh = o3d.t.geometry.TriangleMesh()
|
||||
mesh.vertex.positions = o3d.core.Tensor(self.v_pos.detach().cpu().numpy())
|
||||
mesh.triangle.indices = o3d.core.Tensor(self.t_pos_idx.cpu().numpy())
|
||||
mesh.compute_uvatlas(size=1024)
|
||||
texture_uvs = torch.from_numpy(mesh.triangle.texture_uvs.numpy()).reshape(-1, 2).cuda()
|
||||
indices = torch.arange(self.t_pos_idx.shape[0] * 3).reshape(-1, 3).to(torch.int64).cuda()
|
||||
# Add a wood texture and visualize
|
||||
return texture_uvs, indices
|
||||
|
||||
def _unwrap_uv_xatlas(
|
||||
self, xatlas_chart_options: dict = {}, xatlas_pack_options: dict = {}
|
||||
):
|
||||
lrm.info("Using xatlas to perform UV unwrapping, may take a while ...")
|
||||
|
||||
import xatlas
|
||||
|
||||
atlas = xatlas.Atlas()
|
||||
atlas.add_mesh(
|
||||
self.v_pos.detach().cpu().numpy(),
|
||||
self.t_pos_idx.cpu().numpy(),
|
||||
)
|
||||
co = xatlas.ChartOptions()
|
||||
po = xatlas.PackOptions()
|
||||
for k, v in xatlas_chart_options.items():
|
||||
setattr(co, k, v)
|
||||
for k, v in xatlas_pack_options.items():
|
||||
setattr(po, k, v)
|
||||
atlas.generate(co, po)
|
||||
vmapping, indices, uvs = atlas.get_mesh(0)
|
||||
vmapping = (
|
||||
torch.from_numpy(
|
||||
vmapping.astype(np.uint64, casting="same_kind").view(np.int64)
|
||||
)
|
||||
.to(self.v_pos.device)
|
||||
.long()
|
||||
)
|
||||
uvs = torch.from_numpy(uvs).to(self.v_pos.device).float()
|
||||
indices = (
|
||||
torch.from_numpy(
|
||||
indices.astype(np.uint64, casting="same_kind").view(np.int64)
|
||||
)
|
||||
.to(self.v_pos.device)
|
||||
.long()
|
||||
)
|
||||
return uvs, indices
|
||||
|
||||
def _unwrap_uv_smartuv(self, options: dict = {}):
|
||||
from extern.mesh_process.MeshProcess import (
|
||||
mesh_to_bpy,
|
||||
get_uv_from_bpy,
|
||||
bpy_context,
|
||||
bpy_export,
|
||||
)
|
||||
from CharacterGen.Stage_3D.lrm.utils.misc import time_recorder as tr
|
||||
|
||||
v_pos, t_pos_idx = self.v_pos.cpu().numpy(), self.t_pos_idx.cpu().numpy()
|
||||
with bpy_context():
|
||||
mesh_bpy = mesh_to_bpy("_", v_pos, t_pos_idx)
|
||||
v_tex = get_uv_from_bpy(mesh_bpy, **options).astype(np.float32)
|
||||
|
||||
assert v_tex.shape[0] == self.t_pos_idx.shape[0] * 3
|
||||
|
||||
t_tex_idx = torch.arange(
|
||||
self.t_pos_idx.shape[0] * 3, device=self.t_pos_idx.device, dtype=torch.long
|
||||
).reshape(-1, 3)
|
||||
|
||||
"""
|
||||
# super efficient de-duplication
|
||||
v_tex_u_uint32 = v_tex[..., 0].view(np.uint32)
|
||||
v_tex_v_uint32 = v_tex[..., 1].view(np.uint32)
|
||||
v_hashed = (v_tex_u_uint32.astype(np.uint64) << 32) | v_tex_v_uint32
|
||||
v_hashed = torch.from_numpy(v_hashed.view(np.int64)).to(self.v_pos.device)
|
||||
|
||||
v_tex = torch.from_numpy(v_tex).to(
|
||||
device=self.v_pos.device, dtype=torch.float32
|
||||
)
|
||||
t_pos_idx_f3 = torch.arange(
|
||||
self.t_pos_idx.shape[0] * 3, device=self.t_pos_idx.device, dtype=torch.long
|
||||
).reshape(-1, 3)
|
||||
v_pos_f3 = self.v_pos[self.t_pos_idx].reshape(-1, 3)
|
||||
|
||||
# super efficient de-duplication
|
||||
v_hashed_dedup, inverse_indices = torch.unique(v_hashed, return_inverse=True)
|
||||
dedup_size, full_size = v_hashed_dedup.shape[0], inverse_indices.shape[0]
|
||||
indices = torch.scatter_reduce(
|
||||
torch.full(
|
||||
[dedup_size],
|
||||
fill_value=full_size,
|
||||
device=inverse_indices.device,
|
||||
dtype=torch.long,
|
||||
),
|
||||
index=inverse_indices,
|
||||
src=torch.arange(
|
||||
full_size, device=inverse_indices.device, dtype=torch.int64
|
||||
),
|
||||
dim=0,
|
||||
reduce="amin",
|
||||
)
|
||||
v_tex = v_tex[indices]
|
||||
t_tex_idx = inverse_indices.reshape(-1, 3)
|
||||
|
||||
v_pos = v_pos_f3[indices]
|
||||
t_pos_idx = inverse_indices[t_pos_idx_f3]
|
||||
"""
|
||||
|
||||
return self.v_pos, self.t_pos_idx, v_tex, t_tex_idx
|
||||
|
||||
def unwrap_uv(
|
||||
self,
|
||||
method: str,
|
||||
xatlas_chart_options: dict = {},
|
||||
xatlas_pack_options: dict = {},
|
||||
smartuv_options: dict = {},
|
||||
):
|
||||
if method == "xatlas":
|
||||
with time_recorder_enabled():
|
||||
tr.start("UV unwrapping xatlas")
|
||||
self._v_tex, self._t_tex_idx = self._unwrap_uv_xatlas(
|
||||
xatlas_chart_options, xatlas_pack_options
|
||||
)
|
||||
tr.end("UV unwrapping xatlas")
|
||||
elif method == "open3d":
|
||||
with time_recorder_enabled():
|
||||
tr.start("UV unwrapping o3d")
|
||||
self._v_tex, self._t_tex_idx = self._unwrap_uv_open3d()
|
||||
tr.end("UV unwrapping o3d")
|
||||
elif method == "smartuv":
|
||||
with time_recorder_enabled():
|
||||
tr.start("UV unwrapping smartuv")
|
||||
(
|
||||
self.v_pos,
|
||||
self.t_pos_idx,
|
||||
self._v_tex,
|
||||
self._t_tex_idx,
|
||||
) = self._unwrap_uv_smartuv(smartuv_options)
|
||||
tr.end("UV unwrapping smartuv")
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def set_vertex_color(self, v_rgb):
|
||||
assert v_rgb.shape[0] == self.v_pos.shape[0]
|
||||
self._v_rgb = v_rgb
|
||||
|
||||
def set_uv(self, v_tex, t_tex_idx):
|
||||
self._v_tex = v_tex
|
||||
self._t_tex_idx = t_tex_idx
|
||||
|
||||
@property
|
||||
def has_uv(self):
|
||||
return self._v_tex is not None and self._t_tex_idx is not None
|
||||
|
||||
def _compute_edges(self):
|
||||
# Compute edges
|
||||
edges = torch.cat(
|
||||
[
|
||||
self.t_pos_idx[:, [0, 1]],
|
||||
self.t_pos_idx[:, [1, 2]],
|
||||
self.t_pos_idx[:, [2, 0]],
|
||||
],
|
||||
dim=0,
|
||||
)
|
||||
edges = edges.sort()[0]
|
||||
edges = torch.unique(edges, dim=0)
|
||||
return edges
|
||||
|
||||
def normal_consistency(self) -> Float[Tensor, ""]:
|
||||
edge_nrm: Float[Tensor, "Ne 2 3"] = self.v_nrm[self.edges]
|
||||
nc = (
|
||||
1.0 - torch.cosine_similarity(edge_nrm[:, 0], edge_nrm[:, 1], dim=-1)
|
||||
).mean()
|
||||
return nc
|
||||
|
||||
def _laplacian_uniform(self):
|
||||
# from stable-dreamfusion
|
||||
# https://github.com/ashawkey/stable-dreamfusion/blob/8fb3613e9e4cd1ded1066b46e80ca801dfb9fd06/nerf/renderer.py#L224
|
||||
verts, faces = self.v_pos, self.t_pos_idx
|
||||
|
||||
V = verts.shape[0]
|
||||
F = faces.shape[0]
|
||||
|
||||
# Neighbor indices
|
||||
ii = faces[:, [1, 2, 0]].flatten()
|
||||
jj = faces[:, [2, 0, 1]].flatten()
|
||||
adj = torch.stack([torch.cat([ii, jj]), torch.cat([jj, ii])], dim=0).unique(
|
||||
dim=1
|
||||
)
|
||||
adj_values = torch.ones(adj.shape[1]).to(verts)
|
||||
|
||||
# Diagonal indices
|
||||
diag_idx = adj[0]
|
||||
|
||||
# Build the sparse matrix
|
||||
idx = torch.cat((adj, torch.stack((diag_idx, diag_idx), dim=0)), dim=1)
|
||||
values = torch.cat((-adj_values, adj_values))
|
||||
|
||||
# The coalesce operation sums the duplicate indices, resulting in the
|
||||
# correct diagonal
|
||||
return torch.sparse_coo_tensor(idx, values, (V, V)).coalesce()
|
||||
|
||||
def laplacian(self) -> Float[Tensor, ""]:
|
||||
with torch.no_grad():
|
||||
L = self._laplacian_uniform()
|
||||
loss = L.mm(self.v_pos)
|
||||
loss = loss.norm(dim=1)
|
||||
loss = loss.mean()
|
||||
return loss
|
||||
@@ -0,0 +1,390 @@
|
||||
from dataclasses import dataclass, field
|
||||
from copy import deepcopy
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from einops import rearrange
|
||||
|
||||
from ..utils.base import BaseModule
|
||||
from ..utils.ops import get_activation
|
||||
from ..utils.typing import *
|
||||
|
||||
|
||||
class TriplaneUpsampleNetwork(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
in_channels: int = 1024
|
||||
out_channels: int = 80
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.upsample = nn.ConvTranspose2d(
|
||||
self.cfg.in_channels, self.cfg.out_channels, kernel_size=2, stride=2
|
||||
)
|
||||
|
||||
def forward(
|
||||
self, triplanes: Float[Tensor, "B 3 Ci Hp Wp"]
|
||||
) -> Float[Tensor, "B 3 Co Hp2 Wp2"]:
|
||||
triplanes_up = rearrange(
|
||||
self.upsample(
|
||||
rearrange(triplanes, "B Np Ci Hp Wp -> (B Np) Ci Hp Wp", Np=3)
|
||||
),
|
||||
"(B Np) Co Hp Wp -> B Np Co Hp Wp",
|
||||
Np=3,
|
||||
)
|
||||
return triplanes_up
|
||||
|
||||
|
||||
class MLP(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim_in: int,
|
||||
dim_out: int,
|
||||
n_neurons: int,
|
||||
n_hidden_layers: int,
|
||||
activation: str = "relu",
|
||||
output_activation: Optional[str] = None,
|
||||
bias: bool = True,
|
||||
weight_init: Optional[str] = "kaiming_uniform",
|
||||
bias_init: Optional[str] = None,
|
||||
):
|
||||
super().__init__()
|
||||
layers = [
|
||||
self.make_linear(
|
||||
dim_in,
|
||||
n_neurons,
|
||||
is_first=True,
|
||||
is_last=False,
|
||||
bias=bias,
|
||||
weight_init=weight_init,
|
||||
bias_init=bias_init,
|
||||
),
|
||||
self.make_activation(activation),
|
||||
]
|
||||
for i in range(n_hidden_layers - 1):
|
||||
layers += [
|
||||
self.make_linear(
|
||||
n_neurons,
|
||||
n_neurons,
|
||||
is_first=False,
|
||||
is_last=False,
|
||||
bias=bias,
|
||||
weight_init=weight_init,
|
||||
bias_init=bias_init,
|
||||
),
|
||||
self.make_activation(activation),
|
||||
]
|
||||
layers += [
|
||||
self.make_linear(
|
||||
n_neurons,
|
||||
dim_out,
|
||||
is_first=False,
|
||||
is_last=True,
|
||||
bias=bias,
|
||||
weight_init=weight_init,
|
||||
bias_init=bias_init,
|
||||
)
|
||||
]
|
||||
self.layers = nn.Sequential(*layers)
|
||||
self.output_activation = get_activation(output_activation)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.layers(x)
|
||||
x = self.output_activation(x)
|
||||
return x
|
||||
|
||||
def make_linear(
|
||||
self,
|
||||
dim_in,
|
||||
dim_out,
|
||||
is_first,
|
||||
is_last,
|
||||
bias=True,
|
||||
weight_init=None,
|
||||
bias_init=None,
|
||||
):
|
||||
layer = nn.Linear(dim_in, dim_out, bias=bias)
|
||||
|
||||
if weight_init is None:
|
||||
pass
|
||||
elif weight_init == "kaiming_uniform":
|
||||
torch.nn.init.kaiming_uniform_(layer.weight, nonlinearity="relu")
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if bias:
|
||||
if bias_init is None:
|
||||
pass
|
||||
elif bias_init == "zero":
|
||||
torch.nn.init.zeros_(layer.bias)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
return layer
|
||||
|
||||
def make_activation(self, activation):
|
||||
if activation == "relu":
|
||||
return nn.ReLU(inplace=True)
|
||||
elif activation == "silu":
|
||||
return nn.SiLU(inplace=True)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass
|
||||
class HeadSpec:
|
||||
name: str
|
||||
out_channels: int
|
||||
n_hidden_layers: int
|
||||
output_activation: Optional[str] = None
|
||||
|
||||
|
||||
class MultiHeadMLP(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
in_channels: int = 0
|
||||
n_neurons: int = 0
|
||||
n_hidden_layers_share: int = 0
|
||||
heads: List[HeadSpec] = field(default_factory=lambda: [])
|
||||
activation: str = "relu"
|
||||
bias: bool = True
|
||||
weight_init: Optional[str] = "kaiming_uniform"
|
||||
bias_init: Optional[str] = None
|
||||
chunk_mode: Optional[str] = None
|
||||
chunk_size: int = -1
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
shared_layers = [
|
||||
self.make_linear(
|
||||
self.cfg.in_channels,
|
||||
self.cfg.n_neurons,
|
||||
bias=self.cfg.bias,
|
||||
weight_init=self.cfg.weight_init,
|
||||
bias_init=self.cfg.bias_init,
|
||||
),
|
||||
self.make_activation(self.cfg.activation),
|
||||
]
|
||||
for i in range(self.cfg.n_hidden_layers_share - 1):
|
||||
shared_layers += [
|
||||
self.make_linear(
|
||||
self.cfg.n_neurons,
|
||||
self.cfg.n_neurons,
|
||||
bias=self.cfg.bias,
|
||||
weight_init=self.cfg.weight_init,
|
||||
bias_init=self.cfg.bias_init,
|
||||
),
|
||||
self.make_activation(self.cfg.activation),
|
||||
]
|
||||
self.shared_layers = nn.Sequential(*shared_layers)
|
||||
|
||||
assert len(self.cfg.heads) > 0
|
||||
heads = {}
|
||||
for head in self.cfg.heads:
|
||||
head_layers = []
|
||||
for i in range(head.n_hidden_layers):
|
||||
head_layers += [
|
||||
self.make_linear(
|
||||
self.cfg.n_neurons,
|
||||
self.cfg.n_neurons,
|
||||
bias=self.cfg.bias,
|
||||
weight_init=self.cfg.weight_init,
|
||||
bias_init=self.cfg.bias_init,
|
||||
),
|
||||
self.make_activation(self.cfg.activation),
|
||||
]
|
||||
head_layers += [
|
||||
self.make_linear(
|
||||
self.cfg.n_neurons,
|
||||
head.out_channels,
|
||||
bias=self.cfg.bias,
|
||||
weight_init=self.cfg.weight_init,
|
||||
bias_init=self.cfg.bias_init,
|
||||
),
|
||||
]
|
||||
heads[head.name] = nn.Sequential(*head_layers)
|
||||
self.heads = nn.ModuleDict(heads)
|
||||
|
||||
if self.cfg.chunk_mode is not None:
|
||||
assert self.cfg.chunk_size > 0
|
||||
|
||||
def make_linear(
|
||||
self,
|
||||
dim_in,
|
||||
dim_out,
|
||||
bias=True,
|
||||
weight_init=None,
|
||||
bias_init=None,
|
||||
):
|
||||
layer = nn.Linear(dim_in, dim_out, bias=bias)
|
||||
|
||||
if weight_init is None:
|
||||
pass
|
||||
elif weight_init == "kaiming_uniform":
|
||||
torch.nn.init.kaiming_uniform_(layer.weight, nonlinearity="relu")
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if bias:
|
||||
if bias_init is None:
|
||||
pass
|
||||
elif bias_init == "zero":
|
||||
torch.nn.init.zeros_(layer.bias)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
return layer
|
||||
|
||||
def make_activation(self, activation):
|
||||
if activation == "relu":
|
||||
return nn.ReLU(inplace=True)
|
||||
elif activation == "silu":
|
||||
return nn.SiLU(inplace=True)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def forward(
|
||||
self, x, include: Optional[List] = None, exclude: Optional[List] = None
|
||||
):
|
||||
inp_shape = x.shape[:-1]
|
||||
x = x.reshape(-1, x.shape[-1])
|
||||
|
||||
if self.cfg.chunk_mode is None:
|
||||
shared_features = self.shared_layers(x)
|
||||
elif self.cfg.chunk_mode == "deferred":
|
||||
shared_features = DeferredFunc.apply(
|
||||
self.shared_layers, x, self.cfg.chunk_size
|
||||
)
|
||||
elif self.cfg.chunk_mode == "checkpointing":
|
||||
shared_features = apply_batch_checkpointing(
|
||||
self.shared_layers, x, self.cfg.chunk_size
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
shared_features = shared_features.reshape(*inp_shape, -1)
|
||||
|
||||
if include is not None and exclude is not None:
|
||||
raise ValueError("Cannot specify both include and exclude.")
|
||||
if include is not None:
|
||||
heads = [h for h in self.cfg.heads if h.name in include]
|
||||
elif exclude is not None:
|
||||
heads = [h for h in self.cfg.heads if h.name not in exclude]
|
||||
else:
|
||||
heads = self.cfg.heads
|
||||
|
||||
out = {
|
||||
head.name: get_activation(head.output_activation)(
|
||||
self.heads[head.name](shared_features)
|
||||
)
|
||||
for head in heads
|
||||
}
|
||||
"""
|
||||
# TypeError
|
||||
if self.cfg.chunk_mode is None:
|
||||
out = {
|
||||
head.name: get_activation(head.output_activation)(
|
||||
self.heads[head.name](shared_features)
|
||||
)
|
||||
for head in heads
|
||||
}
|
||||
elif self.cfg.chunk_mode == "deferred":
|
||||
out = {
|
||||
head.name: get_activation(head.output_activation)(
|
||||
DeferredFunc.apply(self.heads[head.name], shared_features, self.cfg.chunk_size)
|
||||
)
|
||||
for head in heads
|
||||
}
|
||||
else:
|
||||
raise NotImplementedError
|
||||
"""
|
||||
return out
|
||||
|
||||
|
||||
class DeferredFunc(torch.autograd.Function):
|
||||
# Note that forward, setup_context, and backward are @staticmethods
|
||||
@staticmethod
|
||||
def forward(ctx, model, x, chunk_size):
|
||||
model_copy = deepcopy(model)
|
||||
model_copy.requires_grad_(False)
|
||||
|
||||
ret = []
|
||||
x_split = torch.split(x, chunk_size, dim=0)
|
||||
|
||||
with torch.no_grad():
|
||||
for cur_x in x_split:
|
||||
ret.append(model_copy(cur_x))
|
||||
|
||||
ctx.model = model
|
||||
ctx.save_for_backward(x.detach(), torch.as_tensor(chunk_size))
|
||||
|
||||
ret = torch.cat(ret, dim=0)
|
||||
|
||||
return ret
|
||||
|
||||
# This function has only a single output, so it gets only one gradient
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
model = ctx.model
|
||||
x, chunk_size = ctx.saved_tensors
|
||||
chunk_size = chunk_size.item()
|
||||
|
||||
model_copy = deepcopy(model)
|
||||
|
||||
x_split = torch.split(x, chunk_size, dim=0)
|
||||
grad_output_split = torch.split(grad_output, chunk_size, 0)
|
||||
grad_input_split = []
|
||||
|
||||
with torch.set_grad_enabled(True):
|
||||
model_copy.requires_grad_(True)
|
||||
model_copy.zero_grad()
|
||||
for cur_x, cur_grad_output in zip(x_split, grad_output_split):
|
||||
cur_x.requires_grad_(True)
|
||||
cur_y = model_copy(cur_x)
|
||||
cur_y.backward(cur_grad_output)
|
||||
|
||||
grad_input_split.append(cur_x.grad.clone())
|
||||
|
||||
grad_input = torch.cat(grad_input_split, dim=0)
|
||||
|
||||
model_copy_params = list(model_copy.parameters())
|
||||
model_params = list(model.parameters())
|
||||
|
||||
for param, param_copy in zip(model_params, model_copy_params):
|
||||
if param.grad is None:
|
||||
param.grad = param_copy.grad.clone()
|
||||
else:
|
||||
param.grad.add_(param_copy.grad)
|
||||
|
||||
return None, grad_input, None
|
||||
|
||||
|
||||
def apply_batch_checkpointing(func, x, chunk_size):
|
||||
if chunk_size >= len(x):
|
||||
# return func(x)
|
||||
return torch.utils.checkpoint.checkpoint(func, x, use_reentrant=False)
|
||||
|
||||
x_split = torch.split(x, chunk_size, dim=0)
|
||||
|
||||
def cat_and_query(y_all, x):
|
||||
return torch.cat([y_all, func(x)])
|
||||
|
||||
y_all = func(x_split[0])
|
||||
for cur_x in x_split[1:]:
|
||||
y_all = torch.utils.checkpoint.checkpoint(
|
||||
cat_and_query, y_all, cur_x, use_reentrant=False
|
||||
)
|
||||
|
||||
return y_all
|
||||
|
||||
|
||||
def get_encoding(n_input_dims: int, config) -> nn.Module:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def get_mlp(n_input_dims, n_output_dims, config) -> nn.Module:
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,66 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
|
||||
from ..networks import MultiHeadMLP
|
||||
from ..background.base import BaseBackground
|
||||
from ..materials.base import BaseMaterial
|
||||
from ...utils.base import BaseModule
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class BaseRenderer(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
radius: float = 1.0
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(
|
||||
self,
|
||||
decoder: MultiHeadMLP,
|
||||
material: BaseMaterial,
|
||||
background: BaseBackground,
|
||||
) -> None:
|
||||
super().configure()
|
||||
|
||||
self.set_decoder(decoder)
|
||||
self.set_material(material)
|
||||
self.set_background(background)
|
||||
|
||||
# set up bounding box
|
||||
self.bbox: Float[Tensor, "2 3"]
|
||||
self.register_buffer(
|
||||
"bbox",
|
||||
torch.as_tensor(
|
||||
[
|
||||
[-self.cfg.radius, -self.cfg.radius, -self.cfg.radius],
|
||||
[self.cfg.radius, self.cfg.radius, self.cfg.radius],
|
||||
],
|
||||
dtype=torch.float32,
|
||||
),
|
||||
)
|
||||
|
||||
def forward(self, *args, **kwargs) -> Dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def decoder(self) -> MultiHeadMLP:
|
||||
return self.non_module("decoder")
|
||||
|
||||
@property
|
||||
def material(self) -> BaseMaterial:
|
||||
return self.non_module("material")
|
||||
|
||||
@property
|
||||
def background(self) -> BaseBackground:
|
||||
return self.non_module("background")
|
||||
|
||||
def set_decoder(self, decoder: MultiHeadMLP) -> None:
|
||||
self.register_non_module("decoder", decoder)
|
||||
|
||||
def set_material(self, material: BaseMaterial) -> None:
|
||||
self.register_non_module("material", material)
|
||||
|
||||
def set_background(self, background: BaseBackground) -> None:
|
||||
self.register_non_module("background", background)
|
||||
@@ -0,0 +1,369 @@
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from einops import rearrange, reduce
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ..renderers.base import BaseRenderer
|
||||
from ..isosurface import MarchingTetrahedraHelper
|
||||
from ...utils.ops import (
|
||||
get_activation,
|
||||
scale_tensor,
|
||||
dot,
|
||||
chunk_batch
|
||||
)
|
||||
from ...utils.rasterize import NVDiffRasterizerContext
|
||||
from ..mesh import Mesh
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class TriplaneDMTetRenderer(BaseRenderer):
|
||||
@dataclass
|
||||
class Config(BaseRenderer.Config):
|
||||
feature_reduction: str = "concat"
|
||||
sdf_activation: Optional[str] = None
|
||||
sdf_bias: Union[str, float] = 0.0
|
||||
sdf_bias_params: Any = None
|
||||
inside_out: bool = False
|
||||
|
||||
isosurface_resolution: int = 256
|
||||
tet_dir: str = "tets/"
|
||||
enable_isosurface_grid_deformation: bool = False
|
||||
eval_chunk_size: int = 262144
|
||||
context_type: str = "gl"
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
super().configure(*args, **kwargs)
|
||||
|
||||
assert self.cfg.feature_reduction in ["concat", "mean"]
|
||||
|
||||
self.ctx = NVDiffRasterizerContext(self.cfg.context_type, self.device)
|
||||
self.isosurface_helper = MarchingTetrahedraHelper(
|
||||
self.cfg.isosurface_resolution,
|
||||
os.path.join(self.cfg.tet_dir, f"{self.cfg.isosurface_resolution}_tets.npz"),
|
||||
).to(self.device)
|
||||
|
||||
def query_triplane(
|
||||
self,
|
||||
positions: Float[Tensor, "*B N 3"],
|
||||
triplanes: Float[Tensor, "*B 3 Cp Hp Wp"],
|
||||
) -> Dict[str, Tensor]:
|
||||
batched = positions.ndim == 3
|
||||
if not batched:
|
||||
# no batch dimension
|
||||
triplanes = triplanes[None, ...]
|
||||
positions = positions[None, ...]
|
||||
# import ipdb
|
||||
# ipdb.set_trace()
|
||||
assert triplanes.ndim == 5 and positions.ndim == 3
|
||||
|
||||
# assume positions in [-1, 1]
|
||||
# normalized to (-1, 1) for grid sample
|
||||
positions = scale_tensor(
|
||||
positions, (-self.cfg.radius, self.cfg.radius), (-1, 1)
|
||||
)
|
||||
|
||||
indices2D: Float[Tensor, "B 3 N 2"] = torch.stack(
|
||||
(positions[..., [0, 1]], positions[..., [0, 2]], positions[..., [1, 2]]),
|
||||
dim=-3,
|
||||
)
|
||||
out: Float[Tensor, "B3 Cp 1 N"] = F.grid_sample(
|
||||
rearrange(triplanes, "B Np Cp Hp Wp -> (B Np) Cp Hp Wp", Np=3),
|
||||
rearrange(indices2D, "B Np N Nd -> (B Np) () N Nd", Np=3),
|
||||
align_corners=False,
|
||||
mode="bilinear",
|
||||
)
|
||||
if self.cfg.feature_reduction == "concat":
|
||||
out = rearrange(out, "(B Np) Cp () N -> B N (Np Cp)", Np=3)
|
||||
elif self.cfg.feature_reduction == "mean":
|
||||
out = reduce(out, "(B Np) Cp () N -> B N Cp", Np=3, reduction="mean")
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
net_out: Dict[str, Float[Tensor, "B N ..."]] = self.decoder(out)
|
||||
assert "sdf" in net_out
|
||||
net_out["sdf"] = get_activation(self.cfg.sdf_activation)(
|
||||
self.get_shifted_sdf(positions, net_out["sdf"])
|
||||
)
|
||||
|
||||
if not batched:
|
||||
net_out = {k: v.squeeze(0) for k, v in net_out.items()}
|
||||
|
||||
return net_out
|
||||
|
||||
def get_shifted_sdf(
|
||||
self, points: Float[Tensor, "*N Di"], sdf: Float[Tensor, "*N 1"]
|
||||
) -> Float[Tensor, "*N 1"]:
|
||||
sdf_bias: Union[float, Float[Tensor, "*N 1"]]
|
||||
if self.cfg.sdf_bias == "ellipsoid":
|
||||
assert (
|
||||
isinstance(self.cfg.sdf_bias_params, Sized)
|
||||
and len(self.cfg.sdf_bias_params) == 3
|
||||
)
|
||||
size = torch.as_tensor(self.cfg.sdf_bias_params).to(points)
|
||||
sdf_bias = ((points / size) ** 2).sum(
|
||||
dim=-1, keepdim=True
|
||||
).sqrt() - 1.0 # pseudo signed distance of an ellipsoid
|
||||
elif self.cfg.sdf_bias == "sphere":
|
||||
assert isinstance(self.cfg.sdf_bias_params, float)
|
||||
radius = self.cfg.sdf_bias_params
|
||||
sdf_bias = (points**2).sum(dim=-1, keepdim=True).sqrt() - radius
|
||||
elif isinstance(self.cfg.sdf_bias, float):
|
||||
sdf_bias = self.cfg.sdf_bias
|
||||
else:
|
||||
raise ValueError(f"Unknown sdf bias {self.cfg.sdf_bias}")
|
||||
return sdf + sdf_bias
|
||||
|
||||
def forward_single(
|
||||
self,
|
||||
triplane: Float[Tensor, "3 Cp Hp Wp"],
|
||||
mvp_mtx: Float[Tensor, "Nv 4 4"],
|
||||
camera_positions: Float[Tensor, "Nv 3"],
|
||||
height: int,
|
||||
width: int,
|
||||
background_color: Optional[Float[Tensor, "3"]],
|
||||
extra_sdf_query: Any = None,
|
||||
) -> Dict[str, Tensor]:
|
||||
Nv = mvp_mtx.shape[0]
|
||||
|
||||
out = {}
|
||||
|
||||
query_vertices = []
|
||||
query_sizes = []
|
||||
|
||||
grid_vertices = scale_tensor(
|
||||
self.isosurface_helper.grid_vertices,
|
||||
self.isosurface_helper.points_range,
|
||||
self.bbox,
|
||||
)
|
||||
|
||||
query_vertices.append(grid_vertices)
|
||||
query_sizes.append(len(grid_vertices))
|
||||
|
||||
if extra_sdf_query is not None:
|
||||
query_vertices.append(extra_sdf_query)
|
||||
query_sizes.append(len(extra_sdf_query))
|
||||
|
||||
query_vertices = torch.cat(query_vertices, dim=0)
|
||||
triplane_out = self.query_triplane(query_vertices, triplane)
|
||||
|
||||
all_sdf = triplane_out["sdf"]
|
||||
if extra_sdf_query is not None:
|
||||
sdf, sdf_ex_query = torch.split(all_sdf, query_sizes)
|
||||
else:
|
||||
sdf, sdf_ex_query = all_sdf, None
|
||||
|
||||
out.update({"sdf_ex_query": sdf_ex_query})
|
||||
|
||||
if self.cfg.enable_isosurface_grid_deformation:
|
||||
all_deformation = triplane_out["deformation"]
|
||||
if extra_sdf_query is not None:
|
||||
deformation, _ = torch.split(all_deformation, query_sizes)
|
||||
else:
|
||||
deformation, _ = all_deformation, None
|
||||
else:
|
||||
deformation = None
|
||||
|
||||
# Fix some sdf if we observe empty shape (full positive or full negative)
|
||||
pos_shape = torch.sum((sdf.squeeze(dim=-1) > 0).int(), dim=-1)
|
||||
neg_shape = torch.sum((sdf.squeeze(dim=-1) < 0).int(), dim=-1)
|
||||
zero_surface = torch.bitwise_or(pos_shape == 0, neg_shape == 0)
|
||||
if torch.sum(zero_surface).item() > 0:
|
||||
lrm.warn("Empty mesh! Fixing by adding fake faces.")
|
||||
sdf = torch.nan_to_num(sdf, nan=0.0, posinf=1.0, neginf=-1.0)
|
||||
update_sdf = torch.zeros_like(sdf)
|
||||
max_sdf = sdf.max()
|
||||
min_sdf = sdf.min()
|
||||
update_sdf[self.isosurface_helper.center_indices] += (
|
||||
-1.0 - max_sdf
|
||||
) # greater than zero
|
||||
update_sdf[self.isosurface_helper.boundary_indices] += (
|
||||
1.0 - min_sdf
|
||||
) # smaller than zero
|
||||
new_sdf = sdf.clone().detach()
|
||||
if zero_surface:
|
||||
new_sdf += update_sdf
|
||||
update_mask = (update_sdf == 0).float()
|
||||
# Regulraization here is used to push the sdf to be a different sign (make it not fully positive or fully negative)
|
||||
sdf_reg_loss = sdf.abs().mean()
|
||||
sdf_reg_loss = sdf_reg_loss * zero_surface.float()
|
||||
sdf = sdf * update_mask + new_sdf * (1 - update_mask)
|
||||
lrm.debug(
|
||||
"max sdf: {}, min sdf: {}".format(sdf.max().item(), sdf.min().item())
|
||||
)
|
||||
out.update({"sdf_reg": sdf_reg_loss})
|
||||
|
||||
# Here we remove the gradient for the bad sdf (full positive or full negative)
|
||||
if zero_surface:
|
||||
sdf = sdf.detach()
|
||||
|
||||
mesh: Mesh = self.isosurface_helper(sdf, deformation=deformation)
|
||||
|
||||
mesh.v_pos = scale_tensor(
|
||||
mesh.v_pos, self.isosurface_helper.points_range, self.bbox
|
||||
) # scale to bbox as the grid vertices are in [0, 1]
|
||||
# import ipdb
|
||||
# ipdb.set_trace()
|
||||
v_pos_clip: Float[Tensor, "Nv V 4"] = self.ctx.vertex_transform(
|
||||
mesh.v_pos, mvp_mtx
|
||||
)
|
||||
rast, _ = self.ctx.rasterize(v_pos_clip, mesh.t_pos_idx, (height, width))
|
||||
mask = rast[..., 3:] > 0
|
||||
mask_aa = self.ctx.antialias(mask.float(), rast, v_pos_clip, mesh.t_pos_idx)
|
||||
|
||||
out.update({"opacity": mask_aa, "mesh": mesh})
|
||||
|
||||
gb_normal, _ = self.ctx.interpolate_one(mesh.v_nrm, rast, mesh.t_pos_idx)
|
||||
gb_normal = F.normalize(gb_normal, dim=-1)
|
||||
gb_normal_aa = torch.lerp(
|
||||
torch.zeros_like(gb_normal), (gb_normal + 1.0) / 2.0, mask.float()
|
||||
)
|
||||
gb_normal_aa = self.ctx.antialias(
|
||||
gb_normal_aa, rast, v_pos_clip, mesh.t_pos_idx
|
||||
)
|
||||
out.update({"comp_normal": gb_normal_aa}) # in [0, 1]
|
||||
|
||||
gb_pos, _ = self.ctx.interpolate_one(mesh.v_pos, rast, mesh.t_pos_idx)
|
||||
|
||||
# FIXME: this depth corresponds to the one provided in the dataset, but assumes camera looking at scene center
|
||||
gb_depth = dot(
|
||||
gb_pos - camera_positions[:, None, None, :],
|
||||
F.normalize(-camera_positions[:, None, None, :], dim=-1),
|
||||
)
|
||||
|
||||
gb_depth = torch.lerp(torch.zeros_like(gb_depth), gb_depth, mask.float())
|
||||
out.update({"depth": gb_depth})
|
||||
|
||||
gb_viewdirs = F.normalize(gb_pos - camera_positions[:, None, None, :], dim=-1)
|
||||
gb_rgb_fg = torch.zeros(
|
||||
(Nv, height, width, 3), device=self.device, dtype=torch.float32
|
||||
)
|
||||
gb_rgb_bg = self.background(dirs=gb_viewdirs, color_spec=background_color)
|
||||
|
||||
selector = mask[..., 0]
|
||||
if selector.sum() > 0:
|
||||
positions = gb_pos[selector]
|
||||
geo_out = self.query_triplane(positions, triplane)
|
||||
|
||||
extra_geo_info = {}
|
||||
if self.material.requires_normal:
|
||||
extra_geo_info["shading_normal"] = gb_normal[selector]
|
||||
if self.material.requires_tangent:
|
||||
gb_tangent, _ = self.ctx.interpolate_one(
|
||||
mesh.v_tng, rast, mesh.t_pos_idx
|
||||
)
|
||||
gb_tangent = F.normalize(gb_tangent, dim=-1)
|
||||
extra_geo_info["tangent"] = gb_tangent[selector]
|
||||
|
||||
rgb_fg = self.material(
|
||||
viewdirs=gb_viewdirs[selector],
|
||||
positions=positions,
|
||||
**extra_geo_info,
|
||||
**geo_out,
|
||||
)
|
||||
|
||||
gb_rgb_fg[selector] = rgb_fg.to(
|
||||
gb_rgb_fg.dtype
|
||||
) # TODO: don't know if this is correct
|
||||
|
||||
gb_rgb = torch.lerp(gb_rgb_bg, gb_rgb_fg, mask.float())
|
||||
gb_rgb_aa = self.ctx.antialias(gb_rgb, rast, v_pos_clip, mesh.t_pos_idx)
|
||||
|
||||
out.update(
|
||||
{"comp_rgb": gb_rgb_aa, "comp_rgb_fg": gb_rgb_fg, "comp_rgb_bg": gb_rgb_bg}
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
def forward(
|
||||
self,
|
||||
triplanes: Float[Tensor, "B 3 Cp Hp Wp"],
|
||||
mvp_mtx: Float[Tensor, "B Nv 4 4"],
|
||||
camera_positions: Float[Tensor, "B Nv 3"],
|
||||
height: int,
|
||||
width: int,
|
||||
background_color: Optional[Float[Tensor, "B 3"]] = None,
|
||||
extra_sdf_query: Optional[List[Float[Tensor, "N 3"]]] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, Tensor]:
|
||||
batch_size = triplanes.shape[0]
|
||||
out_list = []
|
||||
for b in range(batch_size):
|
||||
out_list.append(
|
||||
self.forward_single(
|
||||
triplanes[b],
|
||||
mvp_mtx[b],
|
||||
camera_positions[b],
|
||||
height,
|
||||
width,
|
||||
background_color=background_color[b]
|
||||
if background_color is not None
|
||||
else None,
|
||||
extra_sdf_query=extra_sdf_query[b]
|
||||
if extra_sdf_query is not None
|
||||
else None,
|
||||
)
|
||||
)
|
||||
|
||||
out = defaultdict(list)
|
||||
for out_ in out_list:
|
||||
for k, v in out_.items():
|
||||
out[k].append(v)
|
||||
|
||||
for k, v in out.items():
|
||||
# some properties cannot be batched
|
||||
if isinstance(v[0], torch.Tensor) and (
|
||||
all([vv.ndim == 0 for vv in v])
|
||||
or all([vv.shape[0] == v[0].shape[0] for vv in v])
|
||||
):
|
||||
out[k] = torch.stack(v, dim=0)
|
||||
else:
|
||||
out[k] = v
|
||||
|
||||
return out
|
||||
|
||||
def isosurface(self, triplane: Float[Tensor, "3 Cp Hp Wp"]):
|
||||
grid_vertices = scale_tensor(
|
||||
self.isosurface_helper.grid_vertices,
|
||||
self.isosurface_helper.points_range,
|
||||
self.bbox,
|
||||
)
|
||||
triplane_out = chunk_batch(
|
||||
partial(self.query_triplane, triplanes=triplane), self.cfg.eval_chunk_size, grid_vertices,
|
||||
)
|
||||
|
||||
sdf = triplane_out["sdf"]
|
||||
|
||||
if self.cfg.inside_out:
|
||||
sdf = -sdf
|
||||
|
||||
if self.cfg.enable_isosurface_grid_deformation:
|
||||
deformation = triplane_out["deformation"]
|
||||
else:
|
||||
deformation = None
|
||||
|
||||
mesh: Mesh = self.isosurface_helper(sdf, deformation=deformation)
|
||||
|
||||
mesh.v_pos = scale_tensor(
|
||||
mesh.v_pos, self.isosurface_helper.points_range, self.bbox
|
||||
)
|
||||
|
||||
return mesh
|
||||
|
||||
def query(
|
||||
self, triplane: Float[Tensor, "3 Cp Hp Wp"], points: Float[Tensor, "*N 3"]
|
||||
):
|
||||
input_shape = points.shape[:-1]
|
||||
triplane_out = chunk_batch(
|
||||
partial(self.query_triplane, triplanes=triplane), self.cfg.eval_chunk_size, points.view(-1, 3)
|
||||
)
|
||||
triplane_out = {
|
||||
k: v.view(*input_shape, v.shape[-1]) for k, v in triplane_out.items()
|
||||
}
|
||||
return triplane_out
|
||||
@@ -0,0 +1,268 @@
|
||||
from dataclasses import dataclass
|
||||
import random
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from einops import rearrange
|
||||
|
||||
from ...utils.base import BaseModule
|
||||
from .dinov2 import Dinov2Model, CustomEmbeddings
|
||||
from ..transformers.attention import Modulation
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class NaiveImageTokenizer(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
num_tokens: int = 1024
|
||||
num_channels: int = 768
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
|
||||
def forward(self, images: Float[Tensor, "B N C H W"]) -> Float[Tensor, "B Ct Nt"]:
|
||||
return torch.rand(
|
||||
(
|
||||
images.shape[0],
|
||||
self.cfg.num_channels,
|
||||
self.cfg.num_tokens,
|
||||
),
|
||||
device=images.device,
|
||||
dtype=images.dtype,
|
||||
)
|
||||
|
||||
def detokenize(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DINOV2SingleImageTokenizer(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
pretrained_model_name_or_path: str = "facebook/dinov2-base"
|
||||
width: int = 224
|
||||
height: int = 224
|
||||
modulation: bool = False
|
||||
modulation_zero_init: bool = False
|
||||
modulation_single_layer: bool = False
|
||||
modulation_cond_dim: int = 16
|
||||
freeze_backbone_params: bool = True
|
||||
enable_memory_efficient_attention: bool = False
|
||||
enable_gradient_checkpointing: bool = False
|
||||
use_patch_embeddings: bool = False
|
||||
patch_embeddings_aggr_method: str = "concat"
|
||||
append_plucker_rays: bool = False
|
||||
drop_rate: float = 0.0
|
||||
drop_type: str = "all_but_first"
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
model: Dinov2Model
|
||||
|
||||
if self.cfg.freeze_backbone_params:
|
||||
# freeze dino backbone parameters
|
||||
self.register_non_module(
|
||||
"model",
|
||||
Dinov2Model.from_pretrained(self.cfg.pretrained_model_name_or_path).to(
|
||||
self.device
|
||||
),
|
||||
)
|
||||
|
||||
model = self.non_module("model")
|
||||
for p in model.parameters():
|
||||
p.requires_grad_(False)
|
||||
model.eval()
|
||||
else:
|
||||
self.model = Dinov2Model.from_pretrained(
|
||||
self.cfg.pretrained_model_name_or_path
|
||||
).to(self.device)
|
||||
model = self.model
|
||||
|
||||
if self.cfg.append_plucker_rays:
|
||||
model.expand_input_channels(6)
|
||||
|
||||
model.set_use_memory_efficient_attention_xformers(
|
||||
self.cfg.enable_memory_efficient_attention
|
||||
)
|
||||
model.set_gradient_checkpointing(self.cfg.enable_gradient_checkpointing)
|
||||
|
||||
# add modulation
|
||||
if self.cfg.modulation:
|
||||
modulations = []
|
||||
for layer in model.encoder.layer:
|
||||
norm1_modulation = Modulation(
|
||||
model.config.hidden_size,
|
||||
self.cfg.modulation_cond_dim,
|
||||
zero_init=self.cfg.modulation_zero_init,
|
||||
single_layer=self.cfg.modulation_single_layer,
|
||||
)
|
||||
norm2_modulation = Modulation(
|
||||
model.config.hidden_size,
|
||||
self.cfg.modulation_cond_dim,
|
||||
zero_init=self.cfg.modulation_zero_init,
|
||||
single_layer=self.cfg.modulation_single_layer,
|
||||
)
|
||||
layer.register_ada_norm_modulation(norm1_modulation, norm2_modulation)
|
||||
modulations += [norm1_modulation, norm2_modulation]
|
||||
self.modulations = nn.ModuleList(modulations)
|
||||
|
||||
self.register_buffer(
|
||||
"image_mean",
|
||||
torch.as_tensor([0.485, 0.456, 0.406]).reshape(1, 1, 3, 1, 1),
|
||||
persistent=False,
|
||||
)
|
||||
self.register_buffer(
|
||||
"image_std",
|
||||
torch.as_tensor([0.229, 0.224, 0.225]).reshape(1, 1, 3, 1, 1),
|
||||
persistent=False,
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
images: Float[Tensor, "B *N C H W"],
|
||||
modulation_cond: Optional[Float[Tensor, "B *N Cc"]],
|
||||
plucker_rays: Optional[Float[Tensor, "B *N 6 H W"]],
|
||||
**kwargs
|
||||
) -> Float[Tensor, "B *N Ct Nt"]:
|
||||
model: Dinov2Model
|
||||
if self.cfg.freeze_backbone_params:
|
||||
model = self.non_module("model")
|
||||
else:
|
||||
model = self.model
|
||||
|
||||
packed = False
|
||||
if images.ndim == 4:
|
||||
packed = True
|
||||
images = images.unsqueeze(1)
|
||||
if modulation_cond is not None:
|
||||
assert modulation_cond.ndim == 2
|
||||
modulation_cond = modulation_cond.unsqueeze(1)
|
||||
if plucker_rays is not None:
|
||||
assert plucker_rays.ndim == 4
|
||||
plucker_rays = plucker_rays.unsqueeze(1)
|
||||
|
||||
if (
|
||||
self.training
|
||||
and self.cfg.drop_rate > 0
|
||||
and random.random() < self.cfg.drop_rate
|
||||
):
|
||||
if self.cfg.drop_type == "all_but_first":
|
||||
drop_func = lambda x: x if x is None else x[:, 0:1]
|
||||
images = drop_func(images)
|
||||
modulation_cond = drop_func(modulation_cond)
|
||||
plucker_rays = drop_func(plucker_rays)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
batch_size, n_input_views = images.shape[:2]
|
||||
images = (images - self.image_mean) / self.image_std
|
||||
if self.cfg.append_plucker_rays and plucker_rays is not None:
|
||||
images = torch.cat([images, plucker_rays], dim=2)
|
||||
out = model(
|
||||
rearrange(images, "B N C H W -> (B N) C H W"),
|
||||
modulation_cond=rearrange(modulation_cond, "B N Cc -> (B N) Cc")
|
||||
if modulation_cond is not None
|
||||
else None,
|
||||
)
|
||||
local_features, global_features = out.last_hidden_state, out.pooler_output
|
||||
if self.cfg.use_patch_embeddings:
|
||||
patch_embeddings = out.patch_embeddings
|
||||
if self.cfg.patch_embeddings_aggr_method == "concat":
|
||||
local_features = torch.cat([local_features, patch_embeddings], dim=1)
|
||||
elif self.cfg.patch_embeddings_aggr_method == "add":
|
||||
local_features = local_features + patch_embeddings
|
||||
else:
|
||||
raise NotImplementedError
|
||||
local_features = local_features.permute(0, 2, 1)
|
||||
local_features = rearrange(
|
||||
local_features, "(B N) Ct Nt -> B N Ct Nt", B=batch_size
|
||||
)
|
||||
if packed:
|
||||
local_features = local_features.squeeze(1)
|
||||
|
||||
return local_features
|
||||
|
||||
def detokenize(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DINOV2CustomLowLevelSingleImageTokenizer(DINOV2SingleImageTokenizer):
|
||||
@dataclass
|
||||
class Config(DINOV2SingleImageTokenizer.Config):
|
||||
custom_embeddings_aggr_method: str = "concat"
|
||||
custom_embeddings_scale: float = 1.0
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.custom_embeddings = CustomEmbeddings(
|
||||
self.model.config.image_size,
|
||||
self.model.config.patch_size,
|
||||
self.model.config.num_channels,
|
||||
self.model.config.hidden_size,
|
||||
)
|
||||
self.custom_embeddings.load_state_dict(
|
||||
self.model.embeddings.state_dict(), strict=False
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
images: Float[Tensor, "B *N C H W"],
|
||||
modulation_cond: Optional[Float[Tensor, "B *N Cc"]],
|
||||
**kwargs
|
||||
) -> Float[Tensor, "B *N Ct Nt"]:
|
||||
model: Dinov2Model
|
||||
if self.cfg.freeze_backbone_params:
|
||||
model = self.non_module("model")
|
||||
else:
|
||||
model = self.model
|
||||
|
||||
packed = False
|
||||
if images.ndim == 4:
|
||||
packed = True
|
||||
images = images.unsqueeze(1)
|
||||
if modulation_cond is not None:
|
||||
assert modulation_cond.ndim == 2
|
||||
modulation_cond = modulation_cond.unsqueeze(1)
|
||||
|
||||
batch_size, n_input_views = images.shape[:2]
|
||||
images = (images - self.image_mean) / self.image_std
|
||||
images = rearrange(images, "B N C H W -> (B N) C H W")
|
||||
out = model(
|
||||
images,
|
||||
modulation_cond=rearrange(modulation_cond, "B N Cc -> (B N) Cc")
|
||||
if modulation_cond is not None
|
||||
else None,
|
||||
)
|
||||
local_features, global_features = out.last_hidden_state, out.pooler_output
|
||||
if self.cfg.use_patch_embeddings:
|
||||
patch_embeddings = out.patch_embeddings
|
||||
if self.cfg.patch_embeddings_aggr_method == "concat":
|
||||
local_features = torch.cat([local_features, patch_embeddings], dim=1)
|
||||
elif self.cfg.patch_embeddings_aggr_method == "add":
|
||||
local_features = local_features + patch_embeddings
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
custom_embeddings = (
|
||||
self.custom_embeddings(images) * self.cfg.custom_embeddings_scale
|
||||
)
|
||||
if self.cfg.custom_embeddings_aggr_method == "concat":
|
||||
local_features = torch.cat([local_features, custom_embeddings], dim=1)
|
||||
elif self.cfg.custom_embeddings_aggr_method == "add":
|
||||
local_features = local_features + custom_embeddings
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
local_features = local_features.permute(0, 2, 1)
|
||||
local_features = rearrange(
|
||||
local_features, "(B N) Ct Nt -> B N Ct Nt", B=batch_size
|
||||
)
|
||||
if packed:
|
||||
local_features = local_features.squeeze(1)
|
||||
|
||||
return local_features
|
||||
@@ -0,0 +1,49 @@
|
||||
from dataclasses import dataclass
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from einops import rearrange, repeat
|
||||
|
||||
from ...utils.base import BaseModule
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class TriplaneLearnablePositionalEmbedding(BaseModule):
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
plane_size: int = 32
|
||||
num_channels: int = 1024
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
self.embeddings = nn.Parameter(
|
||||
torch.randn(
|
||||
(3, self.cfg.num_channels, self.cfg.plane_size, self.cfg.plane_size),
|
||||
dtype=torch.float32,
|
||||
)
|
||||
* 1
|
||||
/ math.sqrt(self.cfg.num_channels)
|
||||
)
|
||||
|
||||
def forward(self, batch_size: int) -> Float[Tensor, "B Ct Nt"]:
|
||||
return rearrange(
|
||||
repeat(self.embeddings, "Np Ct Hp Wp -> B Np Ct Hp Wp", B=batch_size),
|
||||
"B Np Ct Hp Wp -> B Ct (Np Hp Wp)",
|
||||
)
|
||||
|
||||
def detokenize(
|
||||
self, tokens: Float[Tensor, "B Ct Nt"]
|
||||
) -> Float[Tensor, "B 3 Ct Hp Wp"]:
|
||||
batch_size, Ct, Nt = tokens.shape
|
||||
assert Nt == self.cfg.plane_size**2 * 3
|
||||
assert Ct == self.cfg.num_channels
|
||||
return rearrange(
|
||||
tokens,
|
||||
"B Ct (Np Hp Wp) -> B Np Ct Hp Wp",
|
||||
Np=3,
|
||||
Hp=self.cfg.plane_size,
|
||||
Wp=self.cfg.plane_size,
|
||||
)
|
||||
@@ -0,0 +1,669 @@
|
||||
# Copyright 2023 The HuggingFace Team. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
|
||||
from diffusers.utils.torch_utils import maybe_allow_in_graph
|
||||
from diffusers.models.activations import get_activation
|
||||
from diffusers.models.attention_processor import Attention
|
||||
from diffusers.models.embeddings import CombinedTimestepLabelEmbeddings
|
||||
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class MemoryEfficientAttentionMixin:
|
||||
def enable_xformers_memory_efficient_attention(
|
||||
self, attention_op: Optional[Callable] = None
|
||||
):
|
||||
r"""
|
||||
Enable memory efficient attention from [xFormers](https://facebookresearch.github.io/xformers/). When this
|
||||
option is enabled, you should observe lower GPU memory usage and a potential speed up during inference. Speed
|
||||
up during training is not guaranteed.
|
||||
|
||||
<Tip warning={true}>
|
||||
|
||||
⚠️ When memory efficient attention and sliced attention are both enabled, memory efficient attention takes
|
||||
precedent.
|
||||
|
||||
</Tip>
|
||||
|
||||
Parameters:
|
||||
attention_op (`Callable`, *optional*):
|
||||
Override the default `None` operator for use as `op` argument to the
|
||||
[`memory_efficient_attention()`](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.memory_efficient_attention)
|
||||
function of xFormers.
|
||||
|
||||
Examples:
|
||||
|
||||
```py
|
||||
>>> import torch
|
||||
>>> from diffusers import DiffusionPipeline
|
||||
>>> from xformers.ops import MemoryEfficientAttentionFlashAttentionOp
|
||||
|
||||
>>> pipe = DiffusionPipeline.from_pretrained("stabilityai/stable-diffusion-2-1", torch_dtype=torch.float16)
|
||||
>>> pipe = pipe.to("cuda")
|
||||
>>> pipe.enable_xformers_memory_efficient_attention(attention_op=MemoryEfficientAttentionFlashAttentionOp)
|
||||
>>> # Workaround for not accepting attention shape using VAE for Flash Attention
|
||||
>>> pipe.vae.enable_xformers_memory_efficient_attention(attention_op=None)
|
||||
```
|
||||
"""
|
||||
self.set_use_memory_efficient_attention_xformers(True, attention_op)
|
||||
|
||||
def disable_xformers_memory_efficient_attention(self):
|
||||
r"""
|
||||
Disable memory efficient attention from [xFormers](https://facebookresearch.github.io/xformers/).
|
||||
"""
|
||||
self.set_use_memory_efficient_attention_xformers(False)
|
||||
|
||||
def set_use_memory_efficient_attention_xformers(
|
||||
self, valid: bool, attention_op: Optional[Callable] = None
|
||||
) -> None:
|
||||
# Recursively walk through all the children.
|
||||
# Any children which exposes the set_use_memory_efficient_attention_xformers method
|
||||
# gets the message
|
||||
def fn_recursive_set_mem_eff(module: torch.nn.Module):
|
||||
if hasattr(module, "set_use_memory_efficient_attention_xformers"):
|
||||
module.set_use_memory_efficient_attention_xformers(valid, attention_op)
|
||||
|
||||
for child in module.children():
|
||||
fn_recursive_set_mem_eff(child)
|
||||
|
||||
for module in self.children():
|
||||
if isinstance(module, torch.nn.Module):
|
||||
fn_recursive_set_mem_eff(module)
|
||||
|
||||
|
||||
@maybe_allow_in_graph
|
||||
class GatedSelfAttentionDense(nn.Module):
|
||||
r"""
|
||||
A gated self-attention dense layer that combines visual features and object features.
|
||||
|
||||
Parameters:
|
||||
query_dim (`int`): The number of channels in the query.
|
||||
context_dim (`int`): The number of channels in the context.
|
||||
n_heads (`int`): The number of heads to use for attention.
|
||||
d_head (`int`): The number of channels in each head.
|
||||
"""
|
||||
|
||||
def __init__(self, query_dim: int, context_dim: int, n_heads: int, d_head: int):
|
||||
super().__init__()
|
||||
|
||||
# we need a linear projection since we need cat visual feature and obj feature
|
||||
self.linear = nn.Linear(context_dim, query_dim)
|
||||
|
||||
self.attn = Attention(query_dim=query_dim, heads=n_heads, dim_head=d_head)
|
||||
self.ff = FeedForward(query_dim, activation_fn="geglu")
|
||||
|
||||
self.norm1 = nn.LayerNorm(query_dim)
|
||||
self.norm2 = nn.LayerNorm(query_dim)
|
||||
|
||||
self.register_parameter("alpha_attn", nn.Parameter(torch.tensor(0.0)))
|
||||
self.register_parameter("alpha_dense", nn.Parameter(torch.tensor(0.0)))
|
||||
|
||||
self.enabled = True
|
||||
|
||||
def forward(self, x: torch.Tensor, objs: torch.Tensor) -> torch.Tensor:
|
||||
if not self.enabled:
|
||||
return x
|
||||
|
||||
n_visual = x.shape[1]
|
||||
objs = self.linear(objs)
|
||||
|
||||
x = (
|
||||
x
|
||||
+ self.alpha_attn.tanh()
|
||||
* self.attn(self.norm1(torch.cat([x, objs], dim=1)))[:, :n_visual, :]
|
||||
)
|
||||
x = x + self.alpha_dense.tanh() * self.ff(self.norm2(x))
|
||||
|
||||
return x
|
||||
|
||||
|
||||
@maybe_allow_in_graph
|
||||
class BasicTransformerBlock(nn.Module, MemoryEfficientAttentionMixin):
|
||||
r"""
|
||||
A basic Transformer block.
|
||||
|
||||
Parameters:
|
||||
dim (`int`): The number of channels in the input and output.
|
||||
num_attention_heads (`int`): The number of heads to use for multi-head attention.
|
||||
attention_head_dim (`int`): The number of channels in each head.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
cross_attention_dim (`int`, *optional*): The size of the encoder_hidden_states vector for cross attention.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward.
|
||||
num_embeds_ada_norm (:
|
||||
obj: `int`, *optional*): The number of diffusion steps used during training. See `Transformer2DModel`.
|
||||
attention_bias (:
|
||||
obj: `bool`, *optional*, defaults to `False`): Configure if the attentions should contain a bias parameter.
|
||||
only_cross_attention (`bool`, *optional*):
|
||||
Whether to use only cross-attention layers. In this case two cross attention layers are used.
|
||||
double_self_attention (`bool`, *optional*):
|
||||
Whether to use two self-attention layers. In this case no cross attention layers are used.
|
||||
upcast_attention (`bool`, *optional*):
|
||||
Whether to upcast the attention computation to float32. This is useful for mixed precision training.
|
||||
norm_elementwise_affine (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use learnable elementwise affine parameters for normalization.
|
||||
norm_type (`str`, *optional*, defaults to `"layer_norm"`):
|
||||
The normalization layer to use. Can be `"layer_norm"`, `"ada_norm"` or `"ada_norm_zero"`.
|
||||
final_dropout (`bool` *optional*, defaults to False):
|
||||
Whether to apply a final dropout after the last feed-forward layer.
|
||||
attention_type (`str`, *optional*, defaults to `"default"`):
|
||||
The type of attention to use. Can be `"default"` or `"gated"` or `"gated-text-image"`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
num_attention_heads: int,
|
||||
attention_head_dim: int,
|
||||
dropout=0.0,
|
||||
cross_attention_dim: Optional[int] = None,
|
||||
activation_fn: str = "geglu",
|
||||
num_embeds_ada_norm: Optional[int] = None,
|
||||
cond_dim_ada_norm_continuous: Optional[int] = None,
|
||||
attention_bias: bool = False,
|
||||
only_cross_attention: bool = False,
|
||||
double_self_attention: bool = False,
|
||||
upcast_attention: bool = False,
|
||||
norm_elementwise_affine: bool = True,
|
||||
norm_type: str = "layer_norm",
|
||||
final_dropout: bool = False,
|
||||
attention_type: str = "default",
|
||||
):
|
||||
super().__init__()
|
||||
self.only_cross_attention = only_cross_attention
|
||||
|
||||
self.use_ada_layer_norm_zero = (
|
||||
num_embeds_ada_norm is not None
|
||||
) and norm_type == "ada_norm_zero"
|
||||
self.use_ada_layer_norm = (
|
||||
num_embeds_ada_norm is not None
|
||||
) and norm_type == "ada_norm"
|
||||
self.use_ada_layer_norm_continuous = (
|
||||
cond_dim_ada_norm_continuous is not None
|
||||
) and norm_type == "ada_norm_continuous"
|
||||
|
||||
assert (
|
||||
int(self.use_ada_layer_norm)
|
||||
+ int(self.use_ada_layer_norm_continuous)
|
||||
+ int(self.use_ada_layer_norm_zero)
|
||||
<= 1
|
||||
)
|
||||
|
||||
if norm_type in ("ada_norm", "ada_norm_zero") and num_embeds_ada_norm is None:
|
||||
raise ValueError(
|
||||
f"`norm_type` is set to {norm_type}, but `num_embeds_ada_norm` is not defined. Please make sure to"
|
||||
f" define `num_embeds_ada_norm` if setting `norm_type` to {norm_type}."
|
||||
)
|
||||
|
||||
# Define 3 blocks. Each block has its own normalization layer.
|
||||
# 1. Self-Attn
|
||||
if self.use_ada_layer_norm:
|
||||
self.norm1 = AdaLayerNorm(dim, num_embeds_ada_norm)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
self.norm1 = AdaLayerNormContinuous(dim, cond_dim_ada_norm_continuous)
|
||||
elif self.use_ada_layer_norm_zero:
|
||||
self.norm1 = AdaLayerNormZero(dim, num_embeds_ada_norm)
|
||||
else:
|
||||
self.norm1 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine)
|
||||
self.attn1 = Attention(
|
||||
query_dim=dim,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
cross_attention_dim=cross_attention_dim if only_cross_attention else None,
|
||||
upcast_attention=upcast_attention,
|
||||
)
|
||||
|
||||
# 2. Cross-Attn
|
||||
if cross_attention_dim is not None or double_self_attention:
|
||||
# We currently only use AdaLayerNormZero for self attention where there will only be one attention block.
|
||||
# I.e. the number of returned modulation chunks from AdaLayerZero would not make sense if returned during
|
||||
# the second cross attention block.
|
||||
if self.use_ada_layer_norm:
|
||||
self.norm2 = AdaLayerNorm(dim, num_embeds_ada_norm)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
self.norm2 = AdaLayerNormContinuous(dim, cond_dim_ada_norm_continuous)
|
||||
else:
|
||||
self.norm2 = nn.LayerNorm(
|
||||
dim, elementwise_affine=norm_elementwise_affine
|
||||
)
|
||||
|
||||
self.attn2 = Attention(
|
||||
query_dim=dim,
|
||||
cross_attention_dim=cross_attention_dim
|
||||
if not double_self_attention
|
||||
else None,
|
||||
heads=num_attention_heads,
|
||||
dim_head=attention_head_dim,
|
||||
dropout=dropout,
|
||||
bias=attention_bias,
|
||||
upcast_attention=upcast_attention,
|
||||
) # is self-attn if encoder_hidden_states is none
|
||||
else:
|
||||
self.norm2 = None
|
||||
self.attn2 = None
|
||||
|
||||
# 3. Feed-forward
|
||||
if self.use_ada_layer_norm_continuous:
|
||||
self.norm3 = AdaLayerNormContinuous(dim, cond_dim_ada_norm_continuous)
|
||||
else:
|
||||
self.norm3 = nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine)
|
||||
self.ff = FeedForward(
|
||||
dim,
|
||||
dropout=dropout,
|
||||
activation_fn=activation_fn,
|
||||
final_dropout=final_dropout,
|
||||
)
|
||||
|
||||
# 4. Fuser
|
||||
if attention_type == "gated" or attention_type == "gated-text-image":
|
||||
self.fuser = GatedSelfAttentionDense(
|
||||
dim, cross_attention_dim, num_attention_heads, attention_head_dim
|
||||
)
|
||||
|
||||
# let chunk size default to None
|
||||
self._chunk_size = None
|
||||
self._chunk_dim = 0
|
||||
|
||||
def set_chunk_feed_forward(self, chunk_size: Optional[int], dim: int):
|
||||
# Sets chunk feed-forward
|
||||
self._chunk_size = chunk_size
|
||||
self._chunk_dim = dim
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.FloatTensor,
|
||||
attention_mask: Optional[torch.FloatTensor] = None,
|
||||
encoder_hidden_states: Optional[torch.FloatTensor] = None,
|
||||
encoder_attention_mask: Optional[torch.FloatTensor] = None,
|
||||
timestep: Optional[torch.LongTensor] = None,
|
||||
modulation_cond: Optional[torch.FloatTensor] = None,
|
||||
cross_attention_kwargs: Dict[str, Any] = None,
|
||||
class_labels: Optional[torch.LongTensor] = None,
|
||||
) -> torch.FloatTensor:
|
||||
# Notice that normalization is always applied before the real computation in the following blocks.
|
||||
# 0. Self-Attention
|
||||
if self.use_ada_layer_norm:
|
||||
norm_hidden_states = self.norm1(hidden_states, timestep)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
norm_hidden_states = self.norm1(hidden_states, modulation_cond)
|
||||
elif self.use_ada_layer_norm_zero:
|
||||
norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1(
|
||||
hidden_states, timestep, class_labels, hidden_dtype=hidden_states.dtype
|
||||
)
|
||||
else:
|
||||
norm_hidden_states = self.norm1(hidden_states)
|
||||
|
||||
# 1. Retrieve lora scale.
|
||||
lora_scale = (
|
||||
cross_attention_kwargs.get("scale", 1.0)
|
||||
if cross_attention_kwargs is not None
|
||||
else 1.0
|
||||
)
|
||||
|
||||
# 2. Prepare GLIGEN inputs
|
||||
cross_attention_kwargs = (
|
||||
cross_attention_kwargs.copy() if cross_attention_kwargs is not None else {}
|
||||
)
|
||||
gligen_kwargs = cross_attention_kwargs.pop("gligen", None)
|
||||
|
||||
attn_output = self.attn1(
|
||||
norm_hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states
|
||||
if self.only_cross_attention
|
||||
else None,
|
||||
attention_mask=attention_mask,
|
||||
**cross_attention_kwargs,
|
||||
)
|
||||
if self.use_ada_layer_norm_zero:
|
||||
attn_output = gate_msa.unsqueeze(1) * attn_output
|
||||
hidden_states = attn_output + hidden_states
|
||||
|
||||
# 2.5 GLIGEN Control
|
||||
if gligen_kwargs is not None:
|
||||
hidden_states = self.fuser(hidden_states, gligen_kwargs["objs"])
|
||||
# 2.5 ends
|
||||
|
||||
# 3. Cross-Attention
|
||||
if self.attn2 is not None:
|
||||
if self.use_ada_layer_norm:
|
||||
norm_hidden_states = self.norm2(hidden_states, timestep)
|
||||
elif self.use_ada_layer_norm_continuous:
|
||||
norm_hidden_states = self.norm2(hidden_states, modulation_cond)
|
||||
else:
|
||||
norm_hidden_states = self.norm2(hidden_states)
|
||||
|
||||
attn_output = self.attn2(
|
||||
norm_hidden_states,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
attention_mask=encoder_attention_mask,
|
||||
**cross_attention_kwargs,
|
||||
)
|
||||
hidden_states = attn_output + hidden_states
|
||||
|
||||
# 4. Feed-forward
|
||||
if self.use_ada_layer_norm_continuous:
|
||||
norm_hidden_states = self.norm3(hidden_states, modulation_cond)
|
||||
else:
|
||||
norm_hidden_states = self.norm3(hidden_states)
|
||||
|
||||
if self.use_ada_layer_norm_zero:
|
||||
norm_hidden_states = (
|
||||
norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
|
||||
)
|
||||
|
||||
if self._chunk_size is not None:
|
||||
# "feed_forward_chunk_size" can be used to save memory
|
||||
if norm_hidden_states.shape[self._chunk_dim] % self._chunk_size != 0:
|
||||
raise ValueError(
|
||||
f"`hidden_states` dimension to be chunked: {norm_hidden_states.shape[self._chunk_dim]} has to be divisible by chunk size: {self._chunk_size}. Make sure to set an appropriate `chunk_size` when calling `unet.enable_forward_chunking`."
|
||||
)
|
||||
|
||||
num_chunks = norm_hidden_states.shape[self._chunk_dim] // self._chunk_size
|
||||
ff_output = torch.cat(
|
||||
[
|
||||
self.ff(hid_slice, scale=lora_scale)
|
||||
for hid_slice in norm_hidden_states.chunk(
|
||||
num_chunks, dim=self._chunk_dim
|
||||
)
|
||||
],
|
||||
dim=self._chunk_dim,
|
||||
)
|
||||
else:
|
||||
ff_output = self.ff(norm_hidden_states, scale=lora_scale)
|
||||
|
||||
if self.use_ada_layer_norm_zero:
|
||||
ff_output = gate_mlp.unsqueeze(1) * ff_output
|
||||
|
||||
hidden_states = ff_output + hidden_states
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class FeedForward(nn.Module):
|
||||
r"""
|
||||
A feed-forward layer.
|
||||
|
||||
Parameters:
|
||||
dim (`int`): The number of channels in the input.
|
||||
dim_out (`int`, *optional*): The number of channels in the output. If not given, defaults to `dim`.
|
||||
mult (`int`, *optional*, defaults to 4): The multiplier to use for the hidden dimension.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to be used in feed-forward.
|
||||
final_dropout (`bool` *optional*, defaults to False): Apply a final dropout.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
dim_out: Optional[int] = None,
|
||||
mult: int = 4,
|
||||
dropout: float = 0.0,
|
||||
activation_fn: str = "geglu",
|
||||
final_dropout: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
inner_dim = int(dim * mult)
|
||||
dim_out = dim_out if dim_out is not None else dim
|
||||
linear_cls = nn.Linear
|
||||
|
||||
if activation_fn == "gelu":
|
||||
act_fn = GELU(dim, inner_dim)
|
||||
if activation_fn == "gelu-approximate":
|
||||
act_fn = GELU(dim, inner_dim, approximate="tanh")
|
||||
elif activation_fn == "geglu":
|
||||
act_fn = GEGLU(dim, inner_dim)
|
||||
elif activation_fn == "geglu-approximate":
|
||||
act_fn = ApproximateGELU(dim, inner_dim)
|
||||
|
||||
self.net = nn.ModuleList([])
|
||||
# project in
|
||||
self.net.append(act_fn)
|
||||
# project dropout
|
||||
self.net.append(nn.Dropout(dropout))
|
||||
# project out
|
||||
self.net.append(linear_cls(inner_dim, dim_out))
|
||||
# FF as used in Vision Transformer, MLP-Mixer, etc. have a final dropout
|
||||
if final_dropout:
|
||||
self.net.append(nn.Dropout(dropout))
|
||||
|
||||
def forward(self, hidden_states: torch.Tensor, scale: float = 1.0) -> torch.Tensor:
|
||||
for module in self.net:
|
||||
hidden_states = module(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class GELU(nn.Module):
|
||||
r"""
|
||||
GELU activation function with tanh approximation support with `approximate="tanh"`.
|
||||
|
||||
Parameters:
|
||||
dim_in (`int`): The number of channels in the input.
|
||||
dim_out (`int`): The number of channels in the output.
|
||||
approximate (`str`, *optional*, defaults to `"none"`): If `"tanh"`, use tanh approximation.
|
||||
"""
|
||||
|
||||
def __init__(self, dim_in: int, dim_out: int, approximate: str = "none"):
|
||||
super().__init__()
|
||||
self.proj = nn.Linear(dim_in, dim_out)
|
||||
self.approximate = approximate
|
||||
|
||||
def gelu(self, gate: torch.Tensor) -> torch.Tensor:
|
||||
if gate.device.type != "mps":
|
||||
return F.gelu(gate, approximate=self.approximate)
|
||||
# mps: gelu is not implemented for float16
|
||||
return F.gelu(gate.to(dtype=torch.float32), approximate=self.approximate).to(
|
||||
dtype=gate.dtype
|
||||
)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
hidden_states = self.proj(hidden_states)
|
||||
hidden_states = self.gelu(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class GEGLU(nn.Module):
|
||||
r"""
|
||||
A variant of the gated linear unit activation function from https://arxiv.org/abs/2002.05202.
|
||||
|
||||
Parameters:
|
||||
dim_in (`int`): The number of channels in the input.
|
||||
dim_out (`int`): The number of channels in the output.
|
||||
"""
|
||||
|
||||
def __init__(self, dim_in: int, dim_out: int):
|
||||
super().__init__()
|
||||
linear_cls = nn.Linear
|
||||
|
||||
self.proj = linear_cls(dim_in, dim_out * 2)
|
||||
|
||||
def gelu(self, gate: torch.Tensor) -> torch.Tensor:
|
||||
if gate.device.type != "mps":
|
||||
return F.gelu(gate)
|
||||
# mps: gelu is not implemented for float16
|
||||
return F.gelu(gate.to(dtype=torch.float32)).to(dtype=gate.dtype)
|
||||
|
||||
def forward(self, hidden_states, scale: float = 1.0):
|
||||
args = ()
|
||||
hidden_states, gate = self.proj(hidden_states, *args).chunk(2, dim=-1)
|
||||
return hidden_states * self.gelu(gate)
|
||||
|
||||
|
||||
class ApproximateGELU(nn.Module):
|
||||
r"""
|
||||
The approximate form of Gaussian Error Linear Unit (GELU). For more details, see section 2:
|
||||
https://arxiv.org/abs/1606.08415.
|
||||
|
||||
Parameters:
|
||||
dim_in (`int`): The number of channels in the input.
|
||||
dim_out (`int`): The number of channels in the output.
|
||||
"""
|
||||
|
||||
def __init__(self, dim_in: int, dim_out: int):
|
||||
super().__init__()
|
||||
self.proj = nn.Linear(dim_in, dim_out)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
x = self.proj(x)
|
||||
return x * torch.sigmoid(1.702 * x)
|
||||
|
||||
|
||||
class AdaLayerNorm(nn.Module):
|
||||
r"""
|
||||
Norm layer modified to incorporate timestep embeddings.
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
num_embeddings (`int`): The size of the dictionary of embeddings.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim: int, num_embeddings: int):
|
||||
super().__init__()
|
||||
self.emb = nn.Embedding(num_embeddings, embedding_dim)
|
||||
self.silu = nn.SiLU()
|
||||
self.linear = nn.Linear(embedding_dim, embedding_dim * 2)
|
||||
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False)
|
||||
|
||||
def forward(self, x: torch.Tensor, timestep: torch.Tensor) -> torch.Tensor:
|
||||
emb = self.linear(self.silu(self.emb(timestep)))
|
||||
scale, shift = torch.chunk(emb, 2, dim=1)
|
||||
x = self.norm(x) * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class AdaLayerNormContinuous(nn.Module):
|
||||
r"""
|
||||
Norm layer modified to incorporate arbitrary continuous embeddings.
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim: int, condition_dim: int):
|
||||
super().__init__()
|
||||
self.silu = nn.SiLU()
|
||||
self.linear1 = nn.Linear(condition_dim, condition_dim)
|
||||
self.linear2 = nn.Linear(condition_dim, embedding_dim * 2)
|
||||
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False)
|
||||
|
||||
def forward(self, x: torch.Tensor, condition: torch.Tensor) -> torch.Tensor:
|
||||
emb = self.linear2(self.silu(self.linear1(condition)))
|
||||
scale, shift = torch.chunk(emb, 2, dim=1)
|
||||
x = self.norm(x) * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class Modulation(nn.Module):
|
||||
def __init__(self, embedding_dim: int, condition_dim: int, zero_init: bool = False, single_layer: bool = False):
|
||||
super().__init__()
|
||||
self.silu = nn.SiLU()
|
||||
if single_layer:
|
||||
self.linear1 = nn.Identity()
|
||||
else:
|
||||
self.linear1 = nn.Linear(condition_dim, condition_dim)
|
||||
|
||||
self.linear2 = nn.Linear(condition_dim, embedding_dim * 2)
|
||||
|
||||
# Only zero init the last linear layer
|
||||
if zero_init:
|
||||
nn.init.zeros_(self.linear2.weight)
|
||||
nn.init.zeros_(self.linear2.bias)
|
||||
|
||||
def forward(self, x: torch.Tensor, condition: torch.Tensor) -> torch.Tensor:
|
||||
emb = self.linear2(self.silu(self.linear1(condition)))
|
||||
scale, shift = torch.chunk(emb, 2, dim=1)
|
||||
x = x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
return x
|
||||
|
||||
|
||||
class AdaLayerNormZero(nn.Module):
|
||||
r"""
|
||||
Norm layer adaptive layer norm zero (adaLN-Zero).
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
num_embeddings (`int`): The size of the dictionary of embeddings.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim: int, num_embeddings: int):
|
||||
super().__init__()
|
||||
|
||||
self.emb = CombinedTimestepLabelEmbeddings(num_embeddings, embedding_dim)
|
||||
|
||||
self.silu = nn.SiLU()
|
||||
self.linear = nn.Linear(embedding_dim, 6 * embedding_dim, bias=True)
|
||||
self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False, eps=1e-6)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
timestep: torch.Tensor,
|
||||
class_labels: torch.LongTensor,
|
||||
hidden_dtype: Optional[torch.dtype] = None,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
emb = self.linear(
|
||||
self.silu(self.emb(timestep, class_labels, hidden_dtype=hidden_dtype))
|
||||
)
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = emb.chunk(
|
||||
6, dim=1
|
||||
)
|
||||
x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None]
|
||||
return x, gate_msa, shift_mlp, scale_mlp, gate_mlp
|
||||
|
||||
|
||||
class AdaGroupNorm(nn.Module):
|
||||
r"""
|
||||
GroupNorm layer modified to incorporate timestep embeddings.
|
||||
|
||||
Parameters:
|
||||
embedding_dim (`int`): The size of each embedding vector.
|
||||
num_embeddings (`int`): The size of the dictionary of embeddings.
|
||||
num_groups (`int`): The number of groups to separate the channels into.
|
||||
act_fn (`str`, *optional*, defaults to `None`): The activation function to use.
|
||||
eps (`float`, *optional*, defaults to `1e-5`): The epsilon value to use for numerical stability.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embedding_dim: int,
|
||||
out_dim: int,
|
||||
num_groups: int,
|
||||
act_fn: Optional[str] = None,
|
||||
eps: float = 1e-5,
|
||||
):
|
||||
super().__init__()
|
||||
self.num_groups = num_groups
|
||||
self.eps = eps
|
||||
|
||||
if act_fn is None:
|
||||
self.act = None
|
||||
else:
|
||||
self.act = get_activation(act_fn)
|
||||
|
||||
self.linear = nn.Linear(embedding_dim, out_dim * 2)
|
||||
|
||||
def forward(self, x: torch.Tensor, emb: torch.Tensor) -> torch.Tensor:
|
||||
if self.act:
|
||||
emb = self.act(emb)
|
||||
emb = self.linear(emb)
|
||||
emb = emb[:, :, None, None]
|
||||
scale, shift = emb.chunk(2, dim=1)
|
||||
|
||||
x = F.group_norm(x, self.num_groups, eps=self.eps)
|
||||
x = x * (1 + scale) + shift
|
||||
return x
|
||||
@@ -0,0 +1,249 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
from ...utils.base import BaseModule
|
||||
from .attention import (
|
||||
BasicTransformerBlock,
|
||||
MemoryEfficientAttentionMixin,
|
||||
)
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class Transformer1D(BaseModule, MemoryEfficientAttentionMixin):
|
||||
"""
|
||||
A 1D Transformer model for sequence data.
|
||||
|
||||
Parameters:
|
||||
num_attention_heads (`int`, *optional*, defaults to 16): The number of heads to use for multi-head attention.
|
||||
attention_head_dim (`int`, *optional*, defaults to 88): The number of channels in each head.
|
||||
in_channels (`int`, *optional*):
|
||||
The number of channels in the input and output (specify if the input is **continuous**).
|
||||
num_layers (`int`, *optional*, defaults to 1): The number of layers of Transformer blocks to use.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
cross_attention_dim (`int`, *optional*): The number of `encoder_hidden_states` dimensions to use.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to use in feed-forward.
|
||||
num_embeds_ada_norm ( `int`, *optional*):
|
||||
The number of diffusion steps used during training. Pass if at least one of the norm_layers is
|
||||
`AdaLayerNorm`. This is fixed during training since it is used to learn a number of embeddings that are
|
||||
added to the hidden states.
|
||||
|
||||
During inference, you can denoise for up to but not more steps than `num_embeds_ada_norm`.
|
||||
attention_bias (`bool`, *optional*):
|
||||
Configure if the `TransformerBlocks` attention should contain a bias parameter.
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
num_attention_heads: int = 16
|
||||
attention_head_dim: int = 88
|
||||
in_channels: Optional[int] = None
|
||||
out_channels: Optional[int] = None
|
||||
num_layers: int = 1
|
||||
dropout: float = 0.0
|
||||
norm_num_groups: int = 32
|
||||
cross_attention_dim: Optional[int] = None
|
||||
attention_bias: bool = False
|
||||
activation_fn: str = "geglu"
|
||||
num_embeds_ada_norm: Optional[int] = None
|
||||
cond_dim_ada_norm_continuous: Optional[int] = None
|
||||
only_cross_attention: bool = False
|
||||
double_self_attention: bool = False
|
||||
upcast_attention: bool = False
|
||||
norm_type: str = "layer_norm"
|
||||
norm_elementwise_affine: bool = True
|
||||
attention_type: str = "default"
|
||||
enable_memory_efficient_attention: bool = False
|
||||
gradient_checkpointing: bool = False
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
|
||||
self.num_attention_heads = self.cfg.num_attention_heads
|
||||
self.attention_head_dim = self.cfg.attention_head_dim
|
||||
inner_dim = self.num_attention_heads * self.attention_head_dim
|
||||
|
||||
linear_cls = nn.Linear
|
||||
|
||||
if self.cfg.norm_type == "layer_norm" and (
|
||||
self.cfg.num_embeds_ada_norm is not None
|
||||
or self.cfg.cond_dim_ada_norm_continuous is not None
|
||||
):
|
||||
raise ValueError("Incorrect norm_type.")
|
||||
|
||||
# 2. Define input layers
|
||||
self.in_channels = self.cfg.in_channels
|
||||
|
||||
self.norm = torch.nn.GroupNorm(
|
||||
num_groups=self.cfg.norm_num_groups,
|
||||
num_channels=self.cfg.in_channels,
|
||||
eps=1e-6,
|
||||
affine=True,
|
||||
)
|
||||
self.proj_in = linear_cls(self.cfg.in_channels, inner_dim)
|
||||
|
||||
# 3. Define transformers blocks
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
inner_dim,
|
||||
self.num_attention_heads,
|
||||
self.attention_head_dim,
|
||||
dropout=self.cfg.dropout,
|
||||
cross_attention_dim=self.cfg.cross_attention_dim,
|
||||
activation_fn=self.cfg.activation_fn,
|
||||
num_embeds_ada_norm=self.cfg.num_embeds_ada_norm,
|
||||
cond_dim_ada_norm_continuous=self.cfg.cond_dim_ada_norm_continuous,
|
||||
attention_bias=self.cfg.attention_bias,
|
||||
only_cross_attention=self.cfg.only_cross_attention,
|
||||
double_self_attention=self.cfg.double_self_attention,
|
||||
upcast_attention=self.cfg.upcast_attention,
|
||||
norm_type=self.cfg.norm_type,
|
||||
norm_elementwise_affine=self.cfg.norm_elementwise_affine,
|
||||
attention_type=self.cfg.attention_type,
|
||||
)
|
||||
for d in range(self.cfg.num_layers)
|
||||
]
|
||||
)
|
||||
|
||||
# 4. Define output layers
|
||||
self.out_channels = (
|
||||
self.cfg.in_channels
|
||||
if self.cfg.out_channels is None
|
||||
else self.cfg.out_channels
|
||||
)
|
||||
|
||||
self.proj_out = linear_cls(inner_dim, self.cfg.in_channels)
|
||||
|
||||
self.gradient_checkpointing = self.cfg.gradient_checkpointing
|
||||
|
||||
self.set_use_memory_efficient_attention_xformers(
|
||||
self.cfg.enable_memory_efficient_attention
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
encoder_hidden_states: Optional[torch.Tensor] = None,
|
||||
timestep: Optional[torch.LongTensor] = None,
|
||||
modulation_cond: Optional[torch.FloatTensor] = None,
|
||||
class_labels: Optional[torch.LongTensor] = None,
|
||||
cross_attention_kwargs: Dict[str, Any] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
encoder_attention_mask: Optional[torch.Tensor] = None,
|
||||
):
|
||||
"""
|
||||
The [`Transformer1DModel`] forward method.
|
||||
|
||||
Args:
|
||||
hidden_states (`torch.LongTensor` of shape `(batch size, num latent pixels)` if discrete, `torch.FloatTensor` of shape `(batch size, channel, height, width)` if continuous):
|
||||
Input `hidden_states`.
|
||||
encoder_hidden_states ( `torch.FloatTensor` of shape `(batch size, sequence len, embed dims)`, *optional*):
|
||||
Conditional embeddings for cross attention layer. If not given, cross-attention defaults to
|
||||
self-attention.
|
||||
timestep ( `torch.LongTensor`, *optional*):
|
||||
Used to indicate denoising step. Optional timestep to be applied as an embedding in `AdaLayerNorm`.
|
||||
class_labels ( `torch.LongTensor` of shape `(batch size, num classes)`, *optional*):
|
||||
Used to indicate class labels conditioning. Optional class labels to be applied as an embedding in
|
||||
`AdaLayerZeroNorm`.
|
||||
cross_attention_kwargs ( `Dict[str, Any]`, *optional*):
|
||||
A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under
|
||||
`self.processor` in
|
||||
[diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
|
||||
attention_mask ( `torch.Tensor`, *optional*):
|
||||
An attention mask of shape `(batch, key_tokens)` is applied to `encoder_hidden_states`. If `1` the mask
|
||||
is kept, otherwise if `0` it is discarded. Mask will be converted into a bias, which adds large
|
||||
negative values to the attention scores corresponding to "discard" tokens.
|
||||
encoder_attention_mask ( `torch.Tensor`, *optional*):
|
||||
Cross-attention mask applied to `encoder_hidden_states`. Two formats supported:
|
||||
|
||||
* Mask `(batch, sequence_length)` True = keep, False = discard.
|
||||
* Bias `(batch, 1, sequence_length)` 0 = keep, -10000 = discard.
|
||||
|
||||
If `ndim == 2`: will be interpreted as a mask, then converted into a bias consistent with the format
|
||||
above. This bias will be added to the cross-attention scores.
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`~models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain
|
||||
tuple.
|
||||
|
||||
Returns:
|
||||
If `return_dict` is True, an [`~models.transformer_2d.Transformer2DModelOutput`] is returned, otherwise a
|
||||
`tuple` where the first element is the sample tensor.
|
||||
"""
|
||||
# ensure attention_mask is a bias, and give it a singleton query_tokens dimension.
|
||||
# we may have done this conversion already, e.g. if we came here via UNet2DConditionModel#forward.
|
||||
# we can tell by counting dims; if ndim == 2: it's a mask rather than a bias.
|
||||
# expects mask of shape:
|
||||
# [batch, key_tokens]
|
||||
# adds singleton query_tokens dimension:
|
||||
# [batch, 1, key_tokens]
|
||||
# this helps to broadcast it as a bias over attention scores, which will be in one of the following shapes:
|
||||
# [batch, heads, query_tokens, key_tokens] (e.g. torch sdp attn)
|
||||
# [batch * heads, query_tokens, key_tokens] (e.g. xformers or classic attn)
|
||||
if attention_mask is not None and attention_mask.ndim == 2:
|
||||
# assume that mask is expressed as:
|
||||
# (1 = keep, 0 = discard)
|
||||
# convert mask into a bias that can be added to attention scores:
|
||||
# (keep = +0, discard = -10000.0)
|
||||
attention_mask = (1 - attention_mask.to(hidden_states.dtype)) * -10000.0
|
||||
attention_mask = attention_mask.unsqueeze(1)
|
||||
|
||||
# convert encoder_attention_mask to a bias the same way we do for attention_mask
|
||||
if encoder_attention_mask is not None and encoder_attention_mask.ndim == 2:
|
||||
encoder_attention_mask = (
|
||||
1 - encoder_attention_mask.to(hidden_states.dtype)
|
||||
) * -10000.0
|
||||
encoder_attention_mask = encoder_attention_mask.unsqueeze(1)
|
||||
|
||||
# 1. Input
|
||||
batch, _, seq_len = hidden_states.shape
|
||||
residual = hidden_states
|
||||
|
||||
hidden_states = self.norm(hidden_states)
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 1).reshape(
|
||||
batch, seq_len, inner_dim
|
||||
)
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
|
||||
# 2. Blocks
|
||||
for block in self.transformer_blocks:
|
||||
if self.training and self.gradient_checkpointing:
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
block,
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
timestep,
|
||||
modulation_cond,
|
||||
cross_attention_kwargs,
|
||||
class_labels,
|
||||
use_reentrant=False,
|
||||
)
|
||||
else:
|
||||
hidden_states = block(
|
||||
hidden_states,
|
||||
attention_mask=attention_mask,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
timestep=timestep,
|
||||
modulation_cond=modulation_cond,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
class_labels=class_labels,
|
||||
)
|
||||
|
||||
# 3. Output
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, seq_len, inner_dim)
|
||||
.permute(0, 2, 1)
|
||||
.contiguous()
|
||||
)
|
||||
|
||||
output = hidden_states + residual
|
||||
|
||||
return output
|
||||
@@ -0,0 +1,414 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import nn
|
||||
from diffusers.models.embeddings import ImagePositionalEmbeddings, PatchEmbed
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ...utils.base import BaseModule
|
||||
from .attention import (
|
||||
BasicTransformerBlock,
|
||||
MemoryEfficientAttentionMixin,
|
||||
)
|
||||
from ...utils.typing import *
|
||||
|
||||
|
||||
class Transformer2D(BaseModule, MemoryEfficientAttentionMixin):
|
||||
"""
|
||||
A 2D Transformer model for image-like data.
|
||||
|
||||
Parameters:
|
||||
num_attention_heads (`int`, *optional*, defaults to 16): The number of heads to use for multi-head attention.
|
||||
attention_head_dim (`int`, *optional*, defaults to 88): The number of channels in each head.
|
||||
in_channels (`int`, *optional*):
|
||||
The number of channels in the input and output (specify if the input is **continuous**).
|
||||
num_layers (`int`, *optional*, defaults to 1): The number of layers of Transformer blocks to use.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
cross_attention_dim (`int`, *optional*): The number of `encoder_hidden_states` dimensions to use.
|
||||
sample_size (`int`, *optional*): The width of the latent images (specify if the input is **discrete**).
|
||||
This is fixed during training since it is used to learn a number of position embeddings.
|
||||
num_vector_embeds (`int`, *optional*):
|
||||
The number of classes of the vector embeddings of the latent pixels (specify if the input is **discrete**).
|
||||
Includes the class for the masked latent pixel.
|
||||
activation_fn (`str`, *optional*, defaults to `"geglu"`): Activation function to use in feed-forward.
|
||||
num_embeds_ada_norm ( `int`, *optional*):
|
||||
The number of diffusion steps used during training. Pass if at least one of the norm_layers is
|
||||
`AdaLayerNorm`. This is fixed during training since it is used to learn a number of embeddings that are
|
||||
added to the hidden states.
|
||||
|
||||
During inference, you can denoise for up to but not more steps than `num_embeds_ada_norm`.
|
||||
attention_bias (`bool`, *optional*):
|
||||
Configure if the `TransformerBlocks` attention should contain a bias parameter.
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class Config(BaseModule.Config):
|
||||
num_attention_heads: int = 16
|
||||
attention_head_dim: int = 88
|
||||
in_channels: Optional[int] = None
|
||||
out_channels: Optional[int] = None
|
||||
num_layers: int = 1
|
||||
dropout: float = 0.0
|
||||
norm_num_groups: int = 32
|
||||
cross_attention_dim: Optional[int] = None
|
||||
attention_bias: bool = False
|
||||
sample_size: Optional[int] = None
|
||||
num_vector_embeds: Optional[int] = None
|
||||
patch_size: Optional[int] = None
|
||||
activation_fn: str = "geglu"
|
||||
num_embeds_ada_norm: Optional[int] = None
|
||||
use_linear_projection: bool = False
|
||||
only_cross_attention: bool = False
|
||||
double_self_attention: bool = False
|
||||
upcast_attention: bool = False
|
||||
norm_type: str = "layer_norm"
|
||||
norm_elementwise_affine: bool = True
|
||||
attention_type: str = "default"
|
||||
enable_memory_efficient_attention: bool = False
|
||||
gradient_checkpointing: bool = False
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self) -> None:
|
||||
super().configure()
|
||||
|
||||
self.use_linear_projection = self.cfg.use_linear_projection
|
||||
self.num_attention_heads = self.cfg.num_attention_heads
|
||||
self.attention_head_dim = self.cfg.attention_head_dim
|
||||
inner_dim = self.num_attention_heads * self.attention_head_dim
|
||||
|
||||
conv_cls = nn.Conv2d
|
||||
linear_cls = nn.Linear
|
||||
|
||||
# 1. Transformer2DModel can process both standard continuous images of shape `(batch_size, num_channels, width, height)` as well as quantized image embeddings of shape `(batch_size, num_image_vectors)`
|
||||
# Define whether input is continuous or discrete depending on configuration
|
||||
self.is_input_continuous = (self.cfg.in_channels is not None) and (
|
||||
self.cfg.patch_size is None
|
||||
)
|
||||
self.is_input_vectorized = self.cfg.num_vector_embeds is not None
|
||||
self.is_input_patches = (
|
||||
self.cfg.in_channels is not None and self.cfg.patch_size is not None
|
||||
)
|
||||
|
||||
if (
|
||||
self.cfg.norm_type == "layer_norm"
|
||||
and self.cfg.num_embeds_ada_norm is not None
|
||||
):
|
||||
deprecation_message = (
|
||||
f"The configuration file of this model: {self.__class__} is outdated. `norm_type` is either not set or"
|
||||
" incorrectly set to `'layer_norm'`.Make sure to set `norm_type` to `'ada_norm'` in the config."
|
||||
" Please make sure to update the config accordingly as leaving `norm_type` might led to incorrect"
|
||||
" results in future versions. If you have downloaded this checkpoint from the Hugging Face Hub, it"
|
||||
" would be very nice if you could open a Pull request for the `transformer/config.json` file"
|
||||
)
|
||||
lrm.warn(deprecation_message)
|
||||
self.cfg.norm_type = "ada_norm"
|
||||
|
||||
if self.is_input_continuous and self.is_input_vectorized:
|
||||
raise ValueError(
|
||||
f"Cannot define both `in_channels`: {self.cfg.in_channels} and `num_vector_embeds`: {self.cfg.num_vector_embeds}. Make"
|
||||
" sure that either `in_channels` or `num_vector_embeds` is None."
|
||||
)
|
||||
elif self.is_input_vectorized and self.is_input_patches:
|
||||
raise ValueError(
|
||||
f"Cannot define both `num_vector_embeds`: {self.cfg.num_vector_embeds} and `patch_size`: {self.cfg.patch_size}. Make"
|
||||
" sure that either `num_vector_embeds` or `num_patches` is None."
|
||||
)
|
||||
elif (
|
||||
not self.is_input_continuous
|
||||
and not self.is_input_vectorized
|
||||
and not self.is_input_patches
|
||||
):
|
||||
raise ValueError(
|
||||
f"Has to define `in_channels`: {self.cfg.in_channels}, `num_vector_embeds`: {self.cfg.num_vector_embeds}, or patch_size:"
|
||||
f" {self.cfg.patch_size}. Make sure that `in_channels`, `num_vector_embeds` or `num_patches` is not None."
|
||||
)
|
||||
|
||||
# 2. Define input layers
|
||||
if self.is_input_continuous:
|
||||
self.in_channels = self.cfg.in_channels
|
||||
|
||||
self.norm = torch.nn.GroupNorm(
|
||||
num_groups=self.cfg.norm_num_groups,
|
||||
num_channels=self.cfg.in_channels,
|
||||
eps=1e-6,
|
||||
affine=True,
|
||||
)
|
||||
if self.cfg.use_linear_projection:
|
||||
self.proj_in = linear_cls(self.cfg.in_channels, inner_dim)
|
||||
else:
|
||||
self.proj_in = conv_cls(
|
||||
self.cfg.in_channels, inner_dim, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
elif self.is_input_vectorized:
|
||||
assert (
|
||||
self.cfg.sample_size is not None
|
||||
), "Transformer2DModel over discrete input must provide sample_size"
|
||||
assert (
|
||||
self.cfg.num_vector_embeds is not None
|
||||
), "Transformer2DModel over discrete input must provide num_embed"
|
||||
|
||||
self.height = self.cfg.sample_size
|
||||
self.width = self.cfg.sample_size
|
||||
self.num_vector_embeds = self.cfg.num_vector_embeds
|
||||
self.num_latent_pixels = self.height * self.width
|
||||
|
||||
self.latent_image_embedding = ImagePositionalEmbeddings(
|
||||
num_embed=self.cfg.num_vector_embeds,
|
||||
embed_dim=inner_dim,
|
||||
height=self.height,
|
||||
width=self.width,
|
||||
)
|
||||
elif self.is_input_patches:
|
||||
assert (
|
||||
self.cfg.sample_size is not None
|
||||
), "Transformer2DModel over patched input must provide sample_size"
|
||||
|
||||
self.height = self.cfg.sample_size
|
||||
self.width = self.cfg.sample_size
|
||||
|
||||
self.patch_size = self.cfg.patch_size
|
||||
self.pos_embed = PatchEmbed(
|
||||
height=self.cfg.sample_size,
|
||||
width=self.cfg.sample_size,
|
||||
patch_size=self.cfg.patch_size,
|
||||
in_channels=self.cfg.in_channels,
|
||||
embed_dim=inner_dim,
|
||||
)
|
||||
|
||||
# 3. Define transformers blocks
|
||||
self.transformer_blocks = nn.ModuleList(
|
||||
[
|
||||
BasicTransformerBlock(
|
||||
inner_dim,
|
||||
self.num_attention_heads,
|
||||
self.attention_head_dim,
|
||||
dropout=self.cfg.dropout,
|
||||
cross_attention_dim=self.cfg.cross_attention_dim,
|
||||
activation_fn=self.cfg.activation_fn,
|
||||
num_embeds_ada_norm=self.cfg.num_embeds_ada_norm,
|
||||
attention_bias=self.cfg.attention_bias,
|
||||
only_cross_attention=self.cfg.only_cross_attention,
|
||||
double_self_attention=self.cfg.double_self_attention,
|
||||
upcast_attention=self.cfg.upcast_attention,
|
||||
norm_type=self.cfg.norm_type,
|
||||
norm_elementwise_affine=self.cfg.norm_elementwise_affine,
|
||||
attention_type=self.cfg.attention_type,
|
||||
)
|
||||
for d in range(self.cfg.num_layers)
|
||||
]
|
||||
)
|
||||
|
||||
# 4. Define output layers
|
||||
self.out_channels = (
|
||||
self.cfg.in_channels
|
||||
if self.cfg.out_channels is None
|
||||
else self.cfg.out_channels
|
||||
)
|
||||
if self.is_input_continuous:
|
||||
# TODO: should use out_channels for continuous projections
|
||||
if self.cfg.use_linear_projection:
|
||||
self.proj_out = linear_cls(inner_dim, self.cfg.in_channels)
|
||||
else:
|
||||
self.proj_out = conv_cls(
|
||||
inner_dim, self.cfg.in_channels, kernel_size=1, stride=1, padding=0
|
||||
)
|
||||
elif self.is_input_vectorized:
|
||||
self.norm_out = nn.LayerNorm(inner_dim)
|
||||
self.out = nn.Linear(inner_dim, self.num_vector_embeds - 1)
|
||||
elif self.is_input_patches:
|
||||
self.norm_out = nn.LayerNorm(inner_dim, elementwise_affine=False, eps=1e-6)
|
||||
self.proj_out_1 = nn.Linear(inner_dim, 2 * inner_dim)
|
||||
self.proj_out_2 = nn.Linear(
|
||||
inner_dim, self.cfg.patch_size * self.cfg.patch_size * self.out_channels
|
||||
)
|
||||
|
||||
self.gradient_checkpointing = self.cfg.gradient_checkpointing
|
||||
|
||||
self.set_use_memory_efficient_attention_xformers(
|
||||
self.cfg.enable_memory_efficient_attention
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
encoder_hidden_states: Optional[torch.Tensor] = None,
|
||||
timestep: Optional[torch.LongTensor] = None,
|
||||
class_labels: Optional[torch.LongTensor] = None,
|
||||
cross_attention_kwargs: Dict[str, Any] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
encoder_attention_mask: Optional[torch.Tensor] = None,
|
||||
):
|
||||
"""
|
||||
The [`Transformer2DModel`] forward method.
|
||||
|
||||
Args:
|
||||
hidden_states (`torch.LongTensor` of shape `(batch size, num latent pixels)` if discrete, `torch.FloatTensor` of shape `(batch size, channel, height, width)` if continuous):
|
||||
Input `hidden_states`.
|
||||
encoder_hidden_states ( `torch.FloatTensor` of shape `(batch size, sequence len, embed dims)`, *optional*):
|
||||
Conditional embeddings for cross attention layer. If not given, cross-attention defaults to
|
||||
self-attention.
|
||||
timestep ( `torch.LongTensor`, *optional*):
|
||||
Used to indicate denoising step. Optional timestep to be applied as an embedding in `AdaLayerNorm`.
|
||||
class_labels ( `torch.LongTensor` of shape `(batch size, num classes)`, *optional*):
|
||||
Used to indicate class labels conditioning. Optional class labels to be applied as an embedding in
|
||||
`AdaLayerZeroNorm`.
|
||||
cross_attention_kwargs ( `Dict[str, Any]`, *optional*):
|
||||
A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under
|
||||
`self.processor` in
|
||||
[diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
|
||||
attention_mask ( `torch.Tensor`, *optional*):
|
||||
An attention mask of shape `(batch, key_tokens)` is applied to `encoder_hidden_states`. If `1` the mask
|
||||
is kept, otherwise if `0` it is discarded. Mask will be converted into a bias, which adds large
|
||||
negative values to the attention scores corresponding to "discard" tokens.
|
||||
encoder_attention_mask ( `torch.Tensor`, *optional*):
|
||||
Cross-attention mask applied to `encoder_hidden_states`. Two formats supported:
|
||||
|
||||
* Mask `(batch, sequence_length)` True = keep, False = discard.
|
||||
* Bias `(batch, 1, sequence_length)` 0 = keep, -10000 = discard.
|
||||
|
||||
If `ndim == 2`: will be interpreted as a mask, then converted into a bias consistent with the format
|
||||
above. This bias will be added to the cross-attention scores.
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`~models.unet_2d_condition.UNet2DConditionOutput`] instead of a plain
|
||||
tuple.
|
||||
|
||||
Returns:
|
||||
If `return_dict` is True, an [`~models.transformer_2d.Transformer2DModelOutput`] is returned, otherwise a
|
||||
`tuple` where the first element is the sample tensor.
|
||||
"""
|
||||
# ensure attention_mask is a bias, and give it a singleton query_tokens dimension.
|
||||
# we may have done this conversion already, e.g. if we came here via UNet2DConditionModel#forward.
|
||||
# we can tell by counting dims; if ndim == 2: it's a mask rather than a bias.
|
||||
# expects mask of shape:
|
||||
# [batch, key_tokens]
|
||||
# adds singleton query_tokens dimension:
|
||||
# [batch, 1, key_tokens]
|
||||
# this helps to broadcast it as a bias over attention scores, which will be in one of the following shapes:
|
||||
# [batch, heads, query_tokens, key_tokens] (e.g. torch sdp attn)
|
||||
# [batch * heads, query_tokens, key_tokens] (e.g. xformers or classic attn)
|
||||
if attention_mask is not None and attention_mask.ndim == 2:
|
||||
# assume that mask is expressed as:
|
||||
# (1 = keep, 0 = discard)
|
||||
# convert mask into a bias that can be added to attention scores:
|
||||
# (keep = +0, discard = -10000.0)
|
||||
attention_mask = (1 - attention_mask.to(hidden_states.dtype)) * -10000.0
|
||||
attention_mask = attention_mask.unsqueeze(1)
|
||||
|
||||
# convert encoder_attention_mask to a bias the same way we do for attention_mask
|
||||
if encoder_attention_mask is not None and encoder_attention_mask.ndim == 2:
|
||||
encoder_attention_mask = (
|
||||
1 - encoder_attention_mask.to(hidden_states.dtype)
|
||||
) * -10000.0
|
||||
encoder_attention_mask = encoder_attention_mask.unsqueeze(1)
|
||||
|
||||
# 1. Input
|
||||
if self.is_input_continuous:
|
||||
batch, _, height, width = hidden_states.shape
|
||||
residual = hidden_states
|
||||
|
||||
hidden_states = self.norm(hidden_states)
|
||||
if not self.use_linear_projection:
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(
|
||||
batch, height * width, inner_dim
|
||||
)
|
||||
else:
|
||||
inner_dim = hidden_states.shape[1]
|
||||
hidden_states = hidden_states.permute(0, 2, 3, 1).reshape(
|
||||
batch, height * width, inner_dim
|
||||
)
|
||||
hidden_states = self.proj_in(hidden_states)
|
||||
|
||||
elif self.is_input_vectorized:
|
||||
hidden_states = self.latent_image_embedding(hidden_states)
|
||||
elif self.is_input_patches:
|
||||
hidden_states = self.pos_embed(hidden_states)
|
||||
|
||||
# 2. Blocks
|
||||
for block in self.transformer_blocks:
|
||||
if self.training and self.gradient_checkpointing:
|
||||
hidden_states = torch.utils.checkpoint.checkpoint(
|
||||
block,
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
timestep,
|
||||
cross_attention_kwargs,
|
||||
class_labels,
|
||||
use_reentrant=False,
|
||||
)
|
||||
else:
|
||||
hidden_states = block(
|
||||
hidden_states,
|
||||
attention_mask=attention_mask,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
timestep=timestep,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
class_labels=class_labels,
|
||||
)
|
||||
|
||||
# 3. Output
|
||||
if self.is_input_continuous:
|
||||
if not self.use_linear_projection:
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, height, width, inner_dim)
|
||||
.permute(0, 3, 1, 2)
|
||||
.contiguous()
|
||||
)
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
else:
|
||||
hidden_states = self.proj_out(hidden_states)
|
||||
hidden_states = (
|
||||
hidden_states.reshape(batch, height, width, inner_dim)
|
||||
.permute(0, 3, 1, 2)
|
||||
.contiguous()
|
||||
)
|
||||
|
||||
output = hidden_states + residual
|
||||
elif self.is_input_vectorized:
|
||||
hidden_states = self.norm_out(hidden_states)
|
||||
logits = self.out(hidden_states)
|
||||
# (batch, self.num_vector_embeds - 1, self.num_latent_pixels)
|
||||
logits = logits.permute(0, 2, 1)
|
||||
|
||||
# log(p(x_0))
|
||||
output = F.log_softmax(logits.double(), dim=1).float()
|
||||
elif self.is_input_patches:
|
||||
# TODO: cleanup!
|
||||
conditioning = self.transformer_blocks[0].norm1.emb(
|
||||
timestep, class_labels, hidden_dtype=hidden_states.dtype
|
||||
)
|
||||
shift, scale = self.proj_out_1(F.silu(conditioning)).chunk(2, dim=1)
|
||||
hidden_states = (
|
||||
self.norm_out(hidden_states) * (1 + scale[:, None]) + shift[:, None]
|
||||
)
|
||||
hidden_states = self.proj_out_2(hidden_states)
|
||||
|
||||
# unpatchify
|
||||
height = width = int(hidden_states.shape[1] ** 0.5)
|
||||
hidden_states = hidden_states.reshape(
|
||||
shape=(
|
||||
-1,
|
||||
height,
|
||||
width,
|
||||
self.patch_size,
|
||||
self.patch_size,
|
||||
self.out_channels,
|
||||
)
|
||||
)
|
||||
hidden_states = torch.einsum("nhwpqc->nchpwq", hidden_states)
|
||||
output = hidden_states.reshape(
|
||||
shape=(
|
||||
-1,
|
||||
self.out_channels,
|
||||
height * self.patch_size,
|
||||
width * self.patch_size,
|
||||
)
|
||||
)
|
||||
|
||||
return output
|
||||
@@ -0,0 +1,283 @@
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import pytorch_lightning as pl
|
||||
import torch.nn.functional as F
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from .utils import parse_optimizer, parse_scheduler
|
||||
from ..utils.base import (
|
||||
Updateable,
|
||||
update_end_if_possible,
|
||||
update_if_possible,
|
||||
)
|
||||
from ..models.exporters.base import Exporter, ExporterOutput
|
||||
from ..utils.config import parse_structured
|
||||
from ..utils.misc import C, cleanup, get_device, load_module_weights
|
||||
#from ..utils.saving import SaverMixin
|
||||
from ..utils.typing import *
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseLossConfig:
|
||||
pass
|
||||
|
||||
#class BaseSystem(pl.LightningModule, Updateable, SaverMixin):
|
||||
class BaseSystem(pl.LightningModule, Updateable):
|
||||
@dataclass
|
||||
class Config:
|
||||
#loss: BaseLossConfig = BaseLossConfig()
|
||||
optimizer: dict = field(default_factory=dict)
|
||||
scheduler: Optional[dict] = None
|
||||
weights: Optional[str] = None
|
||||
weights_ignore_modules: Optional[List[str]] = None
|
||||
weights_mapping: Optional[List[Dict[str, str]]] = None
|
||||
check_train_every_n_steps: int = 0
|
||||
check_val_limit_rank: int = 8
|
||||
cleanup_after_validation_step: bool = False
|
||||
cleanup_after_test_step: bool = False
|
||||
|
||||
exporter_cls: str = "CharacterGen.Stage_3D.lrm.models.exporters.mesh_exporter.MeshExporter"
|
||||
exporter: dict = field(default_factory=lambda: {"fmt": "obj", "save_uv": False})
|
||||
|
||||
cfg: Config
|
||||
|
||||
def __init__(self, cfg, resumed=False) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
self._save_dir: Optional[str] = None
|
||||
self._resumed: bool = resumed
|
||||
self._resumed_eval: bool = False
|
||||
self._resumed_eval_status: dict = {"global_step": 0, "current_epoch": 0}
|
||||
|
||||
self.configure()
|
||||
print(self.cfg.weights)
|
||||
if self.cfg.weights is not None:
|
||||
self.load_weights(
|
||||
self.cfg.weights,
|
||||
self.cfg.weights_ignore_modules,
|
||||
self.cfg.weights_mapping,
|
||||
)
|
||||
print("finish loading!!")
|
||||
self.post_configure()
|
||||
|
||||
def load_weights(
|
||||
self,
|
||||
weights: str,
|
||||
ignore_modules: Optional[List[str]] = None,
|
||||
mapping: Optional[List[Dict[str, str]]] = None,
|
||||
):
|
||||
state_dict, epoch, global_step = load_module_weights(
|
||||
weights,
|
||||
ignore_modules=ignore_modules,
|
||||
mapping=mapping,
|
||||
map_location="cpu",
|
||||
)
|
||||
self.load_state_dict(state_dict, strict=False)
|
||||
# restore step-dependent states
|
||||
self.do_update_step(epoch, global_step, on_load_weights=True)
|
||||
|
||||
def set_resume_status(self, current_epoch: int, global_step: int):
|
||||
# restore correct epoch and global step in eval
|
||||
self._resumed_eval = True
|
||||
self._resumed_eval_status["current_epoch"] = current_epoch
|
||||
self._resumed_eval_status["global_step"] = global_step
|
||||
|
||||
@property
|
||||
def resumed(self):
|
||||
# whether from resumed checkpoint
|
||||
return self._resumed
|
||||
|
||||
@property
|
||||
def true_global_step(self):
|
||||
if self._resumed_eval:
|
||||
return self._resumed_eval_status["global_step"]
|
||||
else:
|
||||
return self.global_step
|
||||
|
||||
@property
|
||||
def true_current_epoch(self):
|
||||
if self._resumed_eval:
|
||||
return self._resumed_eval_status["current_epoch"]
|
||||
else:
|
||||
return self.current_epoch
|
||||
|
||||
def configure(self) -> None:
|
||||
pass
|
||||
|
||||
def post_configure(self) -> None:
|
||||
"""
|
||||
executed after weights are loaded
|
||||
"""
|
||||
pass
|
||||
|
||||
def C(self, value: Any) -> float:
|
||||
return C(value, self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def configure_optimizers(self):
|
||||
optim = parse_optimizer(self.cfg.optimizer, self)
|
||||
ret = {
|
||||
"optimizer": optim,
|
||||
}
|
||||
if self.cfg.scheduler is not None:
|
||||
ret.update(
|
||||
{
|
||||
"lr_scheduler": parse_scheduler(self.cfg.scheduler, optim),
|
||||
}
|
||||
)
|
||||
return ret
|
||||
|
||||
def on_fit_start(self) -> None:
|
||||
if self._save_dir is not None:
|
||||
lrm.info(f"Validation results will be saved to {self._save_dir}")
|
||||
else:
|
||||
lrm.warn(
|
||||
f"Saving directory not set for the system, visualization results will not be saved"
|
||||
)
|
||||
|
||||
def training_step(self, batch, batch_idx):
|
||||
raise NotImplementedError
|
||||
|
||||
def check_train(self, batch, outputs, **kwargs):
|
||||
if (
|
||||
self.global_rank == 0
|
||||
and self.cfg.check_train_every_n_steps > 0
|
||||
and self.true_global_step % self.cfg.check_train_every_n_steps == 0
|
||||
):
|
||||
self.on_check_train(batch, outputs, **kwargs)
|
||||
|
||||
def on_check_train(self, batch, outputs, **kwargs):
|
||||
pass
|
||||
|
||||
def validation_step(self, batch, batch_idx):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_validation_epoch_end(self):
|
||||
pass
|
||||
|
||||
def test_step(self, batch, batch_idx):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_test_epoch_end(self):
|
||||
pass
|
||||
|
||||
def on_test_end(self) -> None:
|
||||
if self._save_dir is not None:
|
||||
lrm.info(f"Test results saved to {self._save_dir}")
|
||||
|
||||
def on_predict_start(self) -> None:
|
||||
pass
|
||||
|
||||
def predict_step(self, batch, batch_idx):
|
||||
batch_size = batch["index"].shape[0]
|
||||
scene_codes = self(batch)
|
||||
for b in range(batch_size):
|
||||
if batch["view_index"][b, 0] == 0:
|
||||
exporter_output: List[ExporterOutput] = self.exporter(
|
||||
batch["index"][b][None], scene_codes[b][None]
|
||||
)
|
||||
for out in exporter_output:
|
||||
save_func_name = f"save_{out.save_type}"
|
||||
if not hasattr(self, save_func_name):
|
||||
raise ValueError(
|
||||
f"{save_func_name} not supported by the SaverMixin"
|
||||
)
|
||||
save_func = getattr(self, save_func_name)
|
||||
save_func(
|
||||
f"it{self.true_global_step}-export/{out.save_name}",
|
||||
**out.params,
|
||||
)
|
||||
if self.exporter.cfg.save_video:
|
||||
self.test_step(batch, batch_idx)
|
||||
|
||||
def on_predict_epoch_end(self) -> None:
|
||||
if self.exporter.cfg.save_video:
|
||||
self.on_test_epoch_end()
|
||||
|
||||
def on_predict_end(self) -> None:
|
||||
if self._save_dir is not None:
|
||||
lrm.info(f"Export assets saved to {self._save_dir}")
|
||||
|
||||
def preprocess_data(self, batch, stage):
|
||||
pass
|
||||
|
||||
"""
|
||||
Implementing on_after_batch_transfer of DataModule does the same.
|
||||
But on_after_batch_transfer does not support DP.
|
||||
"""
|
||||
|
||||
def on_train_batch_start(self, batch, batch_idx, unused=0):
|
||||
self.preprocess_data(batch, "train")
|
||||
self.dataset = self.trainer.train_dataloader.dataset
|
||||
update_if_possible(self.dataset, self.true_current_epoch, self.true_global_step)
|
||||
self.do_update_step(self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def on_validation_batch_start(self, batch, batch_idx, dataloader_idx=0):
|
||||
self.preprocess_data(batch, "validation")
|
||||
self.dataset = self.trainer.val_dataloaders.dataset
|
||||
update_if_possible(self.dataset, self.true_current_epoch, self.true_global_step)
|
||||
self.do_update_step(self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def on_test_batch_start(self, batch, batch_idx, dataloader_idx=0):
|
||||
self.preprocess_data(batch, "test")
|
||||
self.dataset = self.trainer.test_dataloaders.dataset
|
||||
update_if_possible(self.dataset, self.true_current_epoch, self.true_global_step)
|
||||
self.do_update_step(self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def on_predict_batch_start(self, batch, batch_idx, dataloader_idx=0):
|
||||
self.preprocess_data(batch, "predict")
|
||||
self.dataset = self.trainer.predict_dataloaders.dataset
|
||||
update_if_possible(self.dataset, self.true_current_epoch, self.true_global_step)
|
||||
self.do_update_step(self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def on_train_batch_end(self, outputs, batch, batch_idx):
|
||||
self.dataset = self.trainer.train_dataloader.dataset
|
||||
update_end_if_possible(
|
||||
self.dataset, self.true_current_epoch, self.true_global_step
|
||||
)
|
||||
self.do_update_step_end(self.true_current_epoch, self.true_global_step)
|
||||
|
||||
def on_validation_batch_end(self, outputs, batch, batch_idx):
|
||||
self.dataset = self.trainer.val_dataloaders.dataset
|
||||
update_end_if_possible(
|
||||
self.dataset, self.true_current_epoch, self.true_global_step
|
||||
)
|
||||
self.do_update_step_end(self.true_current_epoch, self.true_global_step)
|
||||
if self.cfg.cleanup_after_validation_step:
|
||||
# cleanup to save vram
|
||||
cleanup()
|
||||
|
||||
def on_test_batch_end(self, outputs, batch, batch_idx):
|
||||
self.dataset = self.trainer.test_dataloaders.dataset
|
||||
update_end_if_possible(
|
||||
self.dataset, self.true_current_epoch, self.true_global_step
|
||||
)
|
||||
self.do_update_step_end(self.true_current_epoch, self.true_global_step)
|
||||
if self.cfg.cleanup_after_test_step:
|
||||
# cleanup to save vram
|
||||
cleanup()
|
||||
|
||||
def on_predict_batch_end(self, outputs, batch, batch_idx):
|
||||
self.dataset = self.trainer.predict_dataloaders.dataset
|
||||
update_end_if_possible(
|
||||
self.dataset, self.true_current_epoch, self.true_global_step
|
||||
)
|
||||
self.do_update_step_end(self.true_current_epoch, self.true_global_step)
|
||||
if self.cfg.cleanup_after_test_step:
|
||||
# cleanup to save vram
|
||||
cleanup()
|
||||
|
||||
def update_step(self, epoch: int, global_step: int, on_load_weights: bool = False):
|
||||
pass
|
||||
|
||||
def on_before_optimizer_step(self, optimizer):
|
||||
"""
|
||||
# some gradient-related debugging goes here, example:
|
||||
from lightning.pytorch.utilities import grad_norm
|
||||
norms = grad_norm(self.geometry, norm_type=2)
|
||||
print(norms)
|
||||
for name, p in self.named_parameters():
|
||||
if p.grad is None:
|
||||
lrm.info(f"{name} does not receive gradients!")
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,342 @@
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from einops import rearrange
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from CharacterGen.Stage_3D.lrm.models.mesh import Mesh
|
||||
from CharacterGen.Stage_3D.lrm.systems.base import BaseLossConfig, BaseSystem
|
||||
from CharacterGen.Stage_3D.lrm.utils.ops import binary_cross_entropy, get_plucker_rays
|
||||
from CharacterGen.Stage_3D.lrm.utils.typing import *
|
||||
from CharacterGen.Stage_3D.lrm.models.lpips import LPIPS
|
||||
from CharacterGen.Stage_3D.lrm.utils.misc import time_recorder as tr
|
||||
|
||||
|
||||
@dataclass
|
||||
class MultiviewLRMLossConfig(BaseLossConfig):
|
||||
lambda_mse: Any = 0.0
|
||||
lambda_mse_coarse: Any = 0.0
|
||||
lambda_smooth_l1: Any = 0.0
|
||||
lambda_smooth_l1_coarse: Any = 0.0
|
||||
lambda_lpips: Any = 0.0
|
||||
lambda_lpips_coarse: Any = 0.0
|
||||
lambda_mask: Any = 0.0
|
||||
lambda_mask_coarse: Any = 0.0
|
||||
|
||||
|
||||
class MultiviewLRM(BaseSystem):
|
||||
@dataclass
|
||||
class Config(BaseSystem.Config):
|
||||
#loss: MultiviewLRMLossConfig = MultiviewLRMLossConfig()
|
||||
|
||||
camera_embedder_cls: str = ""
|
||||
camera_embedder: dict = field(default_factory=dict)
|
||||
|
||||
image_tokenizer_cls: str = ""
|
||||
image_tokenizer: dict = field(default_factory=dict)
|
||||
|
||||
tokenizer_cls: str = ""
|
||||
tokenizer: dict = field(default_factory=dict)
|
||||
|
||||
backbone_cls: str = ""
|
||||
backbone: dict = field(default_factory=dict)
|
||||
|
||||
post_processor_cls: str = ""
|
||||
post_processor: dict = field(default_factory=dict)
|
||||
|
||||
decoder_cls: str = ""
|
||||
decoder: dict = field(default_factory=dict)
|
||||
|
||||
material_cls: str = ""
|
||||
material: dict = field(default_factory=dict)
|
||||
|
||||
background_cls: str = ""
|
||||
background: dict = field(default_factory=dict)
|
||||
|
||||
renderer_cls: str = ""
|
||||
renderer: dict = field(default_factory=dict)
|
||||
|
||||
resume_ckpt_path: str = ""
|
||||
|
||||
cfg: Config
|
||||
|
||||
def configure(self):
|
||||
super().configure()
|
||||
self.image_tokenizer = lrm.find(self.cfg.image_tokenizer_cls)(
|
||||
self.cfg.image_tokenizer
|
||||
)
|
||||
if self.cfg.image_tokenizer.modulation:
|
||||
self.camera_embedder = lrm.find(self.cfg.camera_embedder_cls)(
|
||||
self.cfg.camera_embedder
|
||||
)
|
||||
self.tokenizer = lrm.find(self.cfg.tokenizer_cls)(self.cfg.tokenizer)
|
||||
self.backbone = lrm.find(self.cfg.backbone_cls)(self.cfg.backbone)
|
||||
self.post_processor = lrm.find(self.cfg.post_processor_cls)(
|
||||
self.cfg.post_processor
|
||||
)
|
||||
self.decoder = lrm.find(self.cfg.decoder_cls)(self.cfg.decoder)
|
||||
self.material = lrm.find(self.cfg.material_cls)(self.cfg.material)
|
||||
self.background = lrm.find(self.cfg.background_cls)(self.cfg.background)
|
||||
self.renderer = lrm.find(self.cfg.renderer_cls)(
|
||||
self.cfg.renderer, self.decoder, self.material, self.background
|
||||
)
|
||||
|
||||
self.exporter = lrm.find(self.cfg.exporter_cls)(
|
||||
self.cfg.exporter, self.renderer
|
||||
)
|
||||
|
||||
def on_fit_start(self):
|
||||
super().on_fit_start()
|
||||
self.lpips_loss_fn = LPIPS()
|
||||
|
||||
def forward(self, batch: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# batch["rgb_cond"]: B, N_cond, H, W, 3
|
||||
# batch["rgb"]: B, N_render, H, W, 3
|
||||
# batch["c2w_cond"]: B, N_cond, 4, 4
|
||||
# for single image input (like LRM), N_cond = 1
|
||||
|
||||
batch_size, n_input_views = batch["rgb_cond"].shape[:2]
|
||||
|
||||
# Camera modulation
|
||||
camera_embeds: Optional[Float[Tensor, "B Nv Cc"]]
|
||||
if self.cfg.image_tokenizer.modulation:
|
||||
camera_embeds = self.camera_embedder(**batch)
|
||||
else:
|
||||
camera_embeds = None
|
||||
|
||||
tr.start("image tokenizer")
|
||||
input_image_tokens: Float[Tensor, "B Nv Cit Nit"] = self.image_tokenizer(
|
||||
rearrange(batch["rgb_cond"], "B Nv H W C -> B Nv C H W"),
|
||||
modulation_cond=camera_embeds,
|
||||
plucker_rays=rearrange(
|
||||
get_plucker_rays(batch["rays_o_cond"], batch["rays_d_cond"]),
|
||||
"B Nv H W C -> B Nv C H W",
|
||||
)
|
||||
if "rays_o_cond" in batch
|
||||
else None,
|
||||
)
|
||||
tr.end("image tokenizer")
|
||||
|
||||
input_image_tokens = rearrange(
|
||||
input_image_tokens, "B Nv C Nt -> B (Nv Nt) C", Nv=n_input_views
|
||||
)
|
||||
|
||||
tokens: Float[Tensor, "B Ct Nt"] = self.tokenizer(batch_size)
|
||||
|
||||
tr.start("backbone")
|
||||
tokens = self.backbone(
|
||||
tokens,
|
||||
encoder_hidden_states=input_image_tokens,
|
||||
modulation_cond=None,
|
||||
)
|
||||
tr.end("backbone")
|
||||
|
||||
scene_codes = self.post_processor(self.tokenizer.detokenize(tokens))
|
||||
return scene_codes
|
||||
|
||||
def forward_renderer_nerf(
|
||||
self, batch: Dict[str, Any], scene_codes
|
||||
) -> Dict[str, Any]:
|
||||
tr.start("render")
|
||||
render_out = self.renderer(scene_codes, **batch)
|
||||
tr.end("render")
|
||||
return render_out
|
||||
|
||||
def training_step(self, batch, batch_idx):
|
||||
scene_codes = self(batch)
|
||||
out = self.forward_renderer_nerf(batch, scene_codes)
|
||||
|
||||
loss = 0.0
|
||||
|
||||
for suffix in ["", "_coarse"]:
|
||||
if not f"comp_rgb{suffix}" in out:
|
||||
continue
|
||||
|
||||
comp_rgb: Float[Tensor, "B Nv H W 3"] = out["comp_rgb{}".format(suffix)]
|
||||
gt_rgb: Float[Tensor, "B Nv H W 3"] = batch["rgb"]
|
||||
|
||||
self.log(f"train/comp_rgb_min{suffix}", comp_rgb.min())
|
||||
|
||||
loss_mse = F.mse_loss(comp_rgb, gt_rgb, reduction="mean")
|
||||
self.log(f"train/loss_mse{suffix}", loss_mse)
|
||||
loss += loss_mse * self.C(self.cfg.loss[f"lambda_mse{suffix}"])
|
||||
|
||||
loss_smooth_l1 = F.smooth_l1_loss(
|
||||
comp_rgb, gt_rgb, beta=0.1, reduction="mean"
|
||||
)
|
||||
self.log(f"train/loss_smooth_l1{suffix}", loss_smooth_l1)
|
||||
loss += loss_smooth_l1 * self.C(self.cfg.loss[f"lambda_smooth_l1{suffix}"])
|
||||
|
||||
if self.C(self.cfg.loss[f"lambda_lpips{suffix}"]) > 0:
|
||||
loss_lpips = self.lpips_loss_fn(
|
||||
rearrange(comp_rgb, "B Nv H W C -> (B Nv) C H W"),
|
||||
rearrange(gt_rgb, "B Nv H W C -> (B Nv) C H W"),
|
||||
input_range=(0, 1),
|
||||
).mean()
|
||||
self.log(f"train/loss_lpips{suffix}", loss_lpips)
|
||||
loss += loss_lpips * self.C(self.cfg.loss[f"lambda_lpips{suffix}"])
|
||||
|
||||
loss_mask = binary_cross_entropy(
|
||||
out[f"opacity{suffix}"].clamp(1e-5, 1 - 1e-5), batch["mask"]
|
||||
)
|
||||
self.log(f"train/loss_mask{suffix}", loss_mask)
|
||||
loss += loss_mask * self.C(self.cfg.loss[f"lambda_mask{suffix}"])
|
||||
|
||||
for name, value in self.cfg.loss.items():
|
||||
self.log(f"train_params/{name}", self.C(value))
|
||||
|
||||
# will execute self.on_check_train every self.cfg.check_train_every_n_steps steps
|
||||
self.check_train(
|
||||
batch,
|
||||
out,
|
||||
extra=f"m{loss_mse:.2f}_l{loss_smooth_l1:.2f}_p{loss_lpips:.2f}_ma{loss_mask:.2f}",
|
||||
)
|
||||
|
||||
return {"loss": loss}
|
||||
|
||||
def get_input_visualizations(self, batch):
|
||||
return [
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": rearrange(batch["rgb_cond"], "B N H W C -> (B H) (N W) C"),
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
}
|
||||
]
|
||||
|
||||
def get_output_visualizations(self, batch, outputs):
|
||||
out = outputs
|
||||
images = []
|
||||
if "rgb" in batch:
|
||||
images += [
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": rearrange(batch["rgb"], "B N H W C -> (B H) (N W) C"),
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
},
|
||||
{
|
||||
"type": "grayscale",
|
||||
"img": rearrange(batch["mask"], "B N H W C -> (B H) (N W) C")[
|
||||
..., 0
|
||||
],
|
||||
"kwargs": {"cmap": None, "data_range": None},
|
||||
},
|
||||
]
|
||||
for suffix in ["", "_coarse"]:
|
||||
if not f"comp_rgb{suffix}" in out:
|
||||
continue
|
||||
images += [
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": rearrange(
|
||||
out[f"comp_rgb{suffix}"], "B N H W C -> (B H) (N W) C"
|
||||
),
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
},
|
||||
{
|
||||
"type": "grayscale",
|
||||
"img": rearrange(
|
||||
out[f"opacity{suffix}"], "B N H W C -> (B H) (N W) C"
|
||||
)[..., 0],
|
||||
"kwargs": {"cmap": None, "data_range": None},
|
||||
},
|
||||
{
|
||||
"type": "grayscale",
|
||||
"img": rearrange(
|
||||
out[f"depth{suffix}"], "B N H W C -> (B H) (N W) C"
|
||||
)[..., 0],
|
||||
"kwargs": {"cmap": None, "data_range": None},
|
||||
},
|
||||
]
|
||||
return images
|
||||
|
||||
# def check_train(self, batch, outputs, **kwargs):
|
||||
# self.on_check_train(batch, outputs, **kwargs)
|
||||
|
||||
def on_check_train(self, batch, outputs, extra=""):
|
||||
self.save_image_grid(
|
||||
f"it{self.true_global_step}-train.jpg",
|
||||
self.get_output_visualizations(batch, outputs),
|
||||
name="train_step_output",
|
||||
step=self.true_global_step,
|
||||
)
|
||||
# self.save_image_grid(
|
||||
# f"debug/it{self.true_global_step}-{self.global_rank}-{extra}.jpg",
|
||||
# self.get_output_visualizations(batch, outputs),
|
||||
# name="train_step_output",
|
||||
# step=self.true_global_step,
|
||||
# )
|
||||
# self.save_json(
|
||||
# f"debug_list/it{self.true_global_step}-{self.global_rank}-ids.json",
|
||||
# batch["scene_id"],
|
||||
# )
|
||||
|
||||
def validation_step(self, batch, batch_idx):
|
||||
scene_codes = self(batch)
|
||||
out = self.forward_renderer_nerf(batch, scene_codes)
|
||||
if (
|
||||
self.cfg.check_val_limit_rank > 0
|
||||
and self.global_rank < self.cfg.check_val_limit_rank
|
||||
):
|
||||
self.save_image_grid(
|
||||
f"it{self.true_global_step}-validation-{self.global_rank}_{batch_idx}-input.jpg",
|
||||
self.get_input_visualizations(batch),
|
||||
name=f"validation_step_input_{self.global_rank}_{batch_idx}",
|
||||
step=self.true_global_step,
|
||||
)
|
||||
self.save_image_grid(
|
||||
f"it{self.true_global_step}-validation-{self.global_rank}_{batch_idx}.jpg",
|
||||
self.get_output_visualizations(batch, out),
|
||||
name=f"validation_step_output_{self.global_rank}_{batch_idx}",
|
||||
step=self.true_global_step,
|
||||
)
|
||||
|
||||
def test_step(self, batch, batch_idx):
|
||||
# not saved to wandb
|
||||
scene_codes = self(batch)
|
||||
out = self.forward_renderer_nerf(batch, scene_codes)
|
||||
batch_size = batch["index"].shape[0]
|
||||
for b in range(batch_size):
|
||||
if batch["view_index"][b, 0] == 0:
|
||||
self.save_image_grid(
|
||||
f"it{self.true_global_step}-test/{batch['index'][b]}-input.jpg",
|
||||
[
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": rearrange(
|
||||
batch["rgb_cond"][b], "N H W C -> H (N W) C"
|
||||
),
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
},
|
||||
],
|
||||
)
|
||||
self.save_image_grid(
|
||||
f"it{self.true_global_step}-test/{batch['index'][b]}/{batch['view_index'][b,0]}.png",
|
||||
[
|
||||
{
|
||||
"type": "rgb",
|
||||
"img": out["comp_rgb"][b][0],
|
||||
"kwargs": {"data_format": "HWC"},
|
||||
},
|
||||
{
|
||||
"type": "grayscale",
|
||||
"img": out["depth"][b][0, ..., 0],
|
||||
"kwargs": {"cmap": None, "data_range": None},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
def on_test_end(self):
|
||||
if self.global_rank == 0:
|
||||
self.save_img_sequences(
|
||||
f"it{self.true_global_step}-test",
|
||||
"(\d+)\.png",
|
||||
save_format="mp4",
|
||||
fps=30,
|
||||
)
|
||||
|
||||
def get_geometry(self, scene_code: torch.Tensor) -> Mesh:
|
||||
tr.start("Surface extraction")
|
||||
mesh: Mesh = self.renderer.isosurface(scene_code)
|
||||
tr.end("Surface extraction")
|
||||
return mesh
|
||||
@@ -0,0 +1,100 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.optim import lr_scheduler
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
|
||||
|
||||
def get_scheduler(name):
|
||||
if hasattr(lr_scheduler, name):
|
||||
return getattr(lr_scheduler, name)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def getattr_recursive(m, attr):
|
||||
for name in attr.split("."):
|
||||
m = getattr(m, name)
|
||||
return m
|
||||
|
||||
|
||||
def get_parameters(model, name):
|
||||
module = getattr_recursive(model, name)
|
||||
if isinstance(module, nn.Module):
|
||||
return module.parameters()
|
||||
elif isinstance(module, nn.Parameter):
|
||||
return module
|
||||
return []
|
||||
|
||||
|
||||
def parse_optimizer(config, model):
|
||||
if hasattr(config, "params"):
|
||||
params = [
|
||||
{"params": get_parameters(model, name), "name": name, **args}
|
||||
for name, args in config.params.items()
|
||||
]
|
||||
lrm.debug(f"Specify optimizer params: {config.params}")
|
||||
else:
|
||||
params = model.parameters()
|
||||
if config.name in ["FusedAdam"]:
|
||||
import apex
|
||||
|
||||
optim = getattr(apex.optimizers, config.name)(params, **config.args)
|
||||
elif config.name in ["Adam8bit", "AdamW8bit"]:
|
||||
import bitsandbytes as bnb
|
||||
|
||||
optim = bnb.optim.Adam8bit(params, **config.args)
|
||||
else:
|
||||
optim = getattr(torch.optim, config.name)(params, **config.args)
|
||||
return optim
|
||||
|
||||
|
||||
def parse_scheduler_to_instance(config, optimizer):
|
||||
if config.name == "ChainedScheduler":
|
||||
schedulers = [
|
||||
parse_scheduler_to_instance(conf, optimizer) for conf in config.schedulers
|
||||
]
|
||||
scheduler = lr_scheduler.ChainedScheduler(schedulers)
|
||||
elif config.name == "Sequential":
|
||||
schedulers = [
|
||||
parse_scheduler_to_instance(conf, optimizer) for conf in config.schedulers
|
||||
]
|
||||
scheduler = lr_scheduler.SequentialLR(
|
||||
optimizer, schedulers, milestones=config.milestones
|
||||
)
|
||||
else:
|
||||
scheduler = getattr(lr_scheduler, config.name)(optimizer, **config.args)
|
||||
return scheduler
|
||||
|
||||
|
||||
def parse_scheduler(config, optimizer):
|
||||
interval = config.get("interval", "epoch")
|
||||
assert interval in ["epoch", "step"]
|
||||
if config.name == "SequentialLR":
|
||||
scheduler = {
|
||||
"scheduler": lr_scheduler.SequentialLR(
|
||||
optimizer,
|
||||
[
|
||||
parse_scheduler(conf, optimizer)["scheduler"]
|
||||
for conf in config.schedulers
|
||||
],
|
||||
milestones=config.milestones,
|
||||
),
|
||||
"interval": interval,
|
||||
}
|
||||
elif config.name == "ChainedScheduler":
|
||||
scheduler = {
|
||||
"scheduler": lr_scheduler.ChainedScheduler(
|
||||
[
|
||||
parse_scheduler(conf, optimizer)["scheduler"]
|
||||
for conf in config.schedulers
|
||||
]
|
||||
),
|
||||
"interval": interval,
|
||||
}
|
||||
else:
|
||||
scheduler = {
|
||||
"scheduler": get_scheduler(config.name)(optimizer, **config.args),
|
||||
"interval": interval,
|
||||
}
|
||||
return scheduler
|
||||
@@ -0,0 +1,123 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from .config import parse_structured
|
||||
from .misc import get_device, load_module_weights
|
||||
from .typing import *
|
||||
|
||||
|
||||
class Configurable:
|
||||
@dataclass
|
||||
class Config:
|
||||
pass
|
||||
|
||||
def __init__(self, cfg: Optional[dict] = None) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
|
||||
|
||||
class Updateable:
|
||||
def do_update_step(
|
||||
self, epoch: int, global_step: int, on_load_weights: bool = False
|
||||
):
|
||||
for attr in self.__dir__():
|
||||
if attr.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
module = getattr(self, attr)
|
||||
except:
|
||||
continue # ignore attributes like property, which can't be retrived using getattr?
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step(
|
||||
epoch, global_step, on_load_weights=on_load_weights
|
||||
)
|
||||
self.update_step(epoch, global_step, on_load_weights=on_load_weights)
|
||||
|
||||
def do_update_step_end(self, epoch: int, global_step: int):
|
||||
for attr in self.__dir__():
|
||||
if attr.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
module = getattr(self, attr)
|
||||
except:
|
||||
continue # ignore attributes like property, which can't be retrived using getattr?
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step_end(epoch, global_step)
|
||||
self.update_step_end(epoch, global_step)
|
||||
|
||||
def update_step(self, epoch: int, global_step: int, on_load_weights: bool = False):
|
||||
# override this method to implement custom update logic
|
||||
# if on_load_weights is True, you should be careful doing things related to model evaluations,
|
||||
# as the models and tensors are not guarenteed to be on the same device
|
||||
pass
|
||||
|
||||
def update_step_end(self, epoch: int, global_step: int):
|
||||
pass
|
||||
|
||||
|
||||
def update_if_possible(module: Any, epoch: int, global_step: int) -> None:
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step(epoch, global_step)
|
||||
|
||||
|
||||
def update_end_if_possible(module: Any, epoch: int, global_step: int) -> None:
|
||||
if isinstance(module, Updateable):
|
||||
module.do_update_step_end(epoch, global_step)
|
||||
|
||||
|
||||
class BaseObject(Updateable):
|
||||
@dataclass
|
||||
class Config:
|
||||
pass
|
||||
|
||||
cfg: Config # add this to every subclass of BaseObject to enable static type checking
|
||||
|
||||
def __init__(
|
||||
self, cfg: Optional[Union[dict, DictConfig]] = None, *args, **kwargs
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
self.device = get_device()
|
||||
self.configure(*args, **kwargs)
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class BaseModule(nn.Module, Updateable):
|
||||
@dataclass
|
||||
class Config:
|
||||
weights: Optional[str] = None
|
||||
|
||||
cfg: Config # add this to every subclass of BaseModule to enable static type checking
|
||||
|
||||
def __init__(
|
||||
self, cfg: Optional[Union[dict, DictConfig]] = None, *args, **kwargs
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.cfg = parse_structured(self.Config, cfg)
|
||||
self.device = get_device()
|
||||
self._non_modules = {}
|
||||
self.configure(*args, **kwargs)
|
||||
if self.cfg.weights is not None:
|
||||
# format: path/to/weights:module_name
|
||||
weights_path, module_name = self.cfg.weights.split(":")
|
||||
state_dict, epoch, global_step = load_module_weights(
|
||||
weights_path, module_name=module_name, map_location="cpu"
|
||||
)
|
||||
self.load_state_dict(state_dict)
|
||||
self.do_update_step(
|
||||
epoch, global_step, on_load_weights=True
|
||||
) # restore states
|
||||
|
||||
def configure(self, *args, **kwargs) -> None:
|
||||
pass
|
||||
|
||||
def register_non_module(self, name: str, module: nn.Module) -> None:
|
||||
# non-modules won't be treated as model parameters
|
||||
self._non_modules[name] = module
|
||||
|
||||
def non_module(self, name: str):
|
||||
return self._non_modules.get(name, None)
|
||||
@@ -0,0 +1,156 @@
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import pytorch_lightning
|
||||
|
||||
from .config import dump_config
|
||||
from .misc import parse_version
|
||||
|
||||
if parse_version(pytorch_lightning.__version__) > parse_version("1.8"):
|
||||
from pytorch_lightning.callbacks import Callback
|
||||
else:
|
||||
from pytorch_lightning.callbacks.base import Callback
|
||||
|
||||
from pytorch_lightning.callbacks.progress import TQDMProgressBar
|
||||
from pytorch_lightning.utilities.rank_zero import rank_zero_only, rank_zero_warn
|
||||
|
||||
|
||||
class VersionedCallback(Callback):
|
||||
def __init__(self, save_root, version=None, use_version=True):
|
||||
self.save_root = save_root
|
||||
self._version = version
|
||||
self.use_version = use_version
|
||||
|
||||
@property
|
||||
def version(self) -> int:
|
||||
"""Get the experiment version.
|
||||
|
||||
Returns:
|
||||
The experiment version if specified else the next version.
|
||||
"""
|
||||
if self._version is None:
|
||||
self._version = self._get_next_version()
|
||||
return self._version
|
||||
|
||||
def _get_next_version(self):
|
||||
existing_versions = []
|
||||
if os.path.isdir(self.save_root):
|
||||
for f in os.listdir(self.save_root):
|
||||
bn = os.path.basename(f)
|
||||
if bn.startswith("version_"):
|
||||
dir_ver = os.path.splitext(bn)[0].split("_")[1].replace("/", "")
|
||||
existing_versions.append(int(dir_ver))
|
||||
if len(existing_versions) == 0:
|
||||
return 0
|
||||
return max(existing_versions) + 1
|
||||
|
||||
@property
|
||||
def savedir(self):
|
||||
if not self.use_version:
|
||||
return self.save_root
|
||||
return os.path.join(
|
||||
self.save_root,
|
||||
self.version
|
||||
if isinstance(self.version, str)
|
||||
else f"version_{self.version}",
|
||||
)
|
||||
|
||||
|
||||
class CodeSnapshotCallback(VersionedCallback):
|
||||
def __init__(self, save_root, version=None, use_version=True):
|
||||
super().__init__(save_root, version, use_version)
|
||||
|
||||
def get_file_list(self):
|
||||
return [
|
||||
b.decode()
|
||||
for b in set(
|
||||
subprocess.check_output(
|
||||
'git ls-files -- ":!:load/*"', shell=True
|
||||
).splitlines()
|
||||
)
|
||||
| set( # hard code, TODO: use config to exclude folders or files
|
||||
subprocess.check_output(
|
||||
"git ls-files --others --exclude-standard", shell=True
|
||||
).splitlines()
|
||||
)
|
||||
]
|
||||
|
||||
@rank_zero_only
|
||||
def save_code_snapshot(self):
|
||||
os.makedirs(self.savedir, exist_ok=True)
|
||||
for f in self.get_file_list():
|
||||
if not os.path.exists(f) or os.path.isdir(f):
|
||||
continue
|
||||
os.makedirs(os.path.join(self.savedir, os.path.dirname(f)), exist_ok=True)
|
||||
shutil.copyfile(f, os.path.join(self.savedir, f))
|
||||
|
||||
def on_fit_start(self, trainer, pl_module):
|
||||
try:
|
||||
self.save_code_snapshot()
|
||||
except:
|
||||
rank_zero_warn(
|
||||
"Code snapshot is not saved. Please make sure you have git installed and are in a git repository."
|
||||
)
|
||||
|
||||
|
||||
class ConfigSnapshotCallback(VersionedCallback):
|
||||
def __init__(self, config_path, config, save_root, version=None, use_version=True):
|
||||
super().__init__(save_root, version, use_version)
|
||||
self.config_path = config_path
|
||||
self.config = config
|
||||
|
||||
@rank_zero_only
|
||||
def save_config_snapshot(self):
|
||||
os.makedirs(self.savedir, exist_ok=True)
|
||||
dump_config(os.path.join(self.savedir, "parsed.yaml"), self.config)
|
||||
shutil.copyfile(self.config_path, os.path.join(self.savedir, "raw.yaml"))
|
||||
|
||||
def on_fit_start(self, trainer, pl_module):
|
||||
self.save_config_snapshot()
|
||||
|
||||
|
||||
class CustomProgressBar(TQDMProgressBar):
|
||||
def get_metrics(self, *args, **kwargs):
|
||||
# don't show the version number
|
||||
items = super().get_metrics(*args, **kwargs)
|
||||
items.pop("v_num", None)
|
||||
return items
|
||||
|
||||
|
||||
class ProgressCallback(Callback):
|
||||
def __init__(self, save_path):
|
||||
super().__init__()
|
||||
self.save_path = save_path
|
||||
self._file_handle = None
|
||||
|
||||
@property
|
||||
def file_handle(self):
|
||||
if self._file_handle is None:
|
||||
self._file_handle = open(self.save_path, "w")
|
||||
return self._file_handle
|
||||
|
||||
@rank_zero_only
|
||||
def write(self, msg: str) -> None:
|
||||
self.file_handle.seek(0)
|
||||
self.file_handle.truncate()
|
||||
self.file_handle.write(msg)
|
||||
self.file_handle.flush()
|
||||
|
||||
@rank_zero_only
|
||||
def on_train_batch_end(self, trainer, pl_module, *args, **kwargs):
|
||||
self.write(
|
||||
f"Generation progress: {pl_module.true_global_step / trainer.max_steps * 100:.2f}%"
|
||||
)
|
||||
|
||||
@rank_zero_only
|
||||
def on_validation_start(self, trainer, pl_module):
|
||||
self.write(f"Rendering validation image ...")
|
||||
|
||||
@rank_zero_only
|
||||
def on_test_start(self, trainer, pl_module):
|
||||
self.write(f"Rendering video ...")
|
||||
|
||||
@rank_zero_only
|
||||
def on_predict_start(self, trainer, pl_module):
|
||||
self.write(f"Exporting mesh assets ...")
|
||||
@@ -0,0 +1,138 @@
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from .typing import *
|
||||
|
||||
# ============ Register OmegaConf Resolvers ============= #
|
||||
OmegaConf.register_new_resolver("calc_exp_lr_decay_rate", lambda factor, n: factor ** (1.0 / n), replace=True)
|
||||
OmegaConf.register_new_resolver("add", lambda a, b: a + b, replace=True)
|
||||
OmegaConf.register_new_resolver("sub", lambda a, b: a - b, replace=True)
|
||||
OmegaConf.register_new_resolver("mul", lambda a, b: a * b, replace=True)
|
||||
OmegaConf.register_new_resolver("div", lambda a, b: a / b, replace=True)
|
||||
OmegaConf.register_new_resolver("idiv", lambda a, b: a // b, replace=True)
|
||||
OmegaConf.register_new_resolver("basename", lambda p: os.path.basename(p), replace=True)
|
||||
OmegaConf.register_new_resolver("rmspace", lambda s, sub: s.replace(" ", sub), replace=True)
|
||||
OmegaConf.register_new_resolver("tuple2", lambda s: [float(s), float(s)], replace=True)
|
||||
OmegaConf.register_new_resolver("gt0", lambda s: s > 0, replace=True)
|
||||
OmegaConf.register_new_resolver("not", lambda s: not s, replace=True)
|
||||
|
||||
|
||||
def calc_num_train_steps(num_data, batch_size, max_epochs, num_nodes, num_cards=8):
|
||||
return int(num_data / (num_nodes * num_cards * batch_size)) * max_epochs
|
||||
|
||||
|
||||
OmegaConf.register_new_resolver("calc_num_train_steps", calc_num_train_steps)
|
||||
|
||||
# ======================================================= #
|
||||
|
||||
|
||||
# ============== Automatic Name Resolvers =============== #
|
||||
def get_naming_convention(cfg):
|
||||
# TODO
|
||||
name = f"lrm_{cfg.system.backbone.num_layers}"
|
||||
return name
|
||||
|
||||
|
||||
# ======================================================= #
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExperimentConfig:
|
||||
name: str = "default"
|
||||
description: str = ""
|
||||
tag: str = ""
|
||||
seed: int = 0
|
||||
use_timestamp: bool = True
|
||||
timestamp: Optional[str] = None
|
||||
exp_root_dir: str = "outputs"
|
||||
|
||||
### these shouldn't be set manually
|
||||
exp_dir: str = "outputs/default"
|
||||
trial_name: str = "exp"
|
||||
trial_dir: str = "outputs/default/exp"
|
||||
n_gpus: int = 1
|
||||
###
|
||||
|
||||
resume: Optional[str] = None
|
||||
|
||||
data_cls: str = ""
|
||||
data: dict = field(default_factory=dict)
|
||||
|
||||
system_cls: str = ""
|
||||
system: dict = field(default_factory=dict)
|
||||
|
||||
# accept pytorch-lightning trainer parameters
|
||||
# see https://lightning.ai/docs/pytorch/stable/common/trainer.html#trainer-class-api
|
||||
trainer: dict = field(default_factory=dict)
|
||||
|
||||
# accept pytorch-lightning checkpoint callback parameters
|
||||
# see https://lightning.ai/docs/pytorch/stable/api/lightning.pytorch.callbacks.ModelCheckpoint.html#modelcheckpoint
|
||||
checkpoint: dict = field(default_factory=dict)
|
||||
|
||||
|
||||
def load_config(
|
||||
*yamls: str, cli_args: list = [], from_string=False, makedirs=False, **kwargs
|
||||
) -> Any:
|
||||
if from_string:
|
||||
parse_func = OmegaConf.create
|
||||
else:
|
||||
parse_func = OmegaConf.load
|
||||
yaml_confs = []
|
||||
for y in yamls:
|
||||
conf = parse_func(y)
|
||||
extends = conf.pop("extends", None)
|
||||
if extends:
|
||||
assert os.path.exists(extends), f"File {extends} does not exist."
|
||||
yaml_confs.append(OmegaConf.load(extends))
|
||||
yaml_confs.append(conf)
|
||||
cli_conf = OmegaConf.from_cli(cli_args)
|
||||
cfg = OmegaConf.merge(*yaml_confs, cli_conf, kwargs)
|
||||
OmegaConf.resolve(cfg)
|
||||
assert isinstance(cfg, DictConfig)
|
||||
scfg: ExperimentConfig = parse_structured(ExperimentConfig, cfg)
|
||||
|
||||
# post processing
|
||||
# auto naming
|
||||
if scfg.name == "auto":
|
||||
scfg.name = get_naming_convention(scfg)
|
||||
# add timestamp
|
||||
if not scfg.tag and not scfg.use_timestamp:
|
||||
raise ValueError("Either tag is specified or use_timestamp is True.")
|
||||
scfg.trial_name = scfg.tag
|
||||
# if resume from an existing config, scfg.timestamp should not be None
|
||||
if scfg.timestamp is None:
|
||||
scfg.timestamp = ""
|
||||
if scfg.use_timestamp:
|
||||
if scfg.n_gpus > 1:
|
||||
lrm.warn(
|
||||
"Timestamp is disabled when using multiple GPUs, please make sure you have a unique tag."
|
||||
)
|
||||
else:
|
||||
scfg.timestamp = datetime.now().strftime("@%Y%m%d-%H%M%S")
|
||||
# make directories
|
||||
scfg.trial_name += scfg.timestamp
|
||||
scfg.exp_dir = os.path.join(scfg.exp_root_dir, scfg.name)
|
||||
scfg.trial_dir = os.path.join(scfg.exp_dir, scfg.trial_name)
|
||||
|
||||
if makedirs:
|
||||
os.makedirs(scfg.trial_dir, exist_ok=True)
|
||||
|
||||
return scfg
|
||||
|
||||
|
||||
def config_to_primitive(config, resolve: bool = True) -> Any:
|
||||
return OmegaConf.to_container(config, resolve=resolve)
|
||||
|
||||
|
||||
def dump_config(path: str, config) -> None:
|
||||
with open(path, "w") as fp:
|
||||
OmegaConf.save(config=config, f=fp)
|
||||
|
||||
|
||||
def parse_structured(fields: Any, cfg: Optional[Union[dict, DictConfig]] = None) -> Any:
|
||||
scfg = OmegaConf.merge(OmegaConf.structured(fields), cfg)
|
||||
return scfg
|
||||
@@ -0,0 +1,211 @@
|
||||
import gc
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
|
||||
import torch
|
||||
from packaging import version
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from .config import config_to_primitive
|
||||
from .typing import *
|
||||
|
||||
|
||||
def parse_version(ver: str):
|
||||
return version.parse(ver)
|
||||
|
||||
|
||||
def get_rank():
|
||||
# SLURM_PROCID can be set even if SLURM is not managing the multiprocessing,
|
||||
# therefore LOCAL_RANK needs to be checked first
|
||||
rank_keys = ("RANK", "LOCAL_RANK", "SLURM_PROCID", "JSM_NAMESPACE_RANK")
|
||||
for key in rank_keys:
|
||||
rank = os.environ.get(key)
|
||||
if rank is not None:
|
||||
return int(rank)
|
||||
return 0
|
||||
|
||||
|
||||
def get_device():
|
||||
return torch.device(f"cuda:{get_rank()}")
|
||||
|
||||
|
||||
def load_module_weights(
|
||||
path, module_name=None, ignore_modules=None, mapping=None, map_location=None
|
||||
) -> Tuple[dict, int, int]:
|
||||
if module_name is not None and ignore_modules is not None:
|
||||
raise ValueError("module_name and ignore_modules cannot be both set")
|
||||
if map_location is None:
|
||||
map_location = get_device()
|
||||
|
||||
ckpt = torch.load(path, map_location=map_location)
|
||||
state_dict = ckpt["state_dict"]
|
||||
|
||||
if mapping is not None:
|
||||
state_dict_to_load = {}
|
||||
for k, v in state_dict.items():
|
||||
if any([k.startswith(m["to"]) for m in mapping]):
|
||||
pass
|
||||
else:
|
||||
state_dict_to_load[k] = v
|
||||
for k, v in state_dict.items():
|
||||
for m in mapping:
|
||||
if k.startswith(m["from"]):
|
||||
k_dest = k.replace(m["from"], m["to"])
|
||||
lrm.info(f"Mapping {k} => {k_dest}")
|
||||
state_dict_to_load[k_dest] = v.clone()
|
||||
state_dict = state_dict_to_load
|
||||
|
||||
state_dict_to_load = state_dict
|
||||
|
||||
if ignore_modules is not None:
|
||||
state_dict_to_load = {}
|
||||
for k, v in state_dict.items():
|
||||
ignore = any(
|
||||
[k.startswith(ignore_module + ".") for ignore_module in ignore_modules]
|
||||
)
|
||||
if ignore:
|
||||
continue
|
||||
state_dict_to_load[k] = v
|
||||
|
||||
if module_name is not None:
|
||||
state_dict_to_load = {}
|
||||
for k, v in state_dict.items():
|
||||
m = re.match(rf"^{module_name}\.(.*)$", k)
|
||||
if m is None:
|
||||
continue
|
||||
state_dict_to_load[m.group(1)] = v
|
||||
|
||||
return state_dict_to_load, ckpt["epoch"], ckpt["global_step"]
|
||||
|
||||
|
||||
def C(value: Any, epoch: int, global_step: int) -> float:
|
||||
if isinstance(value, int) or isinstance(value, float):
|
||||
pass
|
||||
else:
|
||||
value = config_to_primitive(value)
|
||||
if not isinstance(value, list):
|
||||
raise TypeError("Scalar specification only supports list, got", type(value))
|
||||
if len(value) == 3:
|
||||
value = [0] + value
|
||||
assert len(value) == 4
|
||||
start_step, start_value, end_value, end_step = value
|
||||
if isinstance(end_step, int):
|
||||
current_step = global_step
|
||||
value = start_value + (end_value - start_value) * max(
|
||||
min(1.0, (current_step - start_step) / (end_step - start_step)), 0.0
|
||||
)
|
||||
elif isinstance(end_step, float):
|
||||
current_step = epoch
|
||||
value = start_value + (end_value - start_value) * max(
|
||||
min(1.0, (current_step - start_step) / (end_step - start_step)), 0.0
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def cleanup():
|
||||
gc.collect()
|
||||
torch.cuda.empty_cache()
|
||||
try:
|
||||
import tinycudann as tcnn
|
||||
|
||||
tcnn.free_temporary_memory()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def finish_with_cleanup(func: Callable):
|
||||
def wrapper(*args, **kwargs):
|
||||
out = func(*args, **kwargs)
|
||||
cleanup()
|
||||
return out
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def _distributed_available():
|
||||
return torch.distributed.is_available() and torch.distributed.is_initialized()
|
||||
|
||||
|
||||
def barrier():
|
||||
if not _distributed_available():
|
||||
return
|
||||
else:
|
||||
torch.distributed.barrier()
|
||||
|
||||
|
||||
def broadcast(tensor, src=0):
|
||||
if not _distributed_available():
|
||||
return tensor
|
||||
else:
|
||||
torch.distributed.broadcast(tensor, src=src)
|
||||
return tensor
|
||||
|
||||
|
||||
def enable_gradient(model, enabled: bool = True) -> None:
|
||||
for param in model.parameters():
|
||||
param.requires_grad_(enabled)
|
||||
|
||||
|
||||
class TimeRecorder:
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
self.items = {}
|
||||
self.accumulations = defaultdict(list)
|
||||
self.time_scale = 1000.0 # ms
|
||||
self.time_unit = "ms"
|
||||
self.enabled = False
|
||||
|
||||
def __new__(cls):
|
||||
# singleton
|
||||
if cls._instance is None:
|
||||
cls._instance = super(TimeRecorder, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def enable(self, enabled: bool) -> None:
|
||||
self.enabled = enabled
|
||||
|
||||
def start(self, name: str) -> None:
|
||||
if not self.enabled:
|
||||
return
|
||||
torch.cuda.synchronize()
|
||||
self.items[name] = time.time()
|
||||
|
||||
def end(self, name: str, accumulate: bool = False) -> float:
|
||||
if not self.enabled or name not in self.items:
|
||||
return
|
||||
torch.cuda.synchronize()
|
||||
start_time = self.items.pop(name)
|
||||
delta = time.time() - start_time
|
||||
if accumulate:
|
||||
self.accumulations[name].append(delta)
|
||||
t = delta * self.time_scale
|
||||
lrm.info(f"{name}: {t:.2f}{self.time_unit}")
|
||||
|
||||
def get_accumulation(self, name: str, average: bool = False) -> float:
|
||||
if not self.enabled or name not in self.accumulations:
|
||||
return
|
||||
acc = self.accumulations.pop(name)
|
||||
total = sum(acc)
|
||||
if average:
|
||||
t = total / len(acc) * self.time_scale
|
||||
else:
|
||||
t = total * self.time_scale
|
||||
lrm.info(f"{name} for {len(acc)} times: {t:.2f}{self.time_unit}")
|
||||
|
||||
|
||||
### global time recorder
|
||||
time_recorder = TimeRecorder()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def time_recorder_enabled():
|
||||
enabled = time_recorder.enabled
|
||||
time_recorder.enable(enabled=True)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
time_recorder.enable(enabled=enabled)
|
||||
@@ -0,0 +1,435 @@
|
||||
from collections import defaultdict
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torch.autograd import Function
|
||||
from torch.cuda.amp import custom_bwd, custom_fwd
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from .typing import *
|
||||
|
||||
|
||||
def dot(x, y):
|
||||
return torch.sum(x * y, -1, keepdim=True)
|
||||
|
||||
|
||||
def reflect(x, n):
|
||||
return 2 * dot(x, n) * n - x
|
||||
|
||||
|
||||
ValidScale = Union[Tuple[float, float], Num[Tensor, "2 D"]]
|
||||
|
||||
|
||||
def scale_tensor(
|
||||
dat: Num[Tensor, "... D"], inp_scale: ValidScale, tgt_scale: ValidScale
|
||||
):
|
||||
if inp_scale is None:
|
||||
inp_scale = (0, 1)
|
||||
if tgt_scale is None:
|
||||
tgt_scale = (0, 1)
|
||||
if isinstance(tgt_scale, Tensor):
|
||||
assert dat.shape[-1] == tgt_scale.shape[-1]
|
||||
dat = (dat - inp_scale[0]) / (inp_scale[1] - inp_scale[0])
|
||||
dat = dat * (tgt_scale[1] - tgt_scale[0]) + tgt_scale[0]
|
||||
return dat
|
||||
|
||||
|
||||
class _TruncExp(Function): # pylint: disable=abstract-method
|
||||
# Implementation from torch-ngp:
|
||||
# https://github.com/ashawkey/torch-ngp/blob/93b08a0d4ec1cc6e69d85df7f0acdfb99603b628/activation.py
|
||||
@staticmethod
|
||||
@custom_fwd(cast_inputs=torch.float32)
|
||||
def forward(ctx, x): # pylint: disable=arguments-differ
|
||||
ctx.save_for_backward(x)
|
||||
return torch.exp(x)
|
||||
|
||||
@staticmethod
|
||||
@custom_bwd
|
||||
def backward(ctx, g): # pylint: disable=arguments-differ
|
||||
x = ctx.saved_tensors[0]
|
||||
return g * torch.exp(torch.clamp(x, max=15))
|
||||
|
||||
|
||||
trunc_exp = _TruncExp.apply
|
||||
|
||||
|
||||
def get_activation(name) -> Callable:
|
||||
if name is None:
|
||||
return lambda x: x
|
||||
name = name.lower()
|
||||
if name == "none":
|
||||
return lambda x: x
|
||||
elif name == "lin2srgb":
|
||||
return lambda x: torch.where(
|
||||
x > 0.0031308,
|
||||
torch.pow(torch.clamp(x, min=0.0031308), 1.0 / 2.4) * 1.055 - 0.055,
|
||||
12.92 * x,
|
||||
).clamp(0.0, 1.0)
|
||||
elif name == "exp":
|
||||
return lambda x: torch.exp(x)
|
||||
elif name == "shifted_exp":
|
||||
return lambda x: torch.exp(x - 1.0)
|
||||
elif name == "trunc_exp":
|
||||
return trunc_exp
|
||||
elif name == "shifted_trunc_exp":
|
||||
return lambda x: trunc_exp(x - 1.0)
|
||||
elif name == "sigmoid":
|
||||
return lambda x: torch.sigmoid(x)
|
||||
elif name == "tanh":
|
||||
return lambda x: torch.tanh(x)
|
||||
elif name == "shifted_softplus":
|
||||
return lambda x: F.softplus(x - 1.0)
|
||||
elif name == "scale_-11_01":
|
||||
return lambda x: x * 0.5 + 0.5
|
||||
elif name == "negative":
|
||||
return lambda x: -x
|
||||
else:
|
||||
try:
|
||||
return getattr(F, name)
|
||||
except AttributeError:
|
||||
raise ValueError(f"Unknown activation function: {name}")
|
||||
|
||||
|
||||
def chunk_batch(func: Callable, chunk_size: int, *args, **kwargs) -> Any:
|
||||
if chunk_size <= 0:
|
||||
return func(*args, **kwargs)
|
||||
B = None
|
||||
for arg in list(args) + list(kwargs.values()):
|
||||
if isinstance(arg, torch.Tensor):
|
||||
B = arg.shape[0]
|
||||
break
|
||||
assert (
|
||||
B is not None
|
||||
), "No tensor found in args or kwargs, cannot determine batch size."
|
||||
out = defaultdict(list)
|
||||
out_type = None
|
||||
# max(1, B) to support B == 0
|
||||
for i in range(0, max(1, B), chunk_size):
|
||||
out_chunk = func(
|
||||
*[
|
||||
arg[i : i + chunk_size] if isinstance(arg, torch.Tensor) else arg
|
||||
for arg in args
|
||||
],
|
||||
**{
|
||||
k: arg[i : i + chunk_size] if isinstance(arg, torch.Tensor) else arg
|
||||
for k, arg in kwargs.items()
|
||||
},
|
||||
)
|
||||
if out_chunk is None:
|
||||
continue
|
||||
out_type = type(out_chunk)
|
||||
if isinstance(out_chunk, torch.Tensor):
|
||||
out_chunk = {0: out_chunk}
|
||||
elif isinstance(out_chunk, tuple) or isinstance(out_chunk, list):
|
||||
chunk_length = len(out_chunk)
|
||||
out_chunk = {i: chunk for i, chunk in enumerate(out_chunk)}
|
||||
elif isinstance(out_chunk, dict):
|
||||
pass
|
||||
else:
|
||||
print(
|
||||
f"Return value of func must be in type [torch.Tensor, list, tuple, dict], get {type(out_chunk)}."
|
||||
)
|
||||
exit(1)
|
||||
for k, v in out_chunk.items():
|
||||
v = v if torch.is_grad_enabled() else v.detach()
|
||||
out[k].append(v)
|
||||
|
||||
if out_type is None:
|
||||
return None
|
||||
|
||||
out_merged: Dict[Any, Optional[torch.Tensor]] = {}
|
||||
for k, v in out.items():
|
||||
if all([vv is None for vv in v]):
|
||||
# allow None in return value
|
||||
out_merged[k] = None
|
||||
elif all([isinstance(vv, torch.Tensor) for vv in v]):
|
||||
out_merged[k] = torch.cat(v, dim=0)
|
||||
else:
|
||||
raise TypeError(
|
||||
f"Unsupported types in return value of func: {[type(vv) for vv in v if not isinstance(vv, torch.Tensor)]}"
|
||||
)
|
||||
|
||||
if out_type is torch.Tensor:
|
||||
return out_merged[0]
|
||||
elif out_type in [tuple, list]:
|
||||
return out_type([out_merged[i] for i in range(chunk_length)])
|
||||
elif out_type is dict:
|
||||
return out_merged
|
||||
|
||||
|
||||
def get_ray_directions(
|
||||
H: int,
|
||||
W: int,
|
||||
focal: Union[float, Tuple[float, float]],
|
||||
principal: Optional[Tuple[float, float]] = None,
|
||||
use_pixel_centers: bool = True,
|
||||
normalize: bool = True,
|
||||
) -> Float[Tensor, "H W 3"]:
|
||||
"""
|
||||
Get ray directions for all pixels in camera coordinate.
|
||||
Reference: https://www.scratchapixel.com/lessons/3d-basic-rendering/
|
||||
ray-tracing-generating-camera-rays/standard-coordinate-systems
|
||||
|
||||
Inputs:
|
||||
H, W, focal, principal, use_pixel_centers: image height, width, focal length, principal point and whether use pixel centers
|
||||
Outputs:
|
||||
directions: (H, W, 3), the direction of the rays in camera coordinate
|
||||
"""
|
||||
pixel_center = 0.5 if use_pixel_centers else 0
|
||||
|
||||
if isinstance(focal, float):
|
||||
fx, fy = focal, focal
|
||||
cx, cy = W / 2, H / 2
|
||||
else:
|
||||
fx, fy = focal
|
||||
assert principal is not None
|
||||
cx, cy = principal
|
||||
|
||||
i, j = torch.meshgrid(
|
||||
torch.arange(W, dtype=torch.float32) + pixel_center,
|
||||
torch.arange(H, dtype=torch.float32) + pixel_center,
|
||||
indexing="xy",
|
||||
)
|
||||
|
||||
directions: Float[Tensor, "H W 3"] = torch.stack(
|
||||
[(i - cx) / fx, -(j - cy) / fy, -torch.ones_like(i)], -1
|
||||
)
|
||||
|
||||
if normalize:
|
||||
directions = F.normalize(directions, dim=-1)
|
||||
|
||||
return directions
|
||||
|
||||
|
||||
def get_rays(
|
||||
directions: Float[Tensor, "... 3"],
|
||||
c2w: Float[Tensor, "... 4 4"],
|
||||
keepdim=False,
|
||||
noise_scale=0.0,
|
||||
normalize=False,
|
||||
) -> Tuple[Float[Tensor, "... 3"], Float[Tensor, "... 3"]]:
|
||||
# Rotate ray directions from camera coordinate to the world coordinate
|
||||
assert directions.shape[-1] == 3
|
||||
|
||||
if directions.ndim == 2: # (N_rays, 3)
|
||||
if c2w.ndim == 2: # (4, 4)
|
||||
c2w = c2w[None, :, :]
|
||||
assert c2w.ndim == 3 # (N_rays, 4, 4) or (1, 4, 4)
|
||||
rays_d = (directions[:, None, :] * c2w[:, :3, :3]).sum(-1) # (N_rays, 3)
|
||||
rays_o = c2w[:, :3, 3].expand(rays_d.shape)
|
||||
elif directions.ndim == 3: # (H, W, 3)
|
||||
assert c2w.ndim in [2, 3]
|
||||
if c2w.ndim == 2: # (4, 4)
|
||||
rays_d = (directions[:, :, None, :] * c2w[None, None, :3, :3]).sum(
|
||||
-1
|
||||
) # (H, W, 3)
|
||||
rays_o = c2w[None, None, :3, 3].expand(rays_d.shape)
|
||||
elif c2w.ndim == 3: # (B, 4, 4)
|
||||
rays_d = (directions[None, :, :, None, :] * c2w[:, None, None, :3, :3]).sum(
|
||||
-1
|
||||
) # (B, H, W, 3)
|
||||
rays_o = c2w[:, None, None, :3, 3].expand(rays_d.shape)
|
||||
elif directions.ndim == 4: # (B, H, W, 3)
|
||||
assert c2w.ndim == 3 # (B, 4, 4)
|
||||
rays_d = (directions[:, :, :, None, :] * c2w[:, None, None, :3, :3]).sum(
|
||||
-1
|
||||
) # (B, H, W, 3)
|
||||
rays_o = c2w[:, None, None, :3, 3].expand(rays_d.shape)
|
||||
|
||||
# add camera noise to avoid grid-like artifect
|
||||
# https://github.com/ashawkey/stable-dreamfusion/blob/49c3d4fa01d68a4f027755acf94e1ff6020458cc/nerf/utils.py#L373
|
||||
if noise_scale > 0:
|
||||
rays_o = rays_o + torch.randn(3, device=rays_o.device) * noise_scale
|
||||
rays_d = rays_d + torch.randn(3, device=rays_d.device) * noise_scale
|
||||
|
||||
if normalize:
|
||||
rays_d = F.normalize(rays_d, dim=-1)
|
||||
if not keepdim:
|
||||
rays_o, rays_d = rays_o.reshape(-1, 3), rays_d.reshape(-1, 3)
|
||||
|
||||
return rays_o, rays_d
|
||||
|
||||
|
||||
def get_projection_matrix(
|
||||
fovy: Union[float, Float[Tensor, "B"]], aspect_wh: float, near: float, far: float
|
||||
) -> Float[Tensor, "*B 4 4"]:
|
||||
if isinstance(fovy, float):
|
||||
proj_mtx = torch.zeros(4, 4, dtype=torch.float32)
|
||||
proj_mtx[0, 0] = 1.0 / (math.tan(fovy / 2.0) * aspect_wh)
|
||||
proj_mtx[1, 1] = -1.0 / math.tan(
|
||||
fovy / 2.0
|
||||
) # add a negative sign here as the y axis is flipped in nvdiffrast output
|
||||
proj_mtx[2, 2] = -(far + near) / (far - near)
|
||||
proj_mtx[2, 3] = -2.0 * far * near / (far - near)
|
||||
proj_mtx[3, 2] = -1.0
|
||||
else:
|
||||
batch_size = fovy.shape[0]
|
||||
proj_mtx = torch.zeros(batch_size, 4, 4, dtype=torch.float32)
|
||||
proj_mtx[:, 0, 0] = 1.0 / (torch.tan(fovy / 2.0) * aspect_wh)
|
||||
proj_mtx[:, 1, 1] = -1.0 / torch.tan(
|
||||
fovy / 2.0
|
||||
) # add a negative sign here as the y axis is flipped in nvdiffrast output
|
||||
proj_mtx[:, 2, 2] = -(far + near) / (far - near)
|
||||
proj_mtx[:, 2, 3] = -2.0 * far * near / (far - near)
|
||||
proj_mtx[:, 3, 2] = -1.0
|
||||
return proj_mtx
|
||||
|
||||
|
||||
def get_mvp_matrix(
|
||||
c2w: Float[Tensor, "*B 4 4"], proj_mtx: Float[Tensor, "*B 4 4"]
|
||||
) -> Float[Tensor, "*B 4 4"]:
|
||||
# calculate w2c from c2w: R' = Rt, t' = -Rt * t
|
||||
# mathematically equivalent to (c2w)^-1
|
||||
if c2w.ndim == 2:
|
||||
assert proj_mtx.ndim == 2
|
||||
w2c: Float[Tensor, "4 4"] = torch.zeros(4, 4).to(c2w)
|
||||
w2c[:3, :3] = c2w[:3, :3].permute(1, 0)
|
||||
w2c[:3, 3:] = -c2w[:3, :3].permute(1, 0) @ c2w[:3, 3:]
|
||||
w2c[3, 3] = 1.0
|
||||
else:
|
||||
w2c: Float[Tensor, "B 4 4"] = torch.zeros(c2w.shape[0], 4, 4).to(c2w)
|
||||
w2c[:, :3, :3] = c2w[:, :3, :3].permute(0, 2, 1)
|
||||
w2c[:, :3, 3:] = -c2w[:, :3, :3].permute(0, 2, 1) @ c2w[:, :3, 3:]
|
||||
w2c[:, 3, 3] = 1.0
|
||||
# calculate mvp matrix by proj_mtx @ w2c (mv_mtx)
|
||||
mvp_mtx = proj_mtx @ w2c
|
||||
return mvp_mtx
|
||||
|
||||
|
||||
def get_intrinsic_from_fov(fov, H, W, bs=-1):
|
||||
focal_length = 0.5 * H / np.tan(0.5 * fov)
|
||||
intrinsic = np.identity(3, dtype=np.float32)
|
||||
intrinsic[0, 0] = focal_length
|
||||
intrinsic[1, 1] = focal_length
|
||||
intrinsic[0, 2] = W / 2.0
|
||||
intrinsic[1, 2] = H / 2.0
|
||||
|
||||
if bs > 0:
|
||||
intrinsic = intrinsic[None].repeat(bs, axis=0)
|
||||
|
||||
return torch.from_numpy(intrinsic)
|
||||
|
||||
|
||||
def binary_cross_entropy(input, target):
|
||||
"""
|
||||
F.binary_cross_entropy is not numerically stable in mixed-precision training.
|
||||
"""
|
||||
return -(target * torch.log(input) + (1 - target) * torch.log(1 - input)).mean()
|
||||
|
||||
|
||||
def tet_sdf_diff(
|
||||
vert_sdf: Float[Tensor, "Nv 1"], tet_edges: Integer[Tensor, "Ne 2"]
|
||||
) -> Float[Tensor, ""]:
|
||||
sdf_f1x6x2 = vert_sdf[:, 0][tet_edges.reshape(-1)].reshape(-1, 2)
|
||||
mask = torch.sign(sdf_f1x6x2[..., 0]) != torch.sign(sdf_f1x6x2[..., 1])
|
||||
sdf_f1x6x2 = sdf_f1x6x2[mask]
|
||||
sdf_diff = F.binary_cross_entropy_with_logits(
|
||||
sdf_f1x6x2[..., 0], (sdf_f1x6x2[..., 1] > 0).float()
|
||||
) + F.binary_cross_entropy_with_logits(
|
||||
sdf_f1x6x2[..., 1], (sdf_f1x6x2[..., 0] > 0).float()
|
||||
)
|
||||
return sdf_diff
|
||||
|
||||
|
||||
def validate_empty_rays(ray_indices, t_start, t_end):
|
||||
if ray_indices.nelement() == 0:
|
||||
lrm.warn("Empty rays_indices!")
|
||||
ray_indices = torch.LongTensor([0]).to(ray_indices)
|
||||
t_start = torch.Tensor([0]).to(ray_indices)
|
||||
t_end = torch.Tensor([0]).to(ray_indices)
|
||||
return ray_indices, t_start, t_end
|
||||
|
||||
|
||||
def rays_intersect_bbox(
|
||||
rays_o: Float[Tensor, "N 3"],
|
||||
rays_d: Float[Tensor, "N 3"],
|
||||
radius: Float,
|
||||
near: Float = 0.0,
|
||||
valid_thresh: Float = 0.01,
|
||||
background: bool = False,
|
||||
):
|
||||
input_shape = rays_o.shape[:-1]
|
||||
rays_o, rays_d = rays_o.view(-1, 3), rays_d.view(-1, 3)
|
||||
rays_d_valid = torch.where(
|
||||
rays_d.abs() < 1e-6, torch.full_like(rays_d, 1e-6), rays_d
|
||||
)
|
||||
if type(radius) in [int, float]:
|
||||
radius = torch.FloatTensor(
|
||||
[[-radius, radius], [-radius, radius], [-radius, radius]]
|
||||
).to(rays_o.device)
|
||||
radius = (
|
||||
1.0 - 1.0e-3
|
||||
) * radius # tighten the radius to make sure the intersection point lies in the bounding box
|
||||
interx0 = (radius[..., 1] - rays_o) / rays_d_valid
|
||||
interx1 = (radius[..., 0] - rays_o) / rays_d_valid
|
||||
t_near = torch.minimum(interx0, interx1).amax(dim=-1).clamp_min(near)
|
||||
t_far = torch.maximum(interx0, interx1).amin(dim=-1)
|
||||
|
||||
# check wheter a ray intersects the bbox or not
|
||||
rays_valid = t_far - t_near > valid_thresh
|
||||
|
||||
t_near_valid, t_far_valid = t_near[rays_valid], t_far[rays_valid]
|
||||
global_near = t_near_valid.min().item()
|
||||
global_far = t_far_valid.max().item()
|
||||
|
||||
t_near[torch.where(~rays_valid)] = 0.0
|
||||
t_far[torch.where(~rays_valid)] = 0.0
|
||||
|
||||
t_near = t_near.view(*input_shape, 1)
|
||||
t_far = t_far.view(*input_shape, 1)
|
||||
rays_valid = rays_valid.view(*input_shape)
|
||||
|
||||
return t_near, t_far, rays_valid
|
||||
|
||||
|
||||
def get_plucker_rays(rays_o: Float[Tensor, "*N 3"], rays_d: Float[Tensor, "*N 3"]) -> Float[Tensor, "*N 6"]:
|
||||
rays_o = F.normalize(rays_o, dim=-1)
|
||||
rays_d = F.normalize(rays_d, dim=-1)
|
||||
return torch.cat([
|
||||
rays_o.cross(rays_d),
|
||||
rays_d
|
||||
], dim=-1)
|
||||
|
||||
|
||||
def c2w_to_polar(c2w: Float[Tensor, "4 4"]) -> Tuple[float, float, float]:
|
||||
cam_pos = c2w[:3, 3]
|
||||
x, y, z = cam_pos.tolist()
|
||||
distance = cam_pos.norm().item()
|
||||
elevation = math.asin(z / distance)
|
||||
if abs(x) < 1.0e-5 and abs(y) < 1.0e-5:
|
||||
azimuth = 0
|
||||
else:
|
||||
azimuth = math.atan2(y, x)
|
||||
if azimuth < 0:
|
||||
azimuth += 2 * math.pi
|
||||
|
||||
return elevation, azimuth, distance
|
||||
|
||||
|
||||
def polar_to_c2w(elevation: float, azimuth: float, distance: float) -> Float[Tensor, "4 4"]:
|
||||
"""
|
||||
Compute L = p - C.
|
||||
Normalize L.
|
||||
Compute s = L x u. (cross product)
|
||||
Normalize s.
|
||||
Compute u' = s x L.
|
||||
rotation = [s, u, -l]
|
||||
"""
|
||||
z = distance * math.sin(elevation)
|
||||
x = distance * math.cos(elevation) * math.cos(azimuth)
|
||||
y = distance * math.cos(elevation) * math.sin(azimuth)
|
||||
l = -torch.as_tensor([x, y, z]).float()
|
||||
l = F.normalize(l, dim=0)
|
||||
u = torch.as_tensor([0.0, 0.0, 1.0]).float()
|
||||
s = l.cross(u)
|
||||
s = F.normalize(s, dim=0)
|
||||
u = s.cross(l)
|
||||
rot = torch.stack([s, u, -l], dim=0).T
|
||||
c2w = torch.zeros((4, 4), dtype=torch.float32)
|
||||
c2w[:3, :3] = rot
|
||||
c2w[:3, 3] = torch.as_tensor([x, y, z])
|
||||
c2w[3, 3] = 1
|
||||
return c2w
|
||||
@@ -0,0 +1,81 @@
|
||||
import nvdiffrast.torch as dr
|
||||
import torch
|
||||
|
||||
from .typing import *
|
||||
|
||||
|
||||
class NVDiffRasterizerContext:
|
||||
def __init__(self, context_type: str, device: torch.device) -> None:
|
||||
self.device = device
|
||||
self.ctx = self.initialize_context(context_type, device)
|
||||
|
||||
def initialize_context(
|
||||
self, context_type: str, device: torch.device
|
||||
) -> Union[dr.RasterizeGLContext, dr.RasterizeCudaContext]:
|
||||
context_type = "cuda" # This will throw: RuntimeError: Error building extension 'nvdiffrast_plugin', 'cl.exe' is not recognized as an internal or external command
|
||||
if context_type == "gl":
|
||||
return dr.RasterizeGLContext(device=device)
|
||||
elif context_type == "cuda":
|
||||
return dr.RasterizeCudaContext(device=device)
|
||||
else:
|
||||
raise ValueError(f"Unknown rasterizer context type: {context_type}")
|
||||
|
||||
def vertex_transform(
|
||||
self, verts: Float[Tensor, "Nv 3"], mvp_mtx: Float[Tensor, "B 4 4"]
|
||||
) -> Float[Tensor, "B Nv 4"]:
|
||||
with torch.cuda.amp.autocast(enabled=False):
|
||||
verts_homo = torch.cat(
|
||||
[verts, torch.ones([verts.shape[0], 1]).to(verts)], dim=-1
|
||||
)
|
||||
verts_clip = torch.matmul(verts_homo, mvp_mtx.permute(0, 2, 1))
|
||||
return verts_clip
|
||||
|
||||
def rasterize(
|
||||
self,
|
||||
pos: Float[Tensor, "B Nv 4"],
|
||||
tri: Integer[Tensor, "Nf 3"],
|
||||
resolution: Union[int, Tuple[int, int]],
|
||||
):
|
||||
# rasterize in instance mode (single topology)
|
||||
return dr.rasterize(self.ctx, pos.float(), tri.int(), resolution, grad_db=True)
|
||||
|
||||
def rasterize_one(
|
||||
self,
|
||||
pos: Float[Tensor, "Nv 4"],
|
||||
tri: Integer[Tensor, "Nf 3"],
|
||||
resolution: Union[int, Tuple[int, int]],
|
||||
):
|
||||
# rasterize one single mesh under a single viewpoint
|
||||
rast, rast_db = self.rasterize(pos[None, ...], tri, resolution)
|
||||
return rast[0], rast_db[0]
|
||||
|
||||
def antialias(
|
||||
self,
|
||||
color: Float[Tensor, "B H W C"],
|
||||
rast: Float[Tensor, "B H W 4"],
|
||||
pos: Float[Tensor, "B Nv 4"],
|
||||
tri: Integer[Tensor, "Nf 3"],
|
||||
) -> Float[Tensor, "B H W C"]:
|
||||
return dr.antialias(color.float(), rast, pos.float(), tri.int())
|
||||
|
||||
def interpolate(
|
||||
self,
|
||||
attr: Float[Tensor, "B Nv C"],
|
||||
rast: Float[Tensor, "B H W 4"],
|
||||
tri: Integer[Tensor, "Nf 3"],
|
||||
rast_db=None,
|
||||
diff_attrs=None,
|
||||
) -> Float[Tensor, "B H W C"]:
|
||||
return dr.interpolate(
|
||||
attr.float(), rast, tri.int(), rast_db=rast_db, diff_attrs=diff_attrs
|
||||
)
|
||||
|
||||
def interpolate_one(
|
||||
self,
|
||||
attr: Float[Tensor, "Nv C"],
|
||||
rast: Float[Tensor, "B H W 4"],
|
||||
tri: Integer[Tensor, "Nf 3"],
|
||||
rast_db=None,
|
||||
diff_attrs=None,
|
||||
) -> Float[Tensor, "B H W C"]:
|
||||
return self.interpolate(attr[None, ...], rast, tri, rast_db, diff_attrs)
|
||||
@@ -0,0 +1,725 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import cv2
|
||||
import imageio
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import torch
|
||||
from matplotlib import cm
|
||||
from matplotlib.colors import LinearSegmentedColormap
|
||||
from PIL import Image, ImageDraw
|
||||
from pytorch_lightning.loggers import WandbLogger
|
||||
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from ..models.mesh import Mesh
|
||||
from ..utils.typing import *
|
||||
|
||||
|
||||
class SaverMixin:
|
||||
import wandb
|
||||
_save_dir: Optional[str] = None
|
||||
_wandb_logger: Optional[WandbLogger] = None
|
||||
|
||||
def set_save_dir(self, save_dir: str):
|
||||
self._save_dir = save_dir
|
||||
|
||||
def get_save_dir(self):
|
||||
if self._save_dir is None:
|
||||
raise ValueError("Save dir is not set")
|
||||
return self._save_dir
|
||||
|
||||
def convert_data(self, data):
|
||||
if data is None:
|
||||
return None
|
||||
elif isinstance(data, np.ndarray):
|
||||
return data
|
||||
elif isinstance(data, torch.Tensor):
|
||||
if data.dtype in [torch.float16, torch.bfloat16]:
|
||||
data = data.float()
|
||||
return data.detach().cpu().numpy()
|
||||
elif isinstance(data, list):
|
||||
return [self.convert_data(d) for d in data]
|
||||
elif isinstance(data, dict):
|
||||
return {k: self.convert_data(v) for k, v in data.items()}
|
||||
else:
|
||||
raise TypeError(
|
||||
"Data must be in type numpy.ndarray, torch.Tensor, list or dict, getting",
|
||||
type(data),
|
||||
)
|
||||
|
||||
def get_save_path(self, filename):
|
||||
save_path = os.path.join(self.get_save_dir(), filename)
|
||||
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
||||
return save_path
|
||||
|
||||
DEFAULT_RGB_KWARGS = {"data_format": "HWC", "data_range": (0, 1)}
|
||||
DEFAULT_UV_KWARGS = {
|
||||
"data_format": "HWC",
|
||||
"data_range": (0, 1),
|
||||
"cmap": "checkerboard",
|
||||
}
|
||||
DEFAULT_GRAYSCALE_KWARGS = {"data_range": None, "cmap": "jet"}
|
||||
DEFAULT_GRID_KWARGS = {"align": "max"}
|
||||
|
||||
def get_rgb_image_(self, img, data_format, data_range, rgba=False):
|
||||
img = self.convert_data(img)
|
||||
assert data_format in ["CHW", "HWC"]
|
||||
if data_format == "CHW":
|
||||
img = img.transpose(1, 2, 0)
|
||||
if img.dtype != np.uint8:
|
||||
img = img.clip(min=data_range[0], max=data_range[1])
|
||||
img = (
|
||||
(img - data_range[0]) / (data_range[1] - data_range[0]) * 255.0
|
||||
).astype(np.uint8)
|
||||
nc = 4 if rgba else 3
|
||||
imgs = [img[..., start : start + nc] for start in range(0, img.shape[-1], nc)]
|
||||
imgs = [
|
||||
img_
|
||||
if img_.shape[-1] == nc
|
||||
else np.concatenate(
|
||||
[
|
||||
img_,
|
||||
np.zeros(
|
||||
(img_.shape[0], img_.shape[1], nc - img_.shape[2]),
|
||||
dtype=img_.dtype,
|
||||
),
|
||||
],
|
||||
axis=-1,
|
||||
)
|
||||
for img_ in imgs
|
||||
]
|
||||
img = np.concatenate(imgs, axis=1)
|
||||
if rgba:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)
|
||||
else:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
return img
|
||||
|
||||
def _save_rgb_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_format,
|
||||
data_range,
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
):
|
||||
img = self.get_rgb_image_(img, data_format, data_range)
|
||||
cv2.imwrite(filename, img)
|
||||
if name and self._wandb_logger:
|
||||
self._wandb_logger.log_image(
|
||||
key=name, images=[self.get_save_path(filename)], step=step
|
||||
)
|
||||
|
||||
def save_rgb_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_format=DEFAULT_RGB_KWARGS["data_format"],
|
||||
data_range=DEFAULT_RGB_KWARGS["data_range"],
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
self._save_rgb_image(save_path, img, data_format, data_range, name, step)
|
||||
return save_path
|
||||
|
||||
def get_uv_image_(self, img, data_format, data_range, cmap):
|
||||
img = self.convert_data(img)
|
||||
assert data_format in ["CHW", "HWC"]
|
||||
if data_format == "CHW":
|
||||
img = img.transpose(1, 2, 0)
|
||||
img = img.clip(min=data_range[0], max=data_range[1])
|
||||
img = (img - data_range[0]) / (data_range[1] - data_range[0])
|
||||
assert cmap in ["checkerboard", "color"]
|
||||
if cmap == "checkerboard":
|
||||
n_grid = 64
|
||||
mask = (img * n_grid).astype(int)
|
||||
mask = (mask[..., 0] + mask[..., 1]) % 2 == 0
|
||||
img = np.ones((img.shape[0], img.shape[1], 3), dtype=np.uint8) * 255
|
||||
img[mask] = np.array([255, 0, 255], dtype=np.uint8)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
elif cmap == "color":
|
||||
img_ = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
|
||||
img_[..., 0] = (img[..., 0] * 255).astype(np.uint8)
|
||||
img_[..., 1] = (img[..., 1] * 255).astype(np.uint8)
|
||||
img_ = cv2.cvtColor(img_, cv2.COLOR_RGB2BGR)
|
||||
img = img_
|
||||
return img
|
||||
|
||||
def save_uv_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_format=DEFAULT_UV_KWARGS["data_format"],
|
||||
data_range=DEFAULT_UV_KWARGS["data_range"],
|
||||
cmap=DEFAULT_UV_KWARGS["cmap"],
|
||||
) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
img = self.get_uv_image_(img, data_format, data_range, cmap)
|
||||
cv2.imwrite(save_path, img)
|
||||
return save_path
|
||||
|
||||
def get_grayscale_image_(self, img, data_range, cmap):
|
||||
img = self.convert_data(img)
|
||||
img = np.nan_to_num(img)
|
||||
if data_range is None:
|
||||
img = (img - img.min()) / (img.max() - img.min())
|
||||
else:
|
||||
img = img.clip(data_range[0], data_range[1])
|
||||
img = (img - data_range[0]) / (data_range[1] - data_range[0])
|
||||
assert cmap in [None, "jet", "magma", "spectral"]
|
||||
if cmap == None:
|
||||
img = (img * 255.0).astype(np.uint8)
|
||||
img = np.repeat(img[..., None], 3, axis=2)
|
||||
elif cmap == "jet":
|
||||
img = (img * 255.0).astype(np.uint8)
|
||||
img = cv2.applyColorMap(img, cv2.COLORMAP_JET)
|
||||
elif cmap == "magma":
|
||||
img = 1.0 - img
|
||||
base = cm.get_cmap("magma")
|
||||
num_bins = 256
|
||||
colormap = LinearSegmentedColormap.from_list(
|
||||
f"{base.name}{num_bins}", base(np.linspace(0, 1, num_bins)), num_bins
|
||||
)(np.linspace(0, 1, num_bins))[:, :3]
|
||||
a = np.floor(img * 255.0)
|
||||
b = (a + 1).clip(max=255.0)
|
||||
f = img * 255.0 - a
|
||||
a = a.astype(np.uint16).clip(0, 255)
|
||||
b = b.astype(np.uint16).clip(0, 255)
|
||||
img = colormap[a] + (colormap[b] - colormap[a]) * f[..., None]
|
||||
img = (img * 255.0).astype(np.uint8)
|
||||
elif cmap == "spectral":
|
||||
colormap = plt.get_cmap("Spectral")
|
||||
|
||||
def blend_rgba(image):
|
||||
image = image[..., :3] * image[..., -1:] + (
|
||||
1.0 - image[..., -1:]
|
||||
) # blend A to RGB
|
||||
return image
|
||||
|
||||
img = colormap(img)
|
||||
img = blend_rgba(img)
|
||||
img = (img * 255).astype(np.uint8)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
return img
|
||||
|
||||
def _save_grayscale_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_range,
|
||||
cmap,
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
):
|
||||
img = self.get_grayscale_image_(img, data_range, cmap)
|
||||
cv2.imwrite(filename, img)
|
||||
if name and self._wandb_logger:
|
||||
self._wandb_logger.log_image(
|
||||
key=name, images=[self.get_save_path(filename)], step=step
|
||||
)
|
||||
|
||||
def save_grayscale_image(
|
||||
self,
|
||||
filename,
|
||||
img,
|
||||
data_range=DEFAULT_GRAYSCALE_KWARGS["data_range"],
|
||||
cmap=DEFAULT_GRAYSCALE_KWARGS["cmap"],
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
self._save_grayscale_image(save_path, img, data_range, cmap, name, step)
|
||||
return save_path
|
||||
|
||||
def get_image_grid_(self, imgs, align):
|
||||
if isinstance(imgs[0], list):
|
||||
return np.concatenate(
|
||||
[self.get_image_grid_(row, align) for row in imgs], axis=0
|
||||
)
|
||||
cols = []
|
||||
for col in imgs:
|
||||
assert col["type"] in ["rgb", "uv", "grayscale"]
|
||||
if col["type"] == "rgb":
|
||||
rgb_kwargs = self.DEFAULT_RGB_KWARGS.copy()
|
||||
rgb_kwargs.update(col["kwargs"])
|
||||
cols.append(self.get_rgb_image_(col["img"], **rgb_kwargs))
|
||||
elif col["type"] == "uv":
|
||||
uv_kwargs = self.DEFAULT_UV_KWARGS.copy()
|
||||
uv_kwargs.update(col["kwargs"])
|
||||
cols.append(self.get_uv_image_(col["img"], **uv_kwargs))
|
||||
elif col["type"] == "grayscale":
|
||||
grayscale_kwargs = self.DEFAULT_GRAYSCALE_KWARGS.copy()
|
||||
grayscale_kwargs.update(col["kwargs"])
|
||||
cols.append(self.get_grayscale_image_(col["img"], **grayscale_kwargs))
|
||||
|
||||
if align == "max":
|
||||
h = max([col.shape[0] for col in cols])
|
||||
elif align == "min":
|
||||
h = min([col.shape[0] for col in cols])
|
||||
elif isinstance(align, int):
|
||||
h = align
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported image grid align: {align}, should be min, max, or int"
|
||||
)
|
||||
|
||||
for i in range(len(cols)):
|
||||
if cols[i].shape[0] != h:
|
||||
w = int(cols[i].shape[1] * h / cols[i].shape[0])
|
||||
cols[i] = cv2.resize(cols[i], (w, h), interpolation=cv2.INTER_CUBIC)
|
||||
return np.concatenate(cols, axis=1)
|
||||
|
||||
def save_image_grid(
|
||||
self,
|
||||
filename,
|
||||
imgs,
|
||||
align=DEFAULT_GRID_KWARGS["align"],
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
texts: Optional[List[float]] = None,
|
||||
):
|
||||
save_path = self.get_save_path(filename)
|
||||
img = self.get_image_grid_(imgs, align=align)
|
||||
|
||||
if texts is not None:
|
||||
img = Image.fromarray(img)
|
||||
draw = ImageDraw.Draw(img)
|
||||
black, white = (0, 0, 0), (255, 255, 255)
|
||||
for i, text in enumerate(texts):
|
||||
draw.text((2, (img.size[1] // len(texts)) * i + 1), f"{text}", white)
|
||||
draw.text((0, (img.size[1] // len(texts)) * i + 1), f"{text}", white)
|
||||
draw.text((2, (img.size[1] // len(texts)) * i - 1), f"{text}", white)
|
||||
draw.text((0, (img.size[1] // len(texts)) * i - 1), f"{text}", white)
|
||||
draw.text((1, (img.size[1] // len(texts)) * i), f"{text}", black)
|
||||
img = np.asarray(img)
|
||||
|
||||
cv2.imwrite(save_path, img)
|
||||
if name and self._wandb_logger:
|
||||
self._wandb_logger.log_image(key=name, images=[save_path], step=step)
|
||||
return save_path
|
||||
|
||||
def save_image(self, filename, img) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
img = self.convert_data(img)
|
||||
assert img.dtype == np.uint8 or img.dtype == np.uint16
|
||||
if img.ndim == 3 and img.shape[-1] == 3:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
elif img.ndim == 3 and img.shape[-1] == 4:
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)
|
||||
cv2.imwrite(save_path, img)
|
||||
return save_path
|
||||
|
||||
def save_cubemap(self, filename, img, data_range=(0, 1), rgba=False) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
img = self.convert_data(img)
|
||||
assert img.ndim == 4 and img.shape[0] == 6 and img.shape[1] == img.shape[2]
|
||||
|
||||
imgs_full = []
|
||||
for start in range(0, img.shape[-1], 3):
|
||||
img_ = img[..., start : start + 3]
|
||||
img_ = np.stack(
|
||||
[
|
||||
self.get_rgb_image_(img_[i], "HWC", data_range, rgba=rgba)
|
||||
for i in range(img_.shape[0])
|
||||
],
|
||||
axis=0,
|
||||
)
|
||||
size = img_.shape[1]
|
||||
placeholder = np.zeros((size, size, 3), dtype=np.float32)
|
||||
img_full = np.concatenate(
|
||||
[
|
||||
np.concatenate(
|
||||
[placeholder, img_[2], placeholder, placeholder], axis=1
|
||||
),
|
||||
np.concatenate([img_[1], img_[4], img_[0], img_[5]], axis=1),
|
||||
np.concatenate(
|
||||
[placeholder, img_[3], placeholder, placeholder], axis=1
|
||||
),
|
||||
],
|
||||
axis=0,
|
||||
)
|
||||
imgs_full.append(img_full)
|
||||
|
||||
imgs_full = np.concatenate(imgs_full, axis=1)
|
||||
cv2.imwrite(save_path, imgs_full)
|
||||
return save_path
|
||||
|
||||
def save_data(self, filename, data) -> str:
|
||||
data = self.convert_data(data)
|
||||
if isinstance(data, dict):
|
||||
if not filename.endswith(".npz"):
|
||||
filename += ".npz"
|
||||
save_path = self.get_save_path(filename)
|
||||
np.savez(save_path, **data)
|
||||
else:
|
||||
if not filename.endswith(".npy"):
|
||||
filename += ".npy"
|
||||
save_path = self.get_save_path(filename)
|
||||
np.save(save_path, data)
|
||||
return save_path
|
||||
|
||||
def save_state_dict(self, filename, data) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
torch.save(data, save_path)
|
||||
return save_path
|
||||
|
||||
def save_img_sequence(
|
||||
self,
|
||||
filename,
|
||||
img_dir,
|
||||
matcher,
|
||||
save_format="mp4",
|
||||
fps=30,
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
) -> str:
|
||||
assert save_format in ["gif", "mp4"]
|
||||
if not filename.endswith(save_format):
|
||||
filename += f".{save_format}"
|
||||
save_path = self.get_save_path(filename)
|
||||
matcher = re.compile(matcher)
|
||||
img_dir = os.path.join(self.get_save_dir(), img_dir)
|
||||
imgs = []
|
||||
for f in os.listdir(img_dir):
|
||||
if matcher.search(f):
|
||||
imgs.append(f)
|
||||
imgs = sorted(imgs, key=lambda f: int(matcher.search(f).groups()[0]))
|
||||
imgs = [cv2.imread(os.path.join(img_dir, f)) for f in imgs]
|
||||
|
||||
if save_format == "gif":
|
||||
imgs = [cv2.cvtColor(i, cv2.COLOR_BGR2RGB) for i in imgs]
|
||||
imageio.mimsave(save_path, imgs, fps=fps, palettesize=256)
|
||||
elif save_format == "mp4":
|
||||
imgs = [cv2.cvtColor(i, cv2.COLOR_BGR2RGB) for i in imgs]
|
||||
imageio.mimsave(save_path, imgs, fps=fps)
|
||||
if name and self._wandb_logger:
|
||||
lrm.warn("Wandb logger does not support video logging yet!")
|
||||
return save_path
|
||||
|
||||
def save_img_sequences(
|
||||
self,
|
||||
seq_dir,
|
||||
matcher,
|
||||
save_format="mp4",
|
||||
fps=30,
|
||||
delete=True,
|
||||
name: Optional[str] = None,
|
||||
step: Optional[int] = None,
|
||||
):
|
||||
seq_dir_ = os.path.join(self.get_save_dir(), seq_dir)
|
||||
for f in os.listdir(seq_dir_):
|
||||
img_dir_ = os.path.join(seq_dir_, f)
|
||||
if not os.path.isdir(img_dir_):
|
||||
continue
|
||||
try:
|
||||
self.save_img_sequence(
|
||||
os.path.join(seq_dir, f),
|
||||
os.path.join(seq_dir, f),
|
||||
matcher,
|
||||
save_format=save_format,
|
||||
fps=fps,
|
||||
name=f"{name}_{f}",
|
||||
step=step,
|
||||
)
|
||||
if delete:
|
||||
shutil.rmtree(img_dir_)
|
||||
except:
|
||||
lrm.warn(f"Video saving for directory {seq_dir_} failed!")
|
||||
|
||||
def save_mesh(self, filename, v_pos, t_pos_idx, v_tex=None, t_tex_idx=None) -> str:
|
||||
import trimesh
|
||||
|
||||
save_path = self.get_save_path(filename)
|
||||
v_pos = self.convert_data(v_pos)
|
||||
t_pos_idx = self.convert_data(t_pos_idx)
|
||||
mesh = trimesh.Trimesh(vertices=v_pos, faces=t_pos_idx)
|
||||
mesh.export(save_path)
|
||||
return save_path
|
||||
|
||||
def save_obj(
|
||||
self,
|
||||
filename: str,
|
||||
mesh: Mesh,
|
||||
save_mat: bool = False,
|
||||
save_normal: bool = False,
|
||||
save_uv: bool = False,
|
||||
save_vertex_color: bool = True,
|
||||
map_Kd: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Ks: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Bump: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Pm: Optional[Float[Tensor, "H W 1"]] = None,
|
||||
map_Pr: Optional[Float[Tensor, "H W 1"]] = None,
|
||||
map_format: str = "jpg",
|
||||
) -> List[str]:
|
||||
save_paths: List[str] = []
|
||||
if not filename.endswith(".obj"):
|
||||
filename += ".obj"
|
||||
v_pos, t_pos_idx = self.convert_data(mesh.v_pos), self.convert_data(
|
||||
mesh.t_pos_idx
|
||||
)
|
||||
v_nrm, v_tex, t_tex_idx, v_rgb = None, None, None, None
|
||||
if save_normal:
|
||||
v_nrm = self.convert_data(mesh.v_nrm)
|
||||
if save_uv:
|
||||
v_tex, t_tex_idx = self.convert_data(mesh.v_tex), self.convert_data(
|
||||
mesh.t_tex_idx
|
||||
)
|
||||
if save_vertex_color:
|
||||
v_rgb = self.convert_data(mesh.v_rgb)
|
||||
matname, mtllib = None, None
|
||||
if save_mat:
|
||||
matname = "default"
|
||||
mtl_filename = filename.replace(".obj", ".mtl")
|
||||
mtllib = os.path.basename(mtl_filename)
|
||||
mtl_save_paths = self._save_mtl(
|
||||
mtl_filename,
|
||||
matname,
|
||||
map_Kd=self.convert_data(map_Kd),
|
||||
map_Ks=self.convert_data(map_Ks),
|
||||
map_Bump=self.convert_data(map_Bump),
|
||||
map_Pm=self.convert_data(map_Pm),
|
||||
map_Pr=self.convert_data(map_Pr),
|
||||
map_format=map_format,
|
||||
)
|
||||
save_paths += mtl_save_paths
|
||||
obj_save_path = self._save_obj(
|
||||
filename,
|
||||
v_pos,
|
||||
t_pos_idx,
|
||||
v_nrm=v_nrm,
|
||||
v_tex=v_tex,
|
||||
t_tex_idx=t_tex_idx,
|
||||
v_rgb=v_rgb,
|
||||
matname=matname,
|
||||
mtllib=mtllib,
|
||||
)
|
||||
save_paths.append(obj_save_path)
|
||||
return save_paths
|
||||
|
||||
def _save_obj(
|
||||
self,
|
||||
filename,
|
||||
v_pos,
|
||||
t_pos_idx,
|
||||
v_nrm=None,
|
||||
v_tex=None,
|
||||
t_tex_idx=None,
|
||||
v_rgb=None,
|
||||
matname=None,
|
||||
mtllib=None,
|
||||
) -> str:
|
||||
obj_str = ""
|
||||
if matname is not None:
|
||||
obj_str += f"mtllib {mtllib}\n"
|
||||
obj_str += f"g object\n"
|
||||
obj_str += f"usemtl {matname}\n"
|
||||
for i in range(len(v_pos)):
|
||||
obj_str += f"v {v_pos[i][0]} {v_pos[i][1]} {v_pos[i][2]}"
|
||||
if v_rgb is not None:
|
||||
obj_str += f" {v_rgb[i][0]} {v_rgb[i][1]} {v_rgb[i][2]}"
|
||||
obj_str += "\n"
|
||||
if v_nrm is not None:
|
||||
for v in v_nrm:
|
||||
obj_str += f"vn {v[0]} {v[1]} {v[2]}\n"
|
||||
if v_tex is not None:
|
||||
for v in v_tex:
|
||||
obj_str += f"vt {v[0]} {1.0 - v[1]}\n"
|
||||
|
||||
for i in range(len(t_pos_idx)):
|
||||
obj_str += "f"
|
||||
for j in range(3):
|
||||
obj_str += f" {t_pos_idx[i][j] + 1}/"
|
||||
if v_tex is not None:
|
||||
obj_str += f"{t_tex_idx[i][j] + 1}"
|
||||
obj_str += "/"
|
||||
if v_nrm is not None:
|
||||
obj_str += f"{t_pos_idx[i][j] + 1}"
|
||||
obj_str += "\n"
|
||||
|
||||
save_path = self.get_save_path(filename)
|
||||
with open(save_path, "w") as f:
|
||||
f.write(obj_str)
|
||||
return save_path
|
||||
|
||||
def _save_mtl(
|
||||
self,
|
||||
filename,
|
||||
matname,
|
||||
Ka=(0.0, 0.0, 0.0),
|
||||
Kd=(1.0, 1.0, 1.0),
|
||||
Ks=(0.0, 0.0, 0.0),
|
||||
map_Kd=None,
|
||||
map_Ks=None,
|
||||
map_Bump=None,
|
||||
map_Pm=None,
|
||||
map_Pr=None,
|
||||
map_format="jpg",
|
||||
step: Optional[int] = None,
|
||||
) -> List[str]:
|
||||
mtl_save_path = self.get_save_path(filename)
|
||||
save_paths = [mtl_save_path]
|
||||
mtl_str = f"newmtl {matname}\n"
|
||||
mtl_str += f"Ka {Ka[0]} {Ka[1]} {Ka[2]}\n"
|
||||
if map_Kd is not None:
|
||||
map_Kd_save_path = os.path.join(
|
||||
os.path.dirname(mtl_save_path), f"texture_kd.{map_format}"
|
||||
)
|
||||
mtl_str += f"map_Kd texture_kd.{map_format}\n"
|
||||
self._save_rgb_image(
|
||||
map_Kd_save_path,
|
||||
map_Kd,
|
||||
data_format="HWC",
|
||||
data_range=(0, 1),
|
||||
name=f"{matname}_Kd",
|
||||
step=step,
|
||||
)
|
||||
save_paths.append(map_Kd_save_path)
|
||||
else:
|
||||
mtl_str += f"Kd {Kd[0]} {Kd[1]} {Kd[2]}\n"
|
||||
if map_Ks is not None:
|
||||
map_Ks_save_path = os.path.join(
|
||||
os.path.dirname(mtl_save_path), f"texture_ks.{map_format}"
|
||||
)
|
||||
mtl_str += f"map_Ks texture_ks.{map_format}\n"
|
||||
self._save_rgb_image(
|
||||
map_Ks_save_path,
|
||||
map_Ks,
|
||||
data_format="HWC",
|
||||
data_range=(0, 1),
|
||||
name=f"{matname}_Ks",
|
||||
step=step,
|
||||
)
|
||||
save_paths.append(map_Ks_save_path)
|
||||
else:
|
||||
mtl_str += f"Ks {Ks[0]} {Ks[1]} {Ks[2]}\n"
|
||||
if map_Bump is not None:
|
||||
map_Bump_save_path = os.path.join(
|
||||
os.path.dirname(mtl_save_path), f"texture_nrm.{map_format}"
|
||||
)
|
||||
mtl_str += f"map_Bump texture_nrm.{map_format}\n"
|
||||
self._save_rgb_image(
|
||||
map_Bump_save_path,
|
||||
map_Bump,
|
||||
data_format="HWC",
|
||||
data_range=(0, 1),
|
||||
name=f"{matname}_Bump",
|
||||
step=step,
|
||||
)
|
||||
save_paths.append(map_Bump_save_path)
|
||||
if map_Pm is not None:
|
||||
map_Pm_save_path = os.path.join(
|
||||
os.path.dirname(mtl_save_path), f"texture_metallic.{map_format}"
|
||||
)
|
||||
mtl_str += f"map_Pm texture_metallic.{map_format}\n"
|
||||
self._save_grayscale_image(
|
||||
map_Pm_save_path,
|
||||
map_Pm,
|
||||
data_range=(0, 1),
|
||||
cmap=None,
|
||||
name=f"{matname}_refl",
|
||||
step=step,
|
||||
)
|
||||
save_paths.append(map_Pm_save_path)
|
||||
if map_Pr is not None:
|
||||
map_Pr_save_path = os.path.join(
|
||||
os.path.dirname(mtl_save_path), f"texture_roughness.{map_format}"
|
||||
)
|
||||
mtl_str += f"map_Pr texture_roughness.{map_format}\n"
|
||||
self._save_grayscale_image(
|
||||
map_Pr_save_path,
|
||||
map_Pr,
|
||||
data_range=(0, 1),
|
||||
cmap=None,
|
||||
name=f"{matname}_Ns",
|
||||
step=step,
|
||||
)
|
||||
save_paths.append(map_Pr_save_path)
|
||||
with open(self.get_save_path(filename), "w") as f:
|
||||
f.write(mtl_str)
|
||||
return save_paths
|
||||
|
||||
def save_glb(
|
||||
self,
|
||||
filename: str,
|
||||
mesh: Mesh,
|
||||
save_mat: bool = False,
|
||||
save_normal: bool = True,
|
||||
save_uv: bool = True,
|
||||
save_vertex_color: bool = True,
|
||||
map_Kd: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Ks: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Bump: Optional[Float[Tensor, "H W 3"]] = None,
|
||||
map_Pm: Optional[Float[Tensor, "H W 1"]] = None,
|
||||
map_Pr: Optional[Float[Tensor, "H W 1"]] = None,
|
||||
map_format: str = "jpg",
|
||||
) -> List[str]:
|
||||
save_paths: List[str] = []
|
||||
if not filename.endswith(".glb"):
|
||||
filename += ".glb"
|
||||
v_pos, t_pos_idx = self.convert_data(mesh.v_pos), self.convert_data(
|
||||
mesh.t_pos_idx
|
||||
)
|
||||
v_nrm, v_tex, t_tex_idx, v_rgb = None, None, None, None
|
||||
if save_normal:
|
||||
v_nrm = self.convert_data(mesh.v_nrm)
|
||||
if save_uv:
|
||||
v_tex, t_tex_idx = self.convert_data(mesh.v_tex), self.convert_data(
|
||||
mesh.t_tex_idx
|
||||
)
|
||||
if save_vertex_color:
|
||||
v_rgb = self.convert_data(mesh.v_rgb)
|
||||
|
||||
obj_save_path = self._save_glb(
|
||||
filename,
|
||||
v_pos,
|
||||
t_pos_idx,
|
||||
v_nrm=v_nrm,
|
||||
v_tex=v_tex,
|
||||
t_tex_idx=t_tex_idx,
|
||||
v_rgb=v_rgb,
|
||||
)
|
||||
save_paths.append(obj_save_path)
|
||||
return save_paths
|
||||
|
||||
def _save_glb(
|
||||
self,
|
||||
filename,
|
||||
v_pos,
|
||||
t_pos_idx,
|
||||
v_nrm=None,
|
||||
v_tex=None,
|
||||
t_tex_idx=None,
|
||||
v_rgb=None,
|
||||
matname=None,
|
||||
mtllib=None,
|
||||
) -> str:
|
||||
import trimesh
|
||||
|
||||
mesh = trimesh.Trimesh(
|
||||
vertices=v_pos, faces=t_pos_idx, vertex_normals=v_nrm, vertex_colors=v_rgb
|
||||
)
|
||||
# not tested
|
||||
if v_tex is not None:
|
||||
mesh.visual = trimesh.visual.TextureVisuals(uv=v_tex)
|
||||
|
||||
save_path = self.get_save_path(filename)
|
||||
mesh.export(save_path)
|
||||
return save_path
|
||||
|
||||
def save_file(self, filename, src_path, delete=False) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
shutil.copyfile(src_path, save_path)
|
||||
if delete:
|
||||
os.remove(src_path)
|
||||
return save_path
|
||||
|
||||
def save_json(self, filename, payload) -> str:
|
||||
save_path = self.get_save_path(filename)
|
||||
with open(save_path, "w") as f:
|
||||
f.write(json.dumps(payload))
|
||||
return save_path
|
||||
@@ -0,0 +1,13 @@
|
||||
import pySDF as SDF
|
||||
import numpy as np
|
||||
from CharacterGen.Stage_3D.lrm.models.isosurface import MarchingTetrahedraHelper
|
||||
|
||||
def get_tetra_for_mesh(mesh_path, resolution=128):
|
||||
isosurface_helper = MarchingTetrahedraHelper(resolution, f"load/{resolution}_tets.npz")
|
||||
isosurface_helper.points_range = (-1, 1)
|
||||
mesh = trimesh.load(mesh_path)
|
||||
dmtet = np.load(f"")
|
||||
sdf = SDF(mesh.vertices, mesh.faces)
|
||||
sdf_gt = sdf(isosurface_helper.grid_vertices.numpy())
|
||||
return sdf_gt
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
This module contains type annotations for the project, using
|
||||
1. Python type hints (https://docs.python.org/3/library/typing.html) for Python objects
|
||||
2. jaxtyping (https://github.com/google/jaxtyping/blob/main/API.md) for PyTorch tensors
|
||||
|
||||
Two types of typing checking can be used:
|
||||
1. Static type checking with mypy (install with pip and enabled as the default linter in VSCode)
|
||||
2. Runtime type checking with typeguard (install with pip and triggered at runtime, mainly for tensor dtype and shape checking)
|
||||
"""
|
||||
|
||||
# Basic types
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
NewType,
|
||||
Optional,
|
||||
Sized,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
# Tensor dtype
|
||||
# for jaxtyping usage, see https://github.com/google/jaxtyping/blob/main/API.md
|
||||
from jaxtyping import Bool, Complex, Float, Inexact, Int, Integer, Num, Shaped, UInt
|
||||
|
||||
# Config type
|
||||
from omegaconf import DictConfig
|
||||
|
||||
# PyTorch Tensor type
|
||||
from torch import Tensor
|
||||
|
||||
# Runtime type checking decorator
|
||||
from typeguard import typechecked as typechecker
|
||||
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"locations": [
|
||||
{
|
||||
"transform_matrix": [
|
||||
[ 1.0, 0.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, 1.0, 1.5 ],
|
||||
[ 0.0, 1.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, 0.0, 1.0 ]
|
||||
]
|
||||
},
|
||||
{
|
||||
"transform_matrix": [
|
||||
[ -1.0, 0.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, -1.0, -1.5 ],
|
||||
[ 0.0, 1.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, 0.0, 1.0 ]
|
||||
]
|
||||
},
|
||||
{
|
||||
"transform_matrix": [
|
||||
[ 0.0, 0.0, 1.0, 1.5 ],
|
||||
[ -1.0, 0.0, 0.0, 0.0 ],
|
||||
[ 0.0, 1.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, 0.0, 1.0 ]
|
||||
]
|
||||
},
|
||||
{
|
||||
"transform_matrix": [
|
||||
[ 0.0, 0.0, -1.0, -1.5 ],
|
||||
[ 1.0, 0.0, 0.0, 0.0 ],
|
||||
[ 0.0, 1.0, 0.0, 0.0 ],
|
||||
[ 0.0, 0.0, 0.0, 1.0 ]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# Copyright (c) 2020-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
#
|
||||
# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
|
||||
# property and proprietary rights in and to this material, related
|
||||
# documentation and any modifications thereto. Any use, reproduction,
|
||||
# disclosure or distribution of this material and related documentation
|
||||
# without an express license agreement from NVIDIA CORPORATION or
|
||||
# its affiliates is strictly prohibited.
|
||||
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
|
||||
"""
|
||||
This code segment shows how to use Quartet: https://github.com/crawforddoran/quartet,
|
||||
to generate a tet grid
|
||||
1) Download, compile and run Quartet as described in the link above. Example usage `quartet meshes/cube.obj 0.5 cube_5.tet`
|
||||
2) Run the function below to generate a file `cube_32_tet.tet`
|
||||
"""
|
||||
|
||||
|
||||
def generate_tetrahedron_grid_file(res=32, root=".."):
|
||||
frac = 1.0 / res
|
||||
command = f"cd {root}; ./quartet meshes/cube.obj {frac} meshes/cube_{res}_tet.tet -s meshes/cube_boundary_{res}.obj"
|
||||
os.system(command)
|
||||
|
||||
|
||||
"""
|
||||
This code segment shows how to convert from a quartet .tet file to compressed npz file
|
||||
"""
|
||||
|
||||
|
||||
def convert_from_quartet_to_npz(quartetfile="cube_32_tet.tet", npzfile="32_tets"):
|
||||
file1 = open(quartetfile, "r")
|
||||
header = file1.readline()
|
||||
numvertices = int(header.split(" ")[1])
|
||||
numtets = int(header.split(" ")[2])
|
||||
print(numvertices, numtets)
|
||||
|
||||
# load vertices
|
||||
vertices = np.loadtxt(quartetfile, skiprows=1, max_rows=numvertices)
|
||||
print(vertices.shape)
|
||||
|
||||
# load indices
|
||||
indices = np.loadtxt(
|
||||
quartetfile, dtype=int, skiprows=1 + numvertices, max_rows=numtets
|
||||
)
|
||||
print(indices.shape)
|
||||
|
||||
np.savez_compressed(npzfile, vertices=vertices, indices=indices)
|
||||
|
||||
|
||||
root = "/home/gyc/quartet"
|
||||
for res in [300, 350, 400]:
|
||||
generate_tetrahedron_grid_file(res, root)
|
||||
convert_from_quartet_to_npz(
|
||||
os.path.join(root, f"meshes/cube_{res}_tet.tet"), npzfile=f"{res}_tets"
|
||||
)
|
||||
@@ -0,0 +1,236 @@
|
||||
import sys
|
||||
import os
|
||||
from os.path import dirname
|
||||
from typing import Dict, List
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
import torch.utils.checkpoint
|
||||
|
||||
from diffusers import AutoencoderKL, DDIMScheduler
|
||||
from transformers import CLIPTextModel, CLIPTokenizer, CLIPImageProcessor, CLIPVisionModelWithProjection
|
||||
from torchvision import transforms
|
||||
|
||||
from CharacterGen.Stage_2D.tuneavideo.models.unet_mv2d_condition import UNetMV2DConditionModel
|
||||
from CharacterGen.Stage_2D.tuneavideo.models.unet_mv2d_ref import UNetMV2DRefModel
|
||||
from CharacterGen.Stage_2D.tuneavideo.models.PoseGuider import PoseGuider
|
||||
from CharacterGen.Stage_2D.tuneavideo.pipelines.pipeline_tuneavideo import TuneAVideoPipeline
|
||||
|
||||
from einops import rearrange
|
||||
from PIL import Image
|
||||
import json
|
||||
|
||||
import trimesh
|
||||
from CharacterGen.Stage_3D import lrm
|
||||
from datetime import datetime
|
||||
from pygltflib import GLTF2
|
||||
import pymeshlab
|
||||
|
||||
CHARACTER_GEN_ROOT_ABS_PATH = dirname(__file__)
|
||||
CHARACTER_GEN_2D_DATA_ABS_PATH = os.path.join(CHARACTER_GEN_ROOT_ABS_PATH, "Stage_2D/material")
|
||||
CHARACTER_GEN_3D_ABS_PATH = os.path.join(CHARACTER_GEN_ROOT_ABS_PATH, "Stage_3D")
|
||||
CHARACTER_GEN_3D_DATA_ABS_PATH = os.path.join(CHARACTER_GEN_3D_ABS_PATH, "material")
|
||||
|
||||
sys.path.append(CHARACTER_GEN_3D_ABS_PATH)
|
||||
|
||||
def get_bg_color(bg_color):
|
||||
if bg_color == 'white':
|
||||
bg_color = np.array([1., 1., 1.], dtype=np.float32)
|
||||
elif bg_color == 'black':
|
||||
bg_color = np.array([0., 0., 0.], dtype=np.float32)
|
||||
elif bg_color == 'gray':
|
||||
bg_color = np.array([0.5, 0.5, 0.5], dtype=np.float32)
|
||||
elif bg_color == 'random':
|
||||
bg_color = np.random.rand(3)
|
||||
elif isinstance(bg_color, float):
|
||||
bg_color = np.array([bg_color] * 3, dtype=np.float32)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return bg_color
|
||||
|
||||
def process_image(image, totensor, w=512, h=768):
|
||||
if not image.mode == "RGBA":
|
||||
image = image.convert("RGBA")
|
||||
|
||||
# Find non-transparent pixels
|
||||
non_transparent = np.nonzero(np.array(image)[..., 3])
|
||||
min_x, max_x = non_transparent[1].min(), non_transparent[1].max()
|
||||
min_y, max_y = non_transparent[0].min(), non_transparent[0].max()
|
||||
image = image.crop((min_x, min_y, max_x, max_y))
|
||||
|
||||
# paste to center
|
||||
max_dim = max(image.width, image.height)
|
||||
max_height = max_dim
|
||||
max_width = int(max_dim / 3 * 2)
|
||||
new_image = Image.new("RGBA", (max_width, max_height))
|
||||
left = (max_width - image.width) // 2
|
||||
top = (max_height - image.height) // 2
|
||||
new_image.paste(image, (left, top))
|
||||
|
||||
image = new_image.resize((w, h), resample=Image.Resampling.BICUBIC)
|
||||
image = np.array(image)
|
||||
image = image.astype(np.float32) / 255.
|
||||
assert image.shape[-1] == 4 # RGBA
|
||||
alpha = image[..., 3:4]
|
||||
bg_color = get_bg_color("gray")
|
||||
image = image[..., :3] * alpha + bg_color * (1 - alpha)
|
||||
# save image
|
||||
new_image = Image.fromarray((image * 255).astype(np.uint8))
|
||||
return totensor(image)
|
||||
|
||||
class Inference2D_API:
|
||||
|
||||
def __init__(self,
|
||||
pretrained_model_path: str,
|
||||
checkpoint_root_path: str,
|
||||
image_encoder_path: str,
|
||||
ckpt_dir: str,
|
||||
validation: Dict,
|
||||
local_crossattn: bool = True,
|
||||
unet_from_pretrained_kwargs=None,
|
||||
unet_condition_type=None,
|
||||
use_pose_guider=False,
|
||||
use_shifted_noise=False,
|
||||
use_noise=True,
|
||||
device="cuda",
|
||||
weight_dtype=torch.float16
|
||||
):
|
||||
self.validation = validation
|
||||
self.use_noise = use_noise
|
||||
self.use_shifted_noise = use_shifted_noise
|
||||
self.unet_condition_type = unet_condition_type
|
||||
self.device = device
|
||||
self.weight_dtype = weight_dtype
|
||||
image_encoder_path = os.path.join(checkpoint_root_path, image_encoder_path)
|
||||
ckpt_dir = os.path.join(checkpoint_root_path, ckpt_dir)
|
||||
|
||||
self.tokenizer = CLIPTokenizer.from_pretrained(pretrained_model_path, subfolder="tokenizer")
|
||||
text_encoder = CLIPTextModel.from_pretrained(pretrained_model_path, subfolder="text_encoder")
|
||||
image_encoder = CLIPVisionModelWithProjection.from_pretrained(image_encoder_path)
|
||||
feature_extractor = CLIPImageProcessor()
|
||||
vae = AutoencoderKL.from_pretrained(pretrained_model_path, subfolder="vae")
|
||||
unet = UNetMV2DConditionModel.from_pretrained_2d(pretrained_model_path, subfolder="unet", local_crossattn=local_crossattn, **unet_from_pretrained_kwargs)
|
||||
ref_unet = UNetMV2DRefModel.from_pretrained_2d(pretrained_model_path, subfolder="unet", local_crossattn=local_crossattn, **unet_from_pretrained_kwargs)
|
||||
if use_pose_guider:
|
||||
pose_guider = PoseGuider(noise_latent_channels=4).to(device)
|
||||
else:
|
||||
pose_guider = None
|
||||
|
||||
unet_params = torch.load(os.path.join(ckpt_dir, "pytorch_model.bin"), map_location="cpu")
|
||||
if use_pose_guider:
|
||||
pose_guider_params = torch.load(os.path.join(ckpt_dir, "pytorch_model_1.bin"), map_location="cpu")
|
||||
ref_unet_params = torch.load(os.path.join(ckpt_dir, "pytorch_model_2.bin"), map_location="cpu")
|
||||
pose_guider.load_state_dict(pose_guider_params)
|
||||
else:
|
||||
ref_unet_params = torch.load(os.path.join(ckpt_dir, "pytorch_model_1.bin"), map_location="cpu")
|
||||
unet.load_state_dict(unet_params)
|
||||
ref_unet.load_state_dict(ref_unet_params)
|
||||
|
||||
text_encoder.to(device, dtype=weight_dtype)
|
||||
image_encoder.to(device, dtype=weight_dtype)
|
||||
vae.to(device, dtype=weight_dtype)
|
||||
ref_unet.to(device, dtype=weight_dtype)
|
||||
unet.to(device, dtype=weight_dtype)
|
||||
|
||||
vae.requires_grad_(False)
|
||||
unet.requires_grad_(False)
|
||||
ref_unet.requires_grad_(False)
|
||||
|
||||
noise_scheduler = DDIMScheduler.from_pretrained(pretrained_model_path, subfolder="scheduler")
|
||||
self.validation_pipeline = TuneAVideoPipeline(
|
||||
vae=vae, text_encoder=text_encoder, tokenizer=self.tokenizer, unet=unet, ref_unet=ref_unet,feature_extractor=feature_extractor,image_encoder=image_encoder,
|
||||
scheduler=noise_scheduler
|
||||
)
|
||||
self.validation_pipeline.enable_vae_slicing()
|
||||
self.validation_pipeline.set_progress_bar_config(disable=True)
|
||||
|
||||
@torch.no_grad()
|
||||
def inference(
|
||||
self, input_image, val_width, val_height, prompt="high quality, best quality", prompt_neg=None,
|
||||
guidance_scale=7.5, num_inference_steps=40, seed=100, use_shifted_noise=False, crop=False
|
||||
):
|
||||
|
||||
generator = torch.Generator(device=self.device).manual_seed(seed)
|
||||
totensor = transforms.ToTensor()
|
||||
|
||||
metas = json.load(open(os.path.join(CHARACTER_GEN_2D_DATA_ABS_PATH, "pose.json"), "r"))
|
||||
cameras = []
|
||||
pose_images = []
|
||||
for lm in metas:
|
||||
cameras.append(torch.tensor(np.array(lm[0]).reshape(4, 4).transpose(1,0)[:3, :4]).reshape(-1))
|
||||
if not crop:
|
||||
pose_images.append(totensor(np.asarray(Image.open(os.path.join(CHARACTER_GEN_2D_DATA_ABS_PATH, lm[1])).resize(
|
||||
(val_height, val_width), resample=Image.Resampling.BICUBIC)).astype(np.float32) / 255.))
|
||||
else:
|
||||
pose_image = Image.open(os.path.join(CHARACTER_GEN_2D_DATA_ABS_PATH, lm[1]))
|
||||
crop_area = (128, 0, 640, 768)
|
||||
pose_images.append(totensor(np.array(pose_image.crop(crop_area)).astype(np.float32)) / 255.)
|
||||
camera_matrixs = torch.stack(cameras).unsqueeze(0).to(self.device)
|
||||
pose_imgs_in = torch.stack(pose_images).to(self.device)
|
||||
prompt_ids = self.tokenizer(
|
||||
prompt, max_length=self.tokenizer.model_max_length, padding="max_length", truncation=True, return_tensors="pt"
|
||||
).input_ids[0]
|
||||
|
||||
# (B*Nv, 3, H, W) B==1
|
||||
imgs_in = process_image(input_image, totensor, val_width, val_height)
|
||||
imgs_in = rearrange(imgs_in.unsqueeze(0).unsqueeze(0), "B Nv C H W -> (B Nv) C H W")
|
||||
|
||||
with torch.autocast(self.device, dtype=self.weight_dtype):
|
||||
imgs_in = imgs_in.to(self.device)
|
||||
# B*Nv images
|
||||
out = self.validation_pipeline(prompt=prompt, negative_prompt=prompt_neg, image=imgs_in.to(self.weight_dtype), generator=generator,
|
||||
num_inference_steps=num_inference_steps, guidance_scale=guidance_scale,
|
||||
camera_matrixs=camera_matrixs.to(self.weight_dtype), prompt_ids=prompt_ids,
|
||||
height=val_height, width=val_width, unet_condition_type=self.unet_condition_type,
|
||||
pose_guider=None, pose_image=pose_imgs_in, use_noise=self.use_noise,
|
||||
use_shifted_noise=use_shifted_noise, **self.validation).videos
|
||||
out = rearrange(out, "B C f H W -> (B f) H W C", f=self.validation.video_length)
|
||||
|
||||
torch.cuda.empty_cache()
|
||||
return out
|
||||
|
||||
class Inference3D_API:
|
||||
|
||||
def __init__(self, checkpoint_root_path, cfg, device="cuda"):
|
||||
self.cfg = cfg
|
||||
self.device = device
|
||||
self.cfg.system.weights = os.path.join(checkpoint_root_path, self.cfg.system.weights)
|
||||
self.cfg.system.image_tokenizer.pretrained_model_name_or_path = os.path.join(checkpoint_root_path, self.cfg.system.image_tokenizer.pretrained_model_name_or_path)
|
||||
self.cfg.system.renderer.tet_dir = os.path.join(CHARACTER_GEN_3D_DATA_ABS_PATH, self.cfg.system.renderer.tet_dir)
|
||||
self.system = lrm.find(self.cfg.system_cls)(self.cfg.system).to(self.device)
|
||||
self.system.eval()
|
||||
|
||||
def inference(self, mv_imgs: List[Image.Image]):
|
||||
meta = json.load(open(os.path.join(CHARACTER_GEN_3D_DATA_ABS_PATH, "meta.json")))
|
||||
c2w_cond = [np.array(loc["transform_matrix"]) for loc in meta["locations"]]
|
||||
c2w_cond = torch.from_numpy(np.stack(c2w_cond, axis=0)).float()[None].to(self.device)
|
||||
|
||||
rgb_cond = []
|
||||
new_images = []
|
||||
for pil_img in mv_imgs:
|
||||
image = np.array(pil_img)
|
||||
image = Image.fromarray(image)
|
||||
if image.width != image.height:
|
||||
max_dim = max(image.width, image.height)
|
||||
new_image = Image.new("RGBA", (max_dim, max_dim))
|
||||
left = (max_dim - image.width) // 2
|
||||
top = (max_dim - image.height) // 2
|
||||
new_image.paste(image, (left, top))
|
||||
image = new_image
|
||||
|
||||
image = image.convert('RGB')
|
||||
rgb = image.resize((self.cfg.data.cond_width, self.cfg.data.cond_height), Image.Resampling.BILINEAR)
|
||||
rgb = np.array(rgb).astype(np.float32) / 255.0
|
||||
new_images.append(image)
|
||||
rgb_cond.append(rgb)
|
||||
assert len(rgb_cond) == 4, "Please provide 4 images"
|
||||
|
||||
rgb_cond = torch.from_numpy(np.stack(rgb_cond, axis=0)).float()[None].to(self.device)
|
||||
|
||||
scene_code = self.system({"rgb_cond": rgb_cond, "c2w_cond": c2w_cond})[0]
|
||||
mesh = self.system.get_geometry(scene_code)
|
||||
|
||||
# Flip mesh horizontally
|
||||
#mesh.v_pos[:, 0] = -mesh.v_pos[:, 0]
|
||||
|
||||
return mesh.v_pos.detach(), mesh.t_pos_idx.detach()
|
||||