add stable3dgen
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"_class_name": "ControlNetVAEModel",
|
||||
"_diffusers_version": "0.28.0",
|
||||
"_name_or_path": "weights/yoso-normal-v1-8-1/controlnet",
|
||||
"act_fn": "silu",
|
||||
"addition_embed_type": null,
|
||||
"addition_embed_type_num_heads": 64,
|
||||
"addition_time_embed_dim": null,
|
||||
"attention_head_dim": [
|
||||
5,
|
||||
10,
|
||||
20,
|
||||
20
|
||||
],
|
||||
"block_out_channels": [
|
||||
320,
|
||||
640,
|
||||
1280,
|
||||
1280
|
||||
],
|
||||
"class_embed_type": null,
|
||||
"conditioning_channels": 3,
|
||||
"conditioning_embedding_out_channels": [
|
||||
16,
|
||||
32,
|
||||
96,
|
||||
256
|
||||
],
|
||||
"controlnet_conditioning_channel_order": "rgb",
|
||||
"cross_attention_dim": 1024,
|
||||
"down_block_types": [
|
||||
"CrossAttnDownBlock2D",
|
||||
"CrossAttnDownBlock2D",
|
||||
"CrossAttnDownBlock2D",
|
||||
"DownBlock2D"
|
||||
],
|
||||
"downsample_padding": 1,
|
||||
"encoder_hid_dim": null,
|
||||
"encoder_hid_dim_type": null,
|
||||
"flip_sin_to_cos": true,
|
||||
"freq_shift": 0,
|
||||
"global_pool_conditions": false,
|
||||
"in_channels": 4,
|
||||
"layers_per_block": 2,
|
||||
"mid_block_scale_factor": 1,
|
||||
"mid_block_type": "UNetMidBlock2DCrossAttn",
|
||||
"norm_eps": 1e-05,
|
||||
"norm_num_groups": 32,
|
||||
"num_attention_heads": null,
|
||||
"num_class_embeds": null,
|
||||
"only_cross_attention": false,
|
||||
"projection_class_embeddings_input_dim": null,
|
||||
"resnet_time_scale_shift": "default",
|
||||
"transformer_layers_per_block": 1,
|
||||
"upcast_attention": true,
|
||||
"use_linear_projection": true
|
||||
}
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
# Copyright 2024 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 dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
||||
from diffusers.loaders.single_file_model import FromOriginalModelMixin
|
||||
from diffusers.utils import BaseOutput, logging
|
||||
from diffusers.models.attention_processor import (
|
||||
ADDED_KV_ATTENTION_PROCESSORS,
|
||||
CROSS_ATTENTION_PROCESSORS,
|
||||
AttentionProcessor,
|
||||
AttnAddedKVProcessor,
|
||||
AttnProcessor,
|
||||
)
|
||||
from diffusers.models.embeddings import TextImageProjection, TextImageTimeEmbedding, TextTimeEmbedding, TimestepEmbedding, Timesteps
|
||||
from diffusers.models.modeling_utils import ModelMixin
|
||||
from diffusers.models.unets.unet_2d_blocks import (
|
||||
CrossAttnDownBlock2D,
|
||||
DownBlock2D,
|
||||
UNetMidBlock2D,
|
||||
UNetMidBlock2DCrossAttn,
|
||||
get_down_block,
|
||||
)
|
||||
from diffusers.models.unets.unet_2d_condition import UNet2DConditionModel
|
||||
from diffusers.models.controlnet import ControlNetOutput
|
||||
from diffusers.models import ControlNetModel
|
||||
|
||||
import pdb
|
||||
|
||||
|
||||
class ControlNetVAEModel(ControlNetModel):
|
||||
def forward(
|
||||
self,
|
||||
sample: torch.Tensor,
|
||||
timestep: Union[torch.Tensor, float, int],
|
||||
encoder_hidden_states: torch.Tensor,
|
||||
controlnet_cond: torch.Tensor = None,
|
||||
conditioning_scale: float = 1.0,
|
||||
class_labels: Optional[torch.Tensor] = None,
|
||||
timestep_cond: Optional[torch.Tensor] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
added_cond_kwargs: Optional[Dict[str, torch.Tensor]] = None,
|
||||
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
|
||||
guess_mode: bool = False,
|
||||
return_dict: bool = True,
|
||||
) -> Union[ControlNetOutput, Tuple[Tuple[torch.Tensor, ...], torch.Tensor]]:
|
||||
"""
|
||||
The [`ControlNetVAEModel`] forward method.
|
||||
|
||||
Args:
|
||||
sample (`torch.Tensor`):
|
||||
The noisy input tensor.
|
||||
timestep (`Union[torch.Tensor, float, int]`):
|
||||
The number of timesteps to denoise an input.
|
||||
encoder_hidden_states (`torch.Tensor`):
|
||||
The encoder hidden states.
|
||||
controlnet_cond (`torch.Tensor`):
|
||||
The conditional input tensor of shape `(batch_size, sequence_length, hidden_size)`.
|
||||
conditioning_scale (`float`, defaults to `1.0`):
|
||||
The scale factor for ControlNet outputs.
|
||||
class_labels (`torch.Tensor`, *optional*, defaults to `None`):
|
||||
Optional class labels for conditioning. Their embeddings will be summed with the timestep embeddings.
|
||||
timestep_cond (`torch.Tensor`, *optional*, defaults to `None`):
|
||||
Additional conditional embeddings for timestep. If provided, the embeddings will be summed with the
|
||||
timestep_embedding passed through the `self.time_embedding` layer to obtain the final timestep
|
||||
embeddings.
|
||||
attention_mask (`torch.Tensor`, *optional*, defaults to `None`):
|
||||
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.
|
||||
added_cond_kwargs (`dict`):
|
||||
Additional conditions for the Stable Diffusion XL UNet.
|
||||
cross_attention_kwargs (`dict[str]`, *optional*, defaults to `None`):
|
||||
A kwargs dictionary that if specified is passed along to the `AttnProcessor`.
|
||||
guess_mode (`bool`, defaults to `False`):
|
||||
In this mode, the ControlNet encoder tries its best to recognize the input content of the input even if
|
||||
you remove all prompts. A `guidance_scale` between 3.0 and 5.0 is recommended.
|
||||
return_dict (`bool`, defaults to `True`):
|
||||
Whether or not to return a [`~models.controlnet.ControlNetOutput`] instead of a plain tuple.
|
||||
|
||||
Returns:
|
||||
[`~models.controlnet.ControlNetOutput`] **or** `tuple`:
|
||||
If `return_dict` is `True`, a [`~models.controlnet.ControlNetOutput`] is returned, otherwise a tuple is
|
||||
returned where the first element is the sample tensor.
|
||||
"""
|
||||
# check channel order
|
||||
|
||||
|
||||
channel_order = self.config.controlnet_conditioning_channel_order
|
||||
|
||||
if channel_order == "rgb":
|
||||
# in rgb order by default
|
||||
...
|
||||
elif channel_order == "bgr":
|
||||
controlnet_cond = torch.flip(controlnet_cond, dims=[1])
|
||||
else:
|
||||
raise ValueError(f"unknown `controlnet_conditioning_channel_order`: {channel_order}")
|
||||
|
||||
# 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)
|
||||
|
||||
# 1. time
|
||||
timesteps = timestep
|
||||
if not torch.is_tensor(timesteps):
|
||||
# TODO: this requires sync between CPU and GPU. So try to pass timesteps as tensors if you can
|
||||
# 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=sample.dtype)
|
||||
|
||||
emb = self.time_embedding(t_emb, timestep_cond)
|
||||
aug_emb = None
|
||||
|
||||
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 self.config.class_embed_type == "timestep":
|
||||
class_labels = self.time_proj(class_labels)
|
||||
|
||||
class_emb = self.class_embedding(class_labels).to(dtype=self.dtype)
|
||||
emb = emb + class_emb
|
||||
|
||||
if self.config.addition_embed_type is not None:
|
||||
if self.config.addition_embed_type == "text":
|
||||
aug_emb = self.add_embedding(encoder_hidden_states)
|
||||
|
||||
elif self.config.addition_embed_type == "text_time":
|
||||
if "text_embeds" not in added_cond_kwargs:
|
||||
raise ValueError(
|
||||
f"{self.__class__} has the config param `addition_embed_type` set to 'text_time' which requires the keyword argument `text_embeds` to be passed in `added_cond_kwargs`"
|
||||
)
|
||||
text_embeds = added_cond_kwargs.get("text_embeds")
|
||||
if "time_ids" not in added_cond_kwargs:
|
||||
raise ValueError(
|
||||
f"{self.__class__} has the config param `addition_embed_type` set to 'text_time' which requires the keyword argument `time_ids` to be passed in `added_cond_kwargs`"
|
||||
)
|
||||
time_ids = added_cond_kwargs.get("time_ids")
|
||||
time_embeds = self.add_time_proj(time_ids.flatten())
|
||||
time_embeds = time_embeds.reshape((text_embeds.shape[0], -1))
|
||||
|
||||
add_embeds = torch.concat([text_embeds, time_embeds], dim=-1)
|
||||
add_embeds = add_embeds.to(emb.dtype)
|
||||
aug_emb = self.add_embedding(add_embeds)
|
||||
|
||||
|
||||
emb = emb + aug_emb if aug_emb is not None else emb
|
||||
# 2. pre-process
|
||||
sample = self.conv_in(sample)
|
||||
|
||||
# 3. 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,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
)
|
||||
else:
|
||||
sample, res_samples = downsample_block(hidden_states=sample, temb=emb)
|
||||
|
||||
down_block_res_samples += res_samples
|
||||
|
||||
# 4. mid
|
||||
if self.mid_block is not None:
|
||||
if hasattr(self.mid_block, "has_cross_attention") and self.mid_block.has_cross_attention:
|
||||
sample = self.mid_block(
|
||||
sample,
|
||||
emb,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
attention_mask=attention_mask,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
)
|
||||
else:
|
||||
sample = self.mid_block(sample, emb)
|
||||
|
||||
# 5. Control net blocks
|
||||
|
||||
controlnet_down_block_res_samples = ()
|
||||
|
||||
# NOTE that controlnet downblock is zeroconv, we discard
|
||||
for down_block_res_sample, controlnet_block in zip(down_block_res_samples, self.controlnet_down_blocks):
|
||||
down_block_res_sample = down_block_res_sample
|
||||
controlnet_down_block_res_samples = controlnet_down_block_res_samples + (down_block_res_sample,)
|
||||
|
||||
down_block_res_samples = controlnet_down_block_res_samples
|
||||
|
||||
mid_block_res_sample = sample
|
||||
|
||||
# 6. scaling
|
||||
if guess_mode and not self.config.global_pool_conditions:
|
||||
scales = torch.logspace(-1, 0, len(down_block_res_samples) + 1, device=sample.device) # 0.1 to 1.0
|
||||
scales = scales * conditioning_scale
|
||||
down_block_res_samples = [sample * scale for sample, scale in zip(down_block_res_samples, scales)]
|
||||
mid_block_res_sample = mid_block_res_sample * scales[-1] # last one
|
||||
else:
|
||||
down_block_res_samples = [sample * conditioning_scale for sample in down_block_res_samples]
|
||||
mid_block_res_sample = mid_block_res_sample * conditioning_scale
|
||||
|
||||
if self.config.global_pool_conditions:
|
||||
down_block_res_samples = [
|
||||
torch.mean(sample, dim=(2, 3), keepdim=True) for sample in down_block_res_samples
|
||||
]
|
||||
mid_block_res_sample = torch.mean(mid_block_res_sample, dim=(2, 3), keepdim=True)
|
||||
|
||||
if not return_dict:
|
||||
return (down_block_res_samples, mid_block_res_sample)
|
||||
|
||||
return ControlNetOutput(
|
||||
down_block_res_samples=down_block_res_samples, mid_block_res_sample=mid_block_res_sample
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"_class_name": "YOSONormalsPipeline",
|
||||
"_diffusers_version": "0.28.0",
|
||||
"controlnet": [
|
||||
"controlnetvae",
|
||||
"ControlNetVAEModel"
|
||||
],
|
||||
"requires_safety_checker": true,
|
||||
"unet": [
|
||||
"diffusers",
|
||||
"UNet2DConditionModel"
|
||||
],
|
||||
"vae": [
|
||||
"diffusers",
|
||||
"AutoencoderKL"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"_class_name": "UNet2DConditionModel",
|
||||
"_diffusers_version": "0.28.0",
|
||||
"_name_or_path": "weights/yoso-normal-v1-8-1/unet",
|
||||
"act_fn": "silu",
|
||||
"addition_embed_type": null,
|
||||
"addition_embed_type_num_heads": 64,
|
||||
"addition_time_embed_dim": null,
|
||||
"attention_head_dim": [
|
||||
5,
|
||||
10,
|
||||
20,
|
||||
20
|
||||
],
|
||||
"attention_type": "default",
|
||||
"block_out_channels": [
|
||||
320,
|
||||
640,
|
||||
1280,
|
||||
1280
|
||||
],
|
||||
"center_input_sample": false,
|
||||
"class_embed_type": null,
|
||||
"class_embeddings_concat": false,
|
||||
"conv_in_kernel": 3,
|
||||
"conv_out_kernel": 3,
|
||||
"cross_attention_dim": 1024,
|
||||
"cross_attention_norm": null,
|
||||
"down_block_types": [
|
||||
"CrossAttnDownBlock2D",
|
||||
"CrossAttnDownBlock2D",
|
||||
"CrossAttnDownBlock2D",
|
||||
"DownBlock2D"
|
||||
],
|
||||
"downsample_padding": 1,
|
||||
"dropout": 0.0,
|
||||
"dual_cross_attention": false,
|
||||
"encoder_hid_dim": null,
|
||||
"encoder_hid_dim_type": null,
|
||||
"flip_sin_to_cos": true,
|
||||
"freq_shift": 0,
|
||||
"in_channels": 4,
|
||||
"layers_per_block": 2,
|
||||
"mid_block_only_cross_attention": null,
|
||||
"mid_block_scale_factor": 1,
|
||||
"mid_block_type": "UNetMidBlock2DCrossAttn",
|
||||
"norm_eps": 1e-05,
|
||||
"norm_num_groups": 32,
|
||||
"num_attention_heads": null,
|
||||
"num_class_embeds": null,
|
||||
"only_cross_attention": false,
|
||||
"out_channels": 4,
|
||||
"projection_class_embeddings_input_dim": null,
|
||||
"resnet_out_scale_factor": 1.0,
|
||||
"resnet_skip_time_act": false,
|
||||
"resnet_time_scale_shift": "default",
|
||||
"reverse_transformer_layers_per_block": null,
|
||||
"sample_size": 128,
|
||||
"time_cond_proj_dim": null,
|
||||
"time_embedding_act_fn": null,
|
||||
"time_embedding_dim": null,
|
||||
"time_embedding_type": "positional",
|
||||
"timestep_post_act": null,
|
||||
"transformer_layers_per_block": 1,
|
||||
"up_block_types": [
|
||||
"UpBlock2D",
|
||||
"CrossAttnUpBlock2D",
|
||||
"CrossAttnUpBlock2D",
|
||||
"CrossAttnUpBlock2D"
|
||||
],
|
||||
"upcast_attention": true,
|
||||
"use_linear_projection": true
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"_class_name": "AutoencoderKL",
|
||||
"_diffusers_version": "0.28.0",
|
||||
"_name_or_path": "weights/yoso-normal-v1-8-1/vae",
|
||||
"act_fn": "silu",
|
||||
"block_out_channels": [
|
||||
128,
|
||||
256,
|
||||
512,
|
||||
512
|
||||
],
|
||||
"down_block_types": [
|
||||
"DownEncoderBlock2D",
|
||||
"DownEncoderBlock2D",
|
||||
"DownEncoderBlock2D",
|
||||
"DownEncoderBlock2D"
|
||||
],
|
||||
"force_upcast": true,
|
||||
"in_channels": 3,
|
||||
"latent_channels": 4,
|
||||
"latents_mean": null,
|
||||
"latents_std": null,
|
||||
"layers_per_block": 2,
|
||||
"norm_num_groups": 32,
|
||||
"out_channels": 3,
|
||||
"sample_size": 1024,
|
||||
"scaling_factor": 0.18215,
|
||||
"up_block_types": [
|
||||
"UpDecoderBlock2D",
|
||||
"UpDecoderBlock2D",
|
||||
"UpDecoderBlock2D",
|
||||
"UpDecoderBlock2D"
|
||||
]
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "SLatMeshDecoder",
|
||||
"args": {
|
||||
"resolution": 64,
|
||||
"model_channels": 768,
|
||||
"latent_channels": 8,
|
||||
"num_blocks": 12,
|
||||
"num_heads": 12,
|
||||
"mlp_ratio": 4,
|
||||
"attn_mode": "swin",
|
||||
"window_size": 8,
|
||||
"use_fp16": true,
|
||||
"representation_config": {
|
||||
"use_color": true
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "SLatFlowModel",
|
||||
"args": {
|
||||
"resolution": 64,
|
||||
"in_channels": 8,
|
||||
"out_channels": 8,
|
||||
"model_channels": 1024,
|
||||
"cond_channels": 1024,
|
||||
"num_blocks": 24,
|
||||
"num_heads": 16,
|
||||
"mlp_ratio": 4,
|
||||
"patch_size": 2,
|
||||
"num_io_res_blocks": 2,
|
||||
"io_block_channels": [128],
|
||||
"pe_mode": "ape",
|
||||
"qk_rms_norm": true,
|
||||
"use_fp16": true
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
|
||||
{
|
||||
"name": "SparseStructureDecoder",
|
||||
"args": {
|
||||
"out_channels": 1,
|
||||
"latent_channels": 8,
|
||||
"num_res_blocks": 2,
|
||||
"num_res_blocks_middle": 2,
|
||||
"channels": [512, 128, 32],
|
||||
"use_fp16": true
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "SparseStructureFlowModel",
|
||||
"args": {
|
||||
"resolution": 16,
|
||||
"in_channels": 8,
|
||||
"out_channels": 8,
|
||||
"model_channels": 1024,
|
||||
"cond_channels": 1024,
|
||||
"num_blocks": 24,
|
||||
"num_heads": 16,
|
||||
"mlp_ratio": 4,
|
||||
"patch_size": 1,
|
||||
"pe_mode": "ape",
|
||||
"qk_rms_norm": true,
|
||||
"use_fp16": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "TrellisImageTo3DPipeline",
|
||||
"args": {
|
||||
"models": {
|
||||
"sparse_structure_decoder": "ckpts/ss_dec_conv3d_16l8_fp16",
|
||||
"sparse_structure_flow_model": "ckpts/ss_flow_normal_dit_L_16l8_fp16",
|
||||
"slat_decoder_mesh": "ckpts/slat_dec_mesh_swin8_B_64l8m256c_fp16",
|
||||
"slat_flow_model": "ckpts/slat_flow_normal_dit_L_64l8p2_fp16"
|
||||
},
|
||||
"sparse_structure_sampler": {
|
||||
"name": "FlowEulerGuidanceIntervalSampler",
|
||||
"args": {
|
||||
"sigma_min": 1e-5
|
||||
},
|
||||
"params": {
|
||||
"steps": 25,
|
||||
"cfg_strength": 5.0,
|
||||
"cfg_interval": [0.5, 1.0],
|
||||
"rescale_t": 3.0
|
||||
}
|
||||
},
|
||||
"slat_sampler": {
|
||||
"name": "FlowEulerGuidanceIntervalSampler",
|
||||
"args": {
|
||||
"sigma_min": 1e-5
|
||||
},
|
||||
"params": {
|
||||
"steps": 25,
|
||||
"cfg_strength": 5.0,
|
||||
"cfg_interval": [0.5, 1.0],
|
||||
"rescale_t": 3.0
|
||||
}
|
||||
},
|
||||
"slat_normalization": {
|
||||
"mean": [
|
||||
-2.1687545776367188,
|
||||
-0.004347046371549368,
|
||||
-0.13352349400520325,
|
||||
-0.08418072760105133,
|
||||
-0.5271206498146057,
|
||||
0.7238689064979553,
|
||||
-1.1414450407028198,
|
||||
1.2039363384246826
|
||||
],
|
||||
"std": [
|
||||
2.377650737762451,
|
||||
2.386378288269043,
|
||||
2.124418020248413,
|
||||
2.1748552322387695,
|
||||
2.663944721221924,
|
||||
2.371192216873169,
|
||||
2.6217446327209473,
|
||||
2.684523105621338
|
||||
]
|
||||
},
|
||||
"image_cond_model": "dinov2_vitl14_reg"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#__init__.py
|
||||
import os
|
||||
import sys
|
||||
import torch
|
||||
import logging
|
||||
import platform
|
||||
import folder_paths
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger('ComfyUI-Hi3DGen')
|
||||
|
||||
# Add parent directory to Python path
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
parent_dir = os.path.dirname(current_dir)
|
||||
|
||||
# Add both current and parent dir to handle different installation scenarios
|
||||
if current_dir not in sys.path:
|
||||
sys.path.insert(0, current_dir)
|
||||
if parent_dir not in sys.path:
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
# Add trellis package path
|
||||
trellis_path = os.path.join(current_dir, "trellis")
|
||||
if os.path.exists(trellis_path) and trellis_path not in sys.path:
|
||||
sys.path.insert(0, trellis_path)
|
||||
logger.info(f"Added trellis path to sys.path: {trellis_path}")
|
||||
|
||||
# Add stablx package path
|
||||
stablex_path = os.path.join(current_dir, "stablex")
|
||||
if os.path.exists(trellis_path) and trellis_path not in sys.path:
|
||||
sys.path.insert(0, trellis_path)
|
||||
logger.info(f"Added stablex path to sys.path: {trellis_path}")
|
||||
|
||||
# Verify trellis package is importable
|
||||
try:
|
||||
import trellis
|
||||
logger.info("Trellis package imported successfully")
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import trellis package: {e}")
|
||||
logger.error(f"Current sys.path: {sys.path}")
|
||||
raise
|
||||
|
||||
# Verify stablex package is importable
|
||||
try:
|
||||
import stablex
|
||||
logger.info("stablex package imported successfully")
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import stablex package: {e}")
|
||||
logger.error(f"Current sys.path: {sys.path}")
|
||||
raise
|
||||
|
||||
# Register model paths with ComfyUI
|
||||
try:
|
||||
folder_paths.add_model_folder_path("trellis", os.path.join(folder_paths.models_dir, "trellis"))
|
||||
folder_paths.add_model_folder_path("checkpoints", os.path.join(folder_paths.models_dir, "checkpoints"))
|
||||
except Exception as e:
|
||||
logger.error(f"Error registering model paths: {e}")
|
||||
|
||||
# Register model paths with ComfyUI
|
||||
try:
|
||||
folder_paths.add_model_folder_path("stablex", os.path.join(folder_paths.models_dir, "stablex"))
|
||||
except Exception as e:
|
||||
logger.error(f"Error registering model paths: {e}")
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
# Copyright 2024 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, List, Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
|
||||
from diffusers.models.controlnet import ControlNetOutput
|
||||
from diffusers.models import ControlNetModel
|
||||
|
||||
|
||||
class ControlNetVAEModel(ControlNetModel):
|
||||
def forward(
|
||||
self,
|
||||
sample: torch.Tensor,
|
||||
timestep: Union[torch.Tensor, float, int],
|
||||
encoder_hidden_states: torch.Tensor,
|
||||
controlnet_cond: torch.Tensor = None,
|
||||
conditioning_scale: float = 1.0,
|
||||
class_labels: Optional[torch.Tensor] = None,
|
||||
timestep_cond: Optional[torch.Tensor] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
added_cond_kwargs: Optional[Dict[str, torch.Tensor]] = None,
|
||||
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
|
||||
guess_mode: bool = False,
|
||||
return_dict: bool = True,
|
||||
) -> Union[ControlNetOutput, Tuple[Tuple[torch.Tensor, ...], torch.Tensor]]:
|
||||
"""
|
||||
The [`ControlNetVAEModel`] forward method.
|
||||
|
||||
Args:
|
||||
sample (`torch.Tensor`):
|
||||
The noisy input tensor.
|
||||
timestep (`Union[torch.Tensor, float, int]`):
|
||||
The number of timesteps to denoise an input.
|
||||
encoder_hidden_states (`torch.Tensor`):
|
||||
The encoder hidden states.
|
||||
controlnet_cond (`torch.Tensor`):
|
||||
The conditional input tensor of shape `(batch_size, sequence_length, hidden_size)`.
|
||||
conditioning_scale (`float`, defaults to `1.0`):
|
||||
The scale factor for ControlNet outputs.
|
||||
class_labels (`torch.Tensor`, *optional*, defaults to `None`):
|
||||
Optional class labels for conditioning. Their embeddings will be summed with the timestep embeddings.
|
||||
timestep_cond (`torch.Tensor`, *optional*, defaults to `None`):
|
||||
Additional conditional embeddings for timestep. If provided, the embeddings will be summed with the
|
||||
timestep_embedding passed through the `self.time_embedding` layer to obtain the final timestep
|
||||
embeddings.
|
||||
attention_mask (`torch.Tensor`, *optional*, defaults to `None`):
|
||||
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.
|
||||
added_cond_kwargs (`dict`):
|
||||
Additional conditions for the Stable Diffusion XL UNet.
|
||||
cross_attention_kwargs (`dict[str]`, *optional*, defaults to `None`):
|
||||
A kwargs dictionary that if specified is passed along to the `AttnProcessor`.
|
||||
guess_mode (`bool`, defaults to `False`):
|
||||
In this mode, the ControlNet encoder tries its best to recognize the input content of the input even if
|
||||
you remove all prompts. A `guidance_scale` between 3.0 and 5.0 is recommended.
|
||||
return_dict (`bool`, defaults to `True`):
|
||||
Whether or not to return a [`~models.controlnet.ControlNetOutput`] instead of a plain tuple.
|
||||
|
||||
Returns:
|
||||
[`~models.controlnet.ControlNetOutput`] **or** `tuple`:
|
||||
If `return_dict` is `True`, a [`~models.controlnet.ControlNetOutput`] is returned, otherwise a tuple is
|
||||
returned where the first element is the sample tensor.
|
||||
"""
|
||||
# check channel order
|
||||
|
||||
|
||||
channel_order = self.config.controlnet_conditioning_channel_order
|
||||
|
||||
if channel_order == "rgb":
|
||||
# in rgb order by default
|
||||
...
|
||||
elif channel_order == "bgr":
|
||||
controlnet_cond = torch.flip(controlnet_cond, dims=[1])
|
||||
else:
|
||||
raise ValueError(f"unknown `controlnet_conditioning_channel_order`: {channel_order}")
|
||||
|
||||
# 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)
|
||||
|
||||
# 1. time
|
||||
timesteps = timestep
|
||||
if not torch.is_tensor(timesteps):
|
||||
# TODO: this requires sync between CPU and GPU. So try to pass timesteps as tensors if you can
|
||||
# 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=sample.dtype)
|
||||
|
||||
emb = self.time_embedding(t_emb, timestep_cond)
|
||||
aug_emb = None
|
||||
|
||||
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 self.config.class_embed_type == "timestep":
|
||||
class_labels = self.time_proj(class_labels)
|
||||
|
||||
class_emb = self.class_embedding(class_labels).to(dtype=self.dtype)
|
||||
emb = emb + class_emb
|
||||
|
||||
if self.config.addition_embed_type is not None:
|
||||
if self.config.addition_embed_type == "text":
|
||||
aug_emb = self.add_embedding(encoder_hidden_states)
|
||||
|
||||
elif self.config.addition_embed_type == "text_time":
|
||||
if "text_embeds" not in added_cond_kwargs:
|
||||
raise ValueError(
|
||||
f"{self.__class__} has the config param `addition_embed_type` set to 'text_time' which requires the keyword argument `text_embeds` to be passed in `added_cond_kwargs`"
|
||||
)
|
||||
text_embeds = added_cond_kwargs.get("text_embeds")
|
||||
if "time_ids" not in added_cond_kwargs:
|
||||
raise ValueError(
|
||||
f"{self.__class__} has the config param `addition_embed_type` set to 'text_time' which requires the keyword argument `time_ids` to be passed in `added_cond_kwargs`"
|
||||
)
|
||||
time_ids = added_cond_kwargs.get("time_ids")
|
||||
time_embeds = self.add_time_proj(time_ids.flatten())
|
||||
time_embeds = time_embeds.reshape((text_embeds.shape[0], -1))
|
||||
|
||||
add_embeds = torch.concat([text_embeds, time_embeds], dim=-1)
|
||||
add_embeds = add_embeds.to(emb.dtype)
|
||||
aug_emb = self.add_embedding(add_embeds)
|
||||
|
||||
|
||||
emb = emb + aug_emb if aug_emb is not None else emb
|
||||
# 2. pre-process
|
||||
sample = self.conv_in(sample)
|
||||
|
||||
# 3. 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,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
)
|
||||
else:
|
||||
sample, res_samples = downsample_block(hidden_states=sample, temb=emb)
|
||||
|
||||
down_block_res_samples += res_samples
|
||||
|
||||
# 4. mid
|
||||
if self.mid_block is not None:
|
||||
if hasattr(self.mid_block, "has_cross_attention") and self.mid_block.has_cross_attention:
|
||||
sample = self.mid_block(
|
||||
sample,
|
||||
emb,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
attention_mask=attention_mask,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
)
|
||||
else:
|
||||
sample = self.mid_block(sample, emb)
|
||||
|
||||
# 5. Control net blocks
|
||||
|
||||
controlnet_down_block_res_samples = ()
|
||||
|
||||
# NOTE that controlnet downblock is zeroconv, we discard
|
||||
for down_block_res_sample, controlnet_block in zip(down_block_res_samples, self.controlnet_down_blocks):
|
||||
down_block_res_sample = down_block_res_sample
|
||||
controlnet_down_block_res_samples = controlnet_down_block_res_samples + (down_block_res_sample,)
|
||||
|
||||
down_block_res_samples = controlnet_down_block_res_samples
|
||||
|
||||
mid_block_res_sample = sample
|
||||
|
||||
# 6. scaling
|
||||
if guess_mode and not self.config.global_pool_conditions:
|
||||
scales = torch.logspace(-1, 0, len(down_block_res_samples) + 1, device=sample.device) # 0.1 to 1.0
|
||||
scales = scales * conditioning_scale
|
||||
down_block_res_samples = [sample * scale for sample, scale in zip(down_block_res_samples, scales)]
|
||||
mid_block_res_sample = mid_block_res_sample * scales[-1] # last one
|
||||
else:
|
||||
down_block_res_samples = [sample * conditioning_scale for sample in down_block_res_samples]
|
||||
mid_block_res_sample = mid_block_res_sample * conditioning_scale
|
||||
|
||||
if self.config.global_pool_conditions:
|
||||
down_block_res_samples = [
|
||||
torch.mean(sample, dim=(2, 3), keepdim=True) for sample in down_block_res_samples
|
||||
]
|
||||
mid_block_res_sample = torch.mean(mid_block_res_sample, dim=(2, 3), keepdim=True)
|
||||
|
||||
if not return_dict:
|
||||
return (down_block_res_samples, mid_block_res_sample)
|
||||
|
||||
return ControlNetOutput(
|
||||
down_block_res_samples=down_block_res_samples, mid_block_res_sample=mid_block_res_sample
|
||||
)
|
||||
@@ -0,0 +1,578 @@
|
||||
# Copyright 2024 Marigold authors, PRS ETH Zurich. All rights reserved.
|
||||
# Copyright 2024 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.
|
||||
# --------------------------------------------------------------------------
|
||||
# More information and citation instructions are available on the
|
||||
# --------------------------------------------------------------------------
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from torchvision import transforms
|
||||
import cv2
|
||||
from PIL import Image
|
||||
from tqdm.auto import tqdm
|
||||
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer, CLIPVisionModelWithProjection
|
||||
|
||||
|
||||
from diffusers.image_processor import PipelineImageInput
|
||||
from diffusers.models import (
|
||||
AutoencoderKL,
|
||||
UNet2DConditionModel,
|
||||
ControlNetModel,
|
||||
)
|
||||
from diffusers.schedulers import (
|
||||
DDIMScheduler
|
||||
)
|
||||
|
||||
from diffusers.utils import (
|
||||
BaseOutput,
|
||||
logging,
|
||||
replace_example_docstring,
|
||||
)
|
||||
|
||||
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
from diffusers.pipelines.controlnet import StableDiffusionControlNetPipeline
|
||||
from diffusers.pipelines.pipeline_utils import DiffusionPipeline
|
||||
from diffusers.pipelines.marigold.marigold_image_processing import MarigoldImageProcessor
|
||||
from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker
|
||||
|
||||
import pdb
|
||||
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
EXAMPLE_DOC_STRING = """
|
||||
Examples:
|
||||
```py
|
||||
>>> import diffusers
|
||||
>>> import torch
|
||||
|
||||
>>> pipe = diffusers.MarigoldNormalsPipeline.from_pretrained(
|
||||
... "prs-eth/marigold-normals-lcm-v0-1", variant="fp16", torch_dtype=torch.float16
|
||||
... ).to("cuda")
|
||||
|
||||
>>> image = diffusers.utils.load_image("https://marigoldmonodepth.github.io/images/einstein.jpg")
|
||||
>>> normals = pipe(image)
|
||||
|
||||
>>> vis = pipe.image_processor.visualize_normals(normals.prediction)
|
||||
>>> vis[0].save("einstein_normals.png")
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class YosoOutput(BaseOutput):
|
||||
"""
|
||||
Output class for Marigold monocular normals prediction pipeline.
|
||||
|
||||
Args:
|
||||
prediction (`np.ndarray`, `torch.Tensor`):
|
||||
Predicted normals with values in the range [-1, 1]. The shape is always $numimages \times 3 \times height
|
||||
\times width$, regardless of whether the images were passed as a 4D array or a list.
|
||||
uncertainty (`None`, `np.ndarray`, `torch.Tensor`):
|
||||
Uncertainty maps computed from the ensemble, with values in the range [0, 1]. The shape is $numimages
|
||||
\times 1 \times height \times width$.
|
||||
latent (`None`, `torch.Tensor`):
|
||||
Latent features corresponding to the predictions, compatible with the `latents` argument of the pipeline.
|
||||
The shape is $numimages * numensemble \times 4 \times latentheight \times latentwidth$.
|
||||
"""
|
||||
|
||||
prediction: Union[np.ndarray, torch.Tensor]
|
||||
latent: Union[None, torch.Tensor]
|
||||
gaus_noise: Union[None, torch.Tensor]
|
||||
|
||||
|
||||
class YosoPipeline(StableDiffusionControlNetPipeline):
|
||||
""" Pipeline for monocular normals estimation using the Marigold method: https://marigoldmonodepth.github.io.
|
||||
Pipeline for text-to-image generation using Stable Diffusion with ControlNet guidance.
|
||||
|
||||
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods
|
||||
implemented for all pipelines (downloading, saving, running on a particular device, etc.).
|
||||
|
||||
The pipeline also inherits the following loading methods:
|
||||
- [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] for loading textual inversion embeddings
|
||||
- [`~loaders.LoraLoaderMixin.load_lora_weights`] for loading LoRA weights
|
||||
- [`~loaders.LoraLoaderMixin.save_lora_weights`] for saving LoRA weights
|
||||
- [`~loaders.FromSingleFileMixin.from_single_file`] for loading `.ckpt` files
|
||||
- [`~loaders.IPAdapterMixin.load_ip_adapter`] for loading IP Adapters
|
||||
|
||||
Args:
|
||||
vae ([`AutoencoderKL`]):
|
||||
Variational Auto-Encoder (VAE) model to encode and decode images to and from latent representations.
|
||||
text_encoder ([`~transformers.CLIPTextModel`]):
|
||||
Frozen text-encoder ([clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14)).
|
||||
tokenizer ([`~transformers.CLIPTokenizer`]):
|
||||
A `CLIPTokenizer` to tokenize text.
|
||||
unet ([`UNet2DConditionModel`]):
|
||||
A `UNet2DConditionModel` to denoise the encoded image latents.
|
||||
controlnet ([`ControlNetModel`] or `List[ControlNetModel]`):
|
||||
Provides additional conditioning to the `unet` during the denoising process. If you set multiple
|
||||
ControlNets as a list, the outputs from each ControlNet are added together to create one combined
|
||||
additional conditioning.
|
||||
scheduler ([`SchedulerMixin`]):
|
||||
A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of
|
||||
[`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`].
|
||||
safety_checker ([`StableDiffusionSafetyChecker`]):
|
||||
Classification module that estimates whether generated images could be considered offensive or harmful.
|
||||
Please refer to the [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) for more details
|
||||
about a model's potential harms.
|
||||
feature_extractor ([`~transformers.CLIPImageProcessor`]):
|
||||
A `CLIPImageProcessor` to extract features from generated images; used as inputs to the `safety_checker`.
|
||||
"""
|
||||
|
||||
model_cpu_offload_seq = "text_encoder->image_encoder->unet->vae"
|
||||
_optional_components = ["safety_checker", "feature_extractor", "image_encoder"]
|
||||
_exclude_from_cpu_offload = ["safety_checker"]
|
||||
_callback_tensor_inputs = ["latents", "prompt_embeds", "negative_prompt_embeds"]
|
||||
|
||||
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vae: AutoencoderKL,
|
||||
unet: UNet2DConditionModel,
|
||||
controlnet: Union[ControlNetModel, List[ControlNetModel], Tuple[ControlNetModel]],
|
||||
scheduler: Union[DDIMScheduler] = None,
|
||||
safety_checker: StableDiffusionSafetyChecker = None,
|
||||
text_encoder: CLIPTextModel = None,
|
||||
tokenizer: CLIPTokenizer = None,
|
||||
feature_extractor: CLIPImageProcessor = None,
|
||||
image_encoder: CLIPVisionModelWithProjection = None,
|
||||
requires_safety_checker: bool = False,
|
||||
default_denoising_steps: Optional[int] = 1,
|
||||
default_processing_resolution: Optional[int] = 768,
|
||||
prompt="",
|
||||
empty_text_embedding=None,
|
||||
t_start: Optional[int] = 0,
|
||||
):
|
||||
super().__init__(
|
||||
vae,
|
||||
text_encoder,
|
||||
tokenizer,
|
||||
unet,
|
||||
controlnet,
|
||||
scheduler,
|
||||
safety_checker,
|
||||
feature_extractor,
|
||||
image_encoder,
|
||||
requires_safety_checker,
|
||||
)
|
||||
|
||||
# TODO yoso ImageProcessor
|
||||
self.image_processor = MarigoldImageProcessor(vae_scale_factor=self.vae_scale_factor)
|
||||
self.control_image_processor = MarigoldImageProcessor(vae_scale_factor=self.vae_scale_factor)
|
||||
self.default_denoising_steps = default_denoising_steps
|
||||
self.default_processing_resolution = default_processing_resolution
|
||||
self.empty_text_embedding = empty_text_embedding
|
||||
self.t_start= t_start # target_out latents
|
||||
|
||||
def check_inputs(
|
||||
self,
|
||||
image: PipelineImageInput,
|
||||
num_inference_steps: int,
|
||||
ensemble_size: int,
|
||||
processing_resolution: int,
|
||||
resample_method_input: str,
|
||||
resample_method_output: str,
|
||||
batch_size: int,
|
||||
ensembling_kwargs: Optional[Dict[str, Any]],
|
||||
latents: Optional[torch.Tensor],
|
||||
generator: Optional[Union[torch.Generator, List[torch.Generator]]],
|
||||
output_type: str,
|
||||
output_uncertainty: bool,
|
||||
) -> int:
|
||||
if num_inference_steps is None:
|
||||
raise ValueError("`num_inference_steps` is not specified and could not be resolved from the model config.")
|
||||
if num_inference_steps < 1:
|
||||
raise ValueError("`num_inference_steps` must be positive.")
|
||||
if ensemble_size < 1:
|
||||
raise ValueError("`ensemble_size` must be positive.")
|
||||
if ensemble_size == 2:
|
||||
logger.warning(
|
||||
"`ensemble_size` == 2 results are similar to no ensembling (1); "
|
||||
"consider increasing the value to at least 3."
|
||||
)
|
||||
if ensemble_size == 1 and output_uncertainty:
|
||||
raise ValueError(
|
||||
"Computing uncertainty by setting `output_uncertainty=True` also requires setting `ensemble_size` "
|
||||
"greater than 1."
|
||||
)
|
||||
if processing_resolution is None:
|
||||
raise ValueError(
|
||||
"`processing_resolution` is not specified and could not be resolved from the model config."
|
||||
)
|
||||
if processing_resolution < 0:
|
||||
raise ValueError(
|
||||
"`processing_resolution` must be non-negative: 0 for native resolution, or any positive value for "
|
||||
"downsampled processing."
|
||||
)
|
||||
if processing_resolution % self.vae_scale_factor != 0:
|
||||
raise ValueError(f"`processing_resolution` must be a multiple of {self.vae_scale_factor}.")
|
||||
if resample_method_input not in ("nearest", "nearest-exact", "bilinear", "bicubic", "area"):
|
||||
raise ValueError(
|
||||
"`resample_method_input` takes string values compatible with PIL library: "
|
||||
"nearest, nearest-exact, bilinear, bicubic, area."
|
||||
)
|
||||
if resample_method_output not in ("nearest", "nearest-exact", "bilinear", "bicubic", "area"):
|
||||
raise ValueError(
|
||||
"`resample_method_output` takes string values compatible with PIL library: "
|
||||
"nearest, nearest-exact, bilinear, bicubic, area."
|
||||
)
|
||||
if batch_size < 1:
|
||||
raise ValueError("`batch_size` must be positive.")
|
||||
if output_type not in ["pt", "np"]:
|
||||
raise ValueError("`output_type` must be one of `pt` or `np`.")
|
||||
if latents is not None and generator is not None:
|
||||
raise ValueError("`latents` and `generator` cannot be used together.")
|
||||
if ensembling_kwargs is not None:
|
||||
if not isinstance(ensembling_kwargs, dict):
|
||||
raise ValueError("`ensembling_kwargs` must be a dictionary.")
|
||||
if "reduction" in ensembling_kwargs and ensembling_kwargs["reduction"] not in ("closest", "mean"):
|
||||
raise ValueError("`ensembling_kwargs['reduction']` can be either `'closest'` or `'mean'`.")
|
||||
|
||||
# image checks
|
||||
num_images = 0
|
||||
W, H = None, None
|
||||
if not isinstance(image, list):
|
||||
image = [image]
|
||||
for i, img in enumerate(image):
|
||||
if isinstance(img, np.ndarray) or torch.is_tensor(img):
|
||||
if img.ndim not in (2, 3, 4):
|
||||
raise ValueError(f"`image[{i}]` has unsupported dimensions or shape: {img.shape}.")
|
||||
H_i, W_i = img.shape[-2:]
|
||||
N_i = 1
|
||||
if img.ndim == 4:
|
||||
N_i = img.shape[0]
|
||||
elif isinstance(img, Image.Image):
|
||||
W_i, H_i = img.size
|
||||
N_i = 1
|
||||
else:
|
||||
raise ValueError(f"Unsupported `image[{i}]` type: {type(img)}.")
|
||||
if W is None:
|
||||
W, H = W_i, H_i
|
||||
elif (W, H) != (W_i, H_i):
|
||||
raise ValueError(
|
||||
f"Input `image[{i}]` has incompatible dimensions {(W_i, H_i)} with the previous images {(W, H)}"
|
||||
)
|
||||
num_images += N_i
|
||||
|
||||
# latents checks
|
||||
if latents is not None:
|
||||
if not torch.is_tensor(latents):
|
||||
raise ValueError("`latents` must be a torch.Tensor.")
|
||||
if latents.dim() != 4:
|
||||
raise ValueError(f"`latents` has unsupported dimensions or shape: {latents.shape}.")
|
||||
|
||||
if processing_resolution > 0:
|
||||
max_orig = max(H, W)
|
||||
new_H = H * processing_resolution // max_orig
|
||||
new_W = W * processing_resolution // max_orig
|
||||
if new_H == 0 or new_W == 0:
|
||||
raise ValueError(f"Extreme aspect ratio of the input image: [{W} x {H}]")
|
||||
W, H = new_W, new_H
|
||||
w = (W + self.vae_scale_factor - 1) // self.vae_scale_factor
|
||||
h = (H + self.vae_scale_factor - 1) // self.vae_scale_factor
|
||||
shape_expected = (num_images * ensemble_size, self.vae.config.latent_channels, h, w)
|
||||
|
||||
if latents.shape != shape_expected:
|
||||
raise ValueError(f"`latents` has unexpected shape={latents.shape} expected={shape_expected}.")
|
||||
|
||||
# generator checks
|
||||
if generator is not None:
|
||||
if isinstance(generator, list):
|
||||
if len(generator) != num_images * ensemble_size:
|
||||
raise ValueError(
|
||||
"The number of generators must match the total number of ensemble members for all input images."
|
||||
)
|
||||
if not all(g.device.type == generator[0].device.type for g in generator):
|
||||
raise ValueError("`generator` device placement is not consistent in the list.")
|
||||
elif not isinstance(generator, torch.Generator):
|
||||
raise ValueError(f"Unsupported generator type: {type(generator)}.")
|
||||
|
||||
return num_images
|
||||
|
||||
def progress_bar(self, iterable=None, total=None, desc=None, leave=True):
|
||||
if not hasattr(self, "_progress_bar_config"):
|
||||
self._progress_bar_config = {}
|
||||
elif not isinstance(self._progress_bar_config, dict):
|
||||
raise ValueError(
|
||||
f"`self._progress_bar_config` should be of type `dict`, but is {type(self._progress_bar_config)}."
|
||||
)
|
||||
|
||||
progress_bar_config = dict(**self._progress_bar_config)
|
||||
progress_bar_config["desc"] = progress_bar_config.get("desc", desc)
|
||||
progress_bar_config["leave"] = progress_bar_config.get("leave", leave)
|
||||
if iterable is not None:
|
||||
return tqdm(iterable, **progress_bar_config)
|
||||
elif total is not None:
|
||||
return tqdm(total=total, **progress_bar_config)
|
||||
else:
|
||||
raise ValueError("Either `total` or `iterable` has to be defined.")
|
||||
|
||||
@torch.no_grad()
|
||||
@replace_example_docstring(EXAMPLE_DOC_STRING)
|
||||
def __call__(
|
||||
self,
|
||||
image: PipelineImageInput,
|
||||
prompt: Union[str, List[str]] = None,
|
||||
negative_prompt: Optional[Union[str, List[str]]] = None,
|
||||
num_inference_steps: Optional[int] = None,
|
||||
ensemble_size: int = 1,
|
||||
processing_resolution: Optional[int] = None,
|
||||
match_input_resolution: bool = True,
|
||||
resample_method_input: str = "bilinear",
|
||||
resample_method_output: str = "bilinear",
|
||||
batch_size: int = 1,
|
||||
ensembling_kwargs: Optional[Dict[str, Any]] = None,
|
||||
latents: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,
|
||||
prompt_embeds: Optional[torch.Tensor] = None,
|
||||
negative_prompt_embeds: Optional[torch.Tensor] = None,
|
||||
num_images_per_prompt: Optional[int] = 1,
|
||||
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
|
||||
controlnet_conditioning_scale: Union[float, List[float]] = 1.0,
|
||||
output_type: str = "np",
|
||||
output_uncertainty: bool = False,
|
||||
output_latent: bool = False,
|
||||
skip_preprocess: bool = False,
|
||||
return_dict: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Function invoked when calling the pipeline.
|
||||
|
||||
Args:
|
||||
image (`PIL.Image.Image`, `np.ndarray`, `torch.Tensor`, `List[PIL.Image.Image]`, `List[np.ndarray]`),
|
||||
`List[torch.Tensor]`: An input image or images used as an input for the normals estimation task. For
|
||||
arrays and tensors, the expected value range is between `[0, 1]`. Passing a batch of images is possible
|
||||
by providing a four-dimensional array or a tensor. Additionally, a list of images of two- or
|
||||
three-dimensional arrays or tensors can be passed. In the latter case, all list elements must have the
|
||||
same width and height.
|
||||
num_inference_steps (`int`, *optional*, defaults to `None`):
|
||||
Number of denoising diffusion steps during inference. The default value `None` results in automatic
|
||||
selection. The number of steps should be at least 10 with the full Marigold models, and between 1 and 4
|
||||
for Marigold-LCM models.
|
||||
ensemble_size (`int`, defaults to `1`):
|
||||
Number of ensemble predictions. Recommended values are 5 and higher for better precision, or 1 for
|
||||
faster inference.
|
||||
processing_resolution (`int`, *optional*, defaults to `None`):
|
||||
Effective processing resolution. When set to `0`, matches the larger input image dimension. This
|
||||
produces crisper predictions, but may also lead to the overall loss of global context. The default
|
||||
value `None` resolves to the optimal value from the model config.
|
||||
match_input_resolution (`bool`, *optional*, defaults to `True`):
|
||||
When enabled, the output prediction is resized to match the input dimensions. When disabled, the longer
|
||||
side of the output will equal to `processing_resolution`.
|
||||
resample_method_input (`str`, *optional*, defaults to `"bilinear"`):
|
||||
Resampling method used to resize input images to `processing_resolution`. The accepted values are:
|
||||
`"nearest"`, `"nearest-exact"`, `"bilinear"`, `"bicubic"`, or `"area"`.
|
||||
resample_method_output (`str`, *optional*, defaults to `"bilinear"`):
|
||||
Resampling method used to resize output predictions to match the input resolution. The accepted values
|
||||
are `"nearest"`, `"nearest-exact"`, `"bilinear"`, `"bicubic"`, or `"area"`.
|
||||
batch_size (`int`, *optional*, defaults to `1`):
|
||||
Batch size; only matters when setting `ensemble_size` or passing a tensor of images.
|
||||
ensembling_kwargs (`dict`, *optional*, defaults to `None`)
|
||||
Extra dictionary with arguments for precise ensembling control. The following options are available:
|
||||
- reduction (`str`, *optional*, defaults to `"closest"`): Defines the ensembling function applied in
|
||||
every pixel location, can be either `"closest"` or `"mean"`.
|
||||
latents (`torch.Tensor`, *optional*, defaults to `None`):
|
||||
Latent noise tensors to replace the random initialization. These can be taken from the previous
|
||||
function call's output.
|
||||
generator (`torch.Generator`, or `List[torch.Generator]`, *optional*, defaults to `None`):
|
||||
Random number generator object to ensure reproducibility.
|
||||
output_type (`str`, *optional*, defaults to `"np"`):
|
||||
Preferred format of the output's `prediction` and the optional `uncertainty` fields. The accepted
|
||||
values are: `"np"` (numpy array) or `"pt"` (torch tensor).
|
||||
output_uncertainty (`bool`, *optional*, defaults to `False`):
|
||||
When enabled, the output's `uncertainty` field contains the predictive uncertainty map, provided that
|
||||
the `ensemble_size` argument is set to a value above 2.
|
||||
output_latent (`bool`, *optional*, defaults to `False`):
|
||||
When enabled, the output's `latent` field contains the latent codes corresponding to the predictions
|
||||
within the ensemble. These codes can be saved, modified, and used for subsequent calls with the
|
||||
`latents` argument.
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`~pipelines.marigold.MarigoldDepthOutput`] instead of a plain tuple.
|
||||
|
||||
Examples:
|
||||
|
||||
Returns:
|
||||
[`~pipelines.marigold.MarigoldNormalsOutput`] or `tuple`:
|
||||
If `return_dict` is `True`, [`~pipelines.marigold.MarigoldNormalsOutput`] is returned, otherwise a
|
||||
`tuple` is returned where the first element is the prediction, the second element is the uncertainty
|
||||
(or `None`), and the third is the latent (or `None`).
|
||||
"""
|
||||
|
||||
# 0. Resolving variables.
|
||||
device = self._execution_device
|
||||
dtype = self.dtype
|
||||
|
||||
# Model-specific optimal default values leading to fast and reasonable results.
|
||||
if num_inference_steps is None:
|
||||
num_inference_steps = self.default_denoising_steps
|
||||
if processing_resolution is None:
|
||||
processing_resolution = self.default_processing_resolution
|
||||
|
||||
# 1. Check inputs.
|
||||
num_images = self.check_inputs(
|
||||
image,
|
||||
num_inference_steps,
|
||||
ensemble_size,
|
||||
processing_resolution,
|
||||
resample_method_input,
|
||||
resample_method_output,
|
||||
batch_size,
|
||||
ensembling_kwargs,
|
||||
latents,
|
||||
generator,
|
||||
output_type,
|
||||
output_uncertainty,
|
||||
)
|
||||
|
||||
self.empty_text_embedding = torch.zeros(1, 257, 1024).to(device, dtype)
|
||||
|
||||
|
||||
# 4. Preprocess input images. This function loads input image or images of compatible dimensions `(H, W)`,
|
||||
# optionally downsamples them to the `processing_resolution` `(PH, PW)`, where
|
||||
# `max(PH, PW) == processing_resolution`, and pads the dimensions to `(PPH, PPW)` such that these values are
|
||||
# divisible by the latent space downscaling factor (typically 8 in Stable Diffusion). The default value `None`
|
||||
# of `processing_resolution` resolves to the optimal value from the model config. It is a recommended mode of
|
||||
# operation and leads to the most reasonable results. Using the native image resolution or any other processing
|
||||
# resolution can lead to loss of either fine details or global context in the output predictions.
|
||||
if not skip_preprocess:
|
||||
image, padding, original_resolution = self.image_processor.preprocess(
|
||||
image, processing_resolution, resample_method_input, device, dtype
|
||||
) # [N,3,PPH,PPW]
|
||||
else:
|
||||
padding = (0, 0)
|
||||
original_resolution = image.shape[2:]
|
||||
# 5. Encode input image into latent space. At this step, each of the `N` input images is represented with `E`
|
||||
# ensemble members. Each ensemble member is an independent diffused prediction, just initialized independently.
|
||||
# Latents of each such predictions across all input images and all ensemble members are represented in the
|
||||
# `pred_latent` variable. The variable `image_latent` is of the same shape: it contains each input image encoded
|
||||
# into latent space and replicated `E` times. The latents can be either generated (see `generator` to ensure
|
||||
# reproducibility), or passed explicitly via the `latents` argument. The latter can be set outside the pipeline
|
||||
# code. For example, in the Marigold-LCM video processing demo, the latents initialization of a frame is taken
|
||||
# as a convex combination of the latents output of the pipeline for the previous frame and a newly-sampled
|
||||
# noise. This behavior can be achieved by setting the `output_latent` argument to `True`. The latent space
|
||||
# dimensions are `(h, w)`. Encoding into latent space happens in batches of size `batch_size`.
|
||||
# Model invocation: self.vae.encoder.
|
||||
image_latent, pred_latent, gaus_noise = self.prepare_latents(
|
||||
image, latents, generator, ensemble_size, batch_size
|
||||
) # [N*E,4,h,w], [N*E,4,h,w]
|
||||
|
||||
del image
|
||||
|
||||
# 6. obtain control_output
|
||||
|
||||
cond_scale =controlnet_conditioning_scale
|
||||
down_block_res_samples, mid_block_res_sample = self.controlnet(
|
||||
image_latent.detach(),
|
||||
self.t_start,
|
||||
encoder_hidden_states=self.empty_text_embedding,
|
||||
conditioning_scale=cond_scale,
|
||||
guess_mode=False,
|
||||
return_dict=False,
|
||||
)
|
||||
|
||||
# 7. YOSO sampling
|
||||
latent_x_t = self.unet(
|
||||
pred_latent,
|
||||
self.t_start,
|
||||
encoder_hidden_states=self.empty_text_embedding,
|
||||
down_block_additional_residuals=down_block_res_samples,
|
||||
mid_block_additional_residual=mid_block_res_sample,
|
||||
return_dict=False,
|
||||
)[0]
|
||||
|
||||
|
||||
del (
|
||||
pred_latent,
|
||||
image_latent,
|
||||
)
|
||||
|
||||
# decoder
|
||||
prediction = self.decode_prediction(latent_x_t)
|
||||
prediction = self.image_processor.unpad_image(prediction, padding) # [N*E,3,PH,PW]
|
||||
|
||||
prediction = self.image_processor.resize_antialias(
|
||||
prediction, original_resolution, resample_method_output, is_aa=False
|
||||
) # [N,3,H,W]
|
||||
prediction = self.normalize_normals(prediction) # [N,3,H,W]
|
||||
|
||||
if output_type == "np":
|
||||
prediction = self.image_processor.pt_to_numpy(prediction) # [N,H,W,3]
|
||||
|
||||
# 11. Offload all models
|
||||
self.maybe_free_model_hooks()
|
||||
|
||||
return YosoOutput(
|
||||
prediction=prediction,
|
||||
latent=latent_x_t,
|
||||
gaus_noise=gaus_noise,
|
||||
)
|
||||
|
||||
# Copied from diffusers.pipelines.marigold.pipeline_marigold_depth.MarigoldDepthPipeline.prepare_latents
|
||||
def prepare_latents(
|
||||
self,
|
||||
image: torch.Tensor,
|
||||
latents: Optional[torch.Tensor],
|
||||
generator: Optional[torch.Generator],
|
||||
ensemble_size: int,
|
||||
batch_size: int,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
def retrieve_latents(encoder_output):
|
||||
if hasattr(encoder_output, "latent_dist"):
|
||||
return encoder_output.latent_dist.mode()
|
||||
elif hasattr(encoder_output, "latents"):
|
||||
return encoder_output.latents
|
||||
else:
|
||||
raise AttributeError("Could not access latents of provided encoder_output")
|
||||
|
||||
image_latent = torch.cat(
|
||||
[
|
||||
retrieve_latents(self.vae.encode(image[i : i + batch_size]))
|
||||
for i in range(0, image.shape[0], batch_size)
|
||||
],
|
||||
dim=0,
|
||||
) # [N,4,h,w]
|
||||
image_latent = image_latent * self.vae.config.scaling_factor
|
||||
image_latent = image_latent.repeat_interleave(ensemble_size, dim=0) # [N*E,4,h,w]
|
||||
gaus_noise = torch.randn_like(image_latent)
|
||||
pred_latent = image_latent
|
||||
return image_latent, pred_latent, gaus_noise
|
||||
|
||||
def decode_prediction(self, pred_latent: torch.Tensor) -> torch.Tensor:
|
||||
if pred_latent.dim() != 4 or pred_latent.shape[1] != self.vae.config.latent_channels:
|
||||
raise ValueError(
|
||||
f"Expecting 4D tensor of shape [B,{self.vae.config.latent_channels},H,W]; got {pred_latent.shape}."
|
||||
)
|
||||
|
||||
prediction = self.vae.decode(pred_latent / self.vae.config.scaling_factor, return_dict=False)[0] # [B,3,H,W]
|
||||
|
||||
prediction = self.normalize_normals(prediction) # [B,3,H,W]
|
||||
|
||||
return prediction # [B,3,H,W]
|
||||
|
||||
@staticmethod
|
||||
def normalize_normals(normals: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
|
||||
if normals.dim() != 4 or normals.shape[1] != 3:
|
||||
raise ValueError(f"Expecting 4D tensor of shape [B,3,H,W]; got {normals.shape}.")
|
||||
|
||||
norm = torch.norm(normals, dim=1, keepdim=True)
|
||||
normals /= norm.clamp(min=eps)
|
||||
|
||||
return normals
|
||||
@@ -0,0 +1,5 @@
|
||||
from . import models
|
||||
from . import modules
|
||||
from . import pipelines
|
||||
from . import representations
|
||||
from . import utils
|
||||
@@ -0,0 +1,225 @@
|
||||
# trellis/backend_config.py
|
||||
from typing import *
|
||||
import os
|
||||
import logging
|
||||
import importlib
|
||||
|
||||
# Global variables
|
||||
BACKEND = 'spconv' # Default sparse backend
|
||||
DEBUG = False # Debug mode flag
|
||||
ATTN = 'xformers' # Default attention backend
|
||||
SPCONV_ALGO = 'implicit_gemm' # Default algorithm
|
||||
|
||||
def get_spconv_algo() -> str:
|
||||
"""Get current spconv algorithm."""
|
||||
global SPCONV_ALGO
|
||||
return SPCONV_ALGO
|
||||
|
||||
def set_spconv_algo(algo: Literal['implicit_gemm', 'native', 'auto']) -> bool:
|
||||
"""Set spconv algorithm with validation."""
|
||||
global SPCONV_ALGO
|
||||
|
||||
if algo not in ['implicit_gemm', 'native', 'auto']:
|
||||
logger.warning(f"Invalid spconv algorithm: {algo}. Must be 'implicit_gemm', 'native', or 'auto'")
|
||||
return False
|
||||
|
||||
SPCONV_ALGO = algo
|
||||
os.environ['SPCONV_ALGO'] = algo
|
||||
logger.info(f"Set spconv algorithm to: {algo}")
|
||||
return True
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def _try_import_xformers() -> bool:
|
||||
try:
|
||||
import xformers.ops
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def _try_import_flash_attn() -> bool:
|
||||
try:
|
||||
import flash_attn
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def _try_import_sageattention() -> bool:
|
||||
try:
|
||||
import torch.nn.functional as F
|
||||
from sageattention import sageattn
|
||||
F.scaled_dot_product_attention = sageattn
|
||||
#import sageattention
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def _try_import_spconv() -> bool:
|
||||
try:
|
||||
import spconv
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def _try_import_torchsparse() -> bool:
|
||||
try:
|
||||
import torchsparse
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def get_available_backends() -> Dict[str, bool]:
|
||||
"""Return dict of available attention backends and their status"""
|
||||
return {
|
||||
'xformers': _try_import_xformers(),
|
||||
'flash_attn': _try_import_flash_attn(),
|
||||
'sage': _try_import_sageattention(),
|
||||
'naive': True,
|
||||
'sdpa': True # Always available with PyTorch >= 2.0
|
||||
}
|
||||
|
||||
def get_available_sparse_backends() -> Dict[str, bool]:
|
||||
"""Return dict of available sparse backends and their status"""
|
||||
return {
|
||||
'spconv': _try_import_spconv(),
|
||||
'torchsparse': _try_import_torchsparse()
|
||||
}
|
||||
|
||||
def get_attention_backend() -> str:
|
||||
"""Get current attention backend"""
|
||||
global ATTN
|
||||
return ATTN
|
||||
|
||||
def get_sparse_backend() -> str:
|
||||
"""Get current sparse backend"""
|
||||
global BACKEND
|
||||
return BACKEND
|
||||
|
||||
def get_debug_mode() -> bool:
|
||||
"""Get current debug mode status"""
|
||||
global DEBUG
|
||||
return DEBUG
|
||||
|
||||
def __from_env():
|
||||
"""Initialize settings from environment variables"""
|
||||
global BACKEND
|
||||
global DEBUG
|
||||
global ATTN
|
||||
|
||||
env_sparse_backend = os.environ.get('SPARSE_BACKEND')
|
||||
env_sparse_debug = os.environ.get('SPARSE_DEBUG')
|
||||
env_sparse_attn = os.environ.get('SPARSE_ATTN_BACKEND')
|
||||
|
||||
if env_sparse_backend is not None and env_sparse_backend in ['spconv', 'torchsparse']:
|
||||
BACKEND = env_sparse_backend
|
||||
if env_sparse_debug is not None:
|
||||
DEBUG = env_sparse_debug == '1'
|
||||
if env_sparse_attn is not None and env_sparse_attn in ['xformers', 'flash_attn', 'sage', 'sdpa', 'naive']:
|
||||
ATTN = env_sparse_attn
|
||||
os.environ['SPARSE_ATTN_BACKEND'] = env_sparse_attn
|
||||
os.environ['ATTN_BACKEND'] = env_sparse_attn
|
||||
|
||||
logger.info(f"[SPARSE] Backend: {BACKEND}, Attention: {ATTN}")
|
||||
|
||||
def set_backend(backend: Literal['spconv', 'torchsparse']) -> bool:
|
||||
"""Set sparse backend with validation"""
|
||||
global BACKEND
|
||||
|
||||
backend = backend.lower().strip()
|
||||
logger.info(f"Setting sparse backend to: {backend}")
|
||||
|
||||
if backend == 'spconv':
|
||||
try:
|
||||
import spconv
|
||||
BACKEND = 'spconv'
|
||||
os.environ['SPARSE_BACKEND'] = 'spconv'
|
||||
return True
|
||||
except ImportError:
|
||||
logger.warning("spconv not available")
|
||||
return False
|
||||
|
||||
elif backend == 'torchsparse':
|
||||
try:
|
||||
import torchsparse
|
||||
BACKEND = 'torchsparse'
|
||||
os.environ['SPARSE_BACKEND'] = 'torchsparse'
|
||||
return True
|
||||
except ImportError:
|
||||
logger.warning("torchsparse not available")
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def set_sparse_backend(backend: Literal['spconv', 'torchsparse'], algo: str = None) -> bool:
|
||||
"""Alias for set_backend for backwards compatibility
|
||||
|
||||
Parameters:
|
||||
backend: The sparse backend to use
|
||||
algo: The algorithm to use (only relevant for spconv backend)
|
||||
"""
|
||||
# Call set_backend first
|
||||
result = set_backend(backend)
|
||||
|
||||
# If algorithm is provided and backend was set successfully
|
||||
if algo is not None and result:
|
||||
set_spconv_algo(algo)
|
||||
|
||||
return result
|
||||
|
||||
def set_debug(debug: bool):
|
||||
"""Set debug mode"""
|
||||
global DEBUG
|
||||
DEBUG = debug
|
||||
if debug:
|
||||
os.environ['SPARSE_DEBUG'] = '1'
|
||||
else:
|
||||
os.environ['SPARSE_DEBUG'] = '0'
|
||||
|
||||
def set_attn(attn: Literal['xformers', 'flash_attn', 'sage', 'sdpa', 'naive']) -> bool:
|
||||
"""Set attention backend with validation"""
|
||||
global ATTN
|
||||
|
||||
attn = attn.lower().strip()
|
||||
logger.info(f"Setting attention backend to: {attn}")
|
||||
|
||||
if attn == 'xformers' and _try_import_xformers():
|
||||
ATTN = 'xformers'
|
||||
os.environ['SPARSE_ATTN_BACKEND'] = 'xformers'
|
||||
os.environ['ATTN_BACKEND'] = 'xformers'
|
||||
return True
|
||||
|
||||
elif attn == 'flash_attn' and _try_import_flash_attn():
|
||||
ATTN = 'flash_attn'
|
||||
os.environ['SPARSE_ATTN_BACKEND'] = 'flash_attn'
|
||||
os.environ['ATTN_BACKEND'] = 'flash_attn'
|
||||
return True
|
||||
|
||||
elif attn == 'sage' and _try_import_sageattention():
|
||||
ATTN = 'sage'
|
||||
os.environ['SPARSE_ATTN_BACKEND'] = 'sage'
|
||||
os.environ['ATTN_BACKEND'] = 'sage'
|
||||
return True
|
||||
|
||||
elif attn == 'sdpa':
|
||||
ATTN = 'sdpa'
|
||||
os.environ['SPARSE_ATTN_BACKEND'] = 'sdpa'
|
||||
os.environ['ATTN_BACKEND'] = 'sdpa'
|
||||
return True
|
||||
|
||||
elif attn == 'naive':
|
||||
ATTN = 'naive'
|
||||
os.environ['SPARSE_ATTN_BACKEND'] = 'naive'
|
||||
os.environ['ATTN_BACKEND'] = 'naive'
|
||||
return True
|
||||
|
||||
|
||||
logger.warning(f"Attention backend {attn} not available")
|
||||
return False
|
||||
|
||||
# Add alias for backwards compatibility
|
||||
def set_attention_backend(backend: Literal['xformers', 'flash_attn', 'sage', 'sdpa']) -> bool:
|
||||
"""Alias for set_attn for backwards compatibility"""
|
||||
return set_attn(backend)
|
||||
|
||||
# Initialize from environment variables on module import
|
||||
__from_env()
|
||||
@@ -0,0 +1,112 @@
|
||||
import importlib
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
__attributes = {
|
||||
'SparseStructureEncoder': 'sparse_structure_vae',
|
||||
'SparseStructureDecoder': 'sparse_structure_vae',
|
||||
'SparseStructureFlowModel': 'sparse_structure_flow',
|
||||
'SLatEncoder': 'structured_latent_vae',
|
||||
'SLatGaussianDecoder': 'structured_latent_vae',
|
||||
'SLatRadianceFieldDecoder': 'structured_latent_vae',
|
||||
'SLatMeshDecoder': 'structured_latent_vae',
|
||||
'SLatFlowModel': 'structured_latent_flow',
|
||||
}
|
||||
|
||||
__submodules = []
|
||||
|
||||
__all__ = list(__attributes.keys()) + __submodules
|
||||
|
||||
def __getattr__(name):
|
||||
if name not in globals():
|
||||
if name in __attributes:
|
||||
module_name = __attributes[name]
|
||||
module = importlib.import_module(f".{module_name}", __name__)
|
||||
globals()[name] = getattr(module, name)
|
||||
elif name in __submodules:
|
||||
module = importlib.import_module(f".{name}", __name__)
|
||||
globals()[name] = module
|
||||
else:
|
||||
raise AttributeError(f"module {__name__} has no attribute {name}")
|
||||
return globals()[name]
|
||||
|
||||
|
||||
def from_pretrained(path: str) -> nn.Module:
|
||||
"""
|
||||
Load a pretrained model.
|
||||
|
||||
Args:
|
||||
path (str): Full path to model file or HuggingFace repo ID with model name
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
from safetensors.torch import load_file
|
||||
|
||||
# Split path into directory and model name
|
||||
path = os.path.normpath(path)
|
||||
model_dir = os.path.dirname(os.path.dirname(path)) # Go up two levels (past ckpts/)
|
||||
model_name = os.path.basename(path)
|
||||
|
||||
is_local = os.path.exists(model_dir)
|
||||
|
||||
if is_local:
|
||||
# For local paths
|
||||
print(f"Loading local model: {model_name}")
|
||||
model_name = model_name.replace('ckpts/', '').replace('ckpts\\', '')
|
||||
config_path = os.path.normpath(os.path.join(model_dir, "ckpts", f"{model_name}.json"))
|
||||
weights_path = os.path.normpath(os.path.join(model_dir, "ckpts", f"{model_name}.safetensors"))
|
||||
|
||||
if not os.path.exists(config_path):
|
||||
raise FileNotFoundError(f"Config file not found: {config_path}")
|
||||
if not os.path.exists(weights_path):
|
||||
raise FileNotFoundError(f"Weights file not found: {weights_path}")
|
||||
|
||||
# Load config
|
||||
with open(config_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# Create model
|
||||
model = create_model_from_config(config)
|
||||
|
||||
# Load weights
|
||||
state_dict = load_file(weights_path)
|
||||
model.load_state_dict(state_dict)
|
||||
|
||||
else:
|
||||
# For HuggingFace paths
|
||||
from huggingface_hub import hf_hub_download
|
||||
|
||||
config_file = hf_hub_download(path, f"{model_name}.json")
|
||||
with open(config_file, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
model = create_model_from_config(config)
|
||||
|
||||
weights_file = hf_hub_download(path, f"{model_name}.safetensors")
|
||||
state_dict = load_file(weights_file)
|
||||
model.load_state_dict(state_dict)
|
||||
|
||||
return model
|
||||
|
||||
def create_model_from_config(config):
|
||||
"""Helper function to create model from config"""
|
||||
#print(f"Creating model from config: {config}")
|
||||
model_type = config.get('type') or config.get('name')
|
||||
#print(f"Model type: {model_type}")
|
||||
#print(f"Available model types: {list(__attributes.keys())}")
|
||||
if not model_type in __attributes:
|
||||
raise ValueError(f"Unknown model type: {model_type}")
|
||||
|
||||
model_class = __getattr__(model_type)
|
||||
#print(f"Model class: {model_class}")
|
||||
args = config.get('args', {})
|
||||
#print(f"Model args: {args}")
|
||||
return model_class(**args)
|
||||
|
||||
|
||||
# For Pylance
|
||||
if __name__ == '__main__':
|
||||
from .sparse_structure_vae import SparseStructureEncoder, SparseStructureDecoder
|
||||
from .sparse_structure_flow import SparseStructureFlowModel
|
||||
from .structured_latent_vae import SLatEncoder, SLatGaussianDecoder, SLatRadianceFieldDecoder, SLatMeshDecoder
|
||||
from .structured_latent_flow import SLatFlowModel
|
||||
@@ -0,0 +1,200 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import numpy as np
|
||||
from ..modules.utils import convert_module_to_f16, convert_module_to_f32
|
||||
from ..modules.transformer import AbsolutePositionEmbedder, ModulatedTransformerCrossBlock
|
||||
from ..modules.spatial import patchify, unpatchify
|
||||
|
||||
|
||||
class TimestepEmbedder(nn.Module):
|
||||
"""
|
||||
Embeds scalar timesteps into vector representations.
|
||||
"""
|
||||
def __init__(self, hidden_size, frequency_embedding_size=256):
|
||||
super().__init__()
|
||||
self.mlp = nn.Sequential(
|
||||
nn.Linear(frequency_embedding_size, hidden_size, bias=True),
|
||||
nn.SiLU(),
|
||||
nn.Linear(hidden_size, hidden_size, bias=True),
|
||||
)
|
||||
self.frequency_embedding_size = frequency_embedding_size
|
||||
|
||||
@staticmethod
|
||||
def timestep_embedding(t, dim, max_period=10000):
|
||||
"""
|
||||
Create sinusoidal timestep embeddings.
|
||||
|
||||
Args:
|
||||
t: a 1-D Tensor of N indices, one per batch element.
|
||||
These may be fractional.
|
||||
dim: the dimension of the output.
|
||||
max_period: controls the minimum frequency of the embeddings.
|
||||
|
||||
Returns:
|
||||
an (N, D) Tensor of positional embeddings.
|
||||
"""
|
||||
# https://github.com/openai/glide-text2im/blob/main/glide_text2im/nn.py
|
||||
half = dim // 2
|
||||
freqs = torch.exp(
|
||||
-np.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half
|
||||
).to(device=t.device)
|
||||
args = t[:, None].float() * freqs[None]
|
||||
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
|
||||
if dim % 2:
|
||||
embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
|
||||
return embedding
|
||||
|
||||
def forward(self, t):
|
||||
t_freq = self.timestep_embedding(t, self.frequency_embedding_size)
|
||||
t_emb = self.mlp(t_freq)
|
||||
return t_emb
|
||||
|
||||
|
||||
class SparseStructureFlowModel(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
resolution: int,
|
||||
in_channels: int,
|
||||
model_channels: int,
|
||||
cond_channels: int,
|
||||
out_channels: int,
|
||||
num_blocks: int,
|
||||
num_heads: Optional[int] = None,
|
||||
num_head_channels: Optional[int] = 64,
|
||||
mlp_ratio: float = 4,
|
||||
patch_size: int = 2,
|
||||
pe_mode: Literal["ape", "rope"] = "ape",
|
||||
use_fp16: bool = False,
|
||||
use_checkpoint: bool = False,
|
||||
share_mod: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
qk_rms_norm_cross: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.resolution = resolution
|
||||
self.in_channels = in_channels
|
||||
self.model_channels = model_channels
|
||||
self.cond_channels = cond_channels
|
||||
self.out_channels = out_channels
|
||||
self.num_blocks = num_blocks
|
||||
self.num_heads = num_heads or model_channels // num_head_channels
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.patch_size = patch_size
|
||||
self.pe_mode = pe_mode
|
||||
self.use_fp16 = use_fp16
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.share_mod = share_mod
|
||||
self.qk_rms_norm = qk_rms_norm
|
||||
self.qk_rms_norm_cross = qk_rms_norm_cross
|
||||
self.dtype = torch.float16 if use_fp16 else torch.float32
|
||||
|
||||
self.t_embedder = TimestepEmbedder(model_channels)
|
||||
if share_mod:
|
||||
self.adaLN_modulation = nn.Sequential(
|
||||
nn.SiLU(),
|
||||
nn.Linear(model_channels, 6 * model_channels, bias=True)
|
||||
)
|
||||
|
||||
if pe_mode == "ape":
|
||||
pos_embedder = AbsolutePositionEmbedder(model_channels, 3)
|
||||
coords = torch.meshgrid(*[torch.arange(res, device=self.device) for res in [resolution // patch_size] * 3], indexing='ij')
|
||||
coords = torch.stack(coords, dim=-1).reshape(-1, 3)
|
||||
pos_emb = pos_embedder(coords)
|
||||
self.register_buffer("pos_emb", pos_emb)
|
||||
|
||||
self.input_layer = nn.Linear(in_channels * patch_size**3, model_channels)
|
||||
|
||||
self.blocks = nn.ModuleList([
|
||||
ModulatedTransformerCrossBlock(
|
||||
model_channels,
|
||||
cond_channels,
|
||||
num_heads=self.num_heads,
|
||||
mlp_ratio=self.mlp_ratio,
|
||||
attn_mode='full',
|
||||
use_checkpoint=self.use_checkpoint,
|
||||
use_rope=(pe_mode == "rope"),
|
||||
share_mod=share_mod,
|
||||
qk_rms_norm=self.qk_rms_norm,
|
||||
qk_rms_norm_cross=self.qk_rms_norm_cross,
|
||||
)
|
||||
for _ in range(num_blocks)
|
||||
])
|
||||
|
||||
self.out_layer = nn.Linear(model_channels, out_channels * patch_size**3)
|
||||
|
||||
self.initialize_weights()
|
||||
if use_fp16:
|
||||
self.convert_to_fp16()
|
||||
|
||||
@property
|
||||
def device(self) -> torch.device:
|
||||
"""
|
||||
Return the device of the model.
|
||||
"""
|
||||
return next(self.parameters()).device
|
||||
|
||||
def convert_to_fp16(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float16.
|
||||
"""
|
||||
self.blocks.apply(convert_module_to_f16)
|
||||
|
||||
def convert_to_fp32(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float32.
|
||||
"""
|
||||
self.blocks.apply(convert_module_to_f32)
|
||||
|
||||
def initialize_weights(self) -> None:
|
||||
# Initialize transformer layers:
|
||||
def _basic_init(module):
|
||||
if isinstance(module, nn.Linear):
|
||||
torch.nn.init.xavier_uniform_(module.weight)
|
||||
if module.bias is not None:
|
||||
nn.init.constant_(module.bias, 0)
|
||||
self.apply(_basic_init)
|
||||
|
||||
# Initialize timestep embedding MLP:
|
||||
nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
|
||||
nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
|
||||
|
||||
# Zero-out adaLN modulation layers in DiT blocks:
|
||||
if self.share_mod:
|
||||
nn.init.constant_(self.adaLN_modulation[-1].weight, 0)
|
||||
nn.init.constant_(self.adaLN_modulation[-1].bias, 0)
|
||||
else:
|
||||
for block in self.blocks:
|
||||
nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
|
||||
nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
|
||||
|
||||
# Zero-out output layers:
|
||||
nn.init.constant_(self.out_layer.weight, 0)
|
||||
nn.init.constant_(self.out_layer.bias, 0)
|
||||
|
||||
def forward(self, x: torch.Tensor, t: torch.Tensor, cond: torch.Tensor) -> torch.Tensor:
|
||||
assert [*x.shape] == [x.shape[0], self.in_channels, *[self.resolution] * 3], \
|
||||
f"Input shape mismatch, got {x.shape}, expected {[x.shape[0], self.in_channels, *[self.resolution] * 3]}"
|
||||
|
||||
h = patchify(x, self.patch_size)
|
||||
h = h.view(*h.shape[:2], -1).permute(0, 2, 1).contiguous()
|
||||
|
||||
h = self.input_layer(h)
|
||||
h = h + self.pos_emb[None]
|
||||
t_emb = self.t_embedder(t)
|
||||
if self.share_mod:
|
||||
t_emb = self.adaLN_modulation(t_emb)
|
||||
t_emb = t_emb.type(self.dtype)
|
||||
h = h.type(self.dtype)
|
||||
cond = cond.type(self.dtype)
|
||||
for block in self.blocks:
|
||||
h = block(h, t_emb, cond)
|
||||
h = h.type(x.dtype)
|
||||
h = F.layer_norm(h, h.shape[-1:])
|
||||
h = self.out_layer(h)
|
||||
|
||||
h = h.permute(0, 2, 1).view(h.shape[0], h.shape[2], *[self.resolution // self.patch_size] * 3)
|
||||
h = unpatchify(h, self.patch_size).contiguous()
|
||||
|
||||
return h
|
||||
@@ -0,0 +1,306 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from ..modules.norm import GroupNorm32, ChannelLayerNorm32
|
||||
from ..modules.spatial import pixel_shuffle_3d
|
||||
from ..modules.utils import zero_module, convert_module_to_f16, convert_module_to_f32
|
||||
|
||||
|
||||
def norm_layer(norm_type: str, *args, **kwargs) -> nn.Module:
|
||||
"""
|
||||
Return a normalization layer.
|
||||
"""
|
||||
if norm_type == "group":
|
||||
return GroupNorm32(32, *args, **kwargs)
|
||||
elif norm_type == "layer":
|
||||
return ChannelLayerNorm32(*args, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Invalid norm type {norm_type}")
|
||||
|
||||
|
||||
class ResBlock3d(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
out_channels: Optional[int] = None,
|
||||
norm_type: Literal["group", "layer"] = "layer",
|
||||
):
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.out_channels = out_channels or channels
|
||||
|
||||
self.norm1 = norm_layer(norm_type, channels)
|
||||
self.norm2 = norm_layer(norm_type, self.out_channels)
|
||||
self.conv1 = nn.Conv3d(channels, self.out_channels, 3, padding=1)
|
||||
self.conv2 = zero_module(nn.Conv3d(self.out_channels, self.out_channels, 3, padding=1))
|
||||
self.skip_connection = nn.Conv3d(channels, self.out_channels, 1) if channels != self.out_channels else nn.Identity()
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
h = self.norm1(x)
|
||||
h = F.silu(h)
|
||||
h = self.conv1(h)
|
||||
h = self.norm2(h)
|
||||
h = F.silu(h)
|
||||
h = self.conv2(h)
|
||||
h = h + self.skip_connection(x)
|
||||
return h
|
||||
|
||||
|
||||
class DownsampleBlock3d(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
mode: Literal["conv", "avgpool"] = "conv",
|
||||
):
|
||||
assert mode in ["conv", "avgpool"], f"Invalid mode {mode}"
|
||||
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
|
||||
if mode == "conv":
|
||||
self.conv = nn.Conv3d(in_channels, out_channels, 2, stride=2)
|
||||
elif mode == "avgpool":
|
||||
assert in_channels == out_channels, "Pooling mode requires in_channels to be equal to out_channels"
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
if hasattr(self, "conv"):
|
||||
return self.conv(x)
|
||||
else:
|
||||
return F.avg_pool3d(x, 2)
|
||||
|
||||
|
||||
class UpsampleBlock3d(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
out_channels: int,
|
||||
mode: Literal["conv", "nearest"] = "conv",
|
||||
):
|
||||
assert mode in ["conv", "nearest"], f"Invalid mode {mode}"
|
||||
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
|
||||
if mode == "conv":
|
||||
self.conv = nn.Conv3d(in_channels, out_channels*8, 3, padding=1)
|
||||
elif mode == "nearest":
|
||||
assert in_channels == out_channels, "Nearest mode requires in_channels to be equal to out_channels"
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
if hasattr(self, "conv"):
|
||||
x = self.conv(x)
|
||||
return pixel_shuffle_3d(x, 2)
|
||||
else:
|
||||
return F.interpolate(x, scale_factor=2, mode="nearest")
|
||||
|
||||
|
||||
class SparseStructureEncoder(nn.Module):
|
||||
"""
|
||||
Encoder for Sparse Structure (\mathcal{E}_S in the paper Sec. 3.3).
|
||||
|
||||
Args:
|
||||
in_channels (int): Channels of the input.
|
||||
latent_channels (int): Channels of the latent representation.
|
||||
num_res_blocks (int): Number of residual blocks at each resolution.
|
||||
channels (List[int]): Channels of the encoder blocks.
|
||||
num_res_blocks_middle (int): Number of residual blocks in the middle.
|
||||
norm_type (Literal["group", "layer"]): Type of normalization layer.
|
||||
use_fp16 (bool): Whether to use FP16.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
latent_channels: int,
|
||||
num_res_blocks: int,
|
||||
channels: List[int],
|
||||
num_res_blocks_middle: int = 2,
|
||||
norm_type: Literal["group", "layer"] = "layer",
|
||||
use_fp16: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
self.latent_channels = latent_channels
|
||||
self.num_res_blocks = num_res_blocks
|
||||
self.channels = channels
|
||||
self.num_res_blocks_middle = num_res_blocks_middle
|
||||
self.norm_type = norm_type
|
||||
self.use_fp16 = use_fp16
|
||||
self.dtype = torch.float16 if use_fp16 else torch.float32
|
||||
|
||||
self.input_layer = nn.Conv3d(in_channels, channels[0], 3, padding=1)
|
||||
|
||||
self.blocks = nn.ModuleList([])
|
||||
for i, ch in enumerate(channels):
|
||||
self.blocks.extend([
|
||||
ResBlock3d(ch, ch)
|
||||
for _ in range(num_res_blocks)
|
||||
])
|
||||
if i < len(channels) - 1:
|
||||
self.blocks.append(
|
||||
DownsampleBlock3d(ch, channels[i+1])
|
||||
)
|
||||
|
||||
self.middle_block = nn.Sequential(*[
|
||||
ResBlock3d(channels[-1], channels[-1])
|
||||
for _ in range(num_res_blocks_middle)
|
||||
])
|
||||
|
||||
self.out_layer = nn.Sequential(
|
||||
norm_layer(norm_type, channels[-1]),
|
||||
nn.SiLU(),
|
||||
nn.Conv3d(channels[-1], latent_channels*2, 3, padding=1)
|
||||
)
|
||||
|
||||
if use_fp16:
|
||||
self.convert_to_fp16()
|
||||
|
||||
@property
|
||||
def device(self) -> torch.device:
|
||||
"""
|
||||
Return the device of the model.
|
||||
"""
|
||||
return next(self.parameters()).device
|
||||
|
||||
def convert_to_fp16(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float16.
|
||||
"""
|
||||
self.use_fp16 = True
|
||||
self.dtype = torch.float16
|
||||
self.blocks.apply(convert_module_to_f16)
|
||||
self.middle_block.apply(convert_module_to_f16)
|
||||
|
||||
def convert_to_fp32(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float32.
|
||||
"""
|
||||
self.use_fp16 = False
|
||||
self.dtype = torch.float32
|
||||
self.blocks.apply(convert_module_to_f32)
|
||||
self.middle_block.apply(convert_module_to_f32)
|
||||
|
||||
def forward(self, x: torch.Tensor, sample_posterior: bool = False, return_raw: bool = False) -> torch.Tensor:
|
||||
h = self.input_layer(x)
|
||||
h = h.type(self.dtype)
|
||||
|
||||
for block in self.blocks:
|
||||
h = block(h)
|
||||
h = self.middle_block(h)
|
||||
|
||||
h = h.type(x.dtype)
|
||||
h = self.out_layer(h)
|
||||
|
||||
mean, logvar = h.chunk(2, dim=1)
|
||||
|
||||
if sample_posterior:
|
||||
std = torch.exp(0.5 * logvar)
|
||||
z = mean + std * torch.randn_like(std)
|
||||
else:
|
||||
z = mean
|
||||
|
||||
if return_raw:
|
||||
return z, mean, logvar
|
||||
return z
|
||||
|
||||
|
||||
class SparseStructureDecoder(nn.Module):
|
||||
"""
|
||||
Decoder for Sparse Structure (\mathcal{D}_S in the paper Sec. 3.3).
|
||||
|
||||
Args:
|
||||
out_channels (int): Channels of the output.
|
||||
latent_channels (int): Channels of the latent representation.
|
||||
num_res_blocks (int): Number of residual blocks at each resolution.
|
||||
channels (List[int]): Channels of the decoder blocks.
|
||||
num_res_blocks_middle (int): Number of residual blocks in the middle.
|
||||
norm_type (Literal["group", "layer"]): Type of normalization layer.
|
||||
use_fp16 (bool): Whether to use FP16.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
out_channels: int,
|
||||
latent_channels: int,
|
||||
num_res_blocks: int,
|
||||
channels: List[int],
|
||||
num_res_blocks_middle: int = 2,
|
||||
norm_type: Literal["group", "layer"] = "layer",
|
||||
use_fp16: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.out_channels = out_channels
|
||||
self.latent_channels = latent_channels
|
||||
self.num_res_blocks = num_res_blocks
|
||||
self.channels = channels
|
||||
self.num_res_blocks_middle = num_res_blocks_middle
|
||||
self.norm_type = norm_type
|
||||
self.use_fp16 = use_fp16
|
||||
self.dtype = torch.float16 if use_fp16 else torch.float32
|
||||
|
||||
self.input_layer = nn.Conv3d(latent_channels, channels[0], 3, padding=1)
|
||||
|
||||
self.middle_block = nn.Sequential(*[
|
||||
ResBlock3d(channels[0], channels[0])
|
||||
for _ in range(num_res_blocks_middle)
|
||||
])
|
||||
|
||||
self.blocks = nn.ModuleList([])
|
||||
for i, ch in enumerate(channels):
|
||||
self.blocks.extend([
|
||||
ResBlock3d(ch, ch)
|
||||
for _ in range(num_res_blocks)
|
||||
])
|
||||
if i < len(channels) - 1:
|
||||
self.blocks.append(
|
||||
UpsampleBlock3d(ch, channels[i+1])
|
||||
)
|
||||
|
||||
self.out_layer = nn.Sequential(
|
||||
norm_layer(norm_type, channels[-1]),
|
||||
nn.SiLU(),
|
||||
nn.Conv3d(channels[-1], out_channels, 3, padding=1)
|
||||
)
|
||||
|
||||
if use_fp16:
|
||||
self.convert_to_fp16()
|
||||
|
||||
@property
|
||||
def device(self) -> torch.device:
|
||||
"""
|
||||
Return the device of the model.
|
||||
"""
|
||||
return next(self.parameters()).device
|
||||
|
||||
def convert_to_fp16(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float16.
|
||||
"""
|
||||
self.use_fp16 = True
|
||||
self.dtype = torch.float16
|
||||
self.blocks.apply(convert_module_to_f16)
|
||||
self.middle_block.apply(convert_module_to_f16)
|
||||
|
||||
def convert_to_fp32(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float32.
|
||||
"""
|
||||
self.use_fp16 = False
|
||||
self.dtype = torch.float32
|
||||
self.blocks.apply(convert_module_to_f32)
|
||||
self.middle_block.apply(convert_module_to_f32)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
h = self.input_layer(x)
|
||||
|
||||
h = h.type(self.dtype)
|
||||
|
||||
h = self.middle_block(h)
|
||||
for block in self.blocks:
|
||||
h = block(h)
|
||||
|
||||
h = h.type(x.dtype)
|
||||
h = self.out_layer(h)
|
||||
return h
|
||||
@@ -0,0 +1,262 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import numpy as np
|
||||
from ..modules.utils import zero_module, convert_module_to_f16, convert_module_to_f32
|
||||
from ..modules.transformer import AbsolutePositionEmbedder
|
||||
from ..modules.norm import LayerNorm32
|
||||
from ..modules import sparse as sp
|
||||
from ..modules.sparse.transformer import ModulatedSparseTransformerCrossBlock
|
||||
from .sparse_structure_flow import TimestepEmbedder
|
||||
|
||||
|
||||
class SparseResBlock3d(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
emb_channels: int,
|
||||
out_channels: Optional[int] = None,
|
||||
downsample: bool = False,
|
||||
upsample: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.emb_channels = emb_channels
|
||||
self.out_channels = out_channels or channels
|
||||
self.downsample = downsample
|
||||
self.upsample = upsample
|
||||
|
||||
assert not (downsample and upsample), "Cannot downsample and upsample at the same time"
|
||||
|
||||
self.norm1 = LayerNorm32(channels, elementwise_affine=True, eps=1e-6)
|
||||
self.norm2 = LayerNorm32(self.out_channels, elementwise_affine=False, eps=1e-6)
|
||||
self.conv1 = sp.SparseConv3d(channels, self.out_channels, 3)
|
||||
self.conv2 = zero_module(sp.SparseConv3d(self.out_channels, self.out_channels, 3))
|
||||
self.emb_layers = nn.Sequential(
|
||||
nn.SiLU(),
|
||||
nn.Linear(emb_channels, 2 * self.out_channels, bias=True),
|
||||
)
|
||||
self.skip_connection = sp.SparseLinear(channels, self.out_channels) if channels != self.out_channels else nn.Identity()
|
||||
self.updown = None
|
||||
if self.downsample:
|
||||
self.updown = sp.SparseDownsample(2)
|
||||
elif self.upsample:
|
||||
self.updown = sp.SparseUpsample(2)
|
||||
|
||||
def _updown(self, x: sp.SparseTensor) -> sp.SparseTensor:
|
||||
if self.updown is not None:
|
||||
x = self.updown(x)
|
||||
return x
|
||||
|
||||
def forward(self, x: sp.SparseTensor, emb: torch.Tensor) -> sp.SparseTensor:
|
||||
emb_out = self.emb_layers(emb).type(x.dtype)
|
||||
scale, shift = torch.chunk(emb_out, 2, dim=1)
|
||||
|
||||
x = self._updown(x)
|
||||
h = x.replace(self.norm1(x.feats))
|
||||
h = h.replace(F.silu(h.feats))
|
||||
h = self.conv1(h)
|
||||
h = h.replace(self.norm2(h.feats)) * (1 + scale) + shift
|
||||
h = h.replace(F.silu(h.feats))
|
||||
h = self.conv2(h)
|
||||
h = h + self.skip_connection(x)
|
||||
|
||||
return h
|
||||
|
||||
|
||||
class SLatFlowModel(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
resolution: int,
|
||||
in_channels: int,
|
||||
model_channels: int,
|
||||
cond_channels: int,
|
||||
out_channels: int,
|
||||
num_blocks: int,
|
||||
num_heads: Optional[int] = None,
|
||||
num_head_channels: Optional[int] = 64,
|
||||
mlp_ratio: float = 4,
|
||||
patch_size: int = 2,
|
||||
num_io_res_blocks: int = 2,
|
||||
io_block_channels: List[int] = None,
|
||||
pe_mode: Literal["ape", "rope"] = "ape",
|
||||
use_fp16: bool = False,
|
||||
use_checkpoint: bool = False,
|
||||
use_skip_connection: bool = True,
|
||||
share_mod: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
qk_rms_norm_cross: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.resolution = resolution
|
||||
self.in_channels = in_channels
|
||||
self.model_channels = model_channels
|
||||
self.cond_channels = cond_channels
|
||||
self.out_channels = out_channels
|
||||
self.num_blocks = num_blocks
|
||||
self.num_heads = num_heads or model_channels // num_head_channels
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.patch_size = patch_size
|
||||
self.num_io_res_blocks = num_io_res_blocks
|
||||
self.io_block_channels = io_block_channels
|
||||
self.pe_mode = pe_mode
|
||||
self.use_fp16 = use_fp16
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.use_skip_connection = use_skip_connection
|
||||
self.share_mod = share_mod
|
||||
self.qk_rms_norm = qk_rms_norm
|
||||
self.qk_rms_norm_cross = qk_rms_norm_cross
|
||||
self.dtype = torch.float16 if use_fp16 else torch.float32
|
||||
|
||||
assert int(np.log2(patch_size)) == np.log2(patch_size), "Patch size must be a power of 2"
|
||||
assert np.log2(patch_size) == len(io_block_channels), "Number of IO ResBlocks must match the number of stages"
|
||||
|
||||
self.t_embedder = TimestepEmbedder(model_channels)
|
||||
if share_mod:
|
||||
self.adaLN_modulation = nn.Sequential(
|
||||
nn.SiLU(),
|
||||
nn.Linear(model_channels, 6 * model_channels, bias=True)
|
||||
)
|
||||
|
||||
if pe_mode == "ape":
|
||||
self.pos_embedder = AbsolutePositionEmbedder(model_channels)
|
||||
|
||||
self.input_layer = sp.SparseLinear(in_channels, io_block_channels[0])
|
||||
self.input_blocks = nn.ModuleList([])
|
||||
for chs, next_chs in zip(io_block_channels, io_block_channels[1:] + [model_channels]):
|
||||
self.input_blocks.extend([
|
||||
SparseResBlock3d(
|
||||
chs,
|
||||
model_channels,
|
||||
out_channels=chs,
|
||||
)
|
||||
for _ in range(num_io_res_blocks-1)
|
||||
])
|
||||
self.input_blocks.append(
|
||||
SparseResBlock3d(
|
||||
chs,
|
||||
model_channels,
|
||||
out_channels=next_chs,
|
||||
downsample=True,
|
||||
)
|
||||
)
|
||||
|
||||
self.blocks = nn.ModuleList([
|
||||
ModulatedSparseTransformerCrossBlock(
|
||||
model_channels,
|
||||
cond_channels,
|
||||
num_heads=self.num_heads,
|
||||
mlp_ratio=self.mlp_ratio,
|
||||
attn_mode='full',
|
||||
use_checkpoint=self.use_checkpoint,
|
||||
use_rope=(pe_mode == "rope"),
|
||||
share_mod=self.share_mod,
|
||||
qk_rms_norm=self.qk_rms_norm,
|
||||
qk_rms_norm_cross=self.qk_rms_norm_cross,
|
||||
)
|
||||
for _ in range(num_blocks)
|
||||
])
|
||||
|
||||
self.out_blocks = nn.ModuleList([])
|
||||
for chs, prev_chs in zip(reversed(io_block_channels), [model_channels] + list(reversed(io_block_channels[1:]))):
|
||||
self.out_blocks.append(
|
||||
SparseResBlock3d(
|
||||
prev_chs * 2 if self.use_skip_connection else prev_chs,
|
||||
model_channels,
|
||||
out_channels=chs,
|
||||
upsample=True,
|
||||
)
|
||||
)
|
||||
self.out_blocks.extend([
|
||||
SparseResBlock3d(
|
||||
chs * 2 if self.use_skip_connection else chs,
|
||||
model_channels,
|
||||
out_channels=chs,
|
||||
)
|
||||
for _ in range(num_io_res_blocks-1)
|
||||
])
|
||||
self.out_layer = sp.SparseLinear(io_block_channels[0], out_channels)
|
||||
|
||||
self.initialize_weights()
|
||||
if use_fp16:
|
||||
self.convert_to_fp16()
|
||||
|
||||
@property
|
||||
def device(self) -> torch.device:
|
||||
"""
|
||||
Return the device of the model.
|
||||
"""
|
||||
return next(self.parameters()).device
|
||||
|
||||
def convert_to_fp16(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float16.
|
||||
"""
|
||||
self.input_blocks.apply(convert_module_to_f16)
|
||||
self.blocks.apply(convert_module_to_f16)
|
||||
self.out_blocks.apply(convert_module_to_f16)
|
||||
|
||||
def convert_to_fp32(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float32.
|
||||
"""
|
||||
self.input_blocks.apply(convert_module_to_f32)
|
||||
self.blocks.apply(convert_module_to_f32)
|
||||
self.out_blocks.apply(convert_module_to_f32)
|
||||
|
||||
def initialize_weights(self) -> None:
|
||||
# Initialize transformer layers:
|
||||
def _basic_init(module):
|
||||
if isinstance(module, nn.Linear):
|
||||
torch.nn.init.xavier_uniform_(module.weight)
|
||||
if module.bias is not None:
|
||||
nn.init.constant_(module.bias, 0)
|
||||
self.apply(_basic_init)
|
||||
|
||||
# Initialize timestep embedding MLP:
|
||||
nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
|
||||
nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
|
||||
|
||||
# Zero-out adaLN modulation layers in DiT blocks:
|
||||
if self.share_mod:
|
||||
nn.init.constant_(self.adaLN_modulation[-1].weight, 0)
|
||||
nn.init.constant_(self.adaLN_modulation[-1].bias, 0)
|
||||
else:
|
||||
for block in self.blocks:
|
||||
nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
|
||||
nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
|
||||
|
||||
# Zero-out output layers:
|
||||
nn.init.constant_(self.out_layer.weight, 0)
|
||||
nn.init.constant_(self.out_layer.bias, 0)
|
||||
|
||||
def forward(self, x: sp.SparseTensor, t: torch.Tensor, cond: torch.Tensor) -> sp.SparseTensor:
|
||||
h = self.input_layer(x).type(self.dtype)
|
||||
t_emb = self.t_embedder(t)
|
||||
if self.share_mod:
|
||||
t_emb = self.adaLN_modulation(t_emb)
|
||||
t_emb = t_emb.type(self.dtype)
|
||||
cond = cond.type(self.dtype)
|
||||
|
||||
skips = []
|
||||
# pack with input blocks
|
||||
for block in self.input_blocks:
|
||||
h = block(h, t_emb)
|
||||
skips.append(h.feats)
|
||||
|
||||
if self.pe_mode == "ape":
|
||||
h = h + self.pos_embedder(h.coords[:, 1:]).type(self.dtype)
|
||||
for block in self.blocks:
|
||||
h = block(h, t_emb, cond)
|
||||
|
||||
# unpack with output blocks
|
||||
for block, skip in zip(self.out_blocks, reversed(skips)):
|
||||
if self.use_skip_connection:
|
||||
h = block(h.replace(torch.cat([h.feats, skip], dim=1)), t_emb)
|
||||
else:
|
||||
h = block(h, t_emb)
|
||||
|
||||
h = h.replace(F.layer_norm(h.feats, h.feats.shape[-1:]))
|
||||
h = self.out_layer(h.type(x.dtype))
|
||||
return h
|
||||
@@ -0,0 +1,2 @@
|
||||
from .encoder import SLatEncoder
|
||||
from .decoder_mesh import SLatMeshDecoder
|
||||
@@ -0,0 +1,117 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from ...modules.utils import convert_module_to_f16, convert_module_to_f32
|
||||
from ...modules import sparse as sp
|
||||
from ...modules.transformer import AbsolutePositionEmbedder
|
||||
from ...modules.sparse.transformer import SparseTransformerBlock
|
||||
|
||||
|
||||
def block_attn_config(self):
|
||||
"""
|
||||
Return the attention configuration of the model.
|
||||
"""
|
||||
for i in range(self.num_blocks):
|
||||
if self.attn_mode == "shift_window":
|
||||
yield "serialized", self.window_size, 0, (16 * (i % 2),) * 3, sp.SerializeMode.Z_ORDER
|
||||
elif self.attn_mode == "shift_sequence":
|
||||
yield "serialized", self.window_size, self.window_size // 2 * (i % 2), (0, 0, 0), sp.SerializeMode.Z_ORDER
|
||||
elif self.attn_mode == "shift_order":
|
||||
yield "serialized", self.window_size, 0, (0, 0, 0), sp.SerializeModes[i % 4]
|
||||
elif self.attn_mode == "full":
|
||||
yield "full", None, None, None, None
|
||||
elif self.attn_mode == "swin":
|
||||
yield "windowed", self.window_size, None, self.window_size // 2 * (i % 2), None
|
||||
|
||||
|
||||
class SparseTransformerBase(nn.Module):
|
||||
"""
|
||||
Sparse Transformer without output layers.
|
||||
Serve as the base class for encoder and decoder.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
in_channels: int,
|
||||
model_channels: int,
|
||||
num_blocks: int,
|
||||
num_heads: Optional[int] = None,
|
||||
num_head_channels: Optional[int] = 64,
|
||||
mlp_ratio: float = 4.0,
|
||||
attn_mode: Literal["full", "shift_window", "shift_sequence", "shift_order", "swin"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
pe_mode: Literal["ape", "rope"] = "ape",
|
||||
use_fp16: bool = False,
|
||||
use_checkpoint: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.in_channels = in_channels
|
||||
self.model_channels = model_channels
|
||||
self.num_blocks = num_blocks
|
||||
self.window_size = window_size
|
||||
self.num_heads = num_heads or model_channels // num_head_channels
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.attn_mode = attn_mode
|
||||
self.pe_mode = pe_mode
|
||||
self.use_fp16 = use_fp16
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.qk_rms_norm = qk_rms_norm
|
||||
self.dtype = torch.float16 if use_fp16 else torch.float32
|
||||
|
||||
if pe_mode == "ape":
|
||||
self.pos_embedder = AbsolutePositionEmbedder(model_channels)
|
||||
|
||||
self.input_layer = sp.SparseLinear(in_channels, model_channels)
|
||||
self.blocks = nn.ModuleList([
|
||||
SparseTransformerBlock(
|
||||
model_channels,
|
||||
num_heads=self.num_heads,
|
||||
mlp_ratio=self.mlp_ratio,
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
shift_sequence=shift_sequence,
|
||||
shift_window=shift_window,
|
||||
serialize_mode=serialize_mode,
|
||||
use_checkpoint=self.use_checkpoint,
|
||||
use_rope=(pe_mode == "rope"),
|
||||
qk_rms_norm=self.qk_rms_norm,
|
||||
)
|
||||
for attn_mode, window_size, shift_sequence, shift_window, serialize_mode in block_attn_config(self)
|
||||
])
|
||||
|
||||
@property
|
||||
def device(self) -> torch.device:
|
||||
"""
|
||||
Return the device of the model.
|
||||
"""
|
||||
return next(self.parameters()).device
|
||||
|
||||
def convert_to_fp16(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float16.
|
||||
"""
|
||||
self.blocks.apply(convert_module_to_f16)
|
||||
|
||||
def convert_to_fp32(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float32.
|
||||
"""
|
||||
self.blocks.apply(convert_module_to_f32)
|
||||
|
||||
def initialize_weights(self) -> None:
|
||||
# Initialize transformer layers:
|
||||
def _basic_init(module):
|
||||
if isinstance(module, nn.Linear):
|
||||
torch.nn.init.xavier_uniform_(module.weight)
|
||||
if module.bias is not None:
|
||||
nn.init.constant_(module.bias, 0)
|
||||
self.apply(_basic_init)
|
||||
|
||||
def forward(self, x: sp.SparseTensor) -> sp.SparseTensor:
|
||||
h = self.input_layer(x)
|
||||
if self.pe_mode == "ape":
|
||||
h = h + self.pos_embedder(x.coords[:, 1:])
|
||||
h = h.type(self.dtype)
|
||||
for block in self.blocks:
|
||||
h = block(h)
|
||||
return h
|
||||
@@ -0,0 +1,167 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import numpy as np
|
||||
from ...modules.utils import zero_module, convert_module_to_f16, convert_module_to_f32
|
||||
from ...modules import sparse as sp
|
||||
from .base import SparseTransformerBase
|
||||
from ...representations import MeshExtractResult
|
||||
from ...representations.mesh import SparseFeatures2Mesh
|
||||
|
||||
|
||||
class SparseSubdivideBlock3d(nn.Module):
|
||||
"""
|
||||
A 3D subdivide block that can subdivide the sparse tensor.
|
||||
|
||||
Args:
|
||||
channels: channels in the inputs and outputs.
|
||||
out_channels: if specified, the number of output channels.
|
||||
num_groups: the number of groups for the group norm.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
resolution: int,
|
||||
out_channels: Optional[int] = None,
|
||||
num_groups: int = 32
|
||||
):
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.resolution = resolution
|
||||
self.out_resolution = resolution * 2
|
||||
self.out_channels = out_channels or channels
|
||||
|
||||
self.act_layers = nn.Sequential(
|
||||
sp.SparseGroupNorm32(num_groups, channels),
|
||||
sp.SparseSiLU()
|
||||
)
|
||||
|
||||
self.sub = sp.SparseSubdivide()
|
||||
|
||||
self.out_layers = nn.Sequential(
|
||||
sp.SparseConv3d(channels, self.out_channels, 3, indice_key=f"res_{self.out_resolution}"),
|
||||
sp.SparseGroupNorm32(num_groups, self.out_channels),
|
||||
sp.SparseSiLU(),
|
||||
zero_module(sp.SparseConv3d(self.out_channels, self.out_channels, 3, indice_key=f"res_{self.out_resolution}")),
|
||||
)
|
||||
|
||||
if self.out_channels == channels:
|
||||
self.skip_connection = nn.Identity()
|
||||
else:
|
||||
self.skip_connection = sp.SparseConv3d(channels, self.out_channels, 1, indice_key=f"res_{self.out_resolution}")
|
||||
|
||||
def forward(self, x: sp.SparseTensor) -> sp.SparseTensor:
|
||||
"""
|
||||
Apply the block to a Tensor, conditioned on a timestep embedding.
|
||||
|
||||
Args:
|
||||
x: an [N x C x ...] Tensor of features.
|
||||
Returns:
|
||||
an [N x C x ...] Tensor of outputs.
|
||||
"""
|
||||
h = self.act_layers(x)
|
||||
h = self.sub(h)
|
||||
x = self.sub(x)
|
||||
h = self.out_layers(h)
|
||||
h = h + self.skip_connection(x)
|
||||
return h
|
||||
|
||||
|
||||
class SLatMeshDecoder(SparseTransformerBase):
|
||||
def __init__(
|
||||
self,
|
||||
resolution: int,
|
||||
model_channels: int,
|
||||
latent_channels: int,
|
||||
num_blocks: int,
|
||||
num_heads: Optional[int] = None,
|
||||
num_head_channels: Optional[int] = 64,
|
||||
mlp_ratio: float = 4,
|
||||
attn_mode: Literal["full", "shift_window", "shift_sequence", "shift_order", "swin"] = "swin",
|
||||
window_size: int = 8,
|
||||
pe_mode: Literal["ape", "rope"] = "ape",
|
||||
use_fp16: bool = False,
|
||||
use_checkpoint: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
representation_config: dict = None,
|
||||
):
|
||||
super().__init__(
|
||||
in_channels=latent_channels,
|
||||
model_channels=model_channels,
|
||||
num_blocks=num_blocks,
|
||||
num_heads=num_heads,
|
||||
num_head_channels=num_head_channels,
|
||||
mlp_ratio=mlp_ratio,
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
pe_mode=pe_mode,
|
||||
use_fp16=use_fp16,
|
||||
use_checkpoint=use_checkpoint,
|
||||
qk_rms_norm=qk_rms_norm,
|
||||
)
|
||||
self.resolution = resolution
|
||||
self.rep_config = representation_config
|
||||
self.mesh_extractor = SparseFeatures2Mesh(res=self.resolution*4, use_color=self.rep_config.get('use_color', False))
|
||||
self.out_channels = self.mesh_extractor.feats_channels
|
||||
self.upsample = nn.ModuleList([
|
||||
SparseSubdivideBlock3d(
|
||||
channels=model_channels,
|
||||
resolution=resolution,
|
||||
out_channels=model_channels // 4
|
||||
),
|
||||
SparseSubdivideBlock3d(
|
||||
channels=model_channels // 4,
|
||||
resolution=resolution * 2,
|
||||
out_channels=model_channels // 8
|
||||
)
|
||||
])
|
||||
self.out_layer = sp.SparseLinear(model_channels // 8, self.out_channels)
|
||||
|
||||
self.initialize_weights()
|
||||
if use_fp16:
|
||||
self.convert_to_fp16()
|
||||
|
||||
def initialize_weights(self) -> None:
|
||||
super().initialize_weights()
|
||||
# Zero-out output layers:
|
||||
nn.init.constant_(self.out_layer.weight, 0)
|
||||
nn.init.constant_(self.out_layer.bias, 0)
|
||||
|
||||
def convert_to_fp16(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float16.
|
||||
"""
|
||||
super().convert_to_fp16()
|
||||
self.upsample.apply(convert_module_to_f16)
|
||||
|
||||
def convert_to_fp32(self) -> None:
|
||||
"""
|
||||
Convert the torso of the model to float32.
|
||||
"""
|
||||
super().convert_to_fp32()
|
||||
self.upsample.apply(convert_module_to_f32)
|
||||
|
||||
def to_representation(self, x: sp.SparseTensor) -> List[MeshExtractResult]:
|
||||
"""
|
||||
Convert a batch of network outputs to 3D representations.
|
||||
|
||||
Args:
|
||||
x: The [N x * x C] sparse tensor output by the network.
|
||||
|
||||
Returns:
|
||||
list of representations
|
||||
"""
|
||||
ret = []
|
||||
for i in range(x.shape[0]):
|
||||
mesh = self.mesh_extractor(x[i], training=self.training)
|
||||
ret.append(mesh)
|
||||
return ret
|
||||
|
||||
def forward(self, x: sp.SparseTensor) -> List[MeshExtractResult]:
|
||||
h = super().forward(x)
|
||||
for block in self.upsample:
|
||||
h = block(h)
|
||||
h = h.type(x.dtype)
|
||||
h = self.out_layer(h)
|
||||
return self.to_representation(h)
|
||||
@@ -0,0 +1,72 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from ...modules import sparse as sp
|
||||
from .base import SparseTransformerBase
|
||||
|
||||
|
||||
class SLatEncoder(SparseTransformerBase):
|
||||
def __init__(
|
||||
self,
|
||||
resolution: int,
|
||||
in_channels: int,
|
||||
model_channels: int,
|
||||
latent_channels: int,
|
||||
num_blocks: int,
|
||||
num_heads: Optional[int] = None,
|
||||
num_head_channels: Optional[int] = 64,
|
||||
mlp_ratio: float = 4,
|
||||
attn_mode: Literal["full", "shift_window", "shift_sequence", "shift_order", "swin"] = "swin",
|
||||
window_size: int = 8,
|
||||
pe_mode: Literal["ape", "rope"] = "ape",
|
||||
use_fp16: bool = False,
|
||||
use_checkpoint: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
):
|
||||
super().__init__(
|
||||
in_channels=in_channels,
|
||||
model_channels=model_channels,
|
||||
num_blocks=num_blocks,
|
||||
num_heads=num_heads,
|
||||
num_head_channels=num_head_channels,
|
||||
mlp_ratio=mlp_ratio,
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
pe_mode=pe_mode,
|
||||
use_fp16=use_fp16,
|
||||
use_checkpoint=use_checkpoint,
|
||||
qk_rms_norm=qk_rms_norm,
|
||||
)
|
||||
self.resolution = resolution
|
||||
self.out_layer = sp.SparseLinear(model_channels, 2 * latent_channels)
|
||||
|
||||
self.initialize_weights()
|
||||
if use_fp16:
|
||||
self.convert_to_fp16()
|
||||
|
||||
def initialize_weights(self) -> None:
|
||||
super().initialize_weights()
|
||||
# Zero-out output layers:
|
||||
nn.init.constant_(self.out_layer.weight, 0)
|
||||
nn.init.constant_(self.out_layer.bias, 0)
|
||||
|
||||
def forward(self, x: sp.SparseTensor, sample_posterior=True, return_raw=False):
|
||||
h = super().forward(x)
|
||||
h = h.type(x.dtype)
|
||||
h = h.replace(F.layer_norm(h.feats, h.feats.shape[-1:]))
|
||||
h = self.out_layer(h)
|
||||
|
||||
# Sample from the posterior distribution
|
||||
mean, logvar = h.feats.chunk(2, dim=-1)
|
||||
if sample_posterior:
|
||||
std = torch.exp(0.5 * logvar)
|
||||
z = mean + std * torch.randn_like(std)
|
||||
else:
|
||||
z = mean
|
||||
z = h.replace(z)
|
||||
|
||||
if return_raw:
|
||||
return z, mean, logvar
|
||||
else:
|
||||
return z
|
||||
@@ -0,0 +1,16 @@
|
||||
#from .attention_utils import enable_sage_attention, disable_sage_attention
|
||||
from .attention import (
|
||||
scaled_dot_product_attention,
|
||||
BACKEND,
|
||||
DEBUG,
|
||||
MultiHeadAttention,
|
||||
RotaryPositionEmbedder
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'scaled_dot_product_attention',
|
||||
'BACKEND',
|
||||
'DEBUG',
|
||||
'MultiHeadAttention',
|
||||
'RotaryPositionEmbedder'
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
import os
|
||||
import logging
|
||||
from typing import Literal
|
||||
from trellis.backend_config import (
|
||||
get_attention_backend,
|
||||
get_debug_mode,
|
||||
)
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#ATTN = get_attention_backend()
|
||||
BACKEND = get_attention_backend()
|
||||
DEBUG = get_debug_mode()
|
||||
|
||||
def __from_env():
|
||||
"""Read current backend configuration"""
|
||||
#global ATTN
|
||||
global BACKEND
|
||||
global DEBUG
|
||||
|
||||
# Get current settings from central config
|
||||
#ATTN =
|
||||
BACKEND = get_attention_backend()
|
||||
DEBUG = get_debug_mode()
|
||||
|
||||
print(f"[ATTENTION] Using backend: {BACKEND}")
|
||||
|
||||
from .modules import MultiHeadAttention, RotaryPositionEmbedder
|
||||
from .full_attn import scaled_dot_product_attention
|
||||
|
||||
__all__ = [
|
||||
'scaled_dot_product_attention',
|
||||
'BACKEND',
|
||||
'DEBUG',
|
||||
'MultiHeadAttention',
|
||||
'RotaryPositionEmbedder'
|
||||
]
|
||||
@@ -0,0 +1,158 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import math
|
||||
from trellis.backend_config import (
|
||||
get_attention_backend,
|
||||
get_debug_mode,
|
||||
get_available_backends
|
||||
)
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Get configuration from central config
|
||||
BACKEND = get_attention_backend()
|
||||
DEBUG = get_debug_mode()
|
||||
|
||||
# Get available backends and import if active
|
||||
available_backends = get_available_backends()
|
||||
|
||||
if BACKEND == "xformers" and available_backends['xformers']:
|
||||
import xformers.ops as xops
|
||||
elif BACKEND == "flash_attn" and available_backends['flash_attn']:
|
||||
import flash_attn
|
||||
elif BACKEND == "sage" and available_backends['sage']:
|
||||
import torch.nn.functional as F
|
||||
from sageattention import sageattn
|
||||
F.scaled_dot_product_attention = sageattn
|
||||
elif BACKEND == "sdpa":
|
||||
from torch.nn.functional import scaled_dot_product_attention as sdpa
|
||||
elif BACKEND == "naive":
|
||||
from torch.nn.functional import scaled_dot_product_attention as naive
|
||||
else:
|
||||
raise ValueError(f"Unknown attention module: {BACKEND}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
'scaled_dot_product_attention',
|
||||
]
|
||||
|
||||
|
||||
def _naive_sdpa(q, k, v):
|
||||
"""
|
||||
Naive implementation of scaled dot product attention.
|
||||
"""
|
||||
q = q.permute(0, 2, 1, 3) # [N, H, L, C]
|
||||
k = k.permute(0, 2, 1, 3) # [N, H, L, C]
|
||||
v = v.permute(0, 2, 1, 3) # [N, H, L, C]
|
||||
scale_factor = 1 / math.sqrt(q.size(-1))
|
||||
attn_weight = q @ k.transpose(-2, -1) * scale_factor
|
||||
attn_weight = torch.softmax(attn_weight, dim=-1)
|
||||
out = attn_weight @ v
|
||||
out = out.permute(0, 2, 1, 3) # [N, L, H, C]
|
||||
return out
|
||||
|
||||
|
||||
@overload
|
||||
def scaled_dot_product_attention(qkv: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Apply scaled dot product attention.
|
||||
|
||||
Args:
|
||||
qkv (torch.Tensor): A [N, L, 3, H, C] tensor containing Qs, Ks, and Vs.
|
||||
"""
|
||||
...
|
||||
|
||||
@overload
|
||||
def scaled_dot_product_attention(q: torch.Tensor, kv: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Apply scaled dot product attention.
|
||||
|
||||
Args:
|
||||
q (torch.Tensor): A [N, L, H, C] tensor containing Qs.
|
||||
kv (torch.Tensor): A [N, L, 2, H, C] tensor containing Ks and Vs.
|
||||
"""
|
||||
...
|
||||
|
||||
@overload
|
||||
def scaled_dot_product_attention(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Apply scaled dot product attention.
|
||||
|
||||
Args:
|
||||
q (torch.Tensor): A [N, L, H, Ci] tensor containing Qs.
|
||||
k (torch.Tensor): A [N, L, H, Ci] tensor containing Ks.
|
||||
v (torch.Tensor): A [N, L, H, Co] tensor containing Vs.
|
||||
|
||||
Note:
|
||||
k and v are assumed to have the same coordinate map.
|
||||
"""
|
||||
...
|
||||
|
||||
def scaled_dot_product_attention(*args, **kwargs):
|
||||
arg_names_dict = {
|
||||
1: ['qkv'],
|
||||
2: ['q', 'kv'],
|
||||
3: ['q', 'k', 'v']
|
||||
}
|
||||
num_all_args = len(args) + len(kwargs)
|
||||
assert num_all_args in arg_names_dict, f"Invalid number of arguments, got {num_all_args}, expected 1, 2, or 3"
|
||||
for key in arg_names_dict[num_all_args][len(args):]:
|
||||
assert key in kwargs, f"Missing argument {key}"
|
||||
|
||||
if num_all_args == 1:
|
||||
qkv = args[0] if len(args) > 0 else kwargs['qkv']
|
||||
assert len(qkv.shape) == 5 and qkv.shape[2] == 3, f"Invalid shape for qkv, got {qkv.shape}, expected [N, L, 3, H, C]"
|
||||
device = qkv.device
|
||||
|
||||
elif num_all_args == 2:
|
||||
q = args[0] if len(args) > 0 else kwargs['q']
|
||||
kv = args[1] if len(args) > 1 else kwargs['kv']
|
||||
assert q.shape[0] == kv.shape[0], f"Batch size mismatch, got {q.shape[0]} and {kv.shape[0]}"
|
||||
assert len(q.shape) == 4, f"Invalid shape for q, got {q.shape}, expected [N, L, H, C]"
|
||||
assert len(kv.shape) == 5, f"Invalid shape for kv, got {kv.shape}, expected [N, L, 2, H, C]"
|
||||
device = q.device
|
||||
|
||||
elif num_all_args == 3:
|
||||
q = args[0] if len(args) > 0 else kwargs['q']
|
||||
k = args[1] if len(args) > 1 else kwargs['k']
|
||||
v = args[2] if len(args) > 2 else kwargs['v']
|
||||
assert q.shape[0] == k.shape[0] == v.shape[0], f"Batch size mismatch, got {q.shape[0]}, {k.shape[0]}, and {v.shape[0]}"
|
||||
assert len(q.shape) == 4, f"Invalid shape for q, got {q.shape}, expected [N, L, H, Ci]"
|
||||
assert len(k.shape) == 4, f"Invalid shape for k, got {k.shape}, expected [N, L, H, Ci]"
|
||||
assert len(v.shape) == 4, f"Invalid shape for v, got {v.shape}, expected [N, L, H, Co]"
|
||||
device = q.device
|
||||
|
||||
if BACKEND == 'xformers':
|
||||
if num_all_args == 1:
|
||||
q, k, v = qkv.unbind(dim=2)
|
||||
elif num_all_args == 2:
|
||||
k, v = kv.unbind(dim=2)
|
||||
out = xops.memory_efficient_attention(q, k, v)
|
||||
elif BACKEND == 'flash_attn':
|
||||
if num_all_args == 1:
|
||||
out = flash_attn.flash_attn_qkvpacked_func(qkv)
|
||||
elif num_all_args == 2:
|
||||
out = flash_attn.flash_attn_kvpacked_func(q, kv)
|
||||
elif num_all_args == 3:
|
||||
out = flash_attn.flash_attn_func(q, k, v)
|
||||
elif BACKEND == 'sdpa':
|
||||
if num_all_args == 1:
|
||||
q, k, v = qkv.unbind(dim=2)
|
||||
elif num_all_args == 2:
|
||||
k, v = kv.unbind(dim=2)
|
||||
q = q.permute(0, 2, 1, 3) # [N, H, L, C]
|
||||
k = k.permute(0, 2, 1, 3) # [N, H, L, C]
|
||||
v = v.permute(0, 2, 1, 3) # [N, H, L, C]
|
||||
out = sdpa(q, k, v) # [N, H, L, C]
|
||||
out = out.permute(0, 2, 1, 3) # [N, L, H, C]
|
||||
elif BACKEND == 'naive':
|
||||
if num_all_args == 1:
|
||||
q, k, v = qkv.unbind(dim=2)
|
||||
elif num_all_args == 2:
|
||||
k, v = kv.unbind(dim=2)
|
||||
out = _naive_sdpa(q, k, v)
|
||||
else:
|
||||
raise ValueError(f"Unknown attention module: {BACKEND}")
|
||||
|
||||
return out
|
||||
@@ -0,0 +1,146 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from .full_attn import scaled_dot_product_attention
|
||||
|
||||
|
||||
class MultiHeadRMSNorm(nn.Module):
|
||||
def __init__(self, dim: int, heads: int):
|
||||
super().__init__()
|
||||
self.scale = dim ** 0.5
|
||||
self.gamma = nn.Parameter(torch.ones(heads, dim))
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
return (F.normalize(x.float(), dim = -1) * self.gamma * self.scale).to(x.dtype)
|
||||
|
||||
|
||||
class RotaryPositionEmbedder(nn.Module):
|
||||
def __init__(self, hidden_size: int, in_channels: int = 3):
|
||||
super().__init__()
|
||||
assert hidden_size % 2 == 0, "Hidden size must be divisible by 2"
|
||||
self.hidden_size = hidden_size
|
||||
self.in_channels = in_channels
|
||||
self.freq_dim = hidden_size // in_channels // 2
|
||||
self.freqs = torch.arange(self.freq_dim, dtype=torch.float32) / self.freq_dim
|
||||
self.freqs = 1.0 / (10000 ** self.freqs)
|
||||
|
||||
def _get_phases(self, indices: torch.Tensor) -> torch.Tensor:
|
||||
self.freqs = self.freqs.to(indices.device)
|
||||
phases = torch.outer(indices, self.freqs)
|
||||
phases = torch.polar(torch.ones_like(phases), phases)
|
||||
return phases
|
||||
|
||||
def _rotary_embedding(self, x: torch.Tensor, phases: torch.Tensor) -> torch.Tensor:
|
||||
x_complex = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
|
||||
x_rotated = x_complex * phases
|
||||
x_embed = torch.view_as_real(x_rotated).reshape(*x_rotated.shape[:-1], -1).to(x.dtype)
|
||||
return x_embed
|
||||
|
||||
def forward(self, q: torch.Tensor, k: torch.Tensor, indices: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Args:
|
||||
q (sp.SparseTensor): [..., N, D] tensor of queries
|
||||
k (sp.SparseTensor): [..., N, D] tensor of keys
|
||||
indices (torch.Tensor): [..., N, C] tensor of spatial positions
|
||||
"""
|
||||
if indices is None:
|
||||
indices = torch.arange(q.shape[-2], device=q.device)
|
||||
if len(q.shape) > 2:
|
||||
indices = indices.unsqueeze(0).expand(q.shape[:-2] + (-1,))
|
||||
|
||||
phases = self._get_phases(indices.reshape(-1)).reshape(*indices.shape[:-1], -1)
|
||||
if phases.shape[1] < self.hidden_size // 2:
|
||||
phases = torch.cat([phases, torch.polar(
|
||||
torch.ones(*phases.shape[:-1], self.hidden_size // 2 - phases.shape[1], device=phases.device),
|
||||
torch.zeros(*phases.shape[:-1], self.hidden_size // 2 - phases.shape[1], device=phases.device)
|
||||
)], dim=-1)
|
||||
q_embed = self._rotary_embedding(q, phases)
|
||||
k_embed = self._rotary_embedding(k, phases)
|
||||
return q_embed, k_embed
|
||||
|
||||
|
||||
class MultiHeadAttention(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
num_heads: int,
|
||||
ctx_channels: Optional[int]=None,
|
||||
type: Literal["self", "cross"] = "self",
|
||||
attn_mode: Literal["full", "windowed"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
shift_window: Optional[Tuple[int, int, int]] = None,
|
||||
qkv_bias: bool = True,
|
||||
use_rope: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
assert channels % num_heads == 0
|
||||
assert type in ["self", "cross"], f"Invalid attention type: {type}"
|
||||
assert attn_mode in ["full", "windowed"], f"Invalid attention mode: {attn_mode}"
|
||||
assert type == "self" or attn_mode == "full", "Cross-attention only supports full attention"
|
||||
|
||||
if attn_mode == "windowed":
|
||||
raise NotImplementedError("Windowed attention is not yet implemented")
|
||||
|
||||
self.channels = channels
|
||||
self.head_dim = channels // num_heads
|
||||
self.ctx_channels = ctx_channels if ctx_channels is not None else channels
|
||||
self.num_heads = num_heads
|
||||
self._type = type
|
||||
self.attn_mode = attn_mode
|
||||
self.window_size = window_size
|
||||
self.shift_window = shift_window
|
||||
self.use_rope = use_rope
|
||||
self.qk_rms_norm = qk_rms_norm
|
||||
|
||||
if self._type == "self":
|
||||
self.to_qkv = nn.Linear(channels, channels * 3, bias=qkv_bias)
|
||||
else:
|
||||
self.to_q = nn.Linear(channels, channels, bias=qkv_bias)
|
||||
self.to_kv = nn.Linear(self.ctx_channels, channels * 2, bias=qkv_bias)
|
||||
|
||||
if self.qk_rms_norm:
|
||||
self.q_rms_norm = MultiHeadRMSNorm(self.head_dim, num_heads)
|
||||
self.k_rms_norm = MultiHeadRMSNorm(self.head_dim, num_heads)
|
||||
|
||||
self.to_out = nn.Linear(channels, channels)
|
||||
|
||||
if use_rope:
|
||||
self.rope = RotaryPositionEmbedder(channels)
|
||||
|
||||
def forward(self, x: torch.Tensor, context: Optional[torch.Tensor] = None, indices: Optional[torch.Tensor] = None) -> torch.Tensor:
|
||||
B, L, C = x.shape
|
||||
if self._type == "self":
|
||||
qkv = self.to_qkv(x)
|
||||
qkv = qkv.reshape(B, L, 3, self.num_heads, -1)
|
||||
if self.use_rope:
|
||||
q, k, v = qkv.unbind(dim=2)
|
||||
q, k = self.rope(q, k, indices)
|
||||
qkv = torch.stack([q, k, v], dim=2)
|
||||
if self.attn_mode == "full":
|
||||
if self.qk_rms_norm:
|
||||
q, k, v = qkv.unbind(dim=2)
|
||||
q = self.q_rms_norm(q)
|
||||
k = self.k_rms_norm(k)
|
||||
h = scaled_dot_product_attention(q, k, v)
|
||||
else:
|
||||
h = scaled_dot_product_attention(qkv)
|
||||
elif self.attn_mode == "windowed":
|
||||
raise NotImplementedError("Windowed attention is not yet implemented")
|
||||
else:
|
||||
Lkv = context.shape[1]
|
||||
q = self.to_q(x)
|
||||
kv = self.to_kv(context)
|
||||
q = q.reshape(B, L, self.num_heads, -1)
|
||||
kv = kv.reshape(B, Lkv, 2, self.num_heads, -1)
|
||||
if self.qk_rms_norm:
|
||||
q = self.q_rms_norm(q)
|
||||
k, v = kv.unbind(dim=2)
|
||||
k = self.k_rms_norm(k)
|
||||
h = scaled_dot_product_attention(q, k, v)
|
||||
else:
|
||||
h = scaled_dot_product_attention(q, kv)
|
||||
h = h.reshape(B, L, -1)
|
||||
h = self.to_out(h)
|
||||
return h
|
||||
@@ -0,0 +1,25 @@
|
||||
#sage_attn.py
|
||||
import os
|
||||
from typing import Optional
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from sageattention import sageattn
|
||||
import math
|
||||
|
||||
__all__ = ['SageAttention', 'sage_attention']
|
||||
|
||||
|
||||
def enable_sage_attention():
|
||||
"""
|
||||
Enable SageAttention by replacing PyTorch's scaled_dot_product_attention
|
||||
with sageattn from the SageAttention library.
|
||||
"""
|
||||
F.scaled_dot_product_attention = sageattn
|
||||
return True
|
||||
|
||||
def disable_sage_attention():
|
||||
"""
|
||||
Restore PyTorch's original scaled_dot_product_attention function.
|
||||
"""
|
||||
F.scaled_dot_product_attention = torch.nn.functional.scaled_dot_product_attention
|
||||
return True
|
||||
@@ -0,0 +1,25 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class LayerNorm32(nn.LayerNorm):
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
return super().forward(x.float()).type(x.dtype)
|
||||
|
||||
|
||||
class GroupNorm32(nn.GroupNorm):
|
||||
"""
|
||||
A GroupNorm layer that converts to float32 before the forward pass.
|
||||
"""
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
return super().forward(x.float()).type(x.dtype)
|
||||
|
||||
|
||||
class ChannelLayerNorm32(LayerNorm32):
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
DIM = x.dim()
|
||||
x = x.permute(0, *range(2, DIM), 1).contiguous()
|
||||
x = super().forward(x)
|
||||
x = x.permute(0, DIM-1, *range(1, DIM-1)).contiguous()
|
||||
return x
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
from typing import *
|
||||
import importlib
|
||||
from trellis.backend_config import get_sparse_backend, get_attention_backend, get_debug_mode
|
||||
|
||||
BACKEND = get_sparse_backend()
|
||||
DEBUG = get_debug_mode()
|
||||
ATTN = get_attention_backend()
|
||||
|
||||
if ATTN not in ['xformers', 'flash_attn']:
|
||||
logger.warning(f"Attention backend {ATTN} not supported for sparse attention. Only 'xformers' and 'flash_attn' are available. Defaulting to 'flash_attn'")
|
||||
ATTN = 'flash_attn'
|
||||
|
||||
__attributes = {
|
||||
'SparseTensor': 'basic',
|
||||
'sparse_batch_broadcast': 'basic',
|
||||
'sparse_batch_op': 'basic',
|
||||
'sparse_cat': 'basic',
|
||||
'sparse_unbind': 'basic',
|
||||
'SparseGroupNorm': 'norm',
|
||||
'SparseLayerNorm': 'norm',
|
||||
'SparseGroupNorm32': 'norm',
|
||||
'SparseLayerNorm32': 'norm',
|
||||
'SparseReLU': 'nonlinearity',
|
||||
'SparseSiLU': 'nonlinearity',
|
||||
'SparseGELU': 'nonlinearity',
|
||||
'SparseActivation': 'nonlinearity',
|
||||
'SparseLinear': 'linear',
|
||||
'sparse_scaled_dot_product_attention': 'attention',
|
||||
'SerializeMode': 'attention',
|
||||
'sparse_serialized_scaled_dot_product_self_attention': 'attention',
|
||||
'sparse_windowed_scaled_dot_product_self_attention': 'attention',
|
||||
'SparseMultiHeadAttention': 'attention',
|
||||
'SparseConv3d': 'conv',
|
||||
'SparseInverseConv3d': 'conv',
|
||||
'SparseDownsample': 'spatial',
|
||||
'SparseUpsample': 'spatial',
|
||||
'SparseSubdivide' : 'spatial'
|
||||
}
|
||||
|
||||
__submodules = ['transformer']
|
||||
|
||||
__all__ = list(__attributes.keys()) + __submodules
|
||||
|
||||
def __getattr__(name):
|
||||
if name not in globals():
|
||||
if name in __attributes:
|
||||
module_name = __attributes[name]
|
||||
module = importlib.import_module(f".{module_name}", __name__)
|
||||
globals()[name] = getattr(module, name)
|
||||
elif name in __submodules:
|
||||
module = importlib.import_module(f".{name}", __name__)
|
||||
globals()[name] = module
|
||||
else:
|
||||
raise AttributeError(f"module {__name__} has no attribute {name}")
|
||||
return globals()[name]
|
||||
|
||||
|
||||
# For Pylance
|
||||
if __name__ == '__main__':
|
||||
from .basic import *
|
||||
from .norm import *
|
||||
from .nonlinearity import *
|
||||
from .linear import *
|
||||
from .attention import *
|
||||
from .conv import *
|
||||
from .spatial import *
|
||||
import transformer
|
||||
@@ -0,0 +1,31 @@
|
||||
from .full_attn import *
|
||||
from .serialized_attn import *
|
||||
from .windowed_attn import *
|
||||
from .modules import *
|
||||
import os
|
||||
import logging
|
||||
from typing import Literal
|
||||
from trellis.backend_config import (
|
||||
get_attention_backend,
|
||||
get_debug_mode,
|
||||
)
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#ATTN = get_attention_backend()
|
||||
ATTN = get_attention_backend()
|
||||
DEBUG = get_debug_mode()
|
||||
|
||||
def __from_env():
|
||||
"""Read current backend configuration"""
|
||||
#global ATTN
|
||||
global ATTN
|
||||
global DEBUG
|
||||
|
||||
# Get current settings from central config
|
||||
#ATTN =
|
||||
ATTN = get_attention_backend()
|
||||
DEBUG = get_debug_mode()
|
||||
|
||||
print(f"[ATTENTION] sparse backend: {ATTN}")
|
||||
@@ -0,0 +1,236 @@
|
||||
#trellis\modules\sparse\attention\full_attn.py
|
||||
from typing import *
|
||||
import torch
|
||||
import math
|
||||
from .. import SparseTensor
|
||||
from trellis.backend_config import (
|
||||
get_attention_backend,
|
||||
get_debug_mode,
|
||||
get_available_backends
|
||||
)
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Get configuration from central config
|
||||
ATTN = get_attention_backend()
|
||||
DEBUG = get_debug_mode()
|
||||
|
||||
# Get available backends and import if active
|
||||
available_backends = get_available_backends()
|
||||
|
||||
if ATTN not in ['xformers', 'flash_attn']:
|
||||
logger.warning(f"Attention backend {ATTN} not supported for sparse attention. Only 'xformers' and 'flash_attn' are available. Defaulting to 'flash_attn'")
|
||||
ATTN = 'flash_attn'
|
||||
|
||||
if ATTN == 'xformers' and available_backends['xformers']:
|
||||
import xformers.ops as xops
|
||||
elif ATTN == 'flash_attn' and available_backends['flash_attn']:
|
||||
import flash_attn
|
||||
else:
|
||||
raise ImportError(f"Could not import {ATTN}. Please install either xformers or flash-attn for sparse attention support.")
|
||||
|
||||
|
||||
|
||||
__all__ = [
|
||||
'sparse_scaled_dot_product_attention',
|
||||
]
|
||||
|
||||
|
||||
@overload
|
||||
def sparse_scaled_dot_product_attention(qkv: SparseTensor) -> SparseTensor:
|
||||
"""
|
||||
Apply scaled dot product attention to a sparse tensor.
|
||||
|
||||
Args:
|
||||
qkv (SparseTensor): A [N, *, 3, H, C] sparse tensor containing Qs, Ks, and Vs.
|
||||
"""
|
||||
...
|
||||
|
||||
@overload
|
||||
def sparse_scaled_dot_product_attention(q: SparseTensor, kv: Union[SparseTensor, torch.Tensor]) -> SparseTensor:
|
||||
"""
|
||||
Apply scaled dot product attention to a sparse tensor.
|
||||
|
||||
Args:
|
||||
q (SparseTensor): A [N, *, H, C] sparse tensor containing Qs.
|
||||
kv (SparseTensor or torch.Tensor): A [N, *, 2, H, C] sparse tensor or a [N, L, 2, H, C] dense tensor containing Ks and Vs.
|
||||
"""
|
||||
...
|
||||
|
||||
@overload
|
||||
def sparse_scaled_dot_product_attention(q: torch.Tensor, kv: SparseTensor) -> torch.Tensor:
|
||||
"""
|
||||
Apply scaled dot product attention to a sparse tensor.
|
||||
|
||||
Args:
|
||||
q (SparseTensor): A [N, L, H, C] dense tensor containing Qs.
|
||||
kv (SparseTensor or torch.Tensor): A [N, *, 2, H, C] sparse tensor containing Ks and Vs.
|
||||
"""
|
||||
...
|
||||
|
||||
@overload
|
||||
def sparse_scaled_dot_product_attention(q: SparseTensor, k: SparseTensor, v: SparseTensor) -> SparseTensor:
|
||||
"""
|
||||
Apply scaled dot product attention to a sparse tensor.
|
||||
|
||||
Args:
|
||||
q (SparseTensor): A [N, *, H, Ci] sparse tensor containing Qs.
|
||||
k (SparseTensor): A [N, *, H, Ci] sparse tensor containing Ks.
|
||||
v (SparseTensor): A [N, *, H, Co] sparse tensor containing Vs.
|
||||
|
||||
Note:
|
||||
k and v are assumed to have the same coordinate map.
|
||||
"""
|
||||
...
|
||||
|
||||
@overload
|
||||
def sparse_scaled_dot_product_attention(q: SparseTensor, k: torch.Tensor, v: torch.Tensor) -> SparseTensor:
|
||||
"""
|
||||
Apply scaled dot product attention to a sparse tensor.
|
||||
|
||||
Args:
|
||||
q (SparseTensor): A [N, *, H, Ci] sparse tensor containing Qs.
|
||||
k (torch.Tensor): A [N, L, H, Ci] dense tensor containing Ks.
|
||||
v (torch.Tensor): A [N, L, H, Co] dense tensor containing Vs.
|
||||
"""
|
||||
...
|
||||
|
||||
@overload
|
||||
def sparse_scaled_dot_product_attention(q: torch.Tensor, k: SparseTensor, v: SparseTensor) -> torch.Tensor:
|
||||
"""
|
||||
Apply scaled dot product attention to a sparse tensor.
|
||||
|
||||
Args:
|
||||
q (torch.Tensor): A [N, L, H, Ci] dense tensor containing Qs.
|
||||
k (SparseTensor): A [N, *, H, Ci] sparse tensor containing Ks.
|
||||
v (SparseTensor): A [N, *, H, Co] sparse tensor containing Vs.
|
||||
"""
|
||||
...
|
||||
|
||||
def sparse_scaled_dot_product_attention(*args, **kwargs):
|
||||
arg_names_dict = {
|
||||
1: ['qkv'],
|
||||
2: ['q', 'kv'],
|
||||
3: ['q', 'k', 'v']
|
||||
}
|
||||
num_all_args = len(args) + len(kwargs)
|
||||
assert num_all_args in arg_names_dict, f"Invalid number of arguments, got {num_all_args}, expected 1, 2, or 3"
|
||||
for key in arg_names_dict[num_all_args][len(args):]:
|
||||
assert key in kwargs, f"Missing argument {key}"
|
||||
|
||||
if num_all_args == 1:
|
||||
qkv = args[0] if len(args) > 0 else kwargs['qkv']
|
||||
assert isinstance(qkv, SparseTensor), f"qkv must be a SparseTensor, got {type(qkv)}"
|
||||
assert len(qkv.shape) == 4 and qkv.shape[1] == 3, f"Invalid shape for qkv, got {qkv.shape}, expected [N, *, 3, H, C]"
|
||||
device = qkv.device
|
||||
|
||||
s = qkv
|
||||
q_seqlen = [qkv.layout[i].stop - qkv.layout[i].start for i in range(qkv.shape[0])]
|
||||
kv_seqlen = q_seqlen
|
||||
qkv = qkv.feats # [T, 3, H, C]
|
||||
|
||||
elif num_all_args == 2:
|
||||
q = args[0] if len(args) > 0 else kwargs['q']
|
||||
kv = args[1] if len(args) > 1 else kwargs['kv']
|
||||
assert isinstance(q, SparseTensor) and isinstance(kv, (SparseTensor, torch.Tensor)) or \
|
||||
isinstance(q, torch.Tensor) and isinstance(kv, SparseTensor), \
|
||||
f"Invalid types, got {type(q)} and {type(kv)}"
|
||||
assert q.shape[0] == kv.shape[0], f"Batch size mismatch, got {q.shape[0]} and {kv.shape[0]}"
|
||||
device = q.device
|
||||
|
||||
if isinstance(q, SparseTensor):
|
||||
assert len(q.shape) == 3, f"Invalid shape for q, got {q.shape}, expected [N, *, H, C]"
|
||||
s = q
|
||||
q_seqlen = [q.layout[i].stop - q.layout[i].start for i in range(q.shape[0])]
|
||||
q = q.feats # [T_Q, H, C]
|
||||
else:
|
||||
assert len(q.shape) == 4, f"Invalid shape for q, got {q.shape}, expected [N, L, H, C]"
|
||||
s = None
|
||||
N, L, H, C = q.shape
|
||||
q_seqlen = [L] * N
|
||||
q = q.reshape(N * L, H, C) # [T_Q, H, C]
|
||||
|
||||
if isinstance(kv, SparseTensor):
|
||||
assert len(kv.shape) == 4 and kv.shape[1] == 2, f"Invalid shape for kv, got {kv.shape}, expected [N, *, 2, H, C]"
|
||||
kv_seqlen = [kv.layout[i].stop - kv.layout[i].start for i in range(kv.shape[0])]
|
||||
kv = kv.feats # [T_KV, 2, H, C]
|
||||
else:
|
||||
assert len(kv.shape) == 5, f"Invalid shape for kv, got {kv.shape}, expected [N, L, 2, H, C]"
|
||||
N, L, _, H, C = kv.shape
|
||||
kv_seqlen = [L] * N
|
||||
kv = kv.reshape(N * L, 2, H, C) # [T_KV, 2, H, C]
|
||||
|
||||
elif num_all_args == 3:
|
||||
q = args[0] if len(args) > 0 else kwargs['q']
|
||||
k = args[1] if len(args) > 1 else kwargs['k']
|
||||
v = args[2] if len(args) > 2 else kwargs['v']
|
||||
assert isinstance(q, SparseTensor) and isinstance(k, (SparseTensor, torch.Tensor)) and type(k) == type(v) or \
|
||||
isinstance(q, torch.Tensor) and isinstance(k, SparseTensor) and isinstance(v, SparseTensor), \
|
||||
f"Invalid types, got {type(q)}, {type(k)}, and {type(v)}"
|
||||
assert q.shape[0] == k.shape[0] == v.shape[0], f"Batch size mismatch, got {q.shape[0]}, {k.shape[0]}, and {v.shape[0]}"
|
||||
device = q.device
|
||||
|
||||
if isinstance(q, SparseTensor):
|
||||
assert len(q.shape) == 3, f"Invalid shape for q, got {q.shape}, expected [N, *, H, Ci]"
|
||||
s = q
|
||||
q_seqlen = [q.layout[i].stop - q.layout[i].start for i in range(q.shape[0])]
|
||||
q = q.feats # [T_Q, H, Ci]
|
||||
else:
|
||||
assert len(q.shape) == 4, f"Invalid shape for q, got {q.shape}, expected [N, L, H, Ci]"
|
||||
s = None
|
||||
N, L, H, CI = q.shape
|
||||
q_seqlen = [L] * N
|
||||
q = q.reshape(N * L, H, CI) # [T_Q, H, Ci]
|
||||
|
||||
if isinstance(k, SparseTensor):
|
||||
assert len(k.shape) == 3, f"Invalid shape for k, got {k.shape}, expected [N, *, H, Ci]"
|
||||
assert len(v.shape) == 3, f"Invalid shape for v, got {v.shape}, expected [N, *, H, Co]"
|
||||
kv_seqlen = [k.layout[i].stop - k.layout[i].start for i in range(k.shape[0])]
|
||||
k = k.feats # [T_KV, H, Ci]
|
||||
v = v.feats # [T_KV, H, Co]
|
||||
else:
|
||||
assert len(k.shape) == 4, f"Invalid shape for k, got {k.shape}, expected [N, L, H, Ci]"
|
||||
assert len(v.shape) == 4, f"Invalid shape for v, got {v.shape}, expected [N, L, H, Co]"
|
||||
N, L, H, CI, CO = *k.shape, v.shape[-1]
|
||||
kv_seqlen = [L] * N
|
||||
k = k.reshape(N * L, H, CI) # [T_KV, H, Ci]
|
||||
v = v.reshape(N * L, H, CO) # [T_KV, H, Co]
|
||||
|
||||
if DEBUG:
|
||||
if s is not None:
|
||||
for i in range(s.shape[0]):
|
||||
assert (s.coords[s.layout[i]] == i).all(), f"SparseScaledDotProductSelfAttention: batch index mismatch"
|
||||
if num_all_args in [2, 3]:
|
||||
assert q.shape[:2] == [1, sum(q_seqlen)], f"SparseScaledDotProductSelfAttention: q shape mismatch"
|
||||
if num_all_args == 3:
|
||||
assert k.shape[:2] == [1, sum(kv_seqlen)], f"SparseScaledDotProductSelfAttention: k shape mismatch"
|
||||
assert v.shape[:2] == [1, sum(kv_seqlen)], f"SparseScaledDotProductSelfAttention: v shape mismatch"
|
||||
|
||||
if ATTN == 'xformers':
|
||||
if num_all_args == 1:
|
||||
q, k, v = qkv.unbind(dim=1)
|
||||
elif num_all_args == 2:
|
||||
k, v = kv.unbind(dim=1)
|
||||
q = q.unsqueeze(0)
|
||||
k = k.unsqueeze(0)
|
||||
v = v.unsqueeze(0)
|
||||
mask = xops.fmha.BlockDiagonalMask.from_seqlens(q_seqlen, kv_seqlen)
|
||||
out = xops.memory_efficient_attention(q, k, v, mask)[0]
|
||||
elif ATTN == 'flash_attn':
|
||||
cu_seqlens_q = torch.cat([torch.tensor([0]), torch.cumsum(torch.tensor(q_seqlen), dim=0)]).int().to(device)
|
||||
if num_all_args in [2, 3]:
|
||||
cu_seqlens_kv = torch.cat([torch.tensor([0]), torch.cumsum(torch.tensor(kv_seqlen), dim=0)]).int().to(device)
|
||||
if num_all_args == 1:
|
||||
out = flash_attn.flash_attn_varlen_qkvpacked_func(qkv, cu_seqlens_q, max(q_seqlen))
|
||||
elif num_all_args == 2:
|
||||
out = flash_attn.flash_attn_varlen_kvpacked_func(q, kv, cu_seqlens_q, cu_seqlens_kv, max(q_seqlen), max(kv_seqlen))
|
||||
elif num_all_args == 3:
|
||||
out = flash_attn.flash_attn_varlen_func(q, k, v, cu_seqlens_q, cu_seqlens_kv, max(q_seqlen), max(kv_seqlen))
|
||||
else:
|
||||
raise ValueError(f"Unknown attention module: {ATTN}")
|
||||
|
||||
if s is not None:
|
||||
return s.replace(out)
|
||||
else:
|
||||
return out.reshape(N, L, H, -1)
|
||||
@@ -0,0 +1,139 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from .. import SparseTensor
|
||||
from .full_attn import sparse_scaled_dot_product_attention
|
||||
from .serialized_attn import SerializeMode, sparse_serialized_scaled_dot_product_self_attention
|
||||
from .windowed_attn import sparse_windowed_scaled_dot_product_self_attention
|
||||
from ...attention import RotaryPositionEmbedder
|
||||
|
||||
|
||||
class SparseMultiHeadRMSNorm(nn.Module):
|
||||
def __init__(self, dim: int, heads: int):
|
||||
super().__init__()
|
||||
self.scale = dim ** 0.5
|
||||
self.gamma = nn.Parameter(torch.ones(heads, dim))
|
||||
|
||||
def forward(self, x: Union[SparseTensor, torch.Tensor]) -> Union[SparseTensor, torch.Tensor]:
|
||||
x_type = x.dtype
|
||||
x = x.float()
|
||||
if isinstance(x, SparseTensor):
|
||||
x = x.replace(F.normalize(x.feats, dim=-1))
|
||||
else:
|
||||
x = F.normalize(x, dim=-1)
|
||||
return (x * self.gamma * self.scale).to(x_type)
|
||||
|
||||
|
||||
class SparseMultiHeadAttention(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
num_heads: int,
|
||||
ctx_channels: Optional[int] = None,
|
||||
type: Literal["self", "cross"] = "self",
|
||||
attn_mode: Literal["full", "serialized", "windowed"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
shift_sequence: Optional[int] = None,
|
||||
shift_window: Optional[Tuple[int, int, int]] = None,
|
||||
serialize_mode: Optional[SerializeMode] = None,
|
||||
qkv_bias: bool = True,
|
||||
use_rope: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
assert channels % num_heads == 0
|
||||
assert type in ["self", "cross"], f"Invalid attention type: {type}"
|
||||
assert attn_mode in ["full", "serialized", "windowed"], f"Invalid attention mode: {attn_mode}"
|
||||
assert type == "self" or attn_mode == "full", "Cross-attention only supports full attention"
|
||||
assert type == "self" or use_rope is False, "Rotary position embeddings only supported for self-attention"
|
||||
self.channels = channels
|
||||
self.ctx_channels = ctx_channels if ctx_channels is not None else channels
|
||||
self.num_heads = num_heads
|
||||
self._type = type
|
||||
self.attn_mode = attn_mode
|
||||
self.window_size = window_size
|
||||
self.shift_sequence = shift_sequence
|
||||
self.shift_window = shift_window
|
||||
self.serialize_mode = serialize_mode
|
||||
self.use_rope = use_rope
|
||||
self.qk_rms_norm = qk_rms_norm
|
||||
|
||||
if self._type == "self":
|
||||
self.to_qkv = nn.Linear(channels, channels * 3, bias=qkv_bias)
|
||||
else:
|
||||
self.to_q = nn.Linear(channels, channels, bias=qkv_bias)
|
||||
self.to_kv = nn.Linear(self.ctx_channels, channels * 2, bias=qkv_bias)
|
||||
|
||||
if self.qk_rms_norm:
|
||||
self.q_rms_norm = SparseMultiHeadRMSNorm(channels // num_heads, num_heads)
|
||||
self.k_rms_norm = SparseMultiHeadRMSNorm(channels // num_heads, num_heads)
|
||||
|
||||
self.to_out = nn.Linear(channels, channels)
|
||||
|
||||
if use_rope:
|
||||
self.rope = RotaryPositionEmbedder(channels)
|
||||
|
||||
@staticmethod
|
||||
def _linear(module: nn.Linear, x: Union[SparseTensor, torch.Tensor]) -> Union[SparseTensor, torch.Tensor]:
|
||||
if isinstance(x, SparseTensor):
|
||||
return x.replace(module(x.feats))
|
||||
else:
|
||||
return module(x)
|
||||
|
||||
@staticmethod
|
||||
def _reshape_chs(x: Union[SparseTensor, torch.Tensor], shape: Tuple[int, ...]) -> Union[SparseTensor, torch.Tensor]:
|
||||
if isinstance(x, SparseTensor):
|
||||
return x.reshape(*shape)
|
||||
else:
|
||||
return x.reshape(*x.shape[:2], *shape)
|
||||
|
||||
def _fused_pre(self, x: Union[SparseTensor, torch.Tensor], num_fused: int) -> Union[SparseTensor, torch.Tensor]:
|
||||
if isinstance(x, SparseTensor):
|
||||
x_feats = x.feats.unsqueeze(0)
|
||||
else:
|
||||
x_feats = x
|
||||
x_feats = x_feats.reshape(*x_feats.shape[:2], num_fused, self.num_heads, -1)
|
||||
return x.replace(x_feats.squeeze(0)) if isinstance(x, SparseTensor) else x_feats
|
||||
|
||||
def _rope(self, qkv: SparseTensor) -> SparseTensor:
|
||||
q, k, v = qkv.feats.unbind(dim=1) # [T, H, C]
|
||||
q, k = self.rope(q, k, qkv.coords[:, 1:])
|
||||
qkv = qkv.replace(torch.stack([q, k, v], dim=1))
|
||||
return qkv
|
||||
|
||||
def forward(self, x: Union[SparseTensor, torch.Tensor], context: Optional[Union[SparseTensor, torch.Tensor]] = None) -> Union[SparseTensor, torch.Tensor]:
|
||||
if self._type == "self":
|
||||
qkv = self._linear(self.to_qkv, x)
|
||||
qkv = self._fused_pre(qkv, num_fused=3)
|
||||
if self.use_rope:
|
||||
qkv = self._rope(qkv)
|
||||
if self.qk_rms_norm:
|
||||
q, k, v = qkv.unbind(dim=1)
|
||||
q = self.q_rms_norm(q)
|
||||
k = self.k_rms_norm(k)
|
||||
qkv = qkv.replace(torch.stack([q.feats, k.feats, v.feats], dim=1))
|
||||
if self.attn_mode == "full":
|
||||
h = sparse_scaled_dot_product_attention(qkv)
|
||||
elif self.attn_mode == "serialized":
|
||||
h = sparse_serialized_scaled_dot_product_self_attention(
|
||||
qkv, self.window_size, serialize_mode=self.serialize_mode, shift_sequence=self.shift_sequence, shift_window=self.shift_window
|
||||
)
|
||||
elif self.attn_mode == "windowed":
|
||||
h = sparse_windowed_scaled_dot_product_self_attention(
|
||||
qkv, self.window_size, shift_window=self.shift_window
|
||||
)
|
||||
else:
|
||||
q = self._linear(self.to_q, x)
|
||||
q = self._reshape_chs(q, (self.num_heads, -1))
|
||||
kv = self._linear(self.to_kv, context)
|
||||
kv = self._fused_pre(kv, num_fused=2)
|
||||
if self.qk_rms_norm:
|
||||
q = self.q_rms_norm(q)
|
||||
k, v = kv.unbind(dim=1)
|
||||
k = self.k_rms_norm(k)
|
||||
kv = kv.replace(torch.stack([k.feats, v.feats], dim=1))
|
||||
h = sparse_scaled_dot_product_attention(q, kv)
|
||||
h = self._reshape_chs(h, (-1,))
|
||||
h = self._linear(self.to_out, h)
|
||||
return h
|
||||
@@ -0,0 +1,211 @@
|
||||
from typing import *
|
||||
from enum import Enum
|
||||
import torch
|
||||
import math
|
||||
from .. import SparseTensor
|
||||
from trellis.backend_config import (
|
||||
get_attention_backend,
|
||||
get_debug_mode,
|
||||
get_available_backends
|
||||
)
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Get configuration from central config
|
||||
ATTN = get_attention_backend()
|
||||
DEBUG = get_debug_mode()
|
||||
|
||||
# Get available backends and import if active
|
||||
available_backends = get_available_backends()
|
||||
|
||||
if ATTN not in ['xformers', 'flash_attn']:
|
||||
logger.warning(f"Attention backend {ATTN} not supported for sparse attention. Only 'xformers' and 'flash_attn' are available. Defaulting to 'flash_attn'")
|
||||
ATTN = 'flash_attn'
|
||||
|
||||
if ATTN == 'xformers' and available_backends['xformers']:
|
||||
import xformers.ops as xops
|
||||
elif ATTN == 'flash_attn' and available_backends['flash_attn']:
|
||||
import flash_attn
|
||||
else:
|
||||
raise ImportError(f"Could not import {ATTN}. Please install either xformers or flash-attn for sparse attention support.")
|
||||
|
||||
|
||||
__all__ = [
|
||||
'sparse_serialized_scaled_dot_product_self_attention',
|
||||
]
|
||||
|
||||
|
||||
class SerializeMode(Enum):
|
||||
Z_ORDER = 0
|
||||
Z_ORDER_TRANSPOSED = 1
|
||||
HILBERT = 2
|
||||
HILBERT_TRANSPOSED = 3
|
||||
|
||||
|
||||
SerializeModes = [
|
||||
SerializeMode.Z_ORDER,
|
||||
SerializeMode.Z_ORDER_TRANSPOSED,
|
||||
SerializeMode.HILBERT,
|
||||
SerializeMode.HILBERT_TRANSPOSED
|
||||
]
|
||||
|
||||
|
||||
def calc_serialization(
|
||||
tensor: SparseTensor,
|
||||
window_size: int,
|
||||
serialize_mode: SerializeMode = SerializeMode.Z_ORDER,
|
||||
shift_sequence: int = 0,
|
||||
shift_window: Tuple[int, int, int] = (0, 0, 0)
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, List[int]]:
|
||||
"""
|
||||
Calculate serialization and partitioning for a set of coordinates.
|
||||
|
||||
Args:
|
||||
tensor (SparseTensor): The input tensor.
|
||||
window_size (int): The window size to use.
|
||||
serialize_mode (SerializeMode): The serialization mode to use.
|
||||
shift_sequence (int): The shift of serialized sequence.
|
||||
shift_window (Tuple[int, int, int]): The shift of serialized coordinates.
|
||||
|
||||
Returns:
|
||||
(torch.Tensor, torch.Tensor): Forwards and backwards indices.
|
||||
"""
|
||||
fwd_indices = []
|
||||
bwd_indices = []
|
||||
seq_lens = []
|
||||
seq_batch_indices = []
|
||||
offsets = [0]
|
||||
|
||||
if 'vox2seq' not in globals():
|
||||
import vox2seq
|
||||
|
||||
# Serialize the input
|
||||
serialize_coords = tensor.coords[:, 1:].clone()
|
||||
serialize_coords += torch.tensor(shift_window, dtype=torch.int32, device=tensor.device).reshape(1, 3)
|
||||
if serialize_mode == SerializeMode.Z_ORDER:
|
||||
code = vox2seq.encode(serialize_coords, mode='z_order', permute=[0, 1, 2])
|
||||
elif serialize_mode == SerializeMode.Z_ORDER_TRANSPOSED:
|
||||
code = vox2seq.encode(serialize_coords, mode='z_order', permute=[1, 0, 2])
|
||||
elif serialize_mode == SerializeMode.HILBERT:
|
||||
code = vox2seq.encode(serialize_coords, mode='hilbert', permute=[0, 1, 2])
|
||||
elif serialize_mode == SerializeMode.HILBERT_TRANSPOSED:
|
||||
code = vox2seq.encode(serialize_coords, mode='hilbert', permute=[1, 0, 2])
|
||||
else:
|
||||
raise ValueError(f"Unknown serialize mode: {serialize_mode}")
|
||||
|
||||
for bi, s in enumerate(tensor.layout):
|
||||
num_points = s.stop - s.start
|
||||
num_windows = (num_points + window_size - 1) // window_size
|
||||
valid_window_size = num_points / num_windows
|
||||
to_ordered = torch.argsort(code[s.start:s.stop])
|
||||
if num_windows == 1:
|
||||
fwd_indices.append(to_ordered)
|
||||
bwd_indices.append(torch.zeros_like(to_ordered).scatter_(0, to_ordered, torch.arange(num_points, device=tensor.device)))
|
||||
fwd_indices[-1] += s.start
|
||||
bwd_indices[-1] += offsets[-1]
|
||||
seq_lens.append(num_points)
|
||||
seq_batch_indices.append(bi)
|
||||
offsets.append(offsets[-1] + seq_lens[-1])
|
||||
else:
|
||||
# Partition the input
|
||||
offset = 0
|
||||
mids = [(i + 0.5) * valid_window_size + shift_sequence for i in range(num_windows)]
|
||||
split = [math.floor(i * valid_window_size + shift_sequence) for i in range(num_windows + 1)]
|
||||
bwd_index = torch.zeros((num_points,), dtype=torch.int64, device=tensor.device)
|
||||
for i in range(num_windows):
|
||||
mid = mids[i]
|
||||
valid_start = split[i]
|
||||
valid_end = split[i + 1]
|
||||
padded_start = math.floor(mid - 0.5 * window_size)
|
||||
padded_end = padded_start + window_size
|
||||
fwd_indices.append(to_ordered[torch.arange(padded_start, padded_end, device=tensor.device) % num_points])
|
||||
offset += valid_start - padded_start
|
||||
bwd_index.scatter_(0, fwd_indices[-1][valid_start-padded_start:valid_end-padded_start], torch.arange(offset, offset + valid_end - valid_start, device=tensor.device))
|
||||
offset += padded_end - valid_start
|
||||
fwd_indices[-1] += s.start
|
||||
seq_lens.extend([window_size] * num_windows)
|
||||
seq_batch_indices.extend([bi] * num_windows)
|
||||
bwd_indices.append(bwd_index + offsets[-1])
|
||||
offsets.append(offsets[-1] + num_windows * window_size)
|
||||
|
||||
fwd_indices = torch.cat(fwd_indices)
|
||||
bwd_indices = torch.cat(bwd_indices)
|
||||
|
||||
return fwd_indices, bwd_indices, seq_lens, seq_batch_indices
|
||||
|
||||
|
||||
def sparse_serialized_scaled_dot_product_self_attention(
|
||||
qkv: SparseTensor,
|
||||
window_size: int,
|
||||
serialize_mode: SerializeMode = SerializeMode.Z_ORDER,
|
||||
shift_sequence: int = 0,
|
||||
shift_window: Tuple[int, int, int] = (0, 0, 0)
|
||||
) -> SparseTensor:
|
||||
"""
|
||||
Apply serialized scaled dot product self attention to a sparse tensor.
|
||||
|
||||
Args:
|
||||
qkv (SparseTensor): [N, *, 3, H, C] sparse tensor containing Qs, Ks, and Vs.
|
||||
window_size (int): The window size to use.
|
||||
serialize_mode (SerializeMode): The serialization mode to use.
|
||||
shift_sequence (int): The shift of serialized sequence.
|
||||
shift_window (Tuple[int, int, int]): The shift of serialized coordinates.
|
||||
shift (int): The shift to use.
|
||||
"""
|
||||
assert len(qkv.shape) == 4 and qkv.shape[1] == 3, f"Invalid shape for qkv, got {qkv.shape}, expected [N, *, 3, H, C]"
|
||||
|
||||
serialization_spatial_cache_name = f'serialization_{serialize_mode}_{window_size}_{shift_sequence}_{shift_window}'
|
||||
serialization_spatial_cache = qkv.get_spatial_cache(serialization_spatial_cache_name)
|
||||
if serialization_spatial_cache is None:
|
||||
fwd_indices, bwd_indices, seq_lens, seq_batch_indices = calc_serialization(qkv, window_size, serialize_mode, shift_sequence, shift_window)
|
||||
qkv.register_spatial_cache(serialization_spatial_cache_name, (fwd_indices, bwd_indices, seq_lens, seq_batch_indices))
|
||||
else:
|
||||
fwd_indices, bwd_indices, seq_lens, seq_batch_indices = serialization_spatial_cache
|
||||
|
||||
M = fwd_indices.shape[0]
|
||||
T = qkv.feats.shape[0]
|
||||
H = qkv.feats.shape[2]
|
||||
C = qkv.feats.shape[3]
|
||||
|
||||
qkv_feats = qkv.feats[fwd_indices] # [M, 3, H, C]
|
||||
|
||||
if DEBUG:
|
||||
start = 0
|
||||
qkv_coords = qkv.coords[fwd_indices]
|
||||
for i in range(len(seq_lens)):
|
||||
assert (qkv_coords[start:start+seq_lens[i], 0] == seq_batch_indices[i]).all(), f"SparseWindowedScaledDotProductSelfAttention: batch index mismatch"
|
||||
start += seq_lens[i]
|
||||
|
||||
if all([seq_len == window_size for seq_len in seq_lens]):
|
||||
B = len(seq_lens)
|
||||
N = window_size
|
||||
qkv_feats = qkv_feats.reshape(B, N, 3, H, C)
|
||||
if ATTN == 'xformers':
|
||||
q, k, v = qkv_feats.unbind(dim=2) # [B, N, H, C]
|
||||
out = xops.memory_efficient_attention(q, k, v) # [B, N, H, C]
|
||||
elif ATTN == 'flash_attn':
|
||||
out = flash_attn.flash_attn_qkvpacked_func(qkv_feats) # [B, N, H, C]
|
||||
else:
|
||||
raise ValueError(f"Unknown attention module: {ATTN}")
|
||||
out = out.reshape(B * N, H, C) # [M, H, C]
|
||||
else:
|
||||
if ATTN == 'xformers':
|
||||
q, k, v = qkv_feats.unbind(dim=1) # [M, H, C]
|
||||
q = q.unsqueeze(0) # [1, M, H, C]
|
||||
k = k.unsqueeze(0) # [1, M, H, C]
|
||||
v = v.unsqueeze(0) # [1, M, H, C]
|
||||
mask = xops.fmha.BlockDiagonalMask.from_seqlens(seq_lens)
|
||||
out = xops.memory_efficient_attention(q, k, v, mask)[0] # [M, H, C]
|
||||
elif ATTN == 'flash_attn':
|
||||
cu_seqlens = torch.cat([torch.tensor([0]), torch.cumsum(torch.tensor(seq_lens), dim=0)], dim=0) \
|
||||
.to(qkv.device).int()
|
||||
out = flash_attn.flash_attn_varlen_qkvpacked_func(qkv_feats, cu_seqlens, max(seq_lens)) # [M, H, C]
|
||||
|
||||
out = out[bwd_indices] # [T, H, C]
|
||||
|
||||
if DEBUG:
|
||||
qkv_coords = qkv_coords[bwd_indices]
|
||||
assert torch.equal(qkv_coords, qkv.coords), "SparseWindowedScaledDotProductSelfAttention: coordinate mismatch"
|
||||
|
||||
return qkv.replace(out)
|
||||
@@ -0,0 +1,157 @@
|
||||
from typing import *
|
||||
from enum import Enum
|
||||
import math
|
||||
import os
|
||||
import logging
|
||||
import torch
|
||||
from .. import SparseTensor
|
||||
from trellis.backend_config import (
|
||||
get_attention_backend,
|
||||
get_debug_mode,
|
||||
get_available_backends
|
||||
)
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Get configuration from central config
|
||||
# Get configuration from central config
|
||||
ATTN = get_attention_backend()
|
||||
DEBUG = get_debug_mode()
|
||||
|
||||
# Get available backends and import if active
|
||||
available_backends = get_available_backends()
|
||||
|
||||
if ATTN not in ['xformers', 'flash_attn']:
|
||||
logger.warning(f"Attention backend {ATTN} not supported for sparse attention. Only 'xformers' and 'flash_attn' are available. Defaulting to 'flash_attn'")
|
||||
ATTN = 'flash_attn'
|
||||
|
||||
if ATTN == 'xformers' and available_backends['xformers']:
|
||||
import xformers.ops as xops
|
||||
elif ATTN == 'flash_attn' and available_backends['flash_attn']:
|
||||
import flash_attn
|
||||
else:
|
||||
raise ImportError(f"Could not import {ATTN}. Please install either xformers or flash-attn for sparse attention support.")
|
||||
|
||||
|
||||
__all__ = [
|
||||
'sparse_windowed_scaled_dot_product_self_attention',
|
||||
]
|
||||
|
||||
|
||||
def calc_window_partition(
|
||||
tensor: SparseTensor,
|
||||
window_size: Union[int, Tuple[int, ...]],
|
||||
shift_window: Union[int, Tuple[int, ...]] = 0
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, List[int], List[int]]:
|
||||
"""
|
||||
Calculate serialization and partitioning for a set of coordinates.
|
||||
|
||||
Args:
|
||||
tensor (SparseTensor): The input tensor.
|
||||
window_size (int): The window size to use.
|
||||
shift_window (Tuple[int, ...]): The shift of serialized coordinates.
|
||||
|
||||
Returns:
|
||||
(torch.Tensor): Forwards indices.
|
||||
(torch.Tensor): Backwards indices.
|
||||
(List[int]): Sequence lengths.
|
||||
(List[int]): Sequence batch indices.
|
||||
"""
|
||||
DIM = tensor.coords.shape[1] - 1
|
||||
shift_window = (shift_window,) * DIM if isinstance(shift_window, int) else shift_window
|
||||
window_size = (window_size,) * DIM if isinstance(window_size, int) else window_size
|
||||
shifted_coords = tensor.coords.clone().detach()
|
||||
shifted_coords[:, 1:] += torch.tensor(shift_window, device=tensor.device, dtype=torch.int32).unsqueeze(0)
|
||||
|
||||
MAX_COORDS = shifted_coords[:, 1:].max(dim=0).values.tolist()
|
||||
NUM_WINDOWS = [math.ceil((mc + 1) / ws) for mc, ws in zip(MAX_COORDS, window_size)]
|
||||
OFFSET = torch.cumprod(torch.tensor([1] + NUM_WINDOWS[::-1]), dim=0).tolist()[::-1]
|
||||
|
||||
shifted_coords[:, 1:] //= torch.tensor(window_size, device=tensor.device, dtype=torch.int32).unsqueeze(0)
|
||||
shifted_indices = (shifted_coords * torch.tensor(OFFSET, device=tensor.device, dtype=torch.int32).unsqueeze(0)).sum(dim=1)
|
||||
fwd_indices = torch.argsort(shifted_indices)
|
||||
bwd_indices = torch.empty_like(fwd_indices)
|
||||
bwd_indices[fwd_indices] = torch.arange(fwd_indices.shape[0], device=tensor.device)
|
||||
seq_lens = torch.bincount(shifted_indices)
|
||||
seq_batch_indices = torch.arange(seq_lens.shape[0], device=tensor.device, dtype=torch.int32) // OFFSET[0]
|
||||
mask = seq_lens != 0
|
||||
seq_lens = seq_lens[mask].tolist()
|
||||
seq_batch_indices = seq_batch_indices[mask].tolist()
|
||||
|
||||
return fwd_indices, bwd_indices, seq_lens, seq_batch_indices
|
||||
|
||||
|
||||
def sparse_windowed_scaled_dot_product_self_attention(
|
||||
qkv: SparseTensor,
|
||||
window_size: int,
|
||||
shift_window: Tuple[int, int, int] = (0, 0, 0)
|
||||
) -> SparseTensor:
|
||||
"""
|
||||
Apply windowed scaled dot product self attention to a sparse tensor.
|
||||
|
||||
Args:
|
||||
qkv (SparseTensor): [N, *, 3, H, C] sparse tensor containing Qs, Ks, and Vs.
|
||||
window_size (int): The window size to use.
|
||||
shift_window (Tuple[int, int, int]): The shift of serialized coordinates.
|
||||
shift (int): The shift to use.
|
||||
"""
|
||||
assert len(qkv.shape) == 4 and qkv.shape[1] == 3, f"Invalid shape for qkv, got {qkv.shape}, expected [N, *, 3, H, C]"
|
||||
|
||||
serialization_spatial_cache_name = f'window_partition_{window_size}_{shift_window}'
|
||||
serialization_spatial_cache = qkv.get_spatial_cache(serialization_spatial_cache_name)
|
||||
if serialization_spatial_cache is None:
|
||||
fwd_indices, bwd_indices, seq_lens, seq_batch_indices = calc_window_partition(qkv, window_size, shift_window)
|
||||
qkv.register_spatial_cache(serialization_spatial_cache_name, (fwd_indices, bwd_indices, seq_lens, seq_batch_indices))
|
||||
else:
|
||||
fwd_indices, bwd_indices, seq_lens, seq_batch_indices = serialization_spatial_cache
|
||||
|
||||
M = fwd_indices.shape[0]
|
||||
T = qkv.feats.shape[0]
|
||||
H = qkv.feats.shape[2]
|
||||
C = qkv.feats.shape[3]
|
||||
|
||||
qkv_feats = qkv.feats[fwd_indices] # [M, 3, H, C]
|
||||
|
||||
if DEBUG:
|
||||
start = 0
|
||||
qkv_coords = qkv.coords[fwd_indices]
|
||||
for i in range(len(seq_lens)):
|
||||
seq_coords = qkv_coords[start:start+seq_lens[i]]
|
||||
assert (seq_coords[:, 0] == seq_batch_indices[i]).all(), f"SparseWindowedScaledDotProductSelfAttention: batch index mismatch"
|
||||
assert (seq_coords[:, 1:].max(dim=0).values - seq_coords[:, 1:].min(dim=0).values < window_size).all(), \
|
||||
f"SparseWindowedScaledDotProductSelfAttention: window size exceeded"
|
||||
start += seq_lens[i]
|
||||
|
||||
if all([seq_len == window_size for seq_len in seq_lens]):
|
||||
B = len(seq_lens)
|
||||
N = window_size
|
||||
qkv_feats = qkv_feats.reshape(B, N, 3, H, C)
|
||||
if ATTN == 'xformers':
|
||||
q, k, v = qkv_feats.unbind(dim=2) # [B, N, H, C]
|
||||
out = xops.memory_efficient_attention(q, k, v) # [B, N, H, C]
|
||||
elif ATTN == 'flash_attn':
|
||||
out = flash_attn.flash_attn_qkvpacked_func(qkv_feats) # [B, N, H, C]
|
||||
else:
|
||||
raise ValueError(f"Unknown attention module: {ATTN}")
|
||||
out = out.reshape(B * N, H, C) # [M, H, C]
|
||||
else:
|
||||
if ATTN == 'xformers':
|
||||
q, k, v = qkv_feats.unbind(dim=1) # [M, H, C]
|
||||
q = q.unsqueeze(0) # [1, M, H, C]
|
||||
k = k.unsqueeze(0) # [1, M, H, C]
|
||||
v = v.unsqueeze(0) # [1, M, H, C]
|
||||
mask = xops.fmha.BlockDiagonalMask.from_seqlens(seq_lens)
|
||||
out = xops.memory_efficient_attention(q, k, v, mask)[0] # [M, H, C]
|
||||
elif ATTN == 'flash_attn':
|
||||
cu_seqlens = torch.cat([torch.tensor([0]), torch.cumsum(torch.tensor(seq_lens), dim=0)], dim=0) \
|
||||
.to(qkv.device).int()
|
||||
out = flash_attn.flash_attn_varlen_qkvpacked_func(qkv_feats, cu_seqlens, max(seq_lens)) # [M, H, C]
|
||||
|
||||
out = out[bwd_indices] # [T, H, C]
|
||||
|
||||
if DEBUG:
|
||||
qkv_coords = qkv_coords[bwd_indices]
|
||||
assert torch.equal(qkv_coords, qkv.coords), "SparseWindowedScaledDotProductSelfAttention: coordinate mismatch"
|
||||
|
||||
return qkv.replace(out)
|
||||
@@ -0,0 +1,466 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from trellis.backend_config import get_debug_mode, get_spconv_algo, get_sparse_backend
|
||||
|
||||
|
||||
SparseTensorData = None # Lazy import
|
||||
|
||||
DEBUG = get_debug_mode()
|
||||
|
||||
SPCONV_ALGO = get_spconv_algo()
|
||||
|
||||
BACKEND = get_sparse_backend()
|
||||
__all__ = [
|
||||
'SparseTensor',
|
||||
'sparse_batch_broadcast',
|
||||
'sparse_batch_op',
|
||||
'sparse_cat',
|
||||
'sparse_unbind',
|
||||
]
|
||||
|
||||
|
||||
class SparseTensor:
|
||||
"""
|
||||
Sparse tensor with support for both torchsparse and spconv backends.
|
||||
|
||||
Parameters:
|
||||
- feats (torch.Tensor): Features of the sparse tensor.
|
||||
- coords (torch.Tensor): Coordinates of the sparse tensor.
|
||||
- shape (torch.Size): Shape of the sparse tensor.
|
||||
- layout (List[slice]): Layout of the sparse tensor for each batch
|
||||
- data (SparseTensorData): Sparse tensor data used for convolusion
|
||||
|
||||
NOTE:
|
||||
- Data corresponding to a same batch should be contiguous.
|
||||
- Coords should be in [0, 1023]
|
||||
"""
|
||||
@overload
|
||||
def __init__(self, feats: torch.Tensor, coords: torch.Tensor, shape: Optional[torch.Size] = None, layout: Optional[List[slice]] = None, **kwargs): ...
|
||||
|
||||
@overload
|
||||
def __init__(self, data, shape: Optional[torch.Size] = None, layout: Optional[List[slice]] = None, **kwargs): ...
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Lazy import of sparse tensor backend
|
||||
global SparseTensorData
|
||||
if SparseTensorData is None:
|
||||
import importlib
|
||||
if BACKEND == 'torchsparse':
|
||||
SparseTensorData = importlib.import_module('torchsparse').SparseTensor
|
||||
elif BACKEND == 'spconv':
|
||||
SparseTensorData = importlib.import_module('spconv.pytorch').SparseConvTensor
|
||||
|
||||
method_id = 0
|
||||
if len(args) != 0:
|
||||
method_id = 0 if isinstance(args[0], torch.Tensor) else 1
|
||||
else:
|
||||
method_id = 1 if 'data' in kwargs else 0
|
||||
|
||||
if method_id == 0:
|
||||
feats, coords, shape, layout = args + (None,) * (4 - len(args))
|
||||
if 'feats' in kwargs:
|
||||
feats = kwargs['feats']
|
||||
del kwargs['feats']
|
||||
if 'coords' in kwargs:
|
||||
coords = kwargs['coords']
|
||||
del kwargs['coords']
|
||||
if 'shape' in kwargs:
|
||||
shape = kwargs['shape']
|
||||
del kwargs['shape']
|
||||
if 'layout' in kwargs:
|
||||
layout = kwargs['layout']
|
||||
del kwargs['layout']
|
||||
|
||||
if shape is None:
|
||||
shape = self.__cal_shape(feats, coords)
|
||||
if layout is None:
|
||||
layout = self.__cal_layout(coords, shape[0])
|
||||
if BACKEND == 'torchsparse':
|
||||
self.data = SparseTensorData(feats, coords, **kwargs)
|
||||
elif BACKEND == 'spconv':
|
||||
spatial_shape = list(coords.max(0)[0] + 1)[1:]
|
||||
self.data = SparseTensorData(feats.reshape(feats.shape[0], -1), coords, spatial_shape, shape[0], **kwargs)
|
||||
self.data._features = feats
|
||||
elif method_id == 1:
|
||||
data, shape, layout = args + (None,) * (3 - len(args))
|
||||
if 'data' in kwargs:
|
||||
data = kwargs['data']
|
||||
del kwargs['data']
|
||||
if 'shape' in kwargs:
|
||||
shape = kwargs['shape']
|
||||
del kwargs['shape']
|
||||
if 'layout' in kwargs:
|
||||
layout = kwargs['layout']
|
||||
del kwargs['layout']
|
||||
|
||||
self.data = data
|
||||
if shape is None:
|
||||
shape = self.__cal_shape(self.feats, self.coords)
|
||||
if layout is None:
|
||||
layout = self.__cal_layout(self.coords, shape[0])
|
||||
|
||||
self._shape = shape
|
||||
self._layout = layout
|
||||
self._scale = kwargs.get('scale', (1, 1, 1))
|
||||
self._spatial_cache = kwargs.get('spatial_cache', {})
|
||||
|
||||
if DEBUG:
|
||||
try:
|
||||
assert self.feats.shape[0] == self.coords.shape[0], f"Invalid feats shape: {self.feats.shape}, coords shape: {self.coords.shape}"
|
||||
assert self.shape == self.__cal_shape(self.feats, self.coords), f"Invalid shape: {self.shape}"
|
||||
assert self.layout == self.__cal_layout(self.coords, self.shape[0]), f"Invalid layout: {self.layout}"
|
||||
for i in range(self.shape[0]):
|
||||
assert torch.all(self.coords[self.layout[i], 0] == i), f"The data of batch {i} is not contiguous"
|
||||
except Exception as e:
|
||||
print('Debugging information:')
|
||||
print(f"- Shape: {self.shape}")
|
||||
print(f"- Layout: {self.layout}")
|
||||
print(f"- Scale: {self._scale}")
|
||||
print(f"- Coords: {self.coords}")
|
||||
raise e
|
||||
|
||||
def __cal_shape(self, feats, coords):
|
||||
shape = []
|
||||
shape.append(coords[:, 0].max().item() + 1)
|
||||
shape.extend([*feats.shape[1:]])
|
||||
return torch.Size(shape)
|
||||
|
||||
def __cal_layout(self, coords, batch_size):
|
||||
seq_len = torch.bincount(coords[:, 0], minlength=batch_size)
|
||||
offset = torch.cumsum(seq_len, dim=0)
|
||||
layout = [slice((offset[i] - seq_len[i]).item(), offset[i].item()) for i in range(batch_size)]
|
||||
return layout
|
||||
|
||||
@property
|
||||
def shape(self) -> torch.Size:
|
||||
return self._shape
|
||||
|
||||
def dim(self) -> int:
|
||||
return len(self.shape)
|
||||
|
||||
@property
|
||||
def layout(self) -> List[slice]:
|
||||
return self._layout
|
||||
|
||||
@property
|
||||
def feats(self) -> torch.Tensor:
|
||||
if BACKEND == 'torchsparse':
|
||||
return self.data.F
|
||||
elif BACKEND == 'spconv':
|
||||
return self.data.features
|
||||
|
||||
@feats.setter
|
||||
def feats(self, value: torch.Tensor):
|
||||
if BACKEND == 'torchsparse':
|
||||
self.data.F = value
|
||||
elif BACKEND == 'spconv':
|
||||
self.data.features = value
|
||||
|
||||
@property
|
||||
def coords(self) -> torch.Tensor:
|
||||
if BACKEND == 'torchsparse':
|
||||
return self.data.C
|
||||
elif BACKEND == 'spconv':
|
||||
return self.data.indices
|
||||
|
||||
@coords.setter
|
||||
def coords(self, value: torch.Tensor):
|
||||
if BACKEND == 'torchsparse':
|
||||
self.data.C = value
|
||||
elif BACKEND == 'spconv':
|
||||
self.data.indices = value
|
||||
|
||||
@property
|
||||
def dtype(self):
|
||||
return self.feats.dtype
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
return self.feats.device
|
||||
|
||||
@overload
|
||||
def to(self, dtype: torch.dtype) -> 'SparseTensor': ...
|
||||
|
||||
@overload
|
||||
def to(self, device: Optional[Union[str, torch.device]] = None, dtype: Optional[torch.dtype] = None) -> 'SparseTensor': ...
|
||||
|
||||
def to(self, *args, **kwargs) -> 'SparseTensor':
|
||||
device = None
|
||||
dtype = None
|
||||
if len(args) == 2:
|
||||
device, dtype = args
|
||||
elif len(args) == 1:
|
||||
if isinstance(args[0], torch.dtype):
|
||||
dtype = args[0]
|
||||
else:
|
||||
device = args[0]
|
||||
if 'dtype' in kwargs:
|
||||
assert dtype is None, "to() received multiple values for argument 'dtype'"
|
||||
dtype = kwargs['dtype']
|
||||
if 'device' in kwargs:
|
||||
assert device is None, "to() received multiple values for argument 'device'"
|
||||
device = kwargs['device']
|
||||
|
||||
new_feats = self.feats.to(device=device, dtype=dtype)
|
||||
new_coords = self.coords.to(device=device)
|
||||
return self.replace(new_feats, new_coords)
|
||||
|
||||
def type(self, dtype):
|
||||
new_feats = self.feats.type(dtype)
|
||||
return self.replace(new_feats)
|
||||
|
||||
def cpu(self) -> 'SparseTensor':
|
||||
new_feats = self.feats.cpu()
|
||||
new_coords = self.coords.cpu()
|
||||
return self.replace(new_feats, new_coords)
|
||||
|
||||
def cuda(self) -> 'SparseTensor':
|
||||
new_feats = self.feats.cuda()
|
||||
new_coords = self.coords.cuda()
|
||||
return self.replace(new_feats, new_coords)
|
||||
|
||||
def half(self) -> 'SparseTensor':
|
||||
new_feats = self.feats.half()
|
||||
return self.replace(new_feats)
|
||||
|
||||
def float(self) -> 'SparseTensor':
|
||||
new_feats = self.feats.float()
|
||||
return self.replace(new_feats)
|
||||
|
||||
def detach(self) -> 'SparseTensor':
|
||||
new_coords = self.coords.detach()
|
||||
new_feats = self.feats.detach()
|
||||
return self.replace(new_feats, new_coords)
|
||||
|
||||
def dense(self) -> torch.Tensor:
|
||||
if BACKEND == 'torchsparse':
|
||||
return self.data.dense()
|
||||
elif BACKEND == 'spconv':
|
||||
return self.data.dense()
|
||||
|
||||
def reshape(self, *shape) -> 'SparseTensor':
|
||||
new_feats = self.feats.reshape(self.feats.shape[0], *shape)
|
||||
return self.replace(new_feats)
|
||||
|
||||
def unbind(self, dim: int) -> List['SparseTensor']:
|
||||
return sparse_unbind(self, dim)
|
||||
|
||||
def replace(self, feats: torch.Tensor, coords: Optional[torch.Tensor] = None) -> 'SparseTensor':
|
||||
new_shape = [self.shape[0]]
|
||||
new_shape.extend(feats.shape[1:])
|
||||
if BACKEND == 'torchsparse':
|
||||
new_data = SparseTensorData(
|
||||
feats=feats,
|
||||
coords=self.data.coords if coords is None else coords,
|
||||
stride=self.data.stride,
|
||||
spatial_range=self.data.spatial_range,
|
||||
)
|
||||
new_data._caches = self.data._caches
|
||||
elif BACKEND == 'spconv':
|
||||
new_data = SparseTensorData(
|
||||
self.data.features.reshape(self.data.features.shape[0], -1),
|
||||
self.data.indices,
|
||||
self.data.spatial_shape,
|
||||
self.data.batch_size,
|
||||
self.data.grid,
|
||||
self.data.voxel_num,
|
||||
self.data.indice_dict
|
||||
)
|
||||
new_data._features = feats
|
||||
new_data.benchmark = self.data.benchmark
|
||||
new_data.benchmark_record = self.data.benchmark_record
|
||||
new_data.thrust_allocator = self.data.thrust_allocator
|
||||
new_data._timer = self.data._timer
|
||||
new_data.force_algo = self.data.force_algo
|
||||
new_data.int8_scale = self.data.int8_scale
|
||||
if coords is not None:
|
||||
new_data.indices = coords
|
||||
new_tensor = SparseTensor(new_data, shape=torch.Size(new_shape), layout=self.layout, scale=self._scale, spatial_cache=self._spatial_cache)
|
||||
return new_tensor
|
||||
|
||||
@staticmethod
|
||||
def full(aabb, dim, value, dtype=torch.float32, device=None) -> 'SparseTensor':
|
||||
N, C = dim
|
||||
x = torch.arange(aabb[0], aabb[3] + 1)
|
||||
y = torch.arange(aabb[1], aabb[4] + 1)
|
||||
z = torch.arange(aabb[2], aabb[5] + 1)
|
||||
coords = torch.stack(torch.meshgrid(x, y, z, indexing='ij'), dim=-1).reshape(-1, 3)
|
||||
coords = torch.cat([
|
||||
torch.arange(N).view(-1, 1).repeat(1, coords.shape[0]).view(-1, 1),
|
||||
coords.repeat(N, 1),
|
||||
], dim=1).to(dtype=torch.int32, device=device)
|
||||
feats = torch.full((coords.shape[0], C), value, dtype=dtype, device=device)
|
||||
return SparseTensor(feats=feats, coords=coords)
|
||||
|
||||
def __merge_sparse_cache(self, other: 'SparseTensor') -> dict:
|
||||
new_cache = {}
|
||||
for k in set(list(self._spatial_cache.keys()) + list(other._spatial_cache.keys())):
|
||||
if k in self._spatial_cache:
|
||||
new_cache[k] = self._spatial_cache[k]
|
||||
if k in other._spatial_cache:
|
||||
if k not in new_cache:
|
||||
new_cache[k] = other._spatial_cache[k]
|
||||
else:
|
||||
new_cache[k].update(other._spatial_cache[k])
|
||||
return new_cache
|
||||
|
||||
def __neg__(self) -> 'SparseTensor':
|
||||
return self.replace(-self.feats)
|
||||
|
||||
def __elemwise__(self, other: Union[torch.Tensor, 'SparseTensor'], op: callable) -> 'SparseTensor':
|
||||
if isinstance(other, torch.Tensor):
|
||||
try:
|
||||
other = torch.broadcast_to(other, self.shape)
|
||||
other = sparse_batch_broadcast(self, other)
|
||||
except:
|
||||
pass
|
||||
if isinstance(other, SparseTensor):
|
||||
other = other.feats
|
||||
new_feats = op(self.feats, other)
|
||||
new_tensor = self.replace(new_feats)
|
||||
if isinstance(other, SparseTensor):
|
||||
new_tensor._spatial_cache = self.__merge_sparse_cache(other)
|
||||
return new_tensor
|
||||
|
||||
def __add__(self, other: Union[torch.Tensor, 'SparseTensor', float]) -> 'SparseTensor':
|
||||
return self.__elemwise__(other, torch.add)
|
||||
|
||||
def __radd__(self, other: Union[torch.Tensor, 'SparseTensor', float]) -> 'SparseTensor':
|
||||
return self.__elemwise__(other, torch.add)
|
||||
|
||||
def __sub__(self, other: Union[torch.Tensor, 'SparseTensor', float]) -> 'SparseTensor':
|
||||
return self.__elemwise__(other, torch.sub)
|
||||
|
||||
def __rsub__(self, other: Union[torch.Tensor, 'SparseTensor', float]) -> 'SparseTensor':
|
||||
return self.__elemwise__(other, lambda x, y: torch.sub(y, x))
|
||||
|
||||
def __mul__(self, other: Union[torch.Tensor, 'SparseTensor', float]) -> 'SparseTensor':
|
||||
return self.__elemwise__(other, torch.mul)
|
||||
|
||||
def __rmul__(self, other: Union[torch.Tensor, 'SparseTensor', float]) -> 'SparseTensor':
|
||||
return self.__elemwise__(other, torch.mul)
|
||||
|
||||
def __truediv__(self, other: Union[torch.Tensor, 'SparseTensor', float]) -> 'SparseTensor':
|
||||
return self.__elemwise__(other, torch.div)
|
||||
|
||||
def __rtruediv__(self, other: Union[torch.Tensor, 'SparseTensor', float]) -> 'SparseTensor':
|
||||
return self.__elemwise__(other, lambda x, y: torch.div(y, x))
|
||||
|
||||
def __getitem__(self, idx):
|
||||
if isinstance(idx, int):
|
||||
idx = [idx]
|
||||
elif isinstance(idx, slice):
|
||||
idx = range(*idx.indices(self.shape[0]))
|
||||
elif isinstance(idx, torch.Tensor):
|
||||
if idx.dtype == torch.bool:
|
||||
assert idx.shape == (self.shape[0],), f"Invalid index shape: {idx.shape}"
|
||||
idx = idx.nonzero().squeeze(1)
|
||||
elif idx.dtype in [torch.int32, torch.int64]:
|
||||
assert len(idx.shape) == 1, f"Invalid index shape: {idx.shape}"
|
||||
else:
|
||||
raise ValueError(f"Unknown index type: {idx.dtype}")
|
||||
else:
|
||||
raise ValueError(f"Unknown index type: {type(idx)}")
|
||||
|
||||
coords = []
|
||||
feats = []
|
||||
for new_idx, old_idx in enumerate(idx):
|
||||
coords.append(self.coords[self.layout[old_idx]].clone())
|
||||
coords[-1][:, 0] = new_idx
|
||||
feats.append(self.feats[self.layout[old_idx]])
|
||||
coords = torch.cat(coords, dim=0).contiguous()
|
||||
feats = torch.cat(feats, dim=0).contiguous()
|
||||
return SparseTensor(feats=feats, coords=coords)
|
||||
|
||||
def register_spatial_cache(self, key, value) -> None:
|
||||
"""
|
||||
Register a spatial cache.
|
||||
The spatial cache can be any thing you want to cache.
|
||||
The registery and retrieval of the cache is based on current scale.
|
||||
"""
|
||||
scale_key = str(self._scale)
|
||||
if scale_key not in self._spatial_cache:
|
||||
self._spatial_cache[scale_key] = {}
|
||||
self._spatial_cache[scale_key][key] = value
|
||||
|
||||
def get_spatial_cache(self, key=None):
|
||||
"""
|
||||
Get a spatial cache.
|
||||
"""
|
||||
scale_key = str(self._scale)
|
||||
cur_scale_cache = self._spatial_cache.get(scale_key, {})
|
||||
if key is None:
|
||||
return cur_scale_cache
|
||||
return cur_scale_cache.get(key, None)
|
||||
|
||||
|
||||
def sparse_batch_broadcast(input: SparseTensor, other: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Broadcast a 1D tensor to a sparse tensor along the batch dimension then perform an operation.
|
||||
|
||||
Args:
|
||||
input (torch.Tensor): 1D tensor to broadcast.
|
||||
target (SparseTensor): Sparse tensor to broadcast to.
|
||||
op (callable): Operation to perform after broadcasting. Defaults to torch.add.
|
||||
"""
|
||||
coords, feats = input.coords, input.feats
|
||||
broadcasted = torch.zeros_like(feats)
|
||||
for k in range(input.shape[0]):
|
||||
broadcasted[input.layout[k]] = other[k]
|
||||
return broadcasted
|
||||
|
||||
|
||||
def sparse_batch_op(input: SparseTensor, other: torch.Tensor, op: callable = torch.add) -> SparseTensor:
|
||||
"""
|
||||
Broadcast a 1D tensor to a sparse tensor along the batch dimension then perform an operation.
|
||||
|
||||
Args:
|
||||
input (torch.Tensor): 1D tensor to broadcast.
|
||||
target (SparseTensor): Sparse tensor to broadcast to.
|
||||
op (callable): Operation to perform after broadcasting. Defaults to torch.add.
|
||||
"""
|
||||
return input.replace(op(input.feats, sparse_batch_broadcast(input, other)))
|
||||
|
||||
|
||||
def sparse_cat(inputs: List[SparseTensor], dim: int = 0) -> SparseTensor:
|
||||
"""
|
||||
Concatenate a list of sparse tensors.
|
||||
|
||||
Args:
|
||||
inputs (List[SparseTensor]): List of sparse tensors to concatenate.
|
||||
"""
|
||||
if dim == 0:
|
||||
start = 0
|
||||
coords = []
|
||||
for input in inputs:
|
||||
coords.append(input.coords.clone())
|
||||
coords[-1][:, 0] += start
|
||||
start += input.shape[0]
|
||||
coords = torch.cat(coords, dim=0)
|
||||
feats = torch.cat([input.feats for input in inputs], dim=0)
|
||||
output = SparseTensor(
|
||||
coords=coords,
|
||||
feats=feats,
|
||||
)
|
||||
else:
|
||||
feats = torch.cat([input.feats for input in inputs], dim=dim)
|
||||
output = inputs[0].replace(feats)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def sparse_unbind(input: SparseTensor, dim: int) -> List[SparseTensor]:
|
||||
"""
|
||||
Unbind a sparse tensor along a dimension.
|
||||
|
||||
Args:
|
||||
input (SparseTensor): Sparse tensor to unbind.
|
||||
dim (int): Dimension to unbind.
|
||||
"""
|
||||
if dim == 0:
|
||||
return [input[i] for i in range(input.shape[0])]
|
||||
else:
|
||||
feats = input.feats.unbind(dim)
|
||||
return [input.replace(f) for f in feats]
|
||||
@@ -0,0 +1,29 @@
|
||||
import os
|
||||
import logging
|
||||
from trellis.backend_config import get_sparse_backend, get_spconv_algo
|
||||
|
||||
BACKEND = get_sparse_backend()
|
||||
SPCONV_ALGO = get_spconv_algo()
|
||||
|
||||
def __from_env():
|
||||
import os
|
||||
|
||||
global SPCONV_ALGO
|
||||
env_spconv_algo = os.environ.get('SPCONV_ALGO')
|
||||
if env_spconv_algo is not None and env_spconv_algo in ['auto', 'implicit_gemm', 'native']:
|
||||
SPCONV_ALGO = env_spconv_algo
|
||||
print(f"[SPARSE][CONV] spconv algo: {SPCONV_ALGO}")
|
||||
|
||||
|
||||
__from_env()
|
||||
|
||||
if BACKEND == 'torchsparse':
|
||||
from .conv_torchsparse import *
|
||||
elif BACKEND == 'spconv':
|
||||
from .conv_spconv import *
|
||||
|
||||
__all__ = [
|
||||
"SparseConv3d",
|
||||
"SparseInverseConv3d",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .. import SparseTensor
|
||||
from .. import DEBUG
|
||||
from trellis.backend_config import get_debug_mode, get_spconv_algo, get_sparse_backend
|
||||
|
||||
# Get configuration from central config
|
||||
DEBUG = get_debug_mode()
|
||||
SPCONV_ALGO = get_spconv_algo()
|
||||
BACKEND = get_sparse_backend()
|
||||
|
||||
class SparseConv3d(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, kernel_size, stride=1, dilation=1, padding=None, bias=True, indice_key=None):
|
||||
super(SparseConv3d, self).__init__()
|
||||
if BACKEND == 'spconv':
|
||||
import spconv.pytorch as spconv
|
||||
algo = None
|
||||
if SPCONV_ALGO == 'native':
|
||||
algo = spconv.ConvAlgo.Native
|
||||
elif SPCONV_ALGO == 'implicit_gemm':
|
||||
algo = spconv.ConvAlgo.MaskImplicitGemm
|
||||
if stride == 1 and (padding is None):
|
||||
self.conv = spconv.SubMConv3d(in_channels, out_channels, kernel_size, dilation=dilation, bias=bias, indice_key=indice_key, algo=algo)
|
||||
else:
|
||||
self.conv = spconv.SparseConv3d(in_channels, out_channels, kernel_size, stride=stride, dilation=dilation, padding=padding, bias=bias, indice_key=indice_key, algo=algo)
|
||||
self.stride = tuple(stride) if isinstance(stride, (list, tuple)) else (stride, stride, stride)
|
||||
self.padding = padding
|
||||
|
||||
def forward(self, x: SparseTensor) -> SparseTensor:
|
||||
spatial_changed = any(s != 1 for s in self.stride) or (self.padding is not None)
|
||||
new_data = self.conv(x.data)
|
||||
new_shape = [x.shape[0], self.conv.out_channels]
|
||||
new_layout = None if spatial_changed else x.layout
|
||||
|
||||
if spatial_changed and (x.shape[0] != 1):
|
||||
# spconv was non-1 stride will break the contiguous of the output tensor, sort by the coords
|
||||
fwd = new_data.indices[:, 0].argsort()
|
||||
bwd = torch.zeros_like(fwd).scatter_(0, fwd, torch.arange(fwd.shape[0], device=fwd.device))
|
||||
sorted_feats = new_data.features[fwd]
|
||||
sorted_coords = new_data.indices[fwd]
|
||||
unsorted_data = new_data
|
||||
new_data = spconv.SparseConvTensor(sorted_feats, sorted_coords, unsorted_data.spatial_shape, unsorted_data.batch_size) # type: ignore
|
||||
|
||||
out = SparseTensor(
|
||||
new_data, shape=torch.Size(new_shape), layout=new_layout,
|
||||
scale=tuple([s * stride for s, stride in zip(x._scale, self.stride)]),
|
||||
spatial_cache=x._spatial_cache,
|
||||
)
|
||||
|
||||
if spatial_changed and (x.shape[0] != 1):
|
||||
out.register_spatial_cache(f'conv_{self.stride}_unsorted_data', unsorted_data)
|
||||
out.register_spatial_cache(f'conv_{self.stride}_sort_bwd', bwd)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class SparseInverseConv3d(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, kernel_size, stride=1, dilation=1, bias=True, indice_key=None):
|
||||
super(SparseInverseConv3d, self).__init__()
|
||||
if BACKEND == 'spconv':
|
||||
import spconv.pytorch as spconv
|
||||
self.conv = spconv.SparseInverseConv3d(in_channels, out_channels, kernel_size, bias=bias, indice_key=indice_key)
|
||||
self.stride = tuple(stride) if isinstance(stride, (list, tuple)) else (stride, stride, stride)
|
||||
|
||||
def forward(self, x: SparseTensor) -> SparseTensor:
|
||||
spatial_changed = any(s != 1 for s in self.stride)
|
||||
if spatial_changed:
|
||||
# recover the original spconv order
|
||||
data = x.get_spatial_cache(f'conv_{self.stride}_unsorted_data')
|
||||
bwd = x.get_spatial_cache(f'conv_{self.stride}_sort_bwd')
|
||||
data = data.replace_feature(x.feats[bwd])
|
||||
if DEBUG:
|
||||
assert torch.equal(data.indices, x.coords[bwd]), 'Recover the original order failed'
|
||||
else:
|
||||
data = x.data
|
||||
|
||||
new_data = self.conv(data)
|
||||
new_shape = [x.shape[0], self.conv.out_channels]
|
||||
new_layout = None if spatial_changed else x.layout
|
||||
out = SparseTensor(
|
||||
new_data, shape=torch.Size(new_shape), layout=new_layout,
|
||||
scale=tuple([s // stride for s, stride in zip(x._scale, self.stride)]),
|
||||
spatial_cache=x._spatial_cache,
|
||||
)
|
||||
return out
|
||||
@@ -0,0 +1,43 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .. import SparseTensor
|
||||
from trellis.backend_config import get_debug_mode, get_spconv_algo, get_sparse_backend
|
||||
|
||||
# Get configuration from central config
|
||||
DEBUG = get_debug_mode()
|
||||
SPCONV_ALGO = get_spconv_algo()
|
||||
BACKEND = get_sparse_backend()
|
||||
|
||||
class SparseConv3d(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, kernel_size, stride=1, dilation=1, bias=True, indice_key=None):
|
||||
super(SparseConv3d, self).__init__()
|
||||
if BACKEND == 'torchsparse':
|
||||
import torchsparse
|
||||
self.conv = torchsparse.nn.Conv3d(in_channels, out_channels, kernel_size, stride, 0, dilation, bias)
|
||||
|
||||
def forward(self, x: SparseTensor) -> SparseTensor:
|
||||
out = self.conv(x.data)
|
||||
new_shape = [x.shape[0], self.conv.out_channels]
|
||||
out = SparseTensor(out, shape=torch.Size(new_shape), layout=x.layout if all(s == 1 for s in self.conv.stride) else None)
|
||||
out._spatial_cache = x._spatial_cache
|
||||
out._scale = tuple([s * stride for s, stride in zip(x._scale, self.conv.stride)])
|
||||
return out
|
||||
|
||||
|
||||
class SparseInverseConv3d(nn.Module):
|
||||
def __init__(self, in_channels, out_channels, kernel_size, stride=1, dilation=1, bias=True, indice_key=None):
|
||||
super(SparseInverseConv3d, self).__init__()
|
||||
if BACKEND == 'torchsparse':
|
||||
import torchsparse
|
||||
self.conv = torchsparse.nn.Conv3d(in_channels, out_channels, kernel_size, stride, 0, dilation, bias, transposed=True)
|
||||
|
||||
def forward(self, x: SparseTensor) -> SparseTensor:
|
||||
out = self.conv(x.data)
|
||||
new_shape = [x.shape[0], self.conv.out_channels]
|
||||
out = SparseTensor(out, shape=torch.Size(new_shape), layout=x.layout if all(s == 1 for s in self.conv.stride) else None)
|
||||
out._spatial_cache = x._spatial_cache
|
||||
out._scale = tuple([s // stride for s, stride in zip(x._scale, self.conv.stride)])
|
||||
return out
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from . import SparseTensor
|
||||
|
||||
__all__ = [
|
||||
'SparseLinear'
|
||||
]
|
||||
|
||||
|
||||
class SparseLinear(nn.Linear):
|
||||
def __init__(self, in_features, out_features, bias=True):
|
||||
super(SparseLinear, self).__init__(in_features, out_features, bias)
|
||||
|
||||
def forward(self, input: SparseTensor) -> SparseTensor:
|
||||
return input.replace(super().forward(input.feats))
|
||||
@@ -0,0 +1,35 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from . import SparseTensor
|
||||
|
||||
__all__ = [
|
||||
'SparseReLU',
|
||||
'SparseSiLU',
|
||||
'SparseGELU',
|
||||
'SparseActivation'
|
||||
]
|
||||
|
||||
|
||||
class SparseReLU(nn.ReLU):
|
||||
def forward(self, input: SparseTensor) -> SparseTensor:
|
||||
return input.replace(super().forward(input.feats))
|
||||
|
||||
|
||||
class SparseSiLU(nn.SiLU):
|
||||
def forward(self, input: SparseTensor) -> SparseTensor:
|
||||
return input.replace(super().forward(input.feats))
|
||||
|
||||
|
||||
class SparseGELU(nn.GELU):
|
||||
def forward(self, input: SparseTensor) -> SparseTensor:
|
||||
return input.replace(super().forward(input.feats))
|
||||
|
||||
|
||||
class SparseActivation(nn.Module):
|
||||
def __init__(self, activation: nn.Module):
|
||||
super().__init__()
|
||||
self.activation = activation
|
||||
|
||||
def forward(self, input: SparseTensor) -> SparseTensor:
|
||||
return input.replace(self.activation(input.feats))
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from . import SparseTensor
|
||||
from . import DEBUG
|
||||
|
||||
__all__ = [
|
||||
'SparseGroupNorm',
|
||||
'SparseLayerNorm',
|
||||
'SparseGroupNorm32',
|
||||
'SparseLayerNorm32',
|
||||
]
|
||||
|
||||
|
||||
class SparseGroupNorm(nn.GroupNorm):
|
||||
def __init__(self, num_groups, num_channels, eps=1e-5, affine=True):
|
||||
super(SparseGroupNorm, self).__init__(num_groups, num_channels, eps, affine)
|
||||
|
||||
def forward(self, input: SparseTensor) -> SparseTensor:
|
||||
nfeats = torch.zeros_like(input.feats)
|
||||
for k in range(input.shape[0]):
|
||||
if DEBUG:
|
||||
assert (input.coords[input.layout[k], 0] == k).all(), f"SparseGroupNorm: batch index mismatch"
|
||||
bfeats = input.feats[input.layout[k]]
|
||||
bfeats = bfeats.permute(1, 0).reshape(1, input.shape[1], -1)
|
||||
bfeats = super().forward(bfeats)
|
||||
bfeats = bfeats.reshape(input.shape[1], -1).permute(1, 0)
|
||||
nfeats[input.layout[k]] = bfeats
|
||||
return input.replace(nfeats)
|
||||
|
||||
|
||||
class SparseLayerNorm(nn.LayerNorm):
|
||||
def __init__(self, normalized_shape, eps=1e-5, elementwise_affine=True):
|
||||
super(SparseLayerNorm, self).__init__(normalized_shape, eps, elementwise_affine)
|
||||
|
||||
def forward(self, input: SparseTensor) -> SparseTensor:
|
||||
nfeats = torch.zeros_like(input.feats)
|
||||
for k in range(input.shape[0]):
|
||||
bfeats = input.feats[input.layout[k]]
|
||||
bfeats = bfeats.permute(1, 0).reshape(1, input.shape[1], -1)
|
||||
bfeats = super().forward(bfeats)
|
||||
bfeats = bfeats.reshape(input.shape[1], -1).permute(1, 0)
|
||||
nfeats[input.layout[k]] = bfeats
|
||||
return input.replace(nfeats)
|
||||
|
||||
|
||||
class SparseGroupNorm32(SparseGroupNorm):
|
||||
"""
|
||||
A GroupNorm layer that converts to float32 before the forward pass.
|
||||
"""
|
||||
def forward(self, x: SparseTensor) -> SparseTensor:
|
||||
return super().forward(x.float()).type(x.dtype)
|
||||
|
||||
class SparseLayerNorm32(SparseLayerNorm):
|
||||
"""
|
||||
A LayerNorm layer that converts to float32 before the forward pass.
|
||||
"""
|
||||
def forward(self, x: SparseTensor) -> SparseTensor:
|
||||
return super().forward(x.float()).type(x.dtype)
|
||||
@@ -0,0 +1,110 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from . import SparseTensor
|
||||
|
||||
__all__ = [
|
||||
'SparseDownsample',
|
||||
'SparseUpsample',
|
||||
'SparseSubdivide'
|
||||
]
|
||||
|
||||
|
||||
class SparseDownsample(nn.Module):
|
||||
"""
|
||||
Downsample a sparse tensor by a factor of `factor`.
|
||||
Implemented as average pooling.
|
||||
"""
|
||||
def __init__(self, factor: Union[int, Tuple[int, ...], List[int]]):
|
||||
super(SparseDownsample, self).__init__()
|
||||
self.factor = tuple(factor) if isinstance(factor, (list, tuple)) else factor
|
||||
|
||||
def forward(self, input: SparseTensor) -> SparseTensor:
|
||||
DIM = input.coords.shape[-1] - 1
|
||||
factor = self.factor if isinstance(self.factor, tuple) else (self.factor,) * DIM
|
||||
assert DIM == len(factor), 'Input coordinates must have the same dimension as the downsample factor.'
|
||||
|
||||
coord = list(input.coords.unbind(dim=-1))
|
||||
for i, f in enumerate(factor):
|
||||
coord[i+1] = coord[i+1] // f
|
||||
|
||||
MAX = [coord[i+1].max().item() + 1 for i in range(DIM)]
|
||||
OFFSET = torch.cumprod(torch.tensor(MAX[::-1]), 0).tolist()[::-1] + [1]
|
||||
code = sum([c * o for c, o in zip(coord, OFFSET)])
|
||||
code, idx = code.unique(return_inverse=True)
|
||||
|
||||
new_feats = torch.scatter_reduce(
|
||||
torch.zeros(code.shape[0], input.feats.shape[1], device=input.feats.device, dtype=input.feats.dtype),
|
||||
dim=0,
|
||||
index=idx.unsqueeze(1).expand(-1, input.feats.shape[1]),
|
||||
src=input.feats,
|
||||
reduce='mean'
|
||||
)
|
||||
new_coords = torch.stack(
|
||||
[code // OFFSET[0]] +
|
||||
[(code // OFFSET[i+1]) % MAX[i] for i in range(DIM)],
|
||||
dim=-1
|
||||
)
|
||||
out = SparseTensor(new_feats, new_coords, input.shape,)
|
||||
out._scale = tuple([s // f for s, f in zip(input._scale, factor)])
|
||||
out._spatial_cache = input._spatial_cache
|
||||
|
||||
out.register_spatial_cache(f'upsample_{factor}_coords', input.coords)
|
||||
out.register_spatial_cache(f'upsample_{factor}_layout', input.layout)
|
||||
out.register_spatial_cache(f'upsample_{factor}_idx', idx)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class SparseUpsample(nn.Module):
|
||||
"""
|
||||
Upsample a sparse tensor by a factor of `factor`.
|
||||
Implemented as nearest neighbor interpolation.
|
||||
"""
|
||||
def __init__(self, factor: Union[int, Tuple[int, int, int], List[int]]):
|
||||
super(SparseUpsample, self).__init__()
|
||||
self.factor = tuple(factor) if isinstance(factor, (list, tuple)) else factor
|
||||
|
||||
def forward(self, input: SparseTensor) -> SparseTensor:
|
||||
DIM = input.coords.shape[-1] - 1
|
||||
factor = self.factor if isinstance(self.factor, tuple) else (self.factor,) * DIM
|
||||
assert DIM == len(factor), 'Input coordinates must have the same dimension as the upsample factor.'
|
||||
|
||||
new_coords = input.get_spatial_cache(f'upsample_{factor}_coords')
|
||||
new_layout = input.get_spatial_cache(f'upsample_{factor}_layout')
|
||||
idx = input.get_spatial_cache(f'upsample_{factor}_idx')
|
||||
if any([x is None for x in [new_coords, new_layout, idx]]):
|
||||
raise ValueError('Upsample cache not found. SparseUpsample must be paired with SparseDownsample.')
|
||||
new_feats = input.feats[idx]
|
||||
out = SparseTensor(new_feats, new_coords, input.shape, new_layout)
|
||||
out._scale = tuple([s * f for s, f in zip(input._scale, factor)])
|
||||
out._spatial_cache = input._spatial_cache
|
||||
return out
|
||||
|
||||
class SparseSubdivide(nn.Module):
|
||||
"""
|
||||
Upsample a sparse tensor by a factor of `factor`.
|
||||
Implemented as nearest neighbor interpolation.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(SparseSubdivide, self).__init__()
|
||||
|
||||
def forward(self, input: SparseTensor) -> SparseTensor:
|
||||
DIM = input.coords.shape[-1] - 1
|
||||
# upsample scale=2^DIM
|
||||
n_cube = torch.ones([2] * DIM, device=input.device, dtype=torch.int)
|
||||
n_coords = torch.nonzero(n_cube)
|
||||
n_coords = torch.cat([torch.zeros_like(n_coords[:, :1]), n_coords], dim=-1)
|
||||
factor = n_coords.shape[0]
|
||||
assert factor == 2 ** DIM
|
||||
# print(n_coords.shape)
|
||||
new_coords = input.coords.clone()
|
||||
new_coords[:, 1:] *= 2
|
||||
new_coords = new_coords.unsqueeze(1) + n_coords.unsqueeze(0).to(new_coords.dtype)
|
||||
|
||||
new_feats = input.feats.unsqueeze(1).expand(input.feats.shape[0], factor, *input.feats.shape[1:])
|
||||
out = SparseTensor(new_feats.flatten(0, 1), new_coords.flatten(0, 1), input.shape)
|
||||
out._scale = input._scale * 2
|
||||
out._spatial_cache = input._spatial_cache
|
||||
return out
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
from .blocks import *
|
||||
from .modulated import *
|
||||
@@ -0,0 +1,151 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from ..basic import SparseTensor
|
||||
from ..linear import SparseLinear
|
||||
from ..nonlinearity import SparseGELU
|
||||
from ..attention import SparseMultiHeadAttention, SerializeMode
|
||||
from ...norm import LayerNorm32
|
||||
|
||||
|
||||
class SparseFeedForwardNet(nn.Module):
|
||||
def __init__(self, channels: int, mlp_ratio: float = 4.0):
|
||||
super().__init__()
|
||||
self.mlp = nn.Sequential(
|
||||
SparseLinear(channels, int(channels * mlp_ratio)),
|
||||
SparseGELU(approximate="tanh"),
|
||||
SparseLinear(int(channels * mlp_ratio), channels),
|
||||
)
|
||||
|
||||
def forward(self, x: SparseTensor) -> SparseTensor:
|
||||
return self.mlp(x)
|
||||
|
||||
|
||||
class SparseTransformerBlock(nn.Module):
|
||||
"""
|
||||
Sparse Transformer block (MSA + FFN).
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
num_heads: int,
|
||||
mlp_ratio: float = 4.0,
|
||||
attn_mode: Literal["full", "shift_window", "shift_sequence", "shift_order", "swin"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
shift_sequence: Optional[int] = None,
|
||||
shift_window: Optional[Tuple[int, int, int]] = None,
|
||||
serialize_mode: Optional[SerializeMode] = None,
|
||||
use_checkpoint: bool = False,
|
||||
use_rope: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
qkv_bias: bool = True,
|
||||
ln_affine: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.norm1 = LayerNorm32(channels, elementwise_affine=ln_affine, eps=1e-6)
|
||||
self.norm2 = LayerNorm32(channels, elementwise_affine=ln_affine, eps=1e-6)
|
||||
self.attn = SparseMultiHeadAttention(
|
||||
channels,
|
||||
num_heads=num_heads,
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
shift_sequence=shift_sequence,
|
||||
shift_window=shift_window,
|
||||
serialize_mode=serialize_mode,
|
||||
qkv_bias=qkv_bias,
|
||||
use_rope=use_rope,
|
||||
qk_rms_norm=qk_rms_norm,
|
||||
)
|
||||
self.mlp = SparseFeedForwardNet(
|
||||
channels,
|
||||
mlp_ratio=mlp_ratio,
|
||||
)
|
||||
|
||||
def _forward(self, x: SparseTensor) -> SparseTensor:
|
||||
h = x.replace(self.norm1(x.feats))
|
||||
h = self.attn(h)
|
||||
x = x + h
|
||||
h = x.replace(self.norm2(x.feats))
|
||||
h = self.mlp(h)
|
||||
x = x + h
|
||||
return x
|
||||
|
||||
def forward(self, x: SparseTensor) -> SparseTensor:
|
||||
if self.use_checkpoint:
|
||||
return torch.utils.checkpoint.checkpoint(self._forward, x, use_reentrant=False)
|
||||
else:
|
||||
return self._forward(x)
|
||||
|
||||
|
||||
class SparseTransformerCrossBlock(nn.Module):
|
||||
"""
|
||||
Sparse Transformer cross-attention block (MSA + MCA + FFN).
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
ctx_channels: int,
|
||||
num_heads: int,
|
||||
mlp_ratio: float = 4.0,
|
||||
attn_mode: Literal["full", "shift_window", "shift_sequence", "shift_order", "swin"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
shift_sequence: Optional[int] = None,
|
||||
shift_window: Optional[Tuple[int, int, int]] = None,
|
||||
serialize_mode: Optional[SerializeMode] = None,
|
||||
use_checkpoint: bool = False,
|
||||
use_rope: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
qk_rms_norm_cross: bool = False,
|
||||
qkv_bias: bool = True,
|
||||
ln_affine: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.norm1 = LayerNorm32(channels, elementwise_affine=ln_affine, eps=1e-6)
|
||||
self.norm2 = LayerNorm32(channels, elementwise_affine=ln_affine, eps=1e-6)
|
||||
self.norm3 = LayerNorm32(channels, elementwise_affine=ln_affine, eps=1e-6)
|
||||
self.self_attn = SparseMultiHeadAttention(
|
||||
channels,
|
||||
num_heads=num_heads,
|
||||
type="self",
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
shift_sequence=shift_sequence,
|
||||
shift_window=shift_window,
|
||||
serialize_mode=serialize_mode,
|
||||
qkv_bias=qkv_bias,
|
||||
use_rope=use_rope,
|
||||
qk_rms_norm=qk_rms_norm,
|
||||
)
|
||||
self.cross_attn = SparseMultiHeadAttention(
|
||||
channels,
|
||||
ctx_channels=ctx_channels,
|
||||
num_heads=num_heads,
|
||||
type="cross",
|
||||
attn_mode="full",
|
||||
qkv_bias=qkv_bias,
|
||||
qk_rms_norm=qk_rms_norm_cross,
|
||||
)
|
||||
self.mlp = SparseFeedForwardNet(
|
||||
channels,
|
||||
mlp_ratio=mlp_ratio,
|
||||
)
|
||||
|
||||
def _forward(self, x: SparseTensor, mod: torch.Tensor, context: torch.Tensor):
|
||||
h = x.replace(self.norm1(x.feats))
|
||||
h = self.self_attn(h)
|
||||
x = x + h
|
||||
h = x.replace(self.norm2(x.feats))
|
||||
h = self.cross_attn(h, context)
|
||||
x = x + h
|
||||
h = x.replace(self.norm3(x.feats))
|
||||
h = self.mlp(h)
|
||||
x = x + h
|
||||
return x
|
||||
|
||||
def forward(self, x: SparseTensor, context: torch.Tensor):
|
||||
if self.use_checkpoint:
|
||||
return torch.utils.checkpoint.checkpoint(self._forward, x, context, use_reentrant=False)
|
||||
else:
|
||||
return self._forward(x, context)
|
||||
@@ -0,0 +1,166 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from ..basic import SparseTensor
|
||||
from ..attention import SparseMultiHeadAttention, SerializeMode
|
||||
from ...norm import LayerNorm32
|
||||
from .blocks import SparseFeedForwardNet
|
||||
|
||||
|
||||
class ModulatedSparseTransformerBlock(nn.Module):
|
||||
"""
|
||||
Sparse Transformer block (MSA + FFN) with adaptive layer norm conditioning.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
num_heads: int,
|
||||
mlp_ratio: float = 4.0,
|
||||
attn_mode: Literal["full", "shift_window", "shift_sequence", "shift_order", "swin"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
shift_sequence: Optional[int] = None,
|
||||
shift_window: Optional[Tuple[int, int, int]] = None,
|
||||
serialize_mode: Optional[SerializeMode] = None,
|
||||
use_checkpoint: bool = False,
|
||||
use_rope: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
qkv_bias: bool = True,
|
||||
share_mod: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.share_mod = share_mod
|
||||
self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
|
||||
self.norm2 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
|
||||
self.attn = SparseMultiHeadAttention(
|
||||
channels,
|
||||
num_heads=num_heads,
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
shift_sequence=shift_sequence,
|
||||
shift_window=shift_window,
|
||||
serialize_mode=serialize_mode,
|
||||
qkv_bias=qkv_bias,
|
||||
use_rope=use_rope,
|
||||
qk_rms_norm=qk_rms_norm,
|
||||
)
|
||||
self.mlp = SparseFeedForwardNet(
|
||||
channels,
|
||||
mlp_ratio=mlp_ratio,
|
||||
)
|
||||
if not share_mod:
|
||||
self.adaLN_modulation = nn.Sequential(
|
||||
nn.SiLU(),
|
||||
nn.Linear(channels, 6 * channels, bias=True)
|
||||
)
|
||||
|
||||
def _forward(self, x: SparseTensor, mod: torch.Tensor) -> SparseTensor:
|
||||
if self.share_mod:
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
|
||||
else:
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(mod).chunk(6, dim=1)
|
||||
h = x.replace(self.norm1(x.feats))
|
||||
h = h * (1 + scale_msa) + shift_msa
|
||||
h = self.attn(h)
|
||||
h = h * gate_msa
|
||||
x = x + h
|
||||
h = x.replace(self.norm2(x.feats))
|
||||
h = h * (1 + scale_mlp) + shift_mlp
|
||||
h = self.mlp(h)
|
||||
h = h * gate_mlp
|
||||
x = x + h
|
||||
return x
|
||||
|
||||
def forward(self, x: SparseTensor, mod: torch.Tensor) -> SparseTensor:
|
||||
if self.use_checkpoint:
|
||||
return torch.utils.checkpoint.checkpoint(self._forward, x, mod, use_reentrant=False)
|
||||
else:
|
||||
return self._forward(x, mod)
|
||||
|
||||
|
||||
class ModulatedSparseTransformerCrossBlock(nn.Module):
|
||||
"""
|
||||
Sparse Transformer cross-attention block (MSA + MCA + FFN) with adaptive layer norm conditioning.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
ctx_channels: int,
|
||||
num_heads: int,
|
||||
mlp_ratio: float = 4.0,
|
||||
attn_mode: Literal["full", "shift_window", "shift_sequence", "shift_order", "swin"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
shift_sequence: Optional[int] = None,
|
||||
shift_window: Optional[Tuple[int, int, int]] = None,
|
||||
serialize_mode: Optional[SerializeMode] = None,
|
||||
use_checkpoint: bool = False,
|
||||
use_rope: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
qk_rms_norm_cross: bool = False,
|
||||
qkv_bias: bool = True,
|
||||
share_mod: bool = False,
|
||||
|
||||
):
|
||||
super().__init__()
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.share_mod = share_mod
|
||||
self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
|
||||
self.norm2 = LayerNorm32(channels, elementwise_affine=True, eps=1e-6)
|
||||
self.norm3 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
|
||||
self.self_attn = SparseMultiHeadAttention(
|
||||
channels,
|
||||
num_heads=num_heads,
|
||||
type="self",
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
shift_sequence=shift_sequence,
|
||||
shift_window=shift_window,
|
||||
serialize_mode=serialize_mode,
|
||||
qkv_bias=qkv_bias,
|
||||
use_rope=use_rope,
|
||||
qk_rms_norm=qk_rms_norm,
|
||||
)
|
||||
self.cross_attn = SparseMultiHeadAttention(
|
||||
channels,
|
||||
ctx_channels=ctx_channels,
|
||||
num_heads=num_heads,
|
||||
type="cross",
|
||||
attn_mode="full",
|
||||
qkv_bias=qkv_bias,
|
||||
qk_rms_norm=qk_rms_norm_cross,
|
||||
)
|
||||
self.mlp = SparseFeedForwardNet(
|
||||
channels,
|
||||
mlp_ratio=mlp_ratio,
|
||||
)
|
||||
if not share_mod:
|
||||
self.adaLN_modulation = nn.Sequential(
|
||||
nn.SiLU(),
|
||||
nn.Linear(channels, 6 * channels, bias=True)
|
||||
)
|
||||
|
||||
def _forward(self, x: SparseTensor, mod: torch.Tensor, context: torch.Tensor) -> SparseTensor:
|
||||
if self.share_mod:
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
|
||||
else:
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(mod).chunk(6, dim=1)
|
||||
h = x.replace(self.norm1(x.feats))
|
||||
h = h * (1 + scale_msa) + shift_msa
|
||||
h = self.self_attn(h)
|
||||
h = h * gate_msa
|
||||
x = x + h
|
||||
h = x.replace(self.norm2(x.feats))
|
||||
h = self.cross_attn(h, context)
|
||||
x = x + h
|
||||
h = x.replace(self.norm3(x.feats))
|
||||
h = h * (1 + scale_mlp) + shift_mlp
|
||||
h = self.mlp(h)
|
||||
h = h * gate_mlp
|
||||
x = x + h
|
||||
return x
|
||||
|
||||
def forward(self, x: SparseTensor, mod: torch.Tensor, context: torch.Tensor) -> SparseTensor:
|
||||
if self.use_checkpoint:
|
||||
return torch.utils.checkpoint.checkpoint(self._forward, x, mod, context, use_reentrant=False)
|
||||
else:
|
||||
return self._forward(x, mod, context)
|
||||
@@ -0,0 +1,48 @@
|
||||
import torch
|
||||
|
||||
|
||||
def pixel_shuffle_3d(x: torch.Tensor, scale_factor: int) -> torch.Tensor:
|
||||
"""
|
||||
3D pixel shuffle.
|
||||
"""
|
||||
B, C, H, W, D = x.shape
|
||||
C_ = C // scale_factor**3
|
||||
x = x.reshape(B, C_, scale_factor, scale_factor, scale_factor, H, W, D)
|
||||
x = x.permute(0, 1, 5, 2, 6, 3, 7, 4)
|
||||
x = x.reshape(B, C_, H*scale_factor, W*scale_factor, D*scale_factor)
|
||||
return x
|
||||
|
||||
|
||||
def patchify(x: torch.Tensor, patch_size: int):
|
||||
"""
|
||||
Patchify a tensor.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor): (N, C, *spatial) tensor
|
||||
patch_size (int): Patch size
|
||||
"""
|
||||
DIM = x.dim() - 2
|
||||
for d in range(2, DIM + 2):
|
||||
assert x.shape[d] % patch_size == 0, f"Dimension {d} of input tensor must be divisible by patch size, got {x.shape[d]} and {patch_size}"
|
||||
|
||||
x = x.reshape(*x.shape[:2], *sum([[x.shape[d] // patch_size, patch_size] for d in range(2, DIM + 2)], []))
|
||||
x = x.permute(0, 1, *([2 * i + 3 for i in range(DIM)] + [2 * i + 2 for i in range(DIM)]))
|
||||
x = x.reshape(x.shape[0], x.shape[1] * (patch_size ** DIM), *(x.shape[-DIM:]))
|
||||
return x
|
||||
|
||||
|
||||
def unpatchify(x: torch.Tensor, patch_size: int):
|
||||
"""
|
||||
Unpatchify a tensor.
|
||||
|
||||
Args:
|
||||
x (torch.Tensor): (N, C, *spatial) tensor
|
||||
patch_size (int): Patch size
|
||||
"""
|
||||
DIM = x.dim() - 2
|
||||
assert x.shape[1] % (patch_size ** DIM) == 0, f"Second dimension of input tensor must be divisible by patch size to unpatchify, got {x.shape[1]} and {patch_size ** DIM}"
|
||||
|
||||
x = x.reshape(x.shape[0], x.shape[1] // (patch_size ** DIM), *([patch_size] * DIM), *(x.shape[-DIM:]))
|
||||
x = x.permute(0, 1, *(sum([[2 + DIM + i, 2 + i] for i in range(DIM)], [])))
|
||||
x = x.reshape(x.shape[0], x.shape[1], *[x.shape[2 + 2 * i] * patch_size for i in range(DIM)])
|
||||
return x
|
||||
@@ -0,0 +1,2 @@
|
||||
from .blocks import *
|
||||
from .modulated import *
|
||||
@@ -0,0 +1,182 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from ..attention import MultiHeadAttention
|
||||
from ..norm import LayerNorm32
|
||||
|
||||
|
||||
class AbsolutePositionEmbedder(nn.Module):
|
||||
"""
|
||||
Embeds spatial positions into vector representations.
|
||||
"""
|
||||
def __init__(self, channels: int, in_channels: int = 3):
|
||||
super().__init__()
|
||||
self.channels = channels
|
||||
self.in_channels = in_channels
|
||||
self.freq_dim = channels // in_channels // 2
|
||||
self.freqs = torch.arange(self.freq_dim, dtype=torch.float32) / self.freq_dim
|
||||
self.freqs = 1.0 / (10000 ** self.freqs)
|
||||
|
||||
def _sin_cos_embedding(self, x: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Create sinusoidal position embeddings.
|
||||
|
||||
Args:
|
||||
x: a 1-D Tensor of N indices
|
||||
|
||||
Returns:
|
||||
an (N, D) Tensor of positional embeddings.
|
||||
"""
|
||||
self.freqs = self.freqs.to(x.device)
|
||||
out = torch.outer(x, self.freqs)
|
||||
out = torch.cat([torch.sin(out), torch.cos(out)], dim=-1)
|
||||
return out
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Args:
|
||||
x (torch.Tensor): (N, D) tensor of spatial positions
|
||||
"""
|
||||
N, D = x.shape
|
||||
assert D == self.in_channels, "Input dimension must match number of input channels"
|
||||
embed = self._sin_cos_embedding(x.reshape(-1))
|
||||
embed = embed.reshape(N, -1)
|
||||
if embed.shape[1] < self.channels:
|
||||
embed = torch.cat([embed, torch.zeros(N, self.channels - embed.shape[1], device=embed.device)], dim=-1)
|
||||
return embed
|
||||
|
||||
|
||||
class FeedForwardNet(nn.Module):
|
||||
def __init__(self, channels: int, mlp_ratio: float = 4.0):
|
||||
super().__init__()
|
||||
self.mlp = nn.Sequential(
|
||||
nn.Linear(channels, int(channels * mlp_ratio)),
|
||||
nn.GELU(approximate="tanh"),
|
||||
nn.Linear(int(channels * mlp_ratio), channels),
|
||||
)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
return self.mlp(x)
|
||||
|
||||
|
||||
class TransformerBlock(nn.Module):
|
||||
"""
|
||||
Transformer block (MSA + FFN).
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
num_heads: int,
|
||||
mlp_ratio: float = 4.0,
|
||||
attn_mode: Literal["full", "windowed"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
shift_window: Optional[int] = None,
|
||||
use_checkpoint: bool = False,
|
||||
use_rope: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
qkv_bias: bool = True,
|
||||
ln_affine: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.norm1 = LayerNorm32(channels, elementwise_affine=ln_affine, eps=1e-6)
|
||||
self.norm2 = LayerNorm32(channels, elementwise_affine=ln_affine, eps=1e-6)
|
||||
self.attn = MultiHeadAttention(
|
||||
channels,
|
||||
num_heads=num_heads,
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
shift_window=shift_window,
|
||||
qkv_bias=qkv_bias,
|
||||
use_rope=use_rope,
|
||||
qk_rms_norm=qk_rms_norm,
|
||||
)
|
||||
self.mlp = FeedForwardNet(
|
||||
channels,
|
||||
mlp_ratio=mlp_ratio,
|
||||
)
|
||||
|
||||
def _forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
h = self.norm1(x)
|
||||
h = self.attn(h)
|
||||
x = x + h
|
||||
h = self.norm2(x)
|
||||
h = self.mlp(h)
|
||||
x = x + h
|
||||
return x
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
if self.use_checkpoint:
|
||||
return torch.utils.checkpoint.checkpoint(self._forward, x, use_reentrant=False)
|
||||
else:
|
||||
return self._forward(x)
|
||||
|
||||
|
||||
class TransformerCrossBlock(nn.Module):
|
||||
"""
|
||||
Transformer cross-attention block (MSA + MCA + FFN).
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
ctx_channels: int,
|
||||
num_heads: int,
|
||||
mlp_ratio: float = 4.0,
|
||||
attn_mode: Literal["full", "windowed"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
shift_window: Optional[Tuple[int, int, int]] = None,
|
||||
use_checkpoint: bool = False,
|
||||
use_rope: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
qk_rms_norm_cross: bool = False,
|
||||
qkv_bias: bool = True,
|
||||
ln_affine: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.norm1 = LayerNorm32(channels, elementwise_affine=ln_affine, eps=1e-6)
|
||||
self.norm2 = LayerNorm32(channels, elementwise_affine=ln_affine, eps=1e-6)
|
||||
self.norm3 = LayerNorm32(channels, elementwise_affine=ln_affine, eps=1e-6)
|
||||
self.self_attn = MultiHeadAttention(
|
||||
channels,
|
||||
num_heads=num_heads,
|
||||
type="self",
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
shift_window=shift_window,
|
||||
qkv_bias=qkv_bias,
|
||||
use_rope=use_rope,
|
||||
qk_rms_norm=qk_rms_norm,
|
||||
)
|
||||
self.cross_attn = MultiHeadAttention(
|
||||
channels,
|
||||
ctx_channels=ctx_channels,
|
||||
num_heads=num_heads,
|
||||
type="cross",
|
||||
attn_mode="full",
|
||||
qkv_bias=qkv_bias,
|
||||
qk_rms_norm=qk_rms_norm_cross,
|
||||
)
|
||||
self.mlp = FeedForwardNet(
|
||||
channels,
|
||||
mlp_ratio=mlp_ratio,
|
||||
)
|
||||
|
||||
def _forward(self, x: torch.Tensor, context: torch.Tensor):
|
||||
h = self.norm1(x)
|
||||
h = self.self_attn(h)
|
||||
x = x + h
|
||||
h = self.norm2(x)
|
||||
h = self.cross_attn(h, context)
|
||||
x = x + h
|
||||
h = self.norm3(x)
|
||||
h = self.mlp(h)
|
||||
x = x + h
|
||||
return x
|
||||
|
||||
def forward(self, x: torch.Tensor, context: torch.Tensor):
|
||||
if self.use_checkpoint:
|
||||
return torch.utils.checkpoint.checkpoint(self._forward, x, context, use_reentrant=False)
|
||||
else:
|
||||
return self._forward(x, context)
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from ..attention import MultiHeadAttention
|
||||
from ..norm import LayerNorm32
|
||||
from .blocks import FeedForwardNet
|
||||
|
||||
|
||||
class ModulatedTransformerBlock(nn.Module):
|
||||
"""
|
||||
Transformer block (MSA + FFN) with adaptive layer norm conditioning.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
num_heads: int,
|
||||
mlp_ratio: float = 4.0,
|
||||
attn_mode: Literal["full", "windowed"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
shift_window: Optional[Tuple[int, int, int]] = None,
|
||||
use_checkpoint: bool = False,
|
||||
use_rope: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
qkv_bias: bool = True,
|
||||
share_mod: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.share_mod = share_mod
|
||||
self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
|
||||
self.norm2 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
|
||||
self.attn = MultiHeadAttention(
|
||||
channels,
|
||||
num_heads=num_heads,
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
shift_window=shift_window,
|
||||
qkv_bias=qkv_bias,
|
||||
use_rope=use_rope,
|
||||
qk_rms_norm=qk_rms_norm,
|
||||
)
|
||||
self.mlp = FeedForwardNet(
|
||||
channels,
|
||||
mlp_ratio=mlp_ratio,
|
||||
)
|
||||
if not share_mod:
|
||||
self.adaLN_modulation = nn.Sequential(
|
||||
nn.SiLU(),
|
||||
nn.Linear(channels, 6 * channels, bias=True)
|
||||
)
|
||||
|
||||
def _forward(self, x: torch.Tensor, mod: torch.Tensor) -> torch.Tensor:
|
||||
if self.share_mod:
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
|
||||
else:
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(mod).chunk(6, dim=1)
|
||||
h = self.norm1(x)
|
||||
h = h * (1 + scale_msa.unsqueeze(1)) + shift_msa.unsqueeze(1)
|
||||
h = self.attn(h)
|
||||
h = h * gate_msa.unsqueeze(1)
|
||||
x = x + h
|
||||
h = self.norm2(x)
|
||||
h = h * (1 + scale_mlp.unsqueeze(1)) + shift_mlp.unsqueeze(1)
|
||||
h = self.mlp(h)
|
||||
h = h * gate_mlp.unsqueeze(1)
|
||||
x = x + h
|
||||
return x
|
||||
|
||||
def forward(self, x: torch.Tensor, mod: torch.Tensor) -> torch.Tensor:
|
||||
if self.use_checkpoint:
|
||||
return torch.utils.checkpoint.checkpoint(self._forward, x, mod, use_reentrant=False)
|
||||
else:
|
||||
return self._forward(x, mod)
|
||||
|
||||
|
||||
class ModulatedTransformerCrossBlock(nn.Module):
|
||||
"""
|
||||
Transformer cross-attention block (MSA + MCA + FFN) with adaptive layer norm conditioning.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
channels: int,
|
||||
ctx_channels: int,
|
||||
num_heads: int,
|
||||
mlp_ratio: float = 4.0,
|
||||
attn_mode: Literal["full", "windowed"] = "full",
|
||||
window_size: Optional[int] = None,
|
||||
shift_window: Optional[Tuple[int, int, int]] = None,
|
||||
use_checkpoint: bool = False,
|
||||
use_rope: bool = False,
|
||||
qk_rms_norm: bool = False,
|
||||
qk_rms_norm_cross: bool = False,
|
||||
qkv_bias: bool = True,
|
||||
share_mod: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.use_checkpoint = use_checkpoint
|
||||
self.share_mod = share_mod
|
||||
self.norm1 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
|
||||
self.norm2 = LayerNorm32(channels, elementwise_affine=True, eps=1e-6)
|
||||
self.norm3 = LayerNorm32(channels, elementwise_affine=False, eps=1e-6)
|
||||
self.self_attn = MultiHeadAttention(
|
||||
channels,
|
||||
num_heads=num_heads,
|
||||
type="self",
|
||||
attn_mode=attn_mode,
|
||||
window_size=window_size,
|
||||
shift_window=shift_window,
|
||||
qkv_bias=qkv_bias,
|
||||
use_rope=use_rope,
|
||||
qk_rms_norm=qk_rms_norm,
|
||||
)
|
||||
self.cross_attn = MultiHeadAttention(
|
||||
channels,
|
||||
ctx_channels=ctx_channels,
|
||||
num_heads=num_heads,
|
||||
type="cross",
|
||||
attn_mode="full",
|
||||
qkv_bias=qkv_bias,
|
||||
qk_rms_norm=qk_rms_norm_cross,
|
||||
)
|
||||
self.mlp = FeedForwardNet(
|
||||
channels,
|
||||
mlp_ratio=mlp_ratio,
|
||||
)
|
||||
if not share_mod:
|
||||
self.adaLN_modulation = nn.Sequential(
|
||||
nn.SiLU(),
|
||||
nn.Linear(channels, 6 * channels, bias=True)
|
||||
)
|
||||
|
||||
def _forward(self, x: torch.Tensor, mod: torch.Tensor, context: torch.Tensor):
|
||||
if self.share_mod:
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = mod.chunk(6, dim=1)
|
||||
else:
|
||||
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(mod).chunk(6, dim=1)
|
||||
h = self.norm1(x)
|
||||
h = h * (1 + scale_msa.unsqueeze(1)) + shift_msa.unsqueeze(1)
|
||||
h = self.self_attn(h)
|
||||
h = h * gate_msa.unsqueeze(1)
|
||||
x = x + h
|
||||
h = self.norm2(x)
|
||||
h = self.cross_attn(h, context)
|
||||
x = x + h
|
||||
h = self.norm3(x)
|
||||
h = h * (1 + scale_mlp.unsqueeze(1)) + shift_mlp.unsqueeze(1)
|
||||
h = self.mlp(h)
|
||||
h = h * gate_mlp.unsqueeze(1)
|
||||
x = x + h
|
||||
return x
|
||||
|
||||
def forward(self, x: torch.Tensor, mod: torch.Tensor, context: torch.Tensor):
|
||||
if self.use_checkpoint:
|
||||
return torch.utils.checkpoint.checkpoint(self._forward, x, mod, context, use_reentrant=False)
|
||||
else:
|
||||
return self._forward(x, mod, context)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import torch.nn as nn
|
||||
from ..modules import sparse as sp
|
||||
|
||||
FP16_MODULES = (
|
||||
nn.Conv1d,
|
||||
nn.Conv2d,
|
||||
nn.Conv3d,
|
||||
nn.ConvTranspose1d,
|
||||
nn.ConvTranspose2d,
|
||||
nn.ConvTranspose3d,
|
||||
nn.Linear,
|
||||
sp.SparseConv3d,
|
||||
sp.SparseInverseConv3d,
|
||||
sp.SparseLinear,
|
||||
)
|
||||
|
||||
def convert_module_to_f16(l):
|
||||
"""
|
||||
Convert primitive modules to float16.
|
||||
"""
|
||||
if isinstance(l, FP16_MODULES):
|
||||
for p in l.parameters():
|
||||
p.data = p.data.half()
|
||||
|
||||
|
||||
def convert_module_to_f32(l):
|
||||
"""
|
||||
Convert primitive modules to float32, undoing convert_module_to_f16().
|
||||
"""
|
||||
if isinstance(l, FP16_MODULES):
|
||||
for p in l.parameters():
|
||||
p.data = p.data.float()
|
||||
|
||||
|
||||
def zero_module(module):
|
||||
"""
|
||||
Zero out the parameters of a module and return it.
|
||||
"""
|
||||
for p in module.parameters():
|
||||
p.detach().zero_()
|
||||
return module
|
||||
|
||||
|
||||
def scale_module(module, scale):
|
||||
"""
|
||||
Scale the parameters of a module and return it.
|
||||
"""
|
||||
for p in module.parameters():
|
||||
p.detach().mul_(scale)
|
||||
return module
|
||||
|
||||
|
||||
def modulate(x, shift, scale):
|
||||
return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
||||
@@ -0,0 +1,24 @@
|
||||
from . import samplers
|
||||
from .trellis_image_to_3d import TrellisImageTo3DPipeline
|
||||
|
||||
|
||||
def from_pretrained(path: str):
|
||||
"""
|
||||
Load a pipeline from a model folder or a Hugging Face model hub.
|
||||
|
||||
Args:
|
||||
path: The path to the model. Can be either local path or a Hugging Face model name.
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
is_local = os.path.exists(f"{path}/pipeline.json")
|
||||
|
||||
if is_local:
|
||||
config_file = f"{path}/pipeline.json"
|
||||
else:
|
||||
from huggingface_hub import hf_hub_download
|
||||
config_file = hf_hub_download(path, "pipeline.json")
|
||||
|
||||
with open(config_file, 'r') as f:
|
||||
config = json.load(f)
|
||||
return globals()[config['name']].from_pretrained(path)
|
||||
@@ -0,0 +1,77 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .. import models
|
||||
|
||||
|
||||
class Pipeline:
|
||||
"""
|
||||
A base class for pipelines.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
models: dict[str, nn.Module] = None,
|
||||
):
|
||||
if models is None:
|
||||
return
|
||||
self.models = models
|
||||
for model in self.models.values():
|
||||
model.eval()
|
||||
|
||||
@staticmethod
|
||||
def from_pretrained(path: str) -> "Pipeline":
|
||||
"""
|
||||
Load a pretrained model.
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
is_local = os.path.exists(f"{path}/pipeline.json")
|
||||
|
||||
if is_local:
|
||||
config_file = f"{path}/pipeline.json"
|
||||
with open(config_file, 'r') as f:
|
||||
#print("Loading pipeline.json:")
|
||||
args = json.load(f)['args']
|
||||
#print("Models in pipeline.json:")
|
||||
'''for k, v in args['models'].items():
|
||||
print(f" {k}: {v}")'''
|
||||
|
||||
# For local paths, construct the models dict differently
|
||||
_models = {
|
||||
k: models.from_pretrained(os.path.join(path, v))
|
||||
for k, v in args['models'].items()
|
||||
}
|
||||
else:
|
||||
from huggingface_hub import hf_hub_download
|
||||
config_file = hf_hub_download(path, "pipeline.json")
|
||||
with open(config_file, 'r') as f:
|
||||
args = json.load(f)['args']
|
||||
|
||||
_models = {
|
||||
k: models.from_pretrained(os.path.join(path, v))
|
||||
for k, v in args['models'].items()
|
||||
}
|
||||
|
||||
new_pipeline = Pipeline(_models)
|
||||
new_pipeline._pretrained_args = args
|
||||
return new_pipeline
|
||||
|
||||
@property
|
||||
def device(self) -> torch.device:
|
||||
for model in self.models.values():
|
||||
if hasattr(model, 'device'):
|
||||
return model.device
|
||||
for model in self.models.values():
|
||||
if hasattr(model, 'parameters'):
|
||||
return next(model.parameters()).device
|
||||
raise RuntimeError("No device found.")
|
||||
|
||||
def to(self, device: torch.device) -> None:
|
||||
for model in self.models.values():
|
||||
model.to(device)
|
||||
|
||||
def cuda(self) -> None:
|
||||
self.to(torch.device("cuda"))
|
||||
|
||||
def cpu(self) -> None:
|
||||
self.to(torch.device("cpu"))
|
||||
@@ -0,0 +1,2 @@
|
||||
from .base import Sampler
|
||||
from .flow_euler import FlowEulerSampler, FlowEulerCfgSampler, FlowEulerGuidanceIntervalSampler
|
||||
@@ -0,0 +1,20 @@
|
||||
from typing import *
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class Sampler(ABC):
|
||||
"""
|
||||
A base class for samplers.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def sample(
|
||||
self,
|
||||
model,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Sample from a model.
|
||||
"""
|
||||
pass
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
from typing import *
|
||||
|
||||
|
||||
class ClassifierFreeGuidanceSamplerMixin:
|
||||
"""
|
||||
A mixin class for samplers that apply classifier-free guidance.
|
||||
"""
|
||||
|
||||
def _inference_model(self, model, x_t, t, cond, neg_cond, cfg_strength, **kwargs):
|
||||
pred = super()._inference_model(model, x_t, t, cond, **kwargs)
|
||||
neg_pred = super()._inference_model(model, x_t, t, neg_cond, **kwargs)
|
||||
return (1 + cfg_strength) * pred - cfg_strength * neg_pred
|
||||
@@ -0,0 +1,201 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
from easydict import EasyDict as edict
|
||||
from .base import Sampler
|
||||
from .classifier_free_guidance_mixin import ClassifierFreeGuidanceSamplerMixin
|
||||
from .guidance_interval_mixin import GuidanceIntervalSamplerMixin
|
||||
|
||||
|
||||
class FlowEulerSampler(Sampler):
|
||||
"""
|
||||
Generate samples from a flow-matching model using Euler sampling.
|
||||
|
||||
Args:
|
||||
sigma_min: The minimum scale of noise in flow.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
sigma_min: float,
|
||||
):
|
||||
self.sigma_min = sigma_min
|
||||
|
||||
def _eps_to_xstart(self, x_t, t, eps):
|
||||
assert x_t.shape == eps.shape
|
||||
return (x_t - (self.sigma_min + (1 - self.sigma_min) * t) * eps) / (1 - t)
|
||||
|
||||
def _xstart_to_eps(self, x_t, t, x_0):
|
||||
assert x_t.shape == x_0.shape
|
||||
return (x_t - (1 - t) * x_0) / (self.sigma_min + (1 - self.sigma_min) * t)
|
||||
|
||||
def _v_to_xstart_eps(self, x_t, t, v):
|
||||
assert x_t.shape == v.shape
|
||||
eps = (1 - t) * v + x_t
|
||||
x_0 = (1 - self.sigma_min) * x_t - (self.sigma_min + (1 - self.sigma_min) * t) * v
|
||||
return x_0, eps
|
||||
|
||||
def _inference_model(self, model, x_t, t, cond=None, **kwargs):
|
||||
# Get model dtype for consistency
|
||||
model_dtype = next(model.parameters()).dtype
|
||||
t = torch.tensor([1000 * t] * x_t.shape[0], device=x_t.device, dtype=model_dtype)
|
||||
return model(x_t, t, cond, **kwargs)
|
||||
|
||||
def _get_model_prediction(self, model, x_t, t, cond=None, **kwargs):
|
||||
pred_v = self._inference_model(model, x_t, t, cond, **kwargs)
|
||||
pred_x_0, pred_eps = self._v_to_xstart_eps(x_t=x_t, t=t, v=pred_v)
|
||||
return pred_x_0, pred_eps, pred_v
|
||||
|
||||
@torch.no_grad()
|
||||
def sample_once(
|
||||
self,
|
||||
model,
|
||||
x_t,
|
||||
t: float,
|
||||
t_prev: float,
|
||||
cond: Optional[Any] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Sample x_{t-1} from the model using Euler method.
|
||||
|
||||
Args:
|
||||
model: The model to sample from.
|
||||
x_t: The [N x C x ...] tensor of noisy inputs at time t.
|
||||
t: The current timestep.
|
||||
t_prev: The previous timestep.
|
||||
cond: conditional information.
|
||||
**kwargs: Additional arguments for model inference.
|
||||
|
||||
Returns:
|
||||
a dict containing the following
|
||||
- 'pred_x_prev': x_{t-1}.
|
||||
- 'pred_x_0': a prediction of x_0.
|
||||
"""
|
||||
pred_x_0, pred_eps, pred_v = self._get_model_prediction(model, x_t, t, cond, **kwargs)
|
||||
pred_x_prev = x_t - (t - t_prev) * pred_v
|
||||
return edict({"pred_x_prev": pred_x_prev, "pred_x_0": pred_x_0})
|
||||
|
||||
@torch.no_grad()
|
||||
def sample(
|
||||
self,
|
||||
model,
|
||||
noise,
|
||||
cond: Optional[Any] = None,
|
||||
steps: int = 50,
|
||||
rescale_t: float = 1.0,
|
||||
verbose: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Generate samples from the model using Euler method.
|
||||
|
||||
Args:
|
||||
model: The model to sample from.
|
||||
noise: The initial noise tensor.
|
||||
cond: conditional information.
|
||||
steps: The number of steps to sample.
|
||||
rescale_t: The rescale factor for t.
|
||||
verbose: If True, show a progress bar.
|
||||
**kwargs: Additional arguments for model_inference.
|
||||
|
||||
Returns:
|
||||
a dict containing the following
|
||||
- 'samples': the model samples.
|
||||
- 'pred_x_t': a list of prediction of x_t.
|
||||
- 'pred_x_0': a list of prediction of x_0.
|
||||
"""
|
||||
sample = noise
|
||||
t_seq = np.linspace(1, 0, steps + 1)
|
||||
t_seq = rescale_t * t_seq / (1 + (rescale_t - 1) * t_seq)
|
||||
t_pairs = list((t_seq[i], t_seq[i + 1]) for i in range(steps))
|
||||
ret = edict({"samples": None, "pred_x_t": [], "pred_x_0": []})
|
||||
for t, t_prev in tqdm(t_pairs, desc="Sampling", disable=not verbose):
|
||||
out = self.sample_once(model, sample, t, t_prev, cond, **kwargs)
|
||||
sample = out.pred_x_prev
|
||||
ret.pred_x_t.append(out.pred_x_prev)
|
||||
ret.pred_x_0.append(out.pred_x_0)
|
||||
ret.samples = sample
|
||||
return ret
|
||||
|
||||
|
||||
class FlowEulerCfgSampler(ClassifierFreeGuidanceSamplerMixin, FlowEulerSampler):
|
||||
"""
|
||||
Generate samples from a flow-matching model using Euler sampling with classifier-free guidance.
|
||||
"""
|
||||
@torch.no_grad()
|
||||
def sample(
|
||||
self,
|
||||
model,
|
||||
noise,
|
||||
cond,
|
||||
neg_cond,
|
||||
steps: int = 50,
|
||||
rescale_t: float = 1.0,
|
||||
cfg_strength: float = 3.0,
|
||||
verbose: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Generate samples from the model using Euler method.
|
||||
|
||||
Args:
|
||||
model: The model to sample from.
|
||||
noise: The initial noise tensor.
|
||||
cond: conditional information.
|
||||
neg_cond: negative conditional information.
|
||||
steps: The number of steps to sample.
|
||||
rescale_t: The rescale factor for t.
|
||||
cfg_strength: The strength of classifier-free guidance.
|
||||
verbose: If True, show a progress bar.
|
||||
**kwargs: Additional arguments for model_inference.
|
||||
|
||||
Returns:
|
||||
a dict containing the following
|
||||
- 'samples': the model samples.
|
||||
- 'pred_x_t': a list of prediction of x_t.
|
||||
- 'pred_x_0': a list of prediction of x_0.
|
||||
"""
|
||||
return super().sample(model, noise, cond, steps, rescale_t, verbose, neg_cond=neg_cond, cfg_strength=cfg_strength, **kwargs)
|
||||
|
||||
|
||||
class FlowEulerGuidanceIntervalSampler(GuidanceIntervalSamplerMixin, FlowEulerSampler):
|
||||
"""
|
||||
Generate samples from a flow-matching model using Euler sampling with classifier-free guidance and interval.
|
||||
"""
|
||||
@torch.no_grad()
|
||||
def sample(
|
||||
self,
|
||||
model,
|
||||
noise,
|
||||
cond,
|
||||
neg_cond,
|
||||
steps: int = 50,
|
||||
rescale_t: float = 1.0,
|
||||
cfg_strength: float = 3.0,
|
||||
cfg_interval: Tuple[float, float] = (0.0, 1.0),
|
||||
verbose: bool = True,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Generate samples from the model using Euler method.
|
||||
|
||||
Args:
|
||||
model: The model to sample from.
|
||||
noise: The initial noise tensor.
|
||||
cond: conditional information.
|
||||
neg_cond: negative conditional information.
|
||||
steps: The number of steps to sample.
|
||||
rescale_t: The rescale factor for t.
|
||||
cfg_strength: The strength of classifier-free guidance.
|
||||
cfg_interval: The interval for classifier-free guidance.
|
||||
verbose: If True, show a progress bar.
|
||||
**kwargs: Additional arguments for model_inference.
|
||||
|
||||
Returns:
|
||||
a dict containing the following
|
||||
- 'samples': the model samples.
|
||||
- 'pred_x_t': a list of prediction of x_t.
|
||||
- 'pred_x_0': a list of prediction of x_0.
|
||||
"""
|
||||
return super().sample(model, noise, cond, steps, rescale_t, verbose, neg_cond=neg_cond, cfg_strength=cfg_strength, cfg_interval=cfg_interval, **kwargs)
|
||||
@@ -0,0 +1,15 @@
|
||||
from typing import *
|
||||
|
||||
|
||||
class GuidanceIntervalSamplerMixin:
|
||||
"""
|
||||
A mixin class for samplers that apply classifier-free guidance with interval.
|
||||
"""
|
||||
|
||||
def _inference_model(self, model, x_t, t, cond, neg_cond, cfg_strength, cfg_interval, **kwargs):
|
||||
if cfg_interval[0] <= t <= cfg_interval[1]:
|
||||
pred = super()._inference_model(model, x_t, t, cond, **kwargs)
|
||||
neg_pred = super()._inference_model(model, x_t, t, neg_cond, **kwargs)
|
||||
return (1 + cfg_strength) * pred - cfg_strength * neg_pred
|
||||
else:
|
||||
return super()._inference_model(model, x_t, t, cond, **kwargs)
|
||||
@@ -0,0 +1,575 @@
|
||||
# trellis_image_to_3d.py
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
from easydict import EasyDict as edict
|
||||
from torchvision import transforms
|
||||
from PIL import Image
|
||||
import rembg
|
||||
import gc
|
||||
from .base import Pipeline
|
||||
from . import samplers
|
||||
from ..modules import sparse as sp
|
||||
from ..representations import MeshExtractResult
|
||||
from contextlib import contextmanager
|
||||
from typing import Literal
|
||||
import folder_paths
|
||||
from trellis_model_manager import TrellisModelManager
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("IF_Hi3DGen")
|
||||
|
||||
class TrellisImageTo3DPipeline(Pipeline):
|
||||
"""
|
||||
Pipeline for inferring Trellis image-to-3D models.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): The models to use in the pipeline.
|
||||
sparse_structure_sampler (samplers.Sampler): The sampler for the sparse structure.
|
||||
slat_sampler (samplers.Sampler): The sampler for the structured latent.
|
||||
slat_normalization (dict): The normalization parameters for the structured latent.
|
||||
image_cond_model (str): The name of the image conditioning model.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
models: dict[str, nn.Module] = None,
|
||||
sparse_structure_sampler: samplers.Sampler = None,
|
||||
slat_sampler: samplers.Sampler = None,
|
||||
slat_normalization: dict = None,
|
||||
image_cond_model: str = None,
|
||||
):
|
||||
if models is None:
|
||||
return
|
||||
super().__init__(models)
|
||||
self._device = next(self.models['image_cond_model'].parameters()).device
|
||||
self._models_cpu = {}
|
||||
self.sparse_structure_sampler = sparse_structure_sampler
|
||||
self.slat_sampler = slat_sampler
|
||||
self.sparse_structure_sampler_params = {}
|
||||
self.slat_sampler_params = {}
|
||||
self.slat_normalization = slat_normalization
|
||||
self.rembg_session = None
|
||||
self._init_image_cond_model(image_cond_model)
|
||||
|
||||
@staticmethod
|
||||
def from_pretrained(path: str, dinov2_model: str = None) -> "TrellisImageTo3DPipeline":
|
||||
"""
|
||||
Load a pretrained model.
|
||||
|
||||
Args:
|
||||
path (str): The path to the model. Can be either local path or a Hugging Face repository.
|
||||
dinov2_model (str, optional): Override the DINOv2 model specified in config
|
||||
"""
|
||||
pipeline = super(TrellisImageTo3DPipeline, TrellisImageTo3DPipeline).from_pretrained(path)
|
||||
new_pipeline = TrellisImageTo3DPipeline()
|
||||
new_pipeline.__dict__ = pipeline.__dict__
|
||||
args = pipeline._pretrained_args
|
||||
|
||||
# Store model directory path
|
||||
new_pipeline.model_dir = os.path.dirname(os.path.dirname(path)) # Go up two levels (past ckpts/)
|
||||
|
||||
new_pipeline._models_cpu = {}
|
||||
|
||||
new_pipeline.sparse_structure_sampler = getattr(samplers, args['sparse_structure_sampler']['name'])(**args['sparse_structure_sampler']['args'])
|
||||
new_pipeline.sparse_structure_sampler_params = args['sparse_structure_sampler']['params']
|
||||
|
||||
new_pipeline.slat_sampler = getattr(samplers, args['slat_sampler']['name'])(**args['slat_sampler']['args'])
|
||||
new_pipeline.slat_sampler_params = args['slat_sampler']['params']
|
||||
|
||||
new_pipeline.slat_normalization = args['slat_normalization']
|
||||
|
||||
# Use user-specified model if provided, otherwise use config
|
||||
model_name = dinov2_model or args['image_cond_model']
|
||||
new_pipeline._init_image_cond_model(model_name)
|
||||
|
||||
return new_pipeline
|
||||
|
||||
def _init_image_cond_model(self, name: str):
|
||||
"""
|
||||
Initialize the image conditioning model.
|
||||
"""
|
||||
if not hasattr(self, 'model_dir'):
|
||||
raise AttributeError("Pipeline model_dir not set. Please ensure from_pretrained() is called first.")
|
||||
|
||||
# Create model manager instance with proper config
|
||||
config = getattr(self, 'config', {})
|
||||
model_manager = TrellisModelManager(self.model_dir, config=config)
|
||||
|
||||
try:
|
||||
# This will handle downloading if needed
|
||||
dinov2_model = model_manager.load_dinov2(name)
|
||||
|
||||
# Ensure model is in consistent dtype based on config
|
||||
if getattr(config, 'use_fp16', True):
|
||||
dinov2_model = dinov2_model.half()
|
||||
else:
|
||||
dinov2_model = dinov2_model.float()
|
||||
|
||||
self.models['image_cond_model'] = dinov2_model
|
||||
|
||||
transform = transforms.Compose([
|
||||
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
|
||||
])
|
||||
self.image_cond_model_transform = transform
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading DINOv2 model: {str(e)}")
|
||||
raise
|
||||
|
||||
def preprocess_image(self, input: Image.Image) -> Image.Image:
|
||||
"""
|
||||
Preprocess the input image.
|
||||
"""
|
||||
# if has alpha channel, use it directly; otherwise, remove background
|
||||
has_alpha = False
|
||||
if input.mode == 'RGBA':
|
||||
alpha = np.array(input)[:, :, 3]
|
||||
if not np.all(alpha == 255):
|
||||
has_alpha = True
|
||||
if has_alpha:
|
||||
output = input
|
||||
else:
|
||||
input = input.convert('RGB')
|
||||
max_size = max(input.size)
|
||||
scale = min(1, 1024 / max_size)
|
||||
if scale < 1:
|
||||
input = input.resize((int(input.width * scale), int(input.height * scale)), Image.Resampling.LANCZOS)
|
||||
if getattr(self, 'rembg_session', None) is None:
|
||||
self.rembg_session = rembg.new_session('u2net')
|
||||
output = rembg.remove(input, session=self.rembg_session)
|
||||
output_np = np.array(output)
|
||||
alpha = output_np[:, :, 3]
|
||||
bbox = np.argwhere(alpha > 0.8 * 255)
|
||||
bbox = np.min(bbox[:, 1]), np.min(bbox[:, 0]), np.max(bbox[:, 1]), np.max(bbox[:, 0])
|
||||
center = (bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2
|
||||
size = max(bbox[2] - bbox[0], bbox[3] - bbox[1])
|
||||
size = int(size * 1.2)
|
||||
bbox = center[0] - size // 2, center[1] - size // 2, center[0] + size // 2, center[1] + size // 2
|
||||
output = output.crop(bbox) # type: ignore
|
||||
output = output.resize((518, 518), Image.Resampling.LANCZOS)
|
||||
output = np.array(output).astype(np.float32) / 255
|
||||
output = output[:, :, :3] * output[:, :, 3:4]
|
||||
output = Image.fromarray((output * 255).astype(np.uint8))
|
||||
return output
|
||||
|
||||
@torch.no_grad()
|
||||
def encode_image(self, image: Union[torch.Tensor, list[Image.Image]]) -> torch.Tensor:
|
||||
"""
|
||||
Encode the image.
|
||||
|
||||
Args:
|
||||
image (Union[torch.Tensor, list[Image.Image]]): The image to encode
|
||||
|
||||
Returns:
|
||||
torch.Tensor: The encoded features.
|
||||
"""
|
||||
if isinstance(image, torch.Tensor):
|
||||
assert image.ndim == 4, "Image tensor should be batched (B, C, H, W)"
|
||||
elif isinstance(image, list):
|
||||
assert all(isinstance(i, Image.Image) for i in image), "Image list should be list of PIL images"
|
||||
image = [i.resize((518, 518), Image.LANCZOS) for i in image]
|
||||
image = [np.array(i.convert('RGB')).astype(np.float32) / 255 for i in image]
|
||||
image = [torch.from_numpy(i).permute(2, 0, 1).float() for i in image]
|
||||
image = torch.stack(image).to(self.device)
|
||||
else:
|
||||
raise ValueError(f"Unsupported type of image: {type(image)}")
|
||||
|
||||
# Apply normalization and convert to model dtype
|
||||
image = self.image_cond_model_transform(image)
|
||||
model_dtype = next(self.models['image_cond_model'].parameters()).dtype
|
||||
image = image.to(device=self.device, dtype=model_dtype)
|
||||
|
||||
# Run model inference
|
||||
features = self.models['image_cond_model'](image, is_training=True)['x_prenorm']
|
||||
patchtokens = F.layer_norm(features, features.shape[-1:])
|
||||
return patchtokens
|
||||
|
||||
def get_cond(self, image: Union[torch.Tensor, list[Image.Image]]) -> dict:
|
||||
"""
|
||||
Get the conditioning information for the model.
|
||||
|
||||
Args:
|
||||
image (Union[torch.Tensor, list[Image.Image]]): The image prompts.
|
||||
|
||||
Returns:
|
||||
dict: The conditioning information
|
||||
"""
|
||||
cond = self.encode_image(image)
|
||||
neg_cond = torch.zeros_like(cond)
|
||||
return {
|
||||
'cond': cond,
|
||||
'neg_cond': neg_cond,
|
||||
}
|
||||
|
||||
@property
|
||||
def device(self) -> torch.device:
|
||||
"""Override device property to ensure it persists"""
|
||||
if not hasattr(self, '_device'):
|
||||
self._device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||
return self._device
|
||||
|
||||
def cleanup(self):
|
||||
"""Explicit cleanup method that preserves essential properties"""
|
||||
# Store current device before cleanup
|
||||
current_device = self.device
|
||||
|
||||
# Clear rembg session
|
||||
if hasattr(self, 'rembg_session') and self.rembg_session is not None:
|
||||
del self.rembg_session
|
||||
self.rembg_session = None
|
||||
|
||||
# Clear CPU models
|
||||
if hasattr(self, '_models_cpu'):
|
||||
for key in list(self._models_cpu.keys()):
|
||||
del self._models_cpu[key]
|
||||
self._models_cpu.clear()
|
||||
|
||||
# Clear GPU models while preserving the models dict
|
||||
if hasattr(self, 'models'):
|
||||
for key in list(self.models.keys()):
|
||||
del self.models[key]
|
||||
|
||||
# Force memory cleanup
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
# Restore essential properties
|
||||
self._device = current_device
|
||||
if not hasattr(self, 'models'):
|
||||
self.models = {}
|
||||
if not hasattr(self, '_models_cpu'):
|
||||
self._models_cpu = {}
|
||||
|
||||
def unload_models(self, model_keys: List[str]):
|
||||
"""Unload specific models from GPU memory"""
|
||||
for key in model_keys:
|
||||
if key in self.models:
|
||||
self._models_cpu[key] = self.models[key].cpu()
|
||||
del self.models[key]
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
def load_models(self, model_keys: List[str]):
|
||||
"""Load specific models back to GPU"""
|
||||
for key in model_keys:
|
||||
if key in self._models_cpu:
|
||||
self.models[key] = self._models_cpu[key].to(self.device)
|
||||
# Convert to fp16 if pipeline was configured for it
|
||||
if hasattr(self, '_use_fp16') and self._use_fp16 and hasattr(self.models[key], 'half'):
|
||||
self.models[key] = self.models[key].half()
|
||||
del self._models_cpu[key]
|
||||
|
||||
@torch.no_grad()
|
||||
def sample_sparse_structure(
|
||||
self,
|
||||
cond: dict,
|
||||
num_samples: int = 1,
|
||||
sampler_params: dict = {},
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Sample sparse structures with the given conditioning.
|
||||
|
||||
Args:
|
||||
cond (dict): The conditioning information.
|
||||
num_samples (int): The number of samples to generate.
|
||||
sampler_params (dict): Additional parameters for the sampler.
|
||||
"""
|
||||
# Load required models
|
||||
self.load_models(['sparse_structure_flow_model', 'sparse_structure_decoder'])
|
||||
|
||||
# Sample occupancy latent
|
||||
flow_model = self.models['sparse_structure_flow_model']
|
||||
reso = flow_model.resolution
|
||||
|
||||
# Get model dtype for consistency
|
||||
model_dtype = next(flow_model.parameters()).dtype
|
||||
|
||||
noise = torch.randn(num_samples, flow_model.in_channels, reso, reso, reso).to(self.device, dtype=model_dtype)
|
||||
|
||||
# Ensure conditioning tensors match model dtype
|
||||
cond_converted = {}
|
||||
for key, value in cond.items():
|
||||
if isinstance(value, torch.Tensor):
|
||||
cond_converted[key] = value.to(dtype=model_dtype)
|
||||
else:
|
||||
cond_converted[key] = value
|
||||
|
||||
sampler_params = {**self.sparse_structure_sampler_params, **sampler_params}
|
||||
z_s = self.sparse_structure_sampler.sample(
|
||||
flow_model,
|
||||
noise,
|
||||
**cond_converted,
|
||||
**sampler_params,
|
||||
verbose=True
|
||||
).samples
|
||||
|
||||
# Decode occupancy latent
|
||||
decoder = self.models['sparse_structure_decoder']
|
||||
coords = torch.argwhere(decoder(z_s)>0)[:, [0, 2, 3, 4]].int()
|
||||
|
||||
return coords
|
||||
|
||||
def decode_slat(
|
||||
self,
|
||||
slat: sp.SparseTensor,
|
||||
formats: List[str] = ['mesh', 'gaussian', 'radiance_field'],
|
||||
) -> dict:
|
||||
"""
|
||||
Decode the structured latent.
|
||||
|
||||
Args:
|
||||
slat (sp.SparseTensor): The structured latent.
|
||||
formats (List[str]): The formats to decode the structured latent to.
|
||||
|
||||
Returns:
|
||||
dict: The decoded structured latent.
|
||||
"""
|
||||
ret = {}
|
||||
for format_type in formats:
|
||||
# Load and process one format at a time
|
||||
if format_type == 'mesh':
|
||||
self.load_models(['slat_decoder_mesh'])
|
||||
ret['mesh'] = self.models['slat_decoder_mesh'](slat)
|
||||
self.unload_models(['slat_decoder_mesh'])
|
||||
|
||||
elif format_type == 'gaussian':
|
||||
self.load_models(['slat_decoder_gs'])
|
||||
ret['gaussian'] = self.models['slat_decoder_gs'](slat)
|
||||
self.unload_models(['slat_decoder_gs'])
|
||||
|
||||
elif format_type == 'radiance_field':
|
||||
self.load_models(['slat_decoder_rf'])
|
||||
ret['radiance_field'] = self.models['slat_decoder_rf'](slat)
|
||||
self.unload_models(['slat_decoder_rf'])
|
||||
|
||||
# Force garbage collection after each format
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
return ret
|
||||
|
||||
def sample_slat(
|
||||
self,
|
||||
cond: dict,
|
||||
coords: torch.Tensor,
|
||||
sampler_params: dict = {},
|
||||
) -> sp.SparseTensor:
|
||||
"""
|
||||
Sample structured latent with the given conditioning.
|
||||
|
||||
Args:
|
||||
cond (dict): The conditioning information.
|
||||
coords (torch.Tensor): The coordinates of the sparse structure.
|
||||
sampler_params (dict): Additional parameters for the sampler.
|
||||
"""
|
||||
# Load required models
|
||||
self.load_models(['slat_flow_model'])
|
||||
|
||||
# Sample structured latent
|
||||
flow_model = self.models['slat_flow_model']
|
||||
|
||||
# Get model dtype for consistency
|
||||
model_dtype = next(flow_model.parameters()).dtype
|
||||
|
||||
noise = sp.SparseTensor(
|
||||
feats=torch.randn(coords.shape[0], flow_model.in_channels).to(self.device, dtype=model_dtype),
|
||||
coords=coords,
|
||||
)
|
||||
|
||||
# Ensure conditioning tensors match model dtype
|
||||
cond_converted = {}
|
||||
for key, value in cond.items():
|
||||
if isinstance(value, torch.Tensor):
|
||||
cond_converted[key] = value.to(dtype=model_dtype)
|
||||
else:
|
||||
cond_converted[key] = value
|
||||
|
||||
sampler_params = {**self.slat_sampler_params, **sampler_params}
|
||||
slat = self.slat_sampler.sample(
|
||||
flow_model,
|
||||
noise,
|
||||
**cond_converted,
|
||||
**sampler_params,
|
||||
verbose=True
|
||||
).samples
|
||||
|
||||
std = torch.tensor(self.slat_normalization['std'])[None].to(slat.device, dtype=slat.feats.dtype)
|
||||
mean = torch.tensor(self.slat_normalization['mean'])[None].to(slat.device, dtype=slat.feats.dtype)
|
||||
slat = slat * std + mean
|
||||
|
||||
return slat
|
||||
|
||||
@torch.no_grad()
|
||||
def run(
|
||||
self,
|
||||
image: Image.Image,
|
||||
num_samples: int = 1,
|
||||
seed: int = 42,
|
||||
sparse_structure_sampler_params: dict = {},
|
||||
slat_sampler_params: dict = {},
|
||||
formats: List[str] = ['mesh', 'gaussian', 'radiance_field'],
|
||||
preprocess_image: bool = True,
|
||||
) -> dict:
|
||||
"""
|
||||
Run the pipeline.
|
||||
|
||||
Args:
|
||||
image (Image.Image): The image prompt.
|
||||
num_samples (int): The number of samples to generate.
|
||||
sparse_structure_sampler_params (dict): Additional parameters for the sparse structure sampler.
|
||||
slat_sampler_params (dict): Additional parameters for the structured latent sampler.
|
||||
preprocess_image (bool): Whether to preprocess the image.
|
||||
"""
|
||||
if preprocess_image:
|
||||
image = self.preprocess_image(image)
|
||||
|
||||
# Load and process image conditioning
|
||||
self.load_models(['image_cond_model'])
|
||||
cond = self.get_cond([image])
|
||||
self.unload_models(['image_cond_model'])
|
||||
|
||||
torch.manual_seed(seed)
|
||||
|
||||
# Generate sparse structure
|
||||
coords = self.sample_sparse_structure(cond, num_samples, sparse_structure_sampler_params)
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
# Generate SLAT
|
||||
slat = self.sample_slat(cond, coords, slat_sampler_params)
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
# Unload all models but don't do full cleanup
|
||||
self.unload_models(list(self.models.keys()))
|
||||
|
||||
# Process formats one at a time
|
||||
results = self.decode_slat(slat, formats)
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
return results
|
||||
|
||||
@contextmanager
|
||||
def inject_sampler_multi_image(
|
||||
self,
|
||||
sampler_name: str,
|
||||
num_images: int,
|
||||
num_steps: int,
|
||||
mode: Literal['stochastic', 'multidiffusion'] = 'stochastic',
|
||||
):
|
||||
"""
|
||||
Inject a sampler with multiple images as condition.
|
||||
|
||||
Args:
|
||||
sampler_name (str): The name of the sampler to inject.
|
||||
num_images (int): The number of images to condition on.
|
||||
num_steps (int): The number of steps to run the sampler for.
|
||||
"""
|
||||
sampler = getattr(self, sampler_name)
|
||||
setattr(sampler, f'_old_inference_model', sampler._inference_model)
|
||||
|
||||
if mode == 'stochastic':
|
||||
if num_images > num_steps:
|
||||
print(f"\033[93mWarning: number of conditioning images is greater than number of steps for {sampler_name}. "
|
||||
"This may lead to performance degradation.\033[0m")
|
||||
|
||||
cond_indices = (np.arange(num_steps) % num_images).tolist()
|
||||
def _new_inference_model(self, model, x_t, t, cond, **kwargs):
|
||||
cond_idx = cond_indices.pop(0)
|
||||
cond_i = cond[cond_idx:cond_idx+1]
|
||||
return self._old_inference_model(model, x_t, t, cond=cond_i, **kwargs)
|
||||
|
||||
elif mode == 'multidiffusion':
|
||||
from .samplers import FlowEulerSampler
|
||||
def _new_inference_model(self, model, x_t, t, cond, neg_cond, cfg_strength, cfg_interval, **kwargs):
|
||||
if cfg_interval[0] <= t <= cfg_interval[1]:
|
||||
preds = []
|
||||
for i in range(len(cond)):
|
||||
preds.append(FlowEulerSampler._inference_model(self, model, x_t, t, cond[i:i+1], **kwargs))
|
||||
pred = sum(preds) / len(preds)
|
||||
neg_pred = FlowEulerSampler._inference_model(self, model, x_t, t, neg_cond, **kwargs)
|
||||
return (1 + cfg_strength) * pred - cfg_strength * neg_pred
|
||||
else:
|
||||
preds = []
|
||||
for i in range(len(cond)):
|
||||
preds.append(FlowEulerSampler._inference_model(self, model, x_t, t, cond[i:i+1], **kwargs))
|
||||
pred = sum(preds) / len(preds)
|
||||
return pred
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported mode: {mode}")
|
||||
|
||||
sampler._inference_model = _new_inference_model.__get__(sampler, type(sampler))
|
||||
|
||||
yield
|
||||
|
||||
sampler._inference_model = sampler._old_inference_model
|
||||
delattr(sampler, f'_old_inference_model')
|
||||
|
||||
@torch.no_grad()
|
||||
def run_multi_image(
|
||||
self,
|
||||
images: List[Image.Image],
|
||||
num_samples: int = 1,
|
||||
seed: int = 42,
|
||||
sparse_structure_sampler_params: dict = {},
|
||||
slat_sampler_params: dict = {},
|
||||
formats: List[str] = ['mesh', 'gaussian', 'radiance_field'],
|
||||
preprocess_image: bool = True,
|
||||
mode: Literal['stochastic', 'multidiffusion'] = 'stochastic',
|
||||
) -> dict:
|
||||
"""
|
||||
Run the pipeline with multiple images as condition
|
||||
|
||||
Args:
|
||||
images (List[Image.Image]): The multi-view images of the assets
|
||||
num_samples (int): The number of samples to generate.
|
||||
sparse_structure_sampler_params (dict): Additional parameters for the sparse structure sampler.
|
||||
slat_sampler_params (dict): Additional parameters for the structured latent sampler.
|
||||
preprocess_image (bool): Whether to preprocess the image.
|
||||
"""
|
||||
if preprocess_image:
|
||||
images = [self.preprocess_image(image) for image in images]
|
||||
cond = self.get_cond(images)
|
||||
cond['neg_cond'] = cond['neg_cond'][:1]
|
||||
torch.manual_seed(seed)
|
||||
ss_steps = {**self.sparse_structure_sampler_params, **sparse_structure_sampler_params}.get('steps')
|
||||
with self.inject_sampler_multi_image('sparse_structure_sampler', len(images), ss_steps, mode=mode):
|
||||
coords = self.sample_sparse_structure(cond, num_samples, sparse_structure_sampler_params)
|
||||
slat_steps = {**self.slat_sampler_params, **slat_sampler_params}.get('steps')
|
||||
with self.inject_sampler_multi_image('slat_sampler', len(images), slat_steps, mode=mode):
|
||||
slat = self.sample_slat(cond, coords, slat_sampler_params)
|
||||
return self.decode_slat(slat, formats)
|
||||
|
||||
@contextmanager
|
||||
def inference_context(self):
|
||||
"""Context manager for inference that handles device placement and cleanup."""
|
||||
try:
|
||||
# Move models to device if needed
|
||||
if hasattr(self, '_models_cpu'):
|
||||
for key in self._models_cpu:
|
||||
if key in self.models:
|
||||
continue
|
||||
self.models[key] = self._models_cpu[key].to(self._device)
|
||||
if hasattr(self.models[key], 'eval'):
|
||||
self.models[key].eval()
|
||||
|
||||
with torch.no_grad():
|
||||
yield
|
||||
|
||||
finally:
|
||||
# Cleanup
|
||||
if hasattr(self, '_models_cpu'):
|
||||
for key in list(self.models.keys()):
|
||||
if key in self._models_cpu:
|
||||
self._models_cpu[key] = self.models[key].cpu()
|
||||
del self.models[key]
|
||||
|
||||
# Force memory cleanup
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
@@ -0,0 +1 @@
|
||||
from .mesh import MeshExtractResult
|
||||
@@ -0,0 +1 @@
|
||||
from .cube2mesh import SparseFeatures2Mesh, MeshExtractResult
|
||||
@@ -0,0 +1,143 @@
|
||||
import torch
|
||||
from ...modules.sparse import SparseTensor
|
||||
from easydict import EasyDict as edict
|
||||
from .utils_cube import *
|
||||
from .flexicube import FlexiCubes
|
||||
|
||||
|
||||
class MeshExtractResult:
|
||||
def __init__(self,
|
||||
vertices,
|
||||
faces,
|
||||
vertex_attrs=None,
|
||||
res=64
|
||||
):
|
||||
self.vertices = vertices
|
||||
self.faces = faces.long()
|
||||
self.vertex_attrs = vertex_attrs
|
||||
self.face_normal = self.comput_face_normals(vertices, faces)
|
||||
self.res = res
|
||||
self.success = (vertices.shape[0] != 0 and faces.shape[0] != 0)
|
||||
|
||||
# training only
|
||||
self.tsdf_v = None
|
||||
self.tsdf_s = None
|
||||
self.reg_loss = None
|
||||
|
||||
def comput_face_normals(self, verts, faces):
|
||||
i0 = faces[..., 0].long()
|
||||
i1 = faces[..., 1].long()
|
||||
i2 = faces[..., 2].long()
|
||||
|
||||
v0 = verts[i0, :]
|
||||
v1 = verts[i1, :]
|
||||
v2 = verts[i2, :]
|
||||
face_normals = torch.cross(v1 - v0, v2 - v0, dim=-1)
|
||||
face_normals = torch.nn.functional.normalize(face_normals, dim=1)
|
||||
# print(face_normals.min(), face_normals.max(), face_normals.shape)
|
||||
return face_normals[:, None, :].repeat(1, 3, 1)
|
||||
|
||||
def comput_v_normals(self, verts, faces):
|
||||
i0 = faces[..., 0].long()
|
||||
i1 = faces[..., 1].long()
|
||||
i2 = faces[..., 2].long()
|
||||
|
||||
v0 = verts[i0, :]
|
||||
v1 = verts[i1, :]
|
||||
v2 = verts[i2, :]
|
||||
face_normals = torch.cross(v1 - v0, v2 - v0, dim=-1)
|
||||
v_normals = torch.zeros_like(verts)
|
||||
v_normals.scatter_add_(0, i0[..., None].repeat(1, 3), face_normals)
|
||||
v_normals.scatter_add_(0, i1[..., None].repeat(1, 3), face_normals)
|
||||
v_normals.scatter_add_(0, i2[..., None].repeat(1, 3), face_normals)
|
||||
|
||||
v_normals = torch.nn.functional.normalize(v_normals, dim=1)
|
||||
return v_normals
|
||||
|
||||
|
||||
class SparseFeatures2Mesh:
|
||||
def __init__(self, device="cuda", res=64, use_color=True):
|
||||
'''
|
||||
a model to generate a mesh from sparse features structures using flexicube
|
||||
'''
|
||||
super().__init__()
|
||||
self.device=device
|
||||
self.res = res
|
||||
self.mesh_extractor = FlexiCubes(device=device)
|
||||
self.sdf_bias = -1.0 / res
|
||||
verts, cube = construct_dense_grid(self.res, self.device)
|
||||
self.reg_c = cube.to(self.device)
|
||||
self.reg_v = verts.to(self.device)
|
||||
self.use_color = use_color
|
||||
self._calc_layout()
|
||||
|
||||
def _calc_layout(self):
|
||||
LAYOUTS = {
|
||||
'sdf': {'shape': (8, 1), 'size': 8},
|
||||
'deform': {'shape': (8, 3), 'size': 8 * 3},
|
||||
'weights': {'shape': (21,), 'size': 21}
|
||||
}
|
||||
if self.use_color:
|
||||
'''
|
||||
6 channel color including normal map
|
||||
'''
|
||||
LAYOUTS['color'] = {'shape': (8, 6,), 'size': 8 * 6}
|
||||
self.layouts = edict(LAYOUTS)
|
||||
start = 0
|
||||
for k, v in self.layouts.items():
|
||||
v['range'] = (start, start + v['size'])
|
||||
start += v['size']
|
||||
self.feats_channels = start
|
||||
|
||||
def get_layout(self, feats : torch.Tensor, name : str):
|
||||
if name not in self.layouts:
|
||||
return None
|
||||
return feats[:, self.layouts[name]['range'][0]:self.layouts[name]['range'][1]].reshape(-1, *self.layouts[name]['shape'])
|
||||
|
||||
def __call__(self, cubefeats : SparseTensor, training=False):
|
||||
"""
|
||||
Generates a mesh based on the specified sparse voxel structures.
|
||||
Args:
|
||||
cube_attrs [Nx21] : Sparse Tensor attrs about cube weights
|
||||
verts_attrs [Nx10] : [0:1] SDF [1:4] deform [4:7] color [7:10] normal
|
||||
Returns:
|
||||
return the success tag and ni you loss,
|
||||
"""
|
||||
# add sdf bias to verts_attrs
|
||||
coords = cubefeats.coords[:, 1:]
|
||||
feats = cubefeats.feats
|
||||
|
||||
sdf, deform, color, weights = [self.get_layout(feats, name) for name in ['sdf', 'deform', 'color', 'weights']]
|
||||
sdf += self.sdf_bias
|
||||
v_attrs = [sdf, deform, color] if self.use_color else [sdf, deform]
|
||||
v_pos, v_attrs, reg_loss = sparse_cube2verts(coords, torch.cat(v_attrs, dim=-1), training=training)
|
||||
v_attrs_d = get_dense_attrs(v_pos, v_attrs, res=self.res+1, sdf_init=True)
|
||||
weights_d = get_dense_attrs(coords, weights, res=self.res, sdf_init=False)
|
||||
if self.use_color:
|
||||
sdf_d, deform_d, colors_d = v_attrs_d[..., 0], v_attrs_d[..., 1:4], v_attrs_d[..., 4:]
|
||||
else:
|
||||
sdf_d, deform_d = v_attrs_d[..., 0], v_attrs_d[..., 1:4]
|
||||
colors_d = None
|
||||
|
||||
x_nx3 = get_defomed_verts(self.reg_v, deform_d, self.res)
|
||||
|
||||
vertices, faces, L_dev, colors = self.mesh_extractor(
|
||||
voxelgrid_vertices=x_nx3,
|
||||
scalar_field=sdf_d,
|
||||
cube_idx=self.reg_c,
|
||||
resolution=self.res,
|
||||
beta=weights_d[:, :12],
|
||||
alpha=weights_d[:, 12:20],
|
||||
gamma_f=weights_d[:, 20],
|
||||
voxelgrid_colors=colors_d,
|
||||
training=training)
|
||||
|
||||
mesh = MeshExtractResult(vertices=vertices, faces=faces, vertex_attrs=colors, res=self.res)
|
||||
if training:
|
||||
if mesh.success:
|
||||
reg_loss += L_dev.mean() * 0.5
|
||||
reg_loss += (weights[:,:20]).abs().mean() * 0.2
|
||||
mesh.reg_loss = reg_loss
|
||||
mesh.tsdf_v = get_defomed_verts(v_pos, v_attrs[:, 1:4], self.res)
|
||||
mesh.tsdf_s = v_attrs[:, 0]
|
||||
return mesh
|
||||
@@ -0,0 +1,361 @@
|
||||
# Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
#
|
||||
# NVIDIA CORPORATION & AFFILIATES and its licensors retain all intellectual property
|
||||
# and proprietary rights in and to this software, related documentation
|
||||
# and any modifications thereto. Any use, reproduction, disclosure or
|
||||
# distribution of this software and related documentation without an express
|
||||
# license agreement from NVIDIA CORPORATION & AFFILIATES is strictly prohibited.
|
||||
|
||||
import torch
|
||||
from .tables import *
|
||||
|
||||
__all__ = [
|
||||
'FlexiCubes'
|
||||
]
|
||||
|
||||
|
||||
class FlexiCubes:
|
||||
def __init__(self, device="cuda"):
|
||||
|
||||
self.device = device
|
||||
self.dmc_table = torch.tensor(dmc_table, dtype=torch.long, device=device, requires_grad=False)
|
||||
self.num_vd_table = torch.tensor(num_vd_table,
|
||||
dtype=torch.long, device=device, requires_grad=False)
|
||||
self.check_table = torch.tensor(
|
||||
check_table,
|
||||
dtype=torch.long, device=device, requires_grad=False)
|
||||
|
||||
self.tet_table = torch.tensor(tet_table, dtype=torch.long, device=device, requires_grad=False)
|
||||
self.quad_split_1 = torch.tensor([0, 1, 2, 0, 2, 3], dtype=torch.long, device=device, requires_grad=False)
|
||||
self.quad_split_2 = torch.tensor([0, 1, 3, 3, 1, 2], dtype=torch.long, device=device, requires_grad=False)
|
||||
self.quad_split_train = torch.tensor(
|
||||
[0, 1, 1, 2, 2, 3, 3, 0], dtype=torch.long, device=device, requires_grad=False)
|
||||
|
||||
self.cube_corners = torch.tensor([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [
|
||||
1, 0, 1], [0, 1, 1], [1, 1, 1]], dtype=torch.float, device=device)
|
||||
self.cube_corners_idx = torch.pow(2, torch.arange(8, requires_grad=False))
|
||||
self.cube_edges = torch.tensor([0, 1, 1, 5, 4, 5, 0, 4, 2, 3, 3, 7, 6, 7, 2, 6,
|
||||
2, 0, 3, 1, 7, 5, 6, 4], dtype=torch.long, device=device, requires_grad=False)
|
||||
|
||||
self.edge_dir_table = torch.tensor([0, 2, 0, 2, 0, 2, 0, 2, 1, 1, 1, 1],
|
||||
dtype=torch.long, device=device)
|
||||
self.dir_faces_table = torch.tensor([
|
||||
[[5, 4], [3, 2], [4, 5], [2, 3]],
|
||||
[[5, 4], [1, 0], [4, 5], [0, 1]],
|
||||
[[3, 2], [1, 0], [2, 3], [0, 1]]
|
||||
], dtype=torch.long, device=device)
|
||||
self.adj_pairs = torch.tensor([0, 1, 1, 3, 3, 2, 2, 0], dtype=torch.long, device=device)
|
||||
|
||||
def __call__(self, voxelgrid_vertices, scalar_field, cube_idx, resolution, qef_reg_scale=1e-3,
|
||||
weight_scale=0.99, beta=None, alpha=None, gamma_f=None, voxelgrid_colors=None, training=False):
|
||||
num_vertices = voxelgrid_vertices.shape[0]
|
||||
num_cubes = cube_idx.shape[0]
|
||||
surf_cubes, occ_fx8 = self._identify_surf_cubes(scalar_field, cube_idx)
|
||||
if surf_cubes.sum() == 0:
|
||||
return (
|
||||
torch.zeros((0, 3), device=self.device),
|
||||
torch.zeros((0, 3), dtype=torch.long, device=self.device),
|
||||
torch.zeros((0), device=self.device),
|
||||
torch.zeros((0, voxelgrid_colors.shape[-1]), device=self.device) if voxelgrid_colors is not None else None
|
||||
)
|
||||
beta, alpha, gamma_f = self._normalize_weights(
|
||||
beta, alpha, gamma_f, surf_cubes, weight_scale)
|
||||
|
||||
if voxelgrid_colors is not None:
|
||||
voxelgrid_colors = torch.sigmoid(voxelgrid_colors)
|
||||
|
||||
case_ids = self._get_case_id(occ_fx8, surf_cubes, resolution)
|
||||
|
||||
surf_edges, idx_map, edge_counts, surf_edges_mask = self._identify_surf_edges(
|
||||
scalar_field, cube_idx, surf_cubes
|
||||
)
|
||||
|
||||
vd, L_dev, vd_gamma, vd_idx_map, vd_color = self._compute_vd(
|
||||
voxelgrid_vertices, cube_idx[surf_cubes], surf_edges, scalar_field,
|
||||
case_ids, beta, alpha, gamma_f, idx_map, qef_reg_scale, voxelgrid_colors)
|
||||
vertices, faces, s_edges, edge_indices, vertices_color = self._triangulate(
|
||||
scalar_field, surf_edges, vd, vd_gamma, edge_counts, idx_map,
|
||||
vd_idx_map, surf_edges_mask, training, vd_color)
|
||||
return vertices, faces, L_dev, vertices_color
|
||||
|
||||
def _compute_reg_loss(self, vd, ue, edge_group_to_vd, vd_num_edges):
|
||||
"""
|
||||
Regularizer L_dev as in Equation 8
|
||||
"""
|
||||
dist = torch.norm(ue - torch.index_select(input=vd, index=edge_group_to_vd, dim=0), dim=-1)
|
||||
mean_l2 = torch.zeros_like(vd[:, 0])
|
||||
mean_l2 = (mean_l2).index_add_(0, edge_group_to_vd, dist) / vd_num_edges.squeeze(1).float()
|
||||
mad = (dist - torch.index_select(input=mean_l2, index=edge_group_to_vd, dim=0)).abs()
|
||||
return mad
|
||||
|
||||
def _normalize_weights(self, beta, alpha, gamma_f, surf_cubes, weight_scale):
|
||||
"""
|
||||
Normalizes the given weights to be non-negative. If input weights are None, it creates and returns a set of weights of ones.
|
||||
"""
|
||||
n_cubes = surf_cubes.shape[0]
|
||||
|
||||
if beta is not None:
|
||||
beta = (torch.tanh(beta) * weight_scale + 1)
|
||||
else:
|
||||
beta = torch.ones((n_cubes, 12), dtype=torch.float, device=self.device)
|
||||
|
||||
if alpha is not None:
|
||||
alpha = (torch.tanh(alpha) * weight_scale + 1)
|
||||
else:
|
||||
alpha = torch.ones((n_cubes, 8), dtype=torch.float, device=self.device)
|
||||
|
||||
if gamma_f is not None:
|
||||
gamma_f = torch.sigmoid(gamma_f) * weight_scale + (1 - weight_scale) / 2
|
||||
else:
|
||||
gamma_f = torch.ones((n_cubes), dtype=torch.float, device=self.device)
|
||||
|
||||
return beta[surf_cubes], alpha[surf_cubes], gamma_f[surf_cubes]
|
||||
|
||||
@torch.no_grad()
|
||||
def _get_case_id(self, occ_fx8, surf_cubes, res):
|
||||
"""
|
||||
Obtains the ID of topology cases based on cell corner occupancy. This function resolves the
|
||||
ambiguity in the Dual Marching Cubes (DMC) configurations as described in Section 1.3 of the
|
||||
supplementary material. It should be noted that this function assumes a regular grid.
|
||||
"""
|
||||
case_ids = (occ_fx8[surf_cubes] * self.cube_corners_idx.to(self.device).unsqueeze(0)).sum(-1)
|
||||
|
||||
problem_config = self.check_table.to(self.device)[case_ids]
|
||||
to_check = problem_config[..., 0] == 1
|
||||
problem_config = problem_config[to_check]
|
||||
if not isinstance(res, (list, tuple)):
|
||||
res = [res, res, res]
|
||||
|
||||
# The 'problematic_configs' only contain configurations for surface cubes. Next, we construct a 3D array,
|
||||
# 'problem_config_full', to store configurations for all cubes (with default config for non-surface cubes).
|
||||
# This allows efficient checking on adjacent cubes.
|
||||
problem_config_full = torch.zeros(list(res) + [5], device=self.device, dtype=torch.long)
|
||||
vol_idx = torch.nonzero(problem_config_full[..., 0] == 0) # N, 3
|
||||
vol_idx_problem = vol_idx[surf_cubes][to_check]
|
||||
problem_config_full[vol_idx_problem[..., 0], vol_idx_problem[..., 1], vol_idx_problem[..., 2]] = problem_config
|
||||
vol_idx_problem_adj = vol_idx_problem + problem_config[..., 1:4]
|
||||
|
||||
within_range = (
|
||||
vol_idx_problem_adj[..., 0] >= 0) & (
|
||||
vol_idx_problem_adj[..., 0] < res[0]) & (
|
||||
vol_idx_problem_adj[..., 1] >= 0) & (
|
||||
vol_idx_problem_adj[..., 1] < res[1]) & (
|
||||
vol_idx_problem_adj[..., 2] >= 0) & (
|
||||
vol_idx_problem_adj[..., 2] < res[2])
|
||||
|
||||
vol_idx_problem = vol_idx_problem[within_range]
|
||||
vol_idx_problem_adj = vol_idx_problem_adj[within_range]
|
||||
problem_config = problem_config[within_range]
|
||||
problem_config_adj = problem_config_full[vol_idx_problem_adj[..., 0],
|
||||
vol_idx_problem_adj[..., 1], vol_idx_problem_adj[..., 2]]
|
||||
# If two cubes with cases C16 and C19 share an ambiguous face, both cases are inverted.
|
||||
to_invert = (problem_config_adj[..., 0] == 1)
|
||||
idx = torch.arange(case_ids.shape[0], device=self.device)[to_check][within_range][to_invert]
|
||||
case_ids.index_put_((idx,), problem_config[to_invert][..., -1])
|
||||
return case_ids
|
||||
|
||||
@torch.no_grad()
|
||||
def _identify_surf_edges(self, scalar_field, cube_idx, surf_cubes):
|
||||
"""
|
||||
Identifies grid edges that intersect with the underlying surface by checking for opposite signs. As each edge
|
||||
can be shared by multiple cubes, this function also assigns a unique index to each surface-intersecting edge
|
||||
and marks the cube edges with this index.
|
||||
"""
|
||||
occ_n = scalar_field < 0
|
||||
all_edges = cube_idx[surf_cubes][:, self.cube_edges].reshape(-1, 2)
|
||||
unique_edges, _idx_map, counts = torch.unique(all_edges, dim=0, return_inverse=True, return_counts=True)
|
||||
|
||||
unique_edges = unique_edges.long()
|
||||
mask_edges = occ_n[unique_edges.reshape(-1)].reshape(-1, 2).sum(-1) == 1
|
||||
|
||||
surf_edges_mask = mask_edges[_idx_map]
|
||||
counts = counts[_idx_map]
|
||||
|
||||
mapping = torch.ones((unique_edges.shape[0]), dtype=torch.long, device=cube_idx.device) * -1
|
||||
mapping[mask_edges] = torch.arange(mask_edges.sum(), device=cube_idx.device)
|
||||
# Shaped as [number of cubes x 12 edges per cube]. This is later used to map a cube edge to the unique index
|
||||
# for a surface-intersecting edge. Non-surface-intersecting edges are marked with -1.
|
||||
idx_map = mapping[_idx_map]
|
||||
surf_edges = unique_edges[mask_edges]
|
||||
return surf_edges, idx_map, counts, surf_edges_mask
|
||||
|
||||
@torch.no_grad()
|
||||
def _identify_surf_cubes(self, scalar_field, cube_idx):
|
||||
"""
|
||||
Identifies grid cubes that intersect with the underlying surface by checking if the signs at
|
||||
all corners are not identical.
|
||||
"""
|
||||
occ_n = scalar_field < 0
|
||||
occ_fx8 = occ_n[cube_idx.reshape(-1)].reshape(-1, 8)
|
||||
_occ_sum = torch.sum(occ_fx8, -1)
|
||||
surf_cubes = (_occ_sum > 0) & (_occ_sum < 8)
|
||||
return surf_cubes, occ_fx8
|
||||
|
||||
def _linear_interp(self, edges_weight, edges_x):
|
||||
"""
|
||||
Computes the location of zero-crossings on 'edges_x' using linear interpolation with 'edges_weight'.
|
||||
"""
|
||||
edge_dim = edges_weight.dim() - 2
|
||||
assert edges_weight.shape[edge_dim] == 2
|
||||
edges_weight = torch.cat([torch.index_select(input=edges_weight, index=torch.tensor(1, device=self.device), dim=edge_dim), -
|
||||
torch.index_select(input=edges_weight, index=torch.tensor(0, device=self.device), dim=edge_dim)]
|
||||
, edge_dim)
|
||||
denominator = edges_weight.sum(edge_dim)
|
||||
ue = (edges_x * edges_weight).sum(edge_dim) / denominator
|
||||
return ue
|
||||
|
||||
def _solve_vd_QEF(self, p_bxnx3, norm_bxnx3, c_bx3, qef_reg_scale):
|
||||
p_bxnx3 = p_bxnx3.reshape(-1, 7, 3)
|
||||
norm_bxnx3 = norm_bxnx3.reshape(-1, 7, 3)
|
||||
c_bx3 = c_bx3.reshape(-1, 3)
|
||||
A = norm_bxnx3
|
||||
B = ((p_bxnx3) * norm_bxnx3).sum(-1, keepdims=True)
|
||||
|
||||
A_reg = (torch.eye(3, device=p_bxnx3.device) * qef_reg_scale).unsqueeze(0).repeat(p_bxnx3.shape[0], 1, 1)
|
||||
B_reg = (qef_reg_scale * c_bx3).unsqueeze(-1)
|
||||
A = torch.cat([A, A_reg], 1)
|
||||
B = torch.cat([B, B_reg], 1)
|
||||
dual_verts = torch.linalg.lstsq(A, B).solution.squeeze(-1)
|
||||
return dual_verts
|
||||
|
||||
def _compute_vd(self, voxelgrid_vertices, surf_cubes_fx8, surf_edges, scalar_field,
|
||||
case_ids, beta, alpha, gamma_f, idx_map, qef_reg_scale, voxelgrid_colors):
|
||||
"""
|
||||
Computes the location of dual vertices as described in Section 4.2
|
||||
"""
|
||||
alpha_nx12x2 = torch.index_select(input=alpha, index=self.cube_edges, dim=1).reshape(-1, 12, 2)
|
||||
surf_edges_x = torch.index_select(input=voxelgrid_vertices, index=surf_edges.reshape(-1), dim=0).reshape(-1, 2, 3)
|
||||
surf_edges_s = torch.index_select(input=scalar_field, index=surf_edges.reshape(-1), dim=0).reshape(-1, 2, 1)
|
||||
zero_crossing = self._linear_interp(surf_edges_s, surf_edges_x)
|
||||
|
||||
if voxelgrid_colors is not None:
|
||||
C = voxelgrid_colors.shape[-1]
|
||||
surf_edges_c = torch.index_select(input=voxelgrid_colors, index=surf_edges.reshape(-1), dim=0).reshape(-1, 2, C)
|
||||
|
||||
idx_map = idx_map.reshape(-1, 12)
|
||||
num_vd = torch.index_select(input=self.num_vd_table, index=case_ids, dim=0)
|
||||
edge_group, edge_group_to_vd, edge_group_to_cube, vd_num_edges, vd_gamma = [], [], [], [], []
|
||||
|
||||
# if color is not None:
|
||||
# vd_color = []
|
||||
|
||||
total_num_vd = 0
|
||||
vd_idx_map = torch.zeros((case_ids.shape[0], 12), dtype=torch.long, device=self.device, requires_grad=False)
|
||||
|
||||
for num in torch.unique(num_vd):
|
||||
cur_cubes = (num_vd == num) # consider cubes with the same numbers of vd emitted (for batching)
|
||||
curr_num_vd = cur_cubes.sum() * num
|
||||
curr_edge_group = self.dmc_table[case_ids[cur_cubes], :num].reshape(-1, num * 7)
|
||||
curr_edge_group_to_vd = torch.arange(
|
||||
curr_num_vd, device=self.device).unsqueeze(-1).repeat(1, 7) + total_num_vd
|
||||
total_num_vd += curr_num_vd
|
||||
curr_edge_group_to_cube = torch.arange(idx_map.shape[0], device=self.device)[
|
||||
cur_cubes].unsqueeze(-1).repeat(1, num * 7).reshape_as(curr_edge_group)
|
||||
|
||||
curr_mask = (curr_edge_group != -1)
|
||||
edge_group.append(torch.masked_select(curr_edge_group, curr_mask))
|
||||
edge_group_to_vd.append(torch.masked_select(curr_edge_group_to_vd.reshape_as(curr_edge_group), curr_mask))
|
||||
edge_group_to_cube.append(torch.masked_select(curr_edge_group_to_cube, curr_mask))
|
||||
vd_num_edges.append(curr_mask.reshape(-1, 7).sum(-1, keepdims=True))
|
||||
vd_gamma.append(torch.masked_select(gamma_f, cur_cubes).unsqueeze(-1).repeat(1, num).reshape(-1))
|
||||
# if color is not None:
|
||||
# vd_color.append(color[cur_cubes].unsqueeze(1).repeat(1, num, 1).reshape(-1, 3))
|
||||
|
||||
edge_group = torch.cat(edge_group)
|
||||
edge_group_to_vd = torch.cat(edge_group_to_vd)
|
||||
edge_group_to_cube = torch.cat(edge_group_to_cube)
|
||||
vd_num_edges = torch.cat(vd_num_edges)
|
||||
vd_gamma = torch.cat(vd_gamma)
|
||||
# if color is not None:
|
||||
# vd_color = torch.cat(vd_color)
|
||||
# else:
|
||||
# vd_color = None
|
||||
|
||||
vd = torch.zeros((total_num_vd, 3), device=self.device)
|
||||
beta_sum = torch.zeros((total_num_vd, 1), device=self.device)
|
||||
|
||||
idx_group = torch.gather(input=idx_map.reshape(-1), dim=0, index=edge_group_to_cube * 12 + edge_group)
|
||||
|
||||
x_group = torch.index_select(input=surf_edges_x, index=idx_group.reshape(-1), dim=0).reshape(-1, 2, 3)
|
||||
s_group = torch.index_select(input=surf_edges_s, index=idx_group.reshape(-1), dim=0).reshape(-1, 2, 1)
|
||||
|
||||
|
||||
zero_crossing_group = torch.index_select(
|
||||
input=zero_crossing, index=idx_group.reshape(-1), dim=0).reshape(-1, 3)
|
||||
|
||||
alpha_group = torch.index_select(input=alpha_nx12x2.reshape(-1, 2), dim=0,
|
||||
index=edge_group_to_cube * 12 + edge_group).reshape(-1, 2, 1)
|
||||
ue_group = self._linear_interp(s_group * alpha_group, x_group)
|
||||
|
||||
beta_group = torch.gather(input=beta.reshape(-1), dim=0,
|
||||
index=edge_group_to_cube * 12 + edge_group).reshape(-1, 1)
|
||||
beta_sum = beta_sum.index_add_(0, index=edge_group_to_vd, source=beta_group)
|
||||
vd = vd.index_add_(0, index=edge_group_to_vd, source=ue_group * beta_group) / beta_sum
|
||||
|
||||
'''
|
||||
interpolate colors use the same method as dual vertices
|
||||
'''
|
||||
if voxelgrid_colors is not None:
|
||||
vd_color = torch.zeros((total_num_vd, C), device=self.device)
|
||||
c_group = torch.index_select(input=surf_edges_c, index=idx_group.reshape(-1), dim=0).reshape(-1, 2, C)
|
||||
uc_group = self._linear_interp(s_group * alpha_group, c_group)
|
||||
vd_color = vd_color.index_add_(0, index=edge_group_to_vd, source=uc_group * beta_group) / beta_sum
|
||||
else:
|
||||
vd_color = None
|
||||
|
||||
L_dev = self._compute_reg_loss(vd, zero_crossing_group, edge_group_to_vd, vd_num_edges)
|
||||
|
||||
v_idx = torch.arange(vd.shape[0], device=self.device) # + total_num_vd
|
||||
|
||||
vd_idx_map = (vd_idx_map.reshape(-1)).scatter(dim=0, index=edge_group_to_cube *
|
||||
12 + edge_group, src=v_idx[edge_group_to_vd])
|
||||
|
||||
return vd, L_dev, vd_gamma, vd_idx_map, vd_color
|
||||
|
||||
def _triangulate(self, scalar_field, surf_edges, vd, vd_gamma, edge_counts, idx_map, vd_idx_map, surf_edges_mask, training, vd_color):
|
||||
"""
|
||||
Connects four neighboring dual vertices to form a quadrilateral. The quadrilaterals are then split into
|
||||
triangles based on the gamma parameter, as described in Section 4.3.
|
||||
"""
|
||||
with torch.no_grad():
|
||||
group_mask = (edge_counts == 4) & surf_edges_mask # surface edges shared by 4 cubes.
|
||||
group = idx_map.reshape(-1)[group_mask]
|
||||
vd_idx = vd_idx_map[group_mask]
|
||||
edge_indices, indices = torch.sort(group, stable=True)
|
||||
quad_vd_idx = vd_idx[indices].reshape(-1, 4)
|
||||
|
||||
# Ensure all face directions point towards the positive SDF to maintain consistent winding.
|
||||
s_edges = scalar_field[surf_edges[edge_indices.reshape(-1, 4)[:, 0]].reshape(-1)].reshape(-1, 2)
|
||||
flip_mask = s_edges[:, 0] > 0
|
||||
quad_vd_idx = torch.cat((quad_vd_idx[flip_mask][:, [0, 1, 3, 2]],
|
||||
quad_vd_idx[~flip_mask][:, [2, 3, 1, 0]]))
|
||||
|
||||
quad_gamma = torch.index_select(input=vd_gamma, index=quad_vd_idx.reshape(-1), dim=0).reshape(-1, 4)
|
||||
gamma_02 = quad_gamma[:, 0] * quad_gamma[:, 2]
|
||||
gamma_13 = quad_gamma[:, 1] * quad_gamma[:, 3]
|
||||
if not training:
|
||||
mask = (gamma_02 > gamma_13)
|
||||
faces = torch.zeros((quad_gamma.shape[0], 6), dtype=torch.long, device=quad_vd_idx.device)
|
||||
faces[mask] = quad_vd_idx[mask][:, self.quad_split_1]
|
||||
faces[~mask] = quad_vd_idx[~mask][:, self.quad_split_2]
|
||||
faces = faces.reshape(-1, 3)
|
||||
else:
|
||||
vd_quad = torch.index_select(input=vd, index=quad_vd_idx.reshape(-1), dim=0).reshape(-1, 4, 3)
|
||||
vd_02 = (vd_quad[:, 0] + vd_quad[:, 2]) / 2
|
||||
vd_13 = (vd_quad[:, 1] + vd_quad[:, 3]) / 2
|
||||
weight_sum = (gamma_02 + gamma_13) + 1e-8
|
||||
vd_center = (vd_02 * gamma_02.unsqueeze(-1) + vd_13 * gamma_13.unsqueeze(-1)) / weight_sum.unsqueeze(-1)
|
||||
|
||||
if vd_color is not None:
|
||||
color_quad = torch.index_select(input=vd_color, index=quad_vd_idx.reshape(-1), dim=0).reshape(-1, 4, vd_color.shape[-1])
|
||||
color_02 = (color_quad[:, 0] + color_quad[:, 2]) / 2
|
||||
color_13 = (color_quad[:, 1] + color_quad[:, 3]) / 2
|
||||
color_center = (color_02 * gamma_02.unsqueeze(-1) + color_13 * gamma_13.unsqueeze(-1)) / weight_sum.unsqueeze(-1)
|
||||
vd_color = torch.cat([vd_color, color_center])
|
||||
|
||||
|
||||
vd_center_idx = torch.arange(vd_center.shape[0], device=self.device) + vd.shape[0]
|
||||
vd = torch.cat([vd, vd_center])
|
||||
faces = quad_vd_idx[:, self.quad_split_train].reshape(-1, 4, 2)
|
||||
faces = torch.cat([faces, vd_center_idx.reshape(-1, 1, 1).repeat(1, 4, 1)], -1).reshape(-1, 3)
|
||||
return vd, faces, s_edges, edge_indices, vd_color
|
||||
@@ -0,0 +1,791 @@
|
||||
# Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
#
|
||||
# NVIDIA CORPORATION & AFFILIATES and its licensors retain all intellectual property
|
||||
# and proprietary rights in and to this software, related documentation
|
||||
# and any modifications thereto. Any use, reproduction, disclosure or
|
||||
# distribution of this software and related documentation without an express
|
||||
# license agreement from NVIDIA CORPORATION & AFFILIATES is strictly prohibited.
|
||||
dmc_table = [
|
||||
[[-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 8, 9, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 7, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 5, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 8, 9, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 7, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 7, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 5, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 8, 9, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 7, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 7, 8, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 8, 11, -1, -1, -1], [4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 5, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 8, 9, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 7, 8, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 5, 7, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 8, 9, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 7, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 9, 10, -1, -1, -1], [4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 7, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 5, 9, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 5, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 5, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 8, 9, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 7, 9, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 7, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 8, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 9, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[8, 9, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [1, 3, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 7, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 9, 10, 11, -1, -1], [4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 9, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [1, 3, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 8, 10, 11, -1, -1], [4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 5, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 8, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 8, 9, -1, -1, -1], [1, 3, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 7, 9, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 7, 8, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 8, 9, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 6, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 6, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 6, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 5, 9, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 5, 8, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 6, 8, 9, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 6, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 6, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 5, 6, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 6, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 6, 7, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 6, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 6, 7, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 6, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 4, 6, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 6, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [2, 3, 6, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 6, 7, 8, -1, -1], [4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, -1, -1, -1], [2, 3, 6, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 5, 6, 7, 8], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 6, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 6, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 5, 6, 8], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 5, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 10, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 9, 10, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 8, 9, 10, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 8, 11, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 6, 11, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 9, 10, -1, -1, -1], [4, 6, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 6, 9, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 5, 9, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 5, 10, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 5, 8, 10, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 6, 8, 9, 11, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 6, 9, 11, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 6, 8, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 6, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 6, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 6, 7, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 6, 7, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[6, 7, 8, 9, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 6, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 6, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 6, 8, 9, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [1, 3, 6, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 6, 7, 8, 10, -1], [4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 5, 6, 7, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 6, 7, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 5, 6, 8, 9, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 6, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 8, 9, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 7, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 7, 8, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 7, 9, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 6, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 6, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 6, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[6, 7, 8, 9, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 6, 7, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 6, 7, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 6, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 11, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 8, 11, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 8, 9, 11, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 7, 11, -1, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 7, 8, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [5, 6, 10, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 7, 9, 11, -1], [5, 6, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 9, 10, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 8, 11, -1, -1, -1], [4, 6, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 6, 10, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 6, 8, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[6, 7, 8, 9, 10, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 6, 7, 9, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 6, 7, 8, 10, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 6, 7, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 5, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 5, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 6, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 6, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [1, 2, 5, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 7, -1, -1, -1], [1, 2, 5, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 6, 9, -1, -1], [4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 5, 6, 7, 9], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 6, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 4, 6, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 6, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 6, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 6, 7, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 6, 7, 9], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 6, 7, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 6, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 5, 6, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 6, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 6, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 6, 8, 9, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [1, 3, 5, 6, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, 6, 7, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 6, 9, 11, -1], [4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 6, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 6, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 6, 8, 9, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 6, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 6, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 6, 7, 8, 9, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 6, 7, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[6, 7, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [5, 7, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [5, 7, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 8, 9, -1, -1, -1], [5, 7, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 8, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 5, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [4, 5, 8, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 5, 9, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 9, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [4, 7, 9, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 7, 10, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 7, 8, 10, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[8, 9, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 9, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 8, 10, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 10, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 7, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 5, 7, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 5, 7, 8, 9, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 5, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 5, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [2, 3, 4, 5, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 5, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 7, 9, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 7, 8, 9, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 4, 7, 10], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 8, 9, 10, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 9, 10, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 8, 10, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 10, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 5, 7, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 5, 7, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 5, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 5, 7, 8, 9, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 5, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 4, 5, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 5, 8, 9, 11], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 4, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [1, 2, 4, 7, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 4, 7, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 4, 7, 8, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 2, 8, 9, 11, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 2, 3, 9, 11, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 2, 8, 11, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[2, 3, 11, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 5, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 5, 7, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 5, 7, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[5, 7, 8, 9, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 5, 8, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 5, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 5, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 5, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 4, 7, 9, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 4, 7, 8, 9, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 4, 7, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[4, 7, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[1, 3, 8, 9, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 1, 9, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[0, 3, 8, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]],
|
||||
[[-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]]
|
||||
]
|
||||
num_vd_table = [0, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 2, 2,
|
||||
2, 1, 2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 1, 2, 3, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2,
|
||||
1, 2, 1, 2, 2, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 2, 3, 2, 2, 1, 1, 1, 1,
|
||||
1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 2, 3, 2, 2, 2, 2, 2, 1, 3, 4, 2,
|
||||
2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, 2, 2,
|
||||
3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 3, 2, 3, 2, 4, 2, 2, 2, 2, 1, 2, 1, 2, 1, 1,
|
||||
2, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1,
|
||||
1, 2, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2,
|
||||
1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
|
||||
check_table = [
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 194],
|
||||
[1, -1, 0, 0, 193],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 164],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, -1, 0, 161],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 152],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 145],
|
||||
[1, 0, 0, 1, 144],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, -1, 137],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 133],
|
||||
[1, 0, 1, 0, 132],
|
||||
[1, 1, 0, 0, 131],
|
||||
[1, 1, 0, 0, 130],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 100],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 98],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 1, 96],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 88],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, -1, 0, 82],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 74],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 1, 0, 72],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, -1, 70],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, -1, 0, 0, 67],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, -1, 0, 0, 65],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 56],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, -1, 0, 0, 52],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 44],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 40],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, -1, 38],
|
||||
[1, 0, -1, 0, 37],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, -1, 0, 33],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, -1, 0, 0, 28],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, -1, 0, 26],
|
||||
[1, 0, 0, -1, 25],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, -1, 0, 0, 20],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, -1, 0, 18],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, -1, 9],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, -1, 6],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]
|
||||
]
|
||||
tet_table = [
|
||||
[-1, -1, -1, -1, -1, -1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 4, 4, -1],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[0, 4, 0, 4, 4, -1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, -1, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, -1, 2, 4, 4, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 4, 4, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 4, 2, 4, 4, 2],
|
||||
[0, 4, 0, 4, 4, 0],
|
||||
[2, 0, 2, 0, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 5, 2, 5, 5, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 0, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, -1, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[4, 1, 1, 4, 4, 1],
|
||||
[0, 1, 1, 0, 0, 1],
|
||||
[4, 0, 0, 4, 4, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[-1, 1, 1, 4, 4, 1],
|
||||
[0, 1, 1, 4, 4, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[5, 1, 1, 5, 5, 1],
|
||||
[0, 1, 1, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[8, 8, 8, 8, 8, 8],
|
||||
[1, 1, 1, 4, 4, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 4, 4, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 4, 4, 1],
|
||||
[0, 4, 0, 4, 4, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 5, 5, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[6, 6, 6, 6, 6, 6],
|
||||
[6, -1, 0, 6, 0, 6],
|
||||
[6, 0, 0, 6, 0, 6],
|
||||
[6, 1, 1, 6, 1, 6],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[6, 4, -1, 6, 4, 6],
|
||||
[6, 4, 0, 6, 4, 6],
|
||||
[6, 0, 0, 6, 0, 6],
|
||||
[6, 1, 1, 6, 1, 6],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 2, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 4, 2, 2, 4, 2],
|
||||
[0, 4, 0, 4, 4, 0],
|
||||
[2, 0, 2, 2, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[6, 1, 1, 6, -1, 6],
|
||||
[6, 1, 1, 6, 0, 6],
|
||||
[6, 0, 0, 6, 0, 6],
|
||||
[6, 2, 2, 6, 2, 6],
|
||||
[4, 1, 1, 4, 4, 1],
|
||||
[0, 1, 1, 0, 0, 1],
|
||||
[4, 0, 0, 4, 4, 4],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[6, 1, 1, 6, 4, 6],
|
||||
[6, 1, 1, 6, 4, 6],
|
||||
[6, 0, 0, 6, 0, 6],
|
||||
[6, 2, 2, 6, 2, 6],
|
||||
[5, 1, 1, 5, 5, 1],
|
||||
[0, 1, 1, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[6, 6, 6, 6, 6, 6],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 4, 1],
|
||||
[0, 4, 0, 4, 4, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 5, 0, 5, 0, 5],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[0, 5, 0, 5, 0, 5],
|
||||
[-1, 5, 0, 5, 0, 5],
|
||||
[1, 5, 1, 5, 1, 5],
|
||||
[4, 5, -1, 5, 4, 5],
|
||||
[0, 5, 0, 5, 0, 5],
|
||||
[4, 5, 0, 5, 4, 5],
|
||||
[1, 5, 1, 5, 1, 5],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[0, 4, 0, 4, 4, 4],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[6, 6, 6, 6, 6, 6],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 5, 2, 5, -1, 5],
|
||||
[0, 5, 0, 5, 0, 5],
|
||||
[2, 5, 2, 5, 0, 5],
|
||||
[1, 5, 1, 5, 1, 5],
|
||||
[2, 5, 2, 5, 4, 5],
|
||||
[0, 5, 0, 5, 0, 5],
|
||||
[2, 5, 2, 5, 4, 5],
|
||||
[1, 5, 1, 5, 1, 5],
|
||||
[2, 4, 2, 4, 4, 2],
|
||||
[0, 4, 0, 4, 4, 4],
|
||||
[2, 0, 2, 0, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 6, 2, 6, 6, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 0, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[4, 1, 1, 1, 4, 1],
|
||||
[0, 1, 1, 1, 0, 1],
|
||||
[4, 0, 0, 4, 4, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[1, 1, 1, 1, 4, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 0, 0, 4, 4, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[6, 0, 0, 6, 0, 6],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[6, 6, 6, 6, 6, 6],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[5, 5, 0, 5, 0, 5],
|
||||
[5, 5, 0, 5, 0, 5],
|
||||
[5, 5, 1, 5, 1, 5],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 0, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[4, 4, 0, 4, 4, 4],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[8, 8, 8, 8, 8, 8],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 0, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 1, 1, 4, 4, 1],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 4, 2, 4, 4, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[5, 5, 5, 5, 5, 5],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[4, 4, 4, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[12, 12, 12, 12, 12, 12]
|
||||
]
|
||||
@@ -0,0 +1,61 @@
|
||||
import torch
|
||||
cube_corners = torch.tensor([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], [0, 0, 1], [
|
||||
1, 0, 1], [0, 1, 1], [1, 1, 1]], dtype=torch.int)
|
||||
cube_neighbor = torch.tensor([[1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1], [0, 0, -1]])
|
||||
cube_edges = torch.tensor([0, 1, 1, 5, 4, 5, 0, 4, 2, 3, 3, 7, 6, 7, 2, 6,
|
||||
2, 0, 3, 1, 7, 5, 6, 4], dtype=torch.long, requires_grad=False)
|
||||
|
||||
def construct_dense_grid(res, device='cuda'):
|
||||
'''construct a dense grid based on resolution'''
|
||||
res_v = res + 1
|
||||
vertsid = torch.arange(res_v ** 3, device=device)
|
||||
coordsid = vertsid.reshape(res_v, res_v, res_v)[:res, :res, :res].flatten()
|
||||
cube_corners_bias = (cube_corners[:, 0] * res_v + cube_corners[:, 1]) * res_v + cube_corners[:, 2]
|
||||
cube_fx8 = (coordsid.unsqueeze(1) + cube_corners_bias.unsqueeze(0).to(device))
|
||||
verts = torch.stack([vertsid // (res_v ** 2), (vertsid // res_v) % res_v, vertsid % res_v], dim=1)
|
||||
return verts, cube_fx8
|
||||
|
||||
|
||||
def construct_voxel_grid(coords):
|
||||
verts = (cube_corners.unsqueeze(0).to(coords) + coords.unsqueeze(1)).reshape(-1, 3)
|
||||
verts_unique, inverse_indices = torch.unique(verts, dim=0, return_inverse=True)
|
||||
cubes = inverse_indices.reshape(-1, 8)
|
||||
return verts_unique, cubes
|
||||
|
||||
|
||||
def cubes_to_verts(num_verts, cubes, value, reduce='mean'):
|
||||
"""
|
||||
Args:
|
||||
cubes [Vx8] verts index for each cube
|
||||
value [Vx8xM] value to be scattered
|
||||
Operation:
|
||||
reduced[cubes[i][j]][k] += value[i][k]
|
||||
"""
|
||||
M = value.shape[2] # number of channels
|
||||
reduced = torch.zeros(num_verts, M, device=cubes.device)
|
||||
return torch.scatter_reduce(reduced, 0,
|
||||
cubes.unsqueeze(-1).expand(-1, -1, M).flatten(0, 1),
|
||||
value.flatten(0, 1), reduce=reduce, include_self=False)
|
||||
|
||||
def sparse_cube2verts(coords, feats, training=True):
|
||||
new_coords, cubes = construct_voxel_grid(coords)
|
||||
new_feats = cubes_to_verts(new_coords.shape[0], cubes, feats)
|
||||
if training:
|
||||
con_loss = torch.mean((feats - new_feats[cubes]) ** 2)
|
||||
else:
|
||||
con_loss = 0.0
|
||||
return new_coords, new_feats, con_loss
|
||||
|
||||
|
||||
def get_dense_attrs(coords : torch.Tensor, feats : torch.Tensor, res : int, sdf_init=True):
|
||||
F = feats.shape[-1]
|
||||
dense_attrs = torch.zeros([res] * 3 + [F], device=feats.device)
|
||||
if sdf_init:
|
||||
dense_attrs[..., 0] = 1 # initial outside sdf value
|
||||
dense_attrs[coords[:, 0], coords[:, 1], coords[:, 2], :] = feats
|
||||
return dense_attrs.reshape(-1, F)
|
||||
|
||||
|
||||
def get_defomed_verts(v_pos : torch.Tensor, deform : torch.Tensor, res):
|
||||
return v_pos / res - 0.5 + (1 - 1e-8) / (res * 2) * torch.tanh(deform)
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import torch
|
||||
from typing import Union, List, Optional, Tuple, Dict
|
||||
import nvdiffrast.torch as dr
|
||||
import utils3d
|
||||
class RastContext:
|
||||
def __init__(self, backend='cuda'):
|
||||
self.backend = backend
|
||||
|
||||
def rasterize_triangle_faces(
|
||||
ctx: RastContext,
|
||||
vertices: torch.Tensor,
|
||||
faces: torch.Tensor,
|
||||
width: int,
|
||||
height: int,
|
||||
attr: torch.Tensor = None,
|
||||
uv: torch.Tensor = None,
|
||||
texture: torch.Tensor = None,
|
||||
model: torch.Tensor = None,
|
||||
view: torch.Tensor = None,
|
||||
projection: torch.Tensor = None,
|
||||
antialiasing: Union[bool, List[int]] = True,
|
||||
diff_attrs: Union[None, List[int]] = None,
|
||||
) -> Dict[str, torch.Tensor]:
|
||||
"""
|
||||
Rasterize a mesh with vertex attributes.
|
||||
"""
|
||||
assert vertices.ndim == 3
|
||||
assert faces.ndim == 2
|
||||
|
||||
# Handle vertices dimensions
|
||||
if vertices.shape[-1] == 2:
|
||||
vertices = torch.cat([vertices, torch.zeros_like(vertices[..., :1]), torch.ones_like(vertices[..., :1])], dim=-1)
|
||||
elif vertices.shape[-1] == 3:
|
||||
vertices = torch.cat([vertices, torch.ones_like(vertices[..., :1])], dim=-1)
|
||||
elif vertices.shape[-1] == 4:
|
||||
pass
|
||||
else:
|
||||
raise ValueError(f'Wrong shape of vertices: {vertices.shape}')
|
||||
|
||||
# Calculate MVP matrix
|
||||
mvp = projection if projection is not None else torch.eye(4, device=vertices.device)
|
||||
if view is not None:
|
||||
mvp = mvp @ view
|
||||
if model is not None:
|
||||
mvp = mvp @ model
|
||||
|
||||
# Transform vertices to clip space
|
||||
pos_clip = vertices @ mvp.transpose(-1, -2)
|
||||
faces = faces.contiguous()
|
||||
if attr is not None:
|
||||
attr = attr.contiguous()
|
||||
|
||||
# Rasterize
|
||||
rast_out, rast_db = dr.rasterize(ctx.nvd_ctx, pos_clip, faces, resolution=[height, width], grad_db=True)
|
||||
|
||||
# Extract basic outputs
|
||||
face_id = rast_out[..., 3].flip(1)
|
||||
depth = rast_out[..., 2].flip(1)
|
||||
mask = (face_id > 0).float()
|
||||
depth = (depth * 0.5 + 0.5) * mask + (1.0 - mask)
|
||||
|
||||
ret = {
|
||||
'depth': depth,
|
||||
'mask': mask,
|
||||
'face_id': face_id,
|
||||
}
|
||||
|
||||
# Handle attribute interpolation
|
||||
if attr is not None:
|
||||
image, image_dr = dr.interpolate(attr, rast_out, faces, rast_db, diff_attrs=diff_attrs)
|
||||
|
||||
if antialiasing == True:
|
||||
image = dr.antialias(image, rast_out, pos_clip, faces)
|
||||
elif isinstance(antialiasing, list):
|
||||
aa_image = dr.antialias(image[..., antialiasing], rast_out, pos_clip, faces)
|
||||
image[..., antialiasing] = aa_image
|
||||
|
||||
image = image.flip(1).permute(0, 3, 1, 2)
|
||||
ret['image'] = image
|
||||
|
||||
# Handle UV mapping
|
||||
if uv is not None:
|
||||
uv_map, uv_map_dr = dr.interpolate(uv, rast_out, faces, rast_db, diff_attrs='all')
|
||||
ret['uv'] = uv_map
|
||||
ret['uv_dr'] = uv_map_dr
|
||||
|
||||
if texture is not None:
|
||||
texture_map = dr.texture(texture, uv_map, uv_map_dr)
|
||||
ret['texture'] = texture_map.flip(1).permute(0, 3, 1, 2)
|
||||
|
||||
# Handle derivatives
|
||||
if diff_attrs is not None:
|
||||
image_dr = image_dr.flip(1).permute(0, 3, 1, 2)
|
||||
ret['image_dr'] = image_dr
|
||||
|
||||
return ret
|
||||
@@ -0,0 +1,187 @@
|
||||
import numpy as np
|
||||
import cv2
|
||||
import torch
|
||||
|
||||
|
||||
# Dictionary utils
|
||||
def _dict_merge(dicta, dictb, prefix=''):
|
||||
"""
|
||||
Merge two dictionaries.
|
||||
"""
|
||||
assert isinstance(dicta, dict), 'input must be a dictionary'
|
||||
assert isinstance(dictb, dict), 'input must be a dictionary'
|
||||
dict_ = {}
|
||||
all_keys = set(dicta.keys()).union(set(dictb.keys()))
|
||||
for key in all_keys:
|
||||
if key in dicta.keys() and key in dictb.keys():
|
||||
if isinstance(dicta[key], dict) and isinstance(dictb[key], dict):
|
||||
dict_[key] = _dict_merge(dicta[key], dictb[key], prefix=f'{prefix}.{key}')
|
||||
else:
|
||||
raise ValueError(f'Duplicate key {prefix}.{key} found in both dictionaries. Types: {type(dicta[key])}, {type(dictb[key])}')
|
||||
elif key in dicta.keys():
|
||||
dict_[key] = dicta[key]
|
||||
else:
|
||||
dict_[key] = dictb[key]
|
||||
return dict_
|
||||
|
||||
|
||||
def dict_merge(dicta, dictb):
|
||||
"""
|
||||
Merge two dictionaries.
|
||||
"""
|
||||
return _dict_merge(dicta, dictb, prefix='')
|
||||
|
||||
|
||||
def dict_foreach(dic, func, special_func={}):
|
||||
"""
|
||||
Recursively apply a function to all non-dictionary leaf values in a dictionary.
|
||||
"""
|
||||
assert isinstance(dic, dict), 'input must be a dictionary'
|
||||
for key in dic.keys():
|
||||
if isinstance(dic[key], dict):
|
||||
dic[key] = dict_foreach(dic[key], func)
|
||||
else:
|
||||
if key in special_func.keys():
|
||||
dic[key] = special_func[key](dic[key])
|
||||
else:
|
||||
dic[key] = func(dic[key])
|
||||
return dic
|
||||
|
||||
|
||||
def dict_reduce(dicts, func, special_func={}):
|
||||
"""
|
||||
Reduce a list of dictionaries. Leaf values must be scalars.
|
||||
"""
|
||||
assert isinstance(dicts, list), 'input must be a list of dictionaries'
|
||||
assert all([isinstance(d, dict) for d in dicts]), 'input must be a list of dictionaries'
|
||||
assert len(dicts) > 0, 'input must be a non-empty list of dictionaries'
|
||||
all_keys = set([key for dict_ in dicts for key in dict_.keys()])
|
||||
reduced_dict = {}
|
||||
for key in all_keys:
|
||||
vlist = [dict_[key] for dict_ in dicts if key in dict_.keys()]
|
||||
if isinstance(vlist[0], dict):
|
||||
reduced_dict[key] = dict_reduce(vlist, func, special_func)
|
||||
else:
|
||||
if key in special_func.keys():
|
||||
reduced_dict[key] = special_func[key](vlist)
|
||||
else:
|
||||
reduced_dict[key] = func(vlist)
|
||||
return reduced_dict
|
||||
|
||||
|
||||
def dict_any(dic, func):
|
||||
"""
|
||||
Recursively apply a function to all non-dictionary leaf values in a dictionary.
|
||||
"""
|
||||
assert isinstance(dic, dict), 'input must be a dictionary'
|
||||
for key in dic.keys():
|
||||
if isinstance(dic[key], dict):
|
||||
if dict_any(dic[key], func):
|
||||
return True
|
||||
else:
|
||||
if func(dic[key]):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def dict_all(dic, func):
|
||||
"""
|
||||
Recursively apply a function to all non-dictionary leaf values in a dictionary.
|
||||
"""
|
||||
assert isinstance(dic, dict), 'input must be a dictionary'
|
||||
for key in dic.keys():
|
||||
if isinstance(dic[key], dict):
|
||||
if not dict_all(dic[key], func):
|
||||
return False
|
||||
else:
|
||||
if not func(dic[key]):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def dict_flatten(dic, sep='.'):
|
||||
"""
|
||||
Flatten a nested dictionary into a dictionary with no nested dictionaries.
|
||||
"""
|
||||
assert isinstance(dic, dict), 'input must be a dictionary'
|
||||
flat_dict = {}
|
||||
for key in dic.keys():
|
||||
if isinstance(dic[key], dict):
|
||||
sub_dict = dict_flatten(dic[key], sep=sep)
|
||||
for sub_key in sub_dict.keys():
|
||||
flat_dict[str(key) + sep + str(sub_key)] = sub_dict[sub_key]
|
||||
else:
|
||||
flat_dict[key] = dic[key]
|
||||
return flat_dict
|
||||
|
||||
|
||||
def make_grid(images, nrow=None, ncol=None, aspect_ratio=None):
|
||||
num_images = len(images)
|
||||
if nrow is None and ncol is None:
|
||||
if aspect_ratio is not None:
|
||||
nrow = int(np.round(np.sqrt(num_images / aspect_ratio)))
|
||||
else:
|
||||
nrow = int(np.sqrt(num_images))
|
||||
ncol = (num_images + nrow - 1) // nrow
|
||||
elif nrow is None and ncol is not None:
|
||||
nrow = (num_images + ncol - 1) // ncol
|
||||
elif nrow is not None and ncol is None:
|
||||
ncol = (num_images + nrow - 1) // nrow
|
||||
else:
|
||||
assert nrow * ncol >= num_images, 'nrow * ncol must be greater than or equal to the number of images'
|
||||
|
||||
grid = np.zeros((nrow * images[0].shape[0], ncol * images[0].shape[1], images[0].shape[2]), dtype=images[0].dtype)
|
||||
for i, img in enumerate(images):
|
||||
row = i // ncol
|
||||
col = i % ncol
|
||||
grid[row * img.shape[0]:(row + 1) * img.shape[0], col * img.shape[1]:(col + 1) * img.shape[1]] = img
|
||||
return grid
|
||||
|
||||
|
||||
def notes_on_image(img, notes=None):
|
||||
img = np.pad(img, ((0, 32), (0, 0), (0, 0)), 'constant', constant_values=0)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
if notes is not None:
|
||||
img = cv2.putText(img, notes, (0, img.shape[0] - 4), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 1)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
return img
|
||||
|
||||
|
||||
def save_image_with_notes(img, path, notes=None):
|
||||
"""
|
||||
Save an image with notes.
|
||||
"""
|
||||
if isinstance(img, torch.Tensor):
|
||||
img = img.cpu().numpy().transpose(1, 2, 0)
|
||||
if img.dtype == np.float32 or img.dtype == np.float64:
|
||||
img = np.clip(img * 255, 0, 255).astype(np.uint8)
|
||||
img = notes_on_image(img, notes)
|
||||
cv2.imwrite(path, cv2.cvtColor(img, cv2.COLOR_RGB2BGR))
|
||||
|
||||
|
||||
# debug utils
|
||||
|
||||
def atol(x, y):
|
||||
"""
|
||||
Absolute tolerance.
|
||||
"""
|
||||
return torch.abs(x - y)
|
||||
|
||||
|
||||
def rtol(x, y):
|
||||
"""
|
||||
Relative tolerance.
|
||||
"""
|
||||
return torch.abs(x - y) / torch.clamp_min(torch.maximum(torch.abs(x), torch.abs(y)), 1e-12)
|
||||
|
||||
|
||||
# print utils
|
||||
def indent(s, n=4):
|
||||
"""
|
||||
Indent a string.
|
||||
"""
|
||||
lines = s.split('\n')
|
||||
for i in range(1, len(lines)):
|
||||
lines[i] = ' ' * n + lines[i]
|
||||
return '\n'.join(lines)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import numpy as np
|
||||
|
||||
PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53]
|
||||
|
||||
def radical_inverse(base, n):
|
||||
val = 0
|
||||
inv_base = 1.0 / base
|
||||
inv_base_n = inv_base
|
||||
while n > 0:
|
||||
digit = n % base
|
||||
val += digit * inv_base_n
|
||||
n //= base
|
||||
inv_base_n *= inv_base
|
||||
return val
|
||||
|
||||
def halton_sequence(dim, n):
|
||||
return [radical_inverse(PRIMES[dim], n) for dim in range(dim)]
|
||||
|
||||
def hammersley_sequence(dim, n, num_samples):
|
||||
return [n / num_samples] + halton_sequence(dim - 1, n)
|
||||
|
||||
def sphere_hammersley_sequence(n, num_samples, offset=(0, 0), remap=False):
|
||||
u, v = hammersley_sequence(2, n, num_samples)
|
||||
u += offset[0] / num_samples
|
||||
v += offset[1]
|
||||
if remap:
|
||||
u = 2 * u if u < 0.25 else 2 / 3 * u + 1 / 3
|
||||
theta = np.arccos(1 - 2 * u) - np.pi / 2
|
||||
phi = v * 2 * np.pi
|
||||
return [phi, theta]
|
||||
@@ -0,0 +1,232 @@
|
||||
# trellis_model_manager.py
|
||||
import os
|
||||
import logging
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from safetensors.torch import load_file
|
||||
import folder_paths
|
||||
from huggingface_hub import hf_hub_download, snapshot_download
|
||||
from typing import Dict, Union
|
||||
import json
|
||||
import importlib # Import the importlib module
|
||||
from trellis.modules.utils import convert_module_to_f16, convert_module_to_f32
|
||||
|
||||
logger = logging.getLogger('model_manager')
|
||||
|
||||
__attributes = {
|
||||
'SparseStructureDecoder': 'trellis.models.sparse_structure_vae',
|
||||
'SparseStructureFlowModel': 'trellis.models.sparse_structure_flow',
|
||||
'SLatFlowModel': 'trellis.models.structured_latent_flow',
|
||||
}
|
||||
|
||||
__all__ = list(__attributes.keys())
|
||||
|
||||
def __getattr__(name):
|
||||
if name in __attributes:
|
||||
module_name = __attributes[name]
|
||||
module = importlib.import_module(module_name, package=None) # Import the module
|
||||
return getattr(module, name)
|
||||
raise AttributeError(f"module {__name__} has no attribute {name}")
|
||||
|
||||
class TrellisModelManager:
|
||||
"""
|
||||
Basic manager for Trellis models, using ComfyUI's new model path.
|
||||
"""
|
||||
def __init__(self, model_dir: str, config=None, device: str = "cuda"):
|
||||
"""
|
||||
Initialize the model manager with a specific model directory.
|
||||
|
||||
Args:
|
||||
model_dir (str): Path to model directory (e.g. "models/checkpoints/TRELLIS-image-large")
|
||||
config (dict or object): Global configuration for Trellis
|
||||
device (str): Device to load models on (e.g. "cuda")
|
||||
"""
|
||||
self.model_dir = model_dir
|
||||
# Handle config being either a dict or an object
|
||||
if config is None:
|
||||
self.device = device
|
||||
elif isinstance(config, dict):
|
||||
self.device = config.get('device', device)
|
||||
self.config = config
|
||||
else:
|
||||
self.device = getattr(config, 'device', device)
|
||||
self.config = config
|
||||
self.model = None
|
||||
self.dinov2_model = None
|
||||
|
||||
def load(self) -> None:
|
||||
"""Load model configuration and checkpoints"""
|
||||
try:
|
||||
# Ensure directory exists
|
||||
os.makedirs(self.model_dir, exist_ok=True)
|
||||
ckpts_folder = os.path.join(self.model_dir, "ckpts")
|
||||
os.makedirs(ckpts_folder, exist_ok=True)
|
||||
|
||||
# Download model files if needed
|
||||
if not os.path.exists(os.path.join(self.model_dir, "pipeline.json")):
|
||||
logger.info("Downloading TRELLIS models...")
|
||||
try:
|
||||
# Download main pipeline files
|
||||
snapshot_download(
|
||||
repo_id="Stable-X/trellis-normal-v0-1",
|
||||
local_dir=self.model_dir,
|
||||
local_dir_use_symlinks=False,
|
||||
allow_patterns=["pipeline.json", "README.md"]
|
||||
)
|
||||
# Download checkpoint files
|
||||
snapshot_download(
|
||||
repo_id="Stable-X/trellis-normal-v0-1",
|
||||
local_dir=ckpts_folder,
|
||||
local_dir_use_symlinks=False,
|
||||
allow_patterns=["*.safetensors", "*.json"],
|
||||
cache_dir=os.path.join(self.model_dir, ".cache")
|
||||
)
|
||||
logger.info("Model files downloaded successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Error downloading model files: {str(e)}")
|
||||
raise
|
||||
|
||||
# Load configuration
|
||||
self.config = self._load_config()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in load(): {str(e)}")
|
||||
raise
|
||||
|
||||
def get_checkpoint_path(self, filename: str) -> str:
|
||||
"""
|
||||
Returns the full path to a checkpoint file.
|
||||
"""
|
||||
ckpts_folder = os.path.join(self.model_dir, "ckpts")
|
||||
# Add .safetensors extension if not present
|
||||
if not filename.endswith('.safetensors'):
|
||||
filename = f"{filename}.safetensors"
|
||||
full_path = os.path.join(ckpts_folder, filename)
|
||||
if not os.path.exists(full_path):
|
||||
raise FileNotFoundError(f"Checkpoint file not found: {full_path}")
|
||||
return full_path
|
||||
|
||||
def _load_config(self) -> Dict:
|
||||
"""Load model configuration from pipeline.json"""
|
||||
try:
|
||||
config_path = os.path.join(self.model_dir, "pipeline.json")
|
||||
|
||||
if os.path.exists(config_path):
|
||||
logger.info(f"Loading config from {config_path}")
|
||||
with open(config_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
else:
|
||||
logger.info(f"Config not found locally, downloading from HuggingFace")
|
||||
config_path = hf_hub_download(
|
||||
repo_id=f"JeffreyXiang/{os.path.basename(self.model_dir)}",
|
||||
filename="pipeline.json",
|
||||
cache_dir=os.path.join(self.model_dir, ".cache")
|
||||
)
|
||||
with open(config_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
# Debug: Print raw config
|
||||
logger.info("Raw config contents:")
|
||||
logger.info(json.dumps(config, indent=2))
|
||||
|
||||
if not config:
|
||||
raise ValueError(f"Could not load valid configuration from {self.model_dir}")
|
||||
|
||||
if 'name' not in config:
|
||||
config['name'] = 'TrellisImageTo3DPipeline'
|
||||
|
||||
return config
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading config from {self.model_dir}: {e}")
|
||||
return {
|
||||
'name': 'TrellisImageTo3DPipeline',
|
||||
'version': '1.0'
|
||||
}
|
||||
|
||||
def load_models(self) -> Dict[str, nn.Module]:
|
||||
"""Load all required models with current configuration"""
|
||||
return {
|
||||
'sparse_structure_flow_model': self.get_checkpoint_path("ss_flow_img_dit_L_16l8_fp16"),
|
||||
'slat_flow_model': self.get_checkpoint_path("slat_flow_img_dit_L_64l8p2_fp16")
|
||||
}
|
||||
|
||||
def load_model_components(self) -> Dict[str, nn.Module]:
|
||||
"""Loads individual model components."""
|
||||
models = {}
|
||||
model_paths = self.load_models()
|
||||
for name, path in model_paths.items():
|
||||
models[name] = models.from_pretrained(path, config=self.config)
|
||||
|
||||
# Ensure each model is converted to the desired precision
|
||||
if self.config.get('use_fp16', True):
|
||||
convert_module_to_f16(models[name])
|
||||
else:
|
||||
convert_module_to_f32(models[name])
|
||||
|
||||
# DINOv2 is handled separately
|
||||
# models['image_cond_model'] = self.load_dinov2(self.config.get("dinov2_model", "dinov2_vitl14"))
|
||||
|
||||
return models
|
||||
|
||||
def load_dinov2(self, model_name: str):
|
||||
"""Load DINOv2 model with device, precision, and attention backend management"""
|
||||
try:
|
||||
# Get configuration values
|
||||
use_fp16 = (self.config.get('use_fp16', True)
|
||||
if isinstance(self.config, dict)
|
||||
else getattr(self.config, 'use_fp16', True))
|
||||
|
||||
# Get attention backend from config
|
||||
attention_backend = (self.config.get('attention_backend', 'default')
|
||||
if isinstance(self.config, dict)
|
||||
else getattr(self.config, 'attention_backend', 'default'))
|
||||
|
||||
# Try to load from local path first
|
||||
model_path = folder_paths.get_full_path("classifiers", f"{model_name}.pth")
|
||||
|
||||
if model_path is None:
|
||||
print(f"Downloading {model_name} from torch hub...")
|
||||
try:
|
||||
# Load model architecture with specified attention backend
|
||||
model = torch.hub.load('facebookresearch/dinov2', model_name,
|
||||
pretrained=True,
|
||||
force_reload=False,
|
||||
trust_repo=True)
|
||||
|
||||
# Save model for future use
|
||||
save_dir = os.path.join(folder_paths.models_dir, "classifiers")
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
save_path = os.path.join(save_dir, f"{model_name}.pth")
|
||||
|
||||
# Save on CPU to avoid memory issues
|
||||
model = model.cpu()
|
||||
torch.save(model.state_dict(), save_path)
|
||||
print(f"Saved DINOv2 model to {save_path}")
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to download DINOv2 model: {str(e)}")
|
||||
else:
|
||||
# Load from local path
|
||||
print(f"Loading DINOv2 model from {model_path}")
|
||||
model = torch.hub.load('facebookresearch/dinov2', model_name,
|
||||
pretrained=False,
|
||||
force_reload=False,
|
||||
trust_repo=True)
|
||||
model.load_state_dict(torch.load(model_path))
|
||||
|
||||
# Move model to specified device and apply precision settings
|
||||
model = model.to(self.device)
|
||||
if use_fp16:
|
||||
model = model.half()
|
||||
|
||||
# Set attention backend if specified in config
|
||||
if hasattr(model, 'set_attention_backend') and attention_backend != 'default':
|
||||
model.set_attention_backend(attention_backend)
|
||||
|
||||
model.eval()
|
||||
return model
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Error loading DINOv2 model: {str(e)}")
|
||||
@@ -0,0 +1,385 @@
|
||||
{
|
||||
"id": "e7d35c6d-c1bf-4f60-9b88-7dc8e70a7b70",
|
||||
"revision": 0,
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 8,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "[Comfy3D] StableGen StableX Process Image",
|
||||
"pos": [
|
||||
2500.21044921875,
|
||||
-672.3021850585938
|
||||
],
|
||||
"size": [
|
||||
316.2437438964844,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "stablex_pipe",
|
||||
"type": "DIFFUSERS_PIPE",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "processed_image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
5,
|
||||
8
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] StableGen StableX Process Image"
|
||||
},
|
||||
"widgets_values": [
|
||||
2048,
|
||||
1,
|
||||
365883176136580,
|
||||
"randomize"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "[Comfy3D] Load StableGen StableX Pipeline",
|
||||
"pos": [
|
||||
2504.408203125,
|
||||
-819.4630737304688
|
||||
],
|
||||
"size": [
|
||||
307.7193298339844,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stablex_pipe",
|
||||
"type": "DIFFUSERS_PIPE",
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Load StableGen StableX Pipeline"
|
||||
},
|
||||
"widgets_values": [
|
||||
"yoso-normal-v1-8-1",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "[Comfy3D] Load StableGen Trellis Pipeline",
|
||||
"pos": [
|
||||
2869.568603515625,
|
||||
-928.1470336914062
|
||||
],
|
||||
"size": [
|
||||
272.52362060546875,
|
||||
208.80685424804688
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "trellis_pipe",
|
||||
"type": "DIFFUSERS_PIPE",
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Load StableGen Trellis Pipeline"
|
||||
},
|
||||
"widgets_values": [
|
||||
"trellis-normal-v0-1",
|
||||
"dinov2_vitl14_reg",
|
||||
true,
|
||||
"xformers",
|
||||
"spconv",
|
||||
"implicit_gemm",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "[Comfy3D] Preview 3DMesh",
|
||||
"pos": [
|
||||
3201.992919921875,
|
||||
-664.460693359375
|
||||
],
|
||||
"size": [
|
||||
600,
|
||||
500
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh_file_path",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "mesh_file_path"
|
||||
},
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Preview 3DMesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "[Comfy3D] Save 3D Mesh",
|
||||
"pos": [
|
||||
3206.273193359375,
|
||||
-792.7405395507812
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "save_path",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
7
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] Save 3D Mesh"
|
||||
},
|
||||
"widgets_values": [
|
||||
"StableGenTest/mesh.glb"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
2508.2578125,
|
||||
-465.621337890625
|
||||
],
|
||||
"size": [
|
||||
308.8748779296875,
|
||||
287.5449523925781
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "[Comfy3D] StableGen Trellis Image To 3D",
|
||||
"pos": [
|
||||
2867.12841796875,
|
||||
-667.4380493164062
|
||||
],
|
||||
"size": [
|
||||
290.0210876464844,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "trellis_pipe",
|
||||
"type": "DIFFUSERS_PIPE",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "mesh",
|
||||
"type": "MESH",
|
||||
"links": [
|
||||
6
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "[Comfy3D] StableGen Trellis Image To 3D"
|
||||
},
|
||||
"widgets_values": [
|
||||
"single",
|
||||
226815964612107,
|
||||
"randomize",
|
||||
7.5,
|
||||
12,
|
||||
3,
|
||||
12,
|
||||
0.95
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
2164.98193359375,
|
||||
-667.2369384765625
|
||||
],
|
||||
"size": [
|
||||
274.080078125,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
4
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"squirrel_girl_1.png",
|
||||
"image"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"DIFFUSERS_PIPE"
|
||||
],
|
||||
[
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"DIFFUSERS_PIPE"
|
||||
],
|
||||
[
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
4,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
5,
|
||||
4,
|
||||
0,
|
||||
3,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
6,
|
||||
3,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"MESH"
|
||||
],
|
||||
[
|
||||
7,
|
||||
8,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
8,
|
||||
4,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.863837598531476,
|
||||
"offset": [
|
||||
-1991.379607854965,
|
||||
966.2832144724473
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.20.4",
|
||||
"node_versions": {
|
||||
"ComfyUI-3D-Pack": "5a6f11c6a0f2f6f98ec0eb9e7a7c3264a24ec82a",
|
||||
"comfy-core": "0.3.35"
|
||||
},
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -15,6 +15,8 @@ import torchvision.transforms.functional as TF
|
||||
import numpy as np
|
||||
from safetensors.torch import load_file
|
||||
from einops import rearrange
|
||||
from accelerate import init_empty_weights
|
||||
from accelerate.utils import set_module_tensor_to_device
|
||||
|
||||
from diffusers import (
|
||||
DiffusionPipeline,
|
||||
@@ -30,6 +32,8 @@ from diffusers import (
|
||||
KDPM2AncestralDiscreteScheduler,
|
||||
KDPM2DiscreteScheduler,
|
||||
)
|
||||
from diffusers.models import AutoencoderKL, UNet2DConditionModel
|
||||
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
from plyfile import PlyData
|
||||
@@ -102,6 +106,10 @@ from TRELLIS.trellis.pipelines import TrellisImageTo3DPipeline
|
||||
from TRELLIS.trellis.utils import postprocessing_utils
|
||||
from TripoSG.pipelines.pipeline_triposg import TripoSGPipeline
|
||||
from TripoSG.pipelines.pipeline_triposg_scribble import TripoSGScribblePipeline
|
||||
from Stable3DGen.trellis.pipelines.trellis_image_to_3d import TrellisImageTo3DPipeline as Stable3DGenTrellisImageTo3DPipeline
|
||||
from Stable3DGen.stablex.pipeline_yoso import YosoPipeline
|
||||
from Stable3DGen.stablex.controlnetvae import ControlNetVAEModel
|
||||
from Stable3DGen.trellis.backend_config import set_attention_backend, set_sparse_backend, get_available_backends, get_available_sparse_backends
|
||||
|
||||
os.environ['SPCONV_ALGO'] = 'native'
|
||||
|
||||
@@ -181,6 +189,7 @@ DEVICE = torch.device(DEVICE_STR)
|
||||
|
||||
HF_DOWNLOAD_IGNORE = ["*.yaml", "*.json", "*.py", ".png", ".jpg", ".gif"]
|
||||
|
||||
|
||||
class Preview_3DGS:
|
||||
|
||||
@classmethod
|
||||
@@ -4530,4 +4539,406 @@ class Hunyuan3D_V2_ShapeGen_MV:
|
||||
for fn in (FloaterRemover(), DegenerateFaceRemover(), FaceReducer()):
|
||||
mesh = fn(mesh)
|
||||
|
||||
return (Mesh.load_trimesh(given_mesh=mesh),)
|
||||
return (Mesh.load_trimesh(given_mesh=mesh),)
|
||||
|
||||
|
||||
class Load_StableGen_Trellis_Pipeline:
|
||||
CATEGORY = "Comfy3D/Algorithm"
|
||||
RETURN_TYPES = ("DIFFUSERS_PIPE",)
|
||||
RETURN_NAMES = ("trellis_pipe",)
|
||||
FUNCTION = "load"
|
||||
|
||||
_REPO_ID_BASE = "Stable-X"
|
||||
CKPT_STABLEGEN_PATH = os.path.join(CKPT_DIFFUSERS_PATH, "Stable3DGen")
|
||||
|
||||
_MODES = {
|
||||
"trellis-normal-v0-1": ("trellis-normal-v0-1", 12, 12), # (repo, ss_steps, slat_steps)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
try:
|
||||
attn_backends = get_available_backends()
|
||||
sparse_backends = get_available_sparse_backends()
|
||||
except:
|
||||
attn_backends = {'flash_attn': True, 'sdpa': True}
|
||||
sparse_backends = {'spconv': True}
|
||||
|
||||
available_attn = [k for k, v in attn_backends.items() if v]
|
||||
if not available_attn:
|
||||
available_attn = ['flash_attn']
|
||||
|
||||
available_sparse = [k for k, v in sparse_backends.items() if v]
|
||||
if not available_sparse:
|
||||
available_sparse = ['spconv']
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"model_name": (list(cls._MODES.keys()),),
|
||||
"dinov2_model": (["dinov2_vitl14_reg"],),
|
||||
"use_fp16": ("BOOLEAN", {"default": True}),
|
||||
"attn_backend": (available_attn,),
|
||||
"sparse_backend": (available_sparse,),
|
||||
"spconv_algo": (["implicit_gemm", "native", "auto"],),
|
||||
"smooth_k": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _ensure_weights(cls, repo: str):
|
||||
base_dir = os.path.join(cls.CKPT_STABLEGEN_PATH, "trellis", repo)
|
||||
|
||||
if not os.path.exists(base_dir) or not os.listdir(base_dir):
|
||||
print(f"Downloading {repo} to {base_dir}")
|
||||
os.makedirs(base_dir, exist_ok=True)
|
||||
snapshot_download(
|
||||
repo_id=f"{cls._REPO_ID_BASE}/{repo}",
|
||||
repo_type="model",
|
||||
local_dir=base_dir,
|
||||
resume_download=True,
|
||||
ignore_patterns=HF_DOWNLOAD_IGNORE
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _setup_environment(attn_backend: str, sparse_backend: str, spconv_algo: str, smooth_k: bool):
|
||||
"""Setup environment variables and backends"""
|
||||
try:
|
||||
success = set_attention_backend(attn_backend)
|
||||
if not success:
|
||||
print(f"Warning: Failed to set {attn_backend}, fallback to default")
|
||||
|
||||
success2 = set_sparse_backend(sparse_backend, spconv_algo)
|
||||
if not success2:
|
||||
print(f"Warning: Failed to set {sparse_backend}, fallback to default")
|
||||
|
||||
os.environ['SAGEATTN_SMOOTH_K'] = '1' if smooth_k else '0'
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not setup environment: {e}")
|
||||
|
||||
@staticmethod
|
||||
def _optimize_pipeline(pipeline, use_fp16: bool = True):
|
||||
"""Apply typical optimizations, half-precision, etc."""
|
||||
if DEVICE.type == "cuda":
|
||||
try:
|
||||
if hasattr(pipeline, 'cuda'):
|
||||
pipeline.cuda()
|
||||
|
||||
if use_fp16:
|
||||
if hasattr(pipeline, 'enable_attention_slicing'):
|
||||
pipeline.enable_attention_slicing(slice_size="auto")
|
||||
if hasattr(pipeline, 'half'):
|
||||
pipeline.half()
|
||||
except Exception as e:
|
||||
print(f"Warning: Some pipeline optimizations failed: {str(e)}")
|
||||
|
||||
return pipeline
|
||||
|
||||
@classmethod
|
||||
def _build_pipe(cls, repo: str, dinov2_model: str, use_fp16: bool, attn_backend: str,
|
||||
sparse_backend: str, spconv_algo: str, smooth_k: bool):
|
||||
|
||||
cls._ensure_weights(repo)
|
||||
cls._setup_environment(attn_backend, sparse_backend, spconv_algo, smooth_k)
|
||||
|
||||
model_dir = os.path.join(cls.CKPT_STABLEGEN_PATH, "trellis", repo)
|
||||
|
||||
try:
|
||||
pipe = Stable3DGenTrellisImageTo3DPipeline.from_pretrained(
|
||||
model_dir,
|
||||
dinov2_model=dinov2_model
|
||||
)
|
||||
|
||||
if pipe is None:
|
||||
raise Exception("Pipeline returned None from from_pretrained")
|
||||
|
||||
pipe = cls._optimize_pipeline(pipe, use_fp16)
|
||||
|
||||
return pipe
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to build Trellis pipeline: {e}")
|
||||
|
||||
def load(self, model_name, dinov2_model, use_fp16, attn_backend, sparse_backend, spconv_algo, smooth_k):
|
||||
repo, ss_steps, slat_steps = self._MODES[model_name]
|
||||
|
||||
pipe = self.__class__._build_pipe(repo, dinov2_model, use_fp16, attn_backend, sparse_backend, spconv_algo, smooth_k)
|
||||
# Store default steps
|
||||
pipe.default_ss_steps = ss_steps
|
||||
pipe.default_slat_steps = slat_steps
|
||||
|
||||
return (pipe,)
|
||||
|
||||
|
||||
class Load_StableGen_StableX_Pipeline:
|
||||
CATEGORY = "Comfy3D/Algorithm"
|
||||
RETURN_TYPES = ("DIFFUSERS_PIPE",)
|
||||
RETURN_NAMES = ("stablex_pipe",)
|
||||
FUNCTION = "load"
|
||||
|
||||
_REPO_ID_BASE = "Stable-X"
|
||||
CKPT_STABLEGEN_PATH = os.path.join(CKPT_DIFFUSERS_PATH, "Stable3DGen")
|
||||
|
||||
_MODES = {
|
||||
"yoso-normal-v1-8-1": "yoso-normal-v1-8-1",
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"model_name": (list(cls._MODES.keys()),),
|
||||
"use_fp16": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _ensure_weights(cls, repo: str):
|
||||
base_dir = os.path.join(cls.CKPT_STABLEGEN_PATH, "stablex", repo)
|
||||
|
||||
if not os.path.exists(base_dir) or not os.listdir(base_dir):
|
||||
print(f"Downloading {repo} to {base_dir}")
|
||||
os.makedirs(base_dir, exist_ok=True)
|
||||
snapshot_download(
|
||||
repo_id=f"{cls._REPO_ID_BASE}/{repo}",
|
||||
repo_type="model",
|
||||
local_dir=base_dir,
|
||||
resume_download=True,
|
||||
ignore_patterns=["*text_encoder*", "tokenizer*", "*scheduler*"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _build_pipe(cls, repo: str, use_fp16: bool):
|
||||
cls._ensure_weights(repo)
|
||||
|
||||
model_dir = os.path.join(cls.CKPT_STABLEGEN_PATH, "stablex", repo)
|
||||
torch_dtype = torch.float16 if use_fp16 else torch.float32
|
||||
|
||||
try:
|
||||
config_path = os.path.join(model_dir, 'unet', 'config.json')
|
||||
unet_ckpt_path = os.path.join(model_dir, 'unet', 'diffusion_pytorch_model.fp16.safetensors')
|
||||
|
||||
if not os.path.exists(config_path):
|
||||
raise FileNotFoundError(f"Config not found at {config_path}")
|
||||
|
||||
with open(config_path, 'r', encoding='utf-8') as file:
|
||||
config = json.load(file)
|
||||
|
||||
with init_empty_weights():
|
||||
unet = UNet2DConditionModel(**config)
|
||||
|
||||
if os.path.exists(unet_ckpt_path):
|
||||
unet_sd = load_file(unet_ckpt_path)
|
||||
else:
|
||||
raise FileNotFoundError(f"No checkpoint found at {unet_ckpt_path}")
|
||||
|
||||
for name, param in unet.named_parameters():
|
||||
set_module_tensor_to_device(unet, name, device=DEVICE, dtype=torch_dtype, value=unet_sd[name])
|
||||
|
||||
vae = AutoencoderKL.from_pretrained(
|
||||
model_dir,
|
||||
subfolder="vae",
|
||||
variant="fp16" if use_fp16 else None,
|
||||
torch_dtype=torch_dtype
|
||||
)
|
||||
|
||||
controlnet = ControlNetVAEModel.from_pretrained(
|
||||
model_dir,
|
||||
subfolder="controlnet",
|
||||
variant="fp16" if use_fp16 else None,
|
||||
torch_dtype=torch_dtype
|
||||
)
|
||||
|
||||
pipeline = YosoPipeline(
|
||||
unet=unet,
|
||||
vae=vae,
|
||||
controlnet=controlnet,
|
||||
)
|
||||
|
||||
pipeline.to(DEVICE)
|
||||
|
||||
if torch_dtype == torch.float16 and DEVICE.type == "cuda":
|
||||
pipeline.unet = pipeline.unet.half()
|
||||
pipeline.vae = pipeline.vae.half()
|
||||
pipeline.controlnet = pipeline.controlnet.half()
|
||||
|
||||
return pipeline
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to build StableX pipeline: {e}")
|
||||
|
||||
def load(self, model_name, use_fp16):
|
||||
repo = self._MODES[model_name]
|
||||
pipe = self.__class__._build_pipe(repo, use_fp16)
|
||||
return (pipe,)
|
||||
|
||||
|
||||
class StableGen_Trellis_Image_To_3D:
|
||||
"""
|
||||
3D generation pipeline using Trellis model.
|
||||
"""
|
||||
|
||||
CATEGORY = "Comfy3D/Algorithm"
|
||||
RETURN_TYPES = ("MESH",)
|
||||
RETURN_NAMES = ("mesh",)
|
||||
FUNCTION = "run"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"trellis_pipe": ("DIFFUSERS_PIPE",),
|
||||
"images": ("IMAGE", {"list": True}),
|
||||
"mode": (["single", "multi"], {"default": "single"}),
|
||||
"seed": ("INT", {"default": 1234, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"ss_guidance_strength": ("FLOAT", {"default": 7.5, "min": 0.0, "step": 0.1}),
|
||||
"ss_sampling_steps": ("INT", {"default": 12, "min": 1}),
|
||||
"slat_guidance_strength": ("FLOAT", {"default": 3.0, "min": 0.0, "step": 0.1}),
|
||||
"slat_sampling_steps": ("INT", {"default": 12, "min": 1}),
|
||||
"mesh_simplify": ("FLOAT", {"default": 0.95, "min": 0.9, "max": 1.0, "step": 0.01}),
|
||||
}
|
||||
}
|
||||
|
||||
@torch.no_grad()
|
||||
def run(
|
||||
self,
|
||||
trellis_pipe,
|
||||
images,
|
||||
mode="single",
|
||||
seed=1234,
|
||||
ss_guidance_strength=7.5,
|
||||
ss_sampling_steps=12,
|
||||
slat_guidance_strength=3.0,
|
||||
slat_sampling_steps=12,
|
||||
mesh_simplify=0.95
|
||||
):
|
||||
if isinstance(images, torch.Tensor):
|
||||
images = torch_imgs_to_pils(images)
|
||||
|
||||
if not isinstance(images, list) or len(images) == 0:
|
||||
raise Exception(f"[StableGen_Trellis_Image_To_3D] 'images' must be a non-empty list of PIL images. Got type: {type(images)}, len: {len(images) if hasattr(images, '__len__') else 'no len'}")
|
||||
|
||||
# Use inference context like in working version
|
||||
with trellis_pipe.inference_context():
|
||||
# Prepare input based on mode
|
||||
if mode == "single":
|
||||
if len(images) > 1:
|
||||
print(f"Warning: Single mode selected but {len(images)} images provided. Using first image.")
|
||||
image_input = images[0]
|
||||
|
||||
# Set up generation parameters for single image
|
||||
pipeline_params = {
|
||||
"num_samples": 1,
|
||||
"seed": seed,
|
||||
"formats": ["mesh"],
|
||||
"preprocess_image": True,
|
||||
"sparse_structure_sampler_params": {
|
||||
"steps": ss_sampling_steps,
|
||||
"cfg_strength": ss_guidance_strength,
|
||||
},
|
||||
"slat_sampler_params": {
|
||||
"steps": slat_sampling_steps,
|
||||
"cfg_strength": slat_guidance_strength,
|
||||
}
|
||||
}
|
||||
|
||||
# Generate 3D mesh using single image method
|
||||
outputs = trellis_pipe.run(
|
||||
image_input,
|
||||
**pipeline_params
|
||||
)
|
||||
else: # multi mode
|
||||
# Set up generation parameters for multi image
|
||||
pipeline_params = {
|
||||
"num_samples": 1,
|
||||
"seed": seed,
|
||||
"formats": ["mesh"],
|
||||
"preprocess_image": True,
|
||||
"sparse_structure_sampler_params": {
|
||||
"steps": ss_sampling_steps,
|
||||
"cfg_strength": ss_guidance_strength,
|
||||
},
|
||||
"slat_sampler_params": {
|
||||
"steps": slat_sampling_steps,
|
||||
"cfg_strength": slat_guidance_strength,
|
||||
}
|
||||
}
|
||||
|
||||
outputs = trellis_pipe.run_multi_image(
|
||||
images,
|
||||
**pipeline_params
|
||||
)
|
||||
|
||||
try:
|
||||
mesh_output = outputs['mesh'][0]
|
||||
|
||||
vertices = mesh_output.vertices.cpu().numpy()
|
||||
faces = mesh_output.faces.cpu().numpy()
|
||||
|
||||
transformation_matrix = np.array([[1, 0, 0], [0, 0, -1], [0, 1, 0]])
|
||||
vertices = vertices @ transformation_matrix
|
||||
|
||||
tri_mesh = trimesh.Trimesh(vertices, faces)
|
||||
|
||||
# Simplify mesh if requested
|
||||
if mesh_simplify < 1.0:
|
||||
try:
|
||||
target_faces = int(len(faces) * mesh_simplify)
|
||||
tri_mesh = tri_mesh.simplify_quadric_decimation(target_faces)
|
||||
except Exception as e:
|
||||
print(f"Warning: Mesh simplification failed: {e}. Using original mesh.")
|
||||
|
||||
# Convert to Mesh object
|
||||
mesh = Mesh.load_trimesh(given_mesh=tri_mesh)
|
||||
mesh.auto_normal()
|
||||
|
||||
return (mesh,)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"[StableGen_Trellis_Image_To_3D] 3D generation failed: {str(e)}")
|
||||
|
||||
|
||||
class StableGen_StableX_Process_Image:
|
||||
"""
|
||||
Image processing pipeline using StableX model.
|
||||
"""
|
||||
|
||||
CATEGORY = "Comfy3D/Algorithm"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("processed_image",)
|
||||
FUNCTION = "run"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"stablex_pipe": ("DIFFUSERS_PIPE",),
|
||||
"image": ("IMAGE",),
|
||||
"processing_resolution": ("INT", {"default": 2048, "min": 64, "max": 4096, "step": 16}),
|
||||
"controlnet_strength": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 10.0, "step": 0.01}),
|
||||
"seed": ("INT", {"default": 42, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
@torch.no_grad()
|
||||
def run(self, stablex_pipe, image, processing_resolution=2048, controlnet_strength=1.0, seed=42):
|
||||
if image.dim() == 4:
|
||||
image = image.squeeze(0)
|
||||
|
||||
image_tensor = image.permute(2, 0, 1).unsqueeze(0).to(DEVICE).to(torch.float16)
|
||||
|
||||
generator = torch.Generator(device=DEVICE).manual_seed(seed)
|
||||
|
||||
try:
|
||||
pipe_out = stablex_pipe(
|
||||
image_tensor,
|
||||
controlnet_conditioning_scale=controlnet_strength,
|
||||
processing_resolution=processing_resolution,
|
||||
generator=generator,
|
||||
output_type="pt",
|
||||
)
|
||||
|
||||
processed = (pipe_out.prediction.clip(-1, 1) + 1) / 2
|
||||
out_tensor = processed.permute(0, 2, 3, 1).cpu().float()
|
||||
|
||||
return (out_tensor,)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"[StableGen_StableX_Process_Image] Image processing failed: {str(e)}")
|
||||
|
||||
@@ -38,6 +38,7 @@ matplotlib
|
||||
|
||||
# for process, import & export mesh
|
||||
trimesh
|
||||
fast-simplification
|
||||
plyfile
|
||||
pygltflib
|
||||
xatlas
|
||||
|
||||
Reference in New Issue
Block a user