-214
@@ -1,214 +0,0 @@
|
||||
{
|
||||
"last_node_id": 14,
|
||||
"last_link_id": 13,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 10,
|
||||
"type": "Trellis_LoadModel",
|
||||
"pos": [
|
||||
604.9623413085938,
|
||||
480.64385986328125
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
130
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL_TRELLIS",
|
||||
"links": [
|
||||
10
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "Trellis_LoadModel"
|
||||
},
|
||||
"widgets_values": [
|
||||
"F:/test/ComfyUI/models/diffusers/JeffreyXiang/TRELLIS-image-large",
|
||||
"dinov2_vitl14_reg4_pretrain.pth",
|
||||
"xformers",
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
583.5416870117188,
|
||||
89.96366119384766
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
11
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"image (1).png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "Trellis_Sampler",
|
||||
"pos": [
|
||||
974.942138671875,
|
||||
181.10565185546875
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
438
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 11
|
||||
},
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL_TRELLIS",
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "model_path",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
13
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "Trellis_Sampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
581394181,
|
||||
"randomize",
|
||||
7.5,
|
||||
12,
|
||||
3,
|
||||
12,
|
||||
false,
|
||||
512,
|
||||
0.95,
|
||||
"fast",
|
||||
false,
|
||||
"multidiffusion",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "ShowText|pysssss",
|
||||
"pos": [
|
||||
1379.2635498046875,
|
||||
272.084228515625
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
76
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 13,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowText|pysssss"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
"F:\\ComfyUI311\\ComfyUI\\output/fb690810-7fe2-40ea-981d-baadd7c5e581_0.glb"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
10,
|
||||
10,
|
||||
0,
|
||||
11,
|
||||
1,
|
||||
"MODEL_TRELLIS"
|
||||
],
|
||||
[
|
||||
11,
|
||||
12,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
13,
|
||||
11,
|
||||
0,
|
||||
14,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
-184.26351062065908,
|
||||
40.35328013119408
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -5,8 +5,9 @@
|
||||
|
||||
---
|
||||
|
||||
# Update
|
||||
* 2025/03/27 kaolin support torch2.6 now;
|
||||
# Update 2025/04/5
|
||||
* support txt to 3D and meshto3D
|
||||
* kaolin support torch2.6 now;
|
||||
|
||||
|
||||
# 1. Installation
|
||||
@@ -19,8 +20,8 @@ git clone https://github.com/smthemex/ComfyUI_TRELLIS.git
|
||||
---
|
||||
|
||||
# 2. Requirements
|
||||
本插件的测试环境是python3.11,torch2.5.1 cu124...
|
||||
The testing environment for this node is Python 3.11, torch2.5.1 cu124...
|
||||
本插件的测试环境是python3.11,torch2.6 cu126...
|
||||
The testing environment for this node is Python 3.11, torch2.6 cu126...
|
||||
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
@@ -113,33 +114,23 @@ if pre download ,fill local path in repo like this: x:/your/path/JeffreyXiang/TR
|
||||
| ├── ss_flow_img_dit_L_16l8_fp16.json
|
||||
| ├── ss_flow_img_dit_L_16l8_fp16.safetensors
|
||||
```
|
||||
* 3.2 dinov2
|
||||
因为官方的代码每次加载dinov2都要连GitHub,所以我改成了离线版的,需要下载dinov2模型,[地址](https://dl.fbaipublicfiles.com/dinov2/dinov2_vitl14/dinov2_vitl14_reg4_pretrain.pth)
|
||||
模型放在comfyUI/models/dinov2目录下
|
||||
* 3.2 dinov2 and clip
|
||||
因为官方的代码每次加载dinov2都要连GitHub,所以我改成了离线版的,需要下载dinov2模型,[地址](https://dl.fbaipublicfiles.com/dinov2/dinov2_vitl14/dinov2_vitl14_reg4_pretrain.pth) ,clip是常规的openai/clip-vit-large-patch14 模型,也为方便大陆用户改成了离线版
|
||||
模型放在comfyUI/models/dinov2和clip目录下
|
||||
```
|
||||
├── ComfyUI/models/dinov2
|
||||
| ├── dinov2_vitl14_reg4_pretrain.pth
|
||||
├── ComfyUI/models/clip
|
||||
| ├── clip_l.safetensors
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 4 Example
|
||||
|
||||

|
||||

|
||||

|
||||
* img to 3D or txt to 3D
|
||||

|
||||
|
||||
|
||||
**previous update**
|
||||
* 根据TRELLIS的更新,新增三视参考图渲染模式(示例图为实际效果,此模式速度更快)和高斯保存按钮,针对三视参考图新增图片加载节点(内置正方形裁切),三视图模式开启multi_image生效,否则是常规的单图模式(会输出三个结果);
|
||||
* 如果你输入的图片不是纯色背景,建议开启preprocess_image以获得最好的效果。(此次更新也修复了加载RGBA图片可能导致的变形错误);
|
||||
* 使用 [here](https://github.com/smthemex/ComfyUI_TRELLIS/issues/6) @planb788 方法,我制作了python3.11,torch2.5.1 cu124.的便携包,可以在[Google dirver](https://drive.google.com/file/d/174StpwP3D1qSD0RuhM1XwTene-5VMiL4/view?usp=drive_link) 或者 [夸克网盘](https://pan.quark.cn/s/8e07717bdff7)下载,注意,即便是便携包,也是需要配置VS和python的系统变量路径的;
|
||||
* 增加批量渲染功能,注意过多图片可能会OOM;
|
||||
* According to TRELLIS' update, a three view reference image rendering mode (the example image is the actual effect, which is faster) and a Gaussian save button have been added. For the three view reference image, a new image loading node (with built-in square cropping) has been added,Enabling multi_image in three view mode takes effect, otherwise it will be in regular single image mode (outputting three results);
|
||||
* If the image you input is not a solid color background, it is recommended to enable 'preprocess_image' for the best effect. (This update also fixes deformation errors that may occur when loading RGBA images);
|
||||
* Use [here](https://github.com/smthemex/ComfyUI_TRELLIS/issues/6) , I have created a portable package for ‘Python 3.11, Torch 2.5.1, and CU124’ using the @planb788 method, which can be found on [Google dirver](https://drive.google.com/file/d/174StpwP3D1qSD0RuhM1XwTene-5VMiL4/view?usp=drive_link) Or [夸克网盘](https://pan.quark.cn/s/8e07717bdff7) Download, note that even portable packages require configuring the system variable paths for VS and Python;
|
||||
* Add batch rendering function, be aware that too many images may cause OOM
|
||||
|
||||
# 5 Citation
|
||||
|
||||
|
||||
+109
-38
@@ -1,12 +1,15 @@
|
||||
# !/usr/bin/env python
|
||||
|
||||
# -*- coding: UTF-8 -*-
|
||||
import numpy as np
|
||||
import gc
|
||||
import os
|
||||
import torch
|
||||
import uuid
|
||||
from .app import image_to_3d
|
||||
from .trellis.pipelines import TrellisImageTo3DPipeline
|
||||
|
||||
|
||||
from .app import image_to_3d,prepare_output
|
||||
from .trellis.pipelines import TrellisImageTo3DPipeline,TrellisTextTo3DPipeline
|
||||
from .utils import glb2obj_,obj2fbx_,tensor2imglist,pre_img
|
||||
import folder_paths
|
||||
|
||||
@@ -32,7 +35,7 @@ class Trellis_LoadModel:
|
||||
return {
|
||||
"required": {
|
||||
"repo": ("STRING", {"default": "JeffreyXiang/TRELLIS-image-large"}),
|
||||
"dino": (["none"] + folder_paths.get_filename_list("dinov2"),),
|
||||
"dino_clip": (["none"] + folder_paths.get_filename_list("dinov2")+folder_paths.get_filename_list("clip"),),
|
||||
"attn_backend":(["xformers","flash-attn"],),
|
||||
"spconv_algo":(["auto","flash-native"],),
|
||||
}
|
||||
@@ -43,7 +46,7 @@ class Trellis_LoadModel:
|
||||
FUNCTION = "main_loader"
|
||||
CATEGORY = "Trellis"
|
||||
|
||||
def main_loader(self, repo,dino,attn_backend,spconv_algo):
|
||||
def main_loader(self, repo,dino_clip,attn_backend,spconv_algo):
|
||||
if attn_backend=="xformers":
|
||||
os.environ['ATTN_BACKEND'] = 'xformers'
|
||||
else:
|
||||
@@ -52,17 +55,33 @@ class Trellis_LoadModel:
|
||||
os.environ['SPCONV_ALGO'] = 'auto'
|
||||
else:
|
||||
os.environ['SPCONV_ALGO'] = 'native'
|
||||
|
||||
if dino=="none":
|
||||
raise "need choice dinov2 checkpoint"
|
||||
|
||||
TrellisImageTo3DPipeline.dino=folder_paths.get_full_path("dinov2", dino)
|
||||
TrellisImageTo3DPipeline.dino_moudel=os.path.join(current_path,"facebookresearch/dinov2")
|
||||
if repo:
|
||||
model=TrellisImageTo3DPipeline.from_pretrained(repo)
|
||||
infer_mode="txt" if "text" in repo else "img"
|
||||
else:
|
||||
raise "need fill repo"
|
||||
return (model,)
|
||||
|
||||
if dino_clip=="none" :
|
||||
raise "image mode need choice dinov2 or clip checkpoint"
|
||||
|
||||
if dino_clip!="none" :
|
||||
if infer_mode=="img":
|
||||
TrellisImageTo3DPipeline.dino=folder_paths.get_full_path("dinov2", dino_clip)
|
||||
TrellisImageTo3DPipeline.dino_moudel=os.path.join(current_path,"facebookresearch/dinov2")
|
||||
else:
|
||||
TrellisTextTo3DPipeline.clip=folder_paths.get_full_path("clip", dino_clip)
|
||||
TrellisTextTo3DPipeline.clip_moudel=os.path.join(current_path,"clip")
|
||||
|
||||
|
||||
if repo:
|
||||
infer_mode="txt" if "text" in repo else "img"
|
||||
if infer_mode=="img":
|
||||
model=TrellisImageTo3DPipeline.from_pretrained(repo)
|
||||
else:
|
||||
model=TrellisTextTo3DPipeline.from_pretrained(repo)
|
||||
|
||||
|
||||
|
||||
return ({"model": model,"infer_mode":infer_mode},)
|
||||
|
||||
|
||||
class Trellis_Sampler:
|
||||
@@ -71,8 +90,9 @@ class Trellis_Sampler:
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",), # [B,H,W,C], C=3
|
||||
"model": ("MODEL_TRELLIS",),
|
||||
"prompt": ("STRING", {"multiline": True,"default":"Rugged, metallic texture with orange and white paint finish, suggesting a durable, industrial feel."}),
|
||||
"mesh_path": ("STRING", {"default":"",}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED}),
|
||||
"cfg": ("FLOAT", {"default": 7.5, "min": 0.0, "max": 10.0, "step": 0.1, "round": 0.01}),
|
||||
"steps": ("INT", {"default": 12, "min": 1, "max": 50}),
|
||||
@@ -87,8 +107,10 @@ class Trellis_Sampler:
|
||||
"gaussians2ply": ("BOOLEAN", {"default": False},),
|
||||
"covert2video": ("BOOLEAN", {"default": False},),
|
||||
"glb2obj": ("BOOLEAN", {"default": False},),
|
||||
"glb2fbx": ("BOOLEAN", {"default": False},),
|
||||
}
|
||||
"glb2fbx": ("BOOLEAN", {"default": False},), },
|
||||
"optional":{ "image": ("IMAGE",), # [B,H,W,C], C=3
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING", )
|
||||
@@ -96,30 +118,79 @@ class Trellis_Sampler:
|
||||
FUNCTION = "sampler_main"
|
||||
CATEGORY = "Trellis"
|
||||
|
||||
def sampler_main(self, image, model, seed, cfg, steps,slat_cfg, slat_steps,preprocess_image,texture_size,mesh_simplify,mode,multi_image,multiimage_algo,gaussians2ply,covert2video,glb2obj,glb2fbx):
|
||||
|
||||
image_list,image_batch=tensor2imglist(image) #pil_list,batch
|
||||
|
||||
if multi_image and image_batch % 3 == 0:
|
||||
print("********infer multi image,like Three views ******")
|
||||
image_list=[image_list[i:i + 3] for i in range(0, len(image_list), 3)] #三等分列表
|
||||
is_multiimage=True
|
||||
else:
|
||||
is_multiimage = False
|
||||
trial_id = str(uuid.uuid4())
|
||||
|
||||
output_path = []
|
||||
for i,img in enumerate(image_list):
|
||||
model.cuda()
|
||||
glb=image_to_3d(model,img,preprocess_image,covert2video,trial_id,seed,cfg,steps,slat_cfg,slat_steps,mesh_simplify,texture_size,mode,is_multiimage,gaussians2ply,multiimage_algo)
|
||||
glb_path = f"{folder_paths.get_output_directory()}/{trial_id}_{i}.glb"
|
||||
glb.export(glb_path)
|
||||
output_path.append(glb_path)
|
||||
model.cpu()
|
||||
gc.collect()
|
||||
torch.cuda.empty_cache()
|
||||
print(f"glb save in {glb_path} ")
|
||||
def sampler_main(self, model,prompt, mesh_path, seed, cfg, steps,slat_cfg, slat_steps,preprocess_image,texture_size,mesh_simplify,mode,multi_image,multiimage_algo,gaussians2ply,covert2video,glb2obj,glb2fbx,**kwargs):
|
||||
|
||||
infer_mode=model.get("infer_mode")
|
||||
model=model.get("model")
|
||||
|
||||
if kwargs.get("image") is None and infer_mode=="img":
|
||||
raise "when use img mode,need link a image"
|
||||
|
||||
if isinstance(kwargs.get("image"),torch.Tensor):
|
||||
image_list,image_batch=tensor2imglist(kwargs.get("image")) #pil_list,batch
|
||||
|
||||
if multi_image and image_batch % 3 == 0:
|
||||
print("********infer multi image,like Three views ******")
|
||||
image_list=[image_list[i:i + 3] for i in range(0, len(image_list), 3)] #三等分列表
|
||||
is_multiimage=True
|
||||
else:
|
||||
is_multiimage = False
|
||||
else:
|
||||
image_list,is_multiimage=None,False
|
||||
|
||||
trial_id = str(uuid.uuid4())
|
||||
|
||||
if infer_mode=="img":
|
||||
output_path = []
|
||||
for i,img in enumerate(image_list):
|
||||
model.cuda()
|
||||
glb_path=image_to_3d(model,img,preprocess_image,covert2video,trial_id,seed,cfg,steps,slat_cfg,slat_steps,mesh_simplify,texture_size,mode,is_multiimage,gaussians2ply,multiimage_algo)
|
||||
output_path.append(glb_path)
|
||||
|
||||
else:
|
||||
model.cuda()
|
||||
if mesh_path:
|
||||
if not mesh_path.endswith(".ply"):
|
||||
raise "mesh_path must be .ply format"
|
||||
import open3d as o3d
|
||||
|
||||
base_mesh = o3d.io.read_triangle_mesh(mesh_path)
|
||||
outputs = model.run_variant(
|
||||
base_mesh,
|
||||
prompt=prompt,
|
||||
seed=1,
|
||||
# Optional parameters
|
||||
slat_sampler_params={
|
||||
"steps": slat_steps,
|
||||
"cfg_strength": slat_cfg,
|
||||
},
|
||||
)
|
||||
|
||||
# outputs is a dictionary containing generated 3D assets in different formats:
|
||||
# - outputs['gaussian']: a list of 3D Gaussians
|
||||
# - outputs['radiance_field']: a list of radiance fields
|
||||
# - outputs['mesh']: a list of meshes
|
||||
|
||||
else:
|
||||
outputs = model.run(
|
||||
prompt,
|
||||
seed=seed,
|
||||
formats=["gaussian", "mesh"],
|
||||
sparse_structure_sampler_params={
|
||||
"steps": steps,
|
||||
"cfg_strength": cfg,
|
||||
},
|
||||
slat_sampler_params={
|
||||
"steps": slat_steps,
|
||||
"cfg_strength": slat_cfg,
|
||||
},
|
||||
)
|
||||
output_path=prepare_output(outputs,covert2video,trial_id,False,gaussians2ply,mesh_simplify,texture_size,mode)
|
||||
output_path=[output_path]
|
||||
model.cpu()
|
||||
gc.collect()
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
if glb2obj:
|
||||
obj_paths=[]
|
||||
for path in output_path:
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
|
||||
from .TRELLIS_node import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS",'WEB_DIRECTORY']
|
||||
|
||||
@@ -1,13 +1,64 @@
|
||||
import os.path
|
||||
|
||||
import random
|
||||
from typing import *
|
||||
import torch
|
||||
import numpy as np
|
||||
from .trellis.utils import render_utils, postprocessing_utils
|
||||
import imageio
|
||||
import folder_paths
|
||||
|
||||
from easydict import EasyDict as edict
|
||||
from .trellis.pipelines import TrellisTextTo3DPipeline
|
||||
from .trellis.representations import Gaussian, MeshExtractResult
|
||||
from .trellis.utils import render_utils, postprocessing_utils
|
||||
MAX_SEED = np.iinfo(np.int32).max
|
||||
|
||||
def image_to_3d(pipeline,image,preprocess_image:bool,covert2video:bool,trial_id: str, seed: int,ss_guidance_strength: float, ss_sampling_steps: int, slat_guidance_strength: float, slat_sampling_steps: int,mesh_simplify,texture_size,mode,is_multiimage,Gaussians2PLY,multiimage_algo):
|
||||
def pack_state(gs: Gaussian, mesh: MeshExtractResult) -> dict:
|
||||
return {
|
||||
'gaussian': {
|
||||
**gs.init_params,
|
||||
'_xyz': gs._xyz.cpu().numpy(),
|
||||
'_features_dc': gs._features_dc.cpu().numpy(),
|
||||
'_scaling': gs._scaling.cpu().numpy(),
|
||||
'_rotation': gs._rotation.cpu().numpy(),
|
||||
'_opacity': gs._opacity.cpu().numpy(),
|
||||
},
|
||||
'mesh': {
|
||||
'vertices': mesh.vertices.cpu().numpy(),
|
||||
'faces': mesh.faces.cpu().numpy(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def unpack_state(state: dict) -> Tuple[Gaussian, edict, str]:
|
||||
gs = Gaussian(
|
||||
aabb=state['gaussian']['aabb'],
|
||||
sh_degree=state['gaussian']['sh_degree'],
|
||||
mininum_kernel_size=state['gaussian']['mininum_kernel_size'],
|
||||
scaling_bias=state['gaussian']['scaling_bias'],
|
||||
opacity_bias=state['gaussian']['opacity_bias'],
|
||||
scaling_activation=state['gaussian']['scaling_activation'],
|
||||
)
|
||||
gs._xyz = torch.tensor(state['gaussian']['_xyz'], device='cuda')
|
||||
gs._features_dc = torch.tensor(state['gaussian']['_features_dc'], device='cuda')
|
||||
gs._scaling = torch.tensor(state['gaussian']['_scaling'], device='cuda')
|
||||
gs._rotation = torch.tensor(state['gaussian']['_rotation'], device='cuda')
|
||||
gs._opacity = torch.tensor(state['gaussian']['_opacity'], device='cuda')
|
||||
|
||||
mesh = edict(
|
||||
vertices=torch.tensor(state['mesh']['vertices'], device='cuda'),
|
||||
faces=torch.tensor(state['mesh']['faces'], device='cuda'),
|
||||
)
|
||||
|
||||
return gs, mesh
|
||||
|
||||
|
||||
def get_seed(randomize_seed: bool, seed: int) -> int:
|
||||
"""
|
||||
Get the random seed.
|
||||
"""
|
||||
return np.random.randint(0, MAX_SEED) if randomize_seed else seed
|
||||
|
||||
def image_to_3d(pipeline,image,preprocess_image:bool,covert2video:bool,trial_id: str, seed: int,ss_guidance_strength: float,
|
||||
ss_sampling_steps: int, slat_guidance_strength: float, slat_sampling_steps: int,mesh_simplify,texture_size,mode,is_multiimage,Gaussians2PLY,multiimage_algo):
|
||||
"""
|
||||
Convert an image to a 3D model.
|
||||
|
||||
@@ -55,7 +106,11 @@ def image_to_3d(pipeline,image,preprocess_image:bool,covert2video:bool,trial_id:
|
||||
},
|
||||
mode=multiimage_algo,
|
||||
)
|
||||
|
||||
|
||||
return prepare_output(outputs,covert2video,trial_id,is_multiimage,Gaussians2PLY,mesh_simplify,texture_size,mode)
|
||||
|
||||
|
||||
def prepare_output(outputs,covert2video,trial_id,is_multiimage,Gaussians2PLY,mesh_simplify,texture_size,mode):
|
||||
if covert2video:
|
||||
video_path = f"{trial_id}.mp4"
|
||||
if is_multiimage:
|
||||
@@ -72,20 +127,22 @@ def image_to_3d(pipeline,image,preprocess_image:bool,covert2video:bool,trial_id:
|
||||
video = render_utils.render_video(outputs['mesh'][0])['normal']
|
||||
imageio.mimsave(f"{trial_id}_mesh.mp4", video, fps=30)
|
||||
|
||||
state = pack_state(outputs['gaussian'][0], outputs['mesh'][0])
|
||||
torch.cuda.empty_cache()
|
||||
gs, mesh = unpack_state(state)
|
||||
if Gaussians2PLY:
|
||||
outputs['gaussian'][0].save_ply(f"{trial_id}.ply")
|
||||
|
||||
gs.save_ply(f"{trial_id}.ply")
|
||||
|
||||
glb = postprocessing_utils.to_glb(
|
||||
outputs['gaussian'][0],
|
||||
outputs['mesh'][0],
|
||||
gs,
|
||||
mesh,
|
||||
# Optional parameters
|
||||
simplify=mesh_simplify, # Ratio of triangles to remove in the simplification process
|
||||
texture_size=texture_size, # Size of the texture used for the GLB
|
||||
mode=mode,
|
||||
uv_map=f"{folder_paths.get_output_directory()}/{trial_id}_uv_map.png",
|
||||
)
|
||||
|
||||
return glb
|
||||
|
||||
|
||||
|
||||
uv_map=f"{folder_paths.get_output_directory()}/{trial_id}_uv_map.png",)
|
||||
prefix = ''.join(random.choice("0123456789") for _ in range(5))
|
||||
glb_path = f"{folder_paths.get_output_directory()}/{trial_id}_{prefix}.glb"
|
||||
glb.export(glb_path)
|
||||
print(f"glb save in {glb_path} ")
|
||||
return glb_path
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 361 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 312 KiB |
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"_name_or_path": "clip-vit-large-patch14/",
|
||||
"architectures": [
|
||||
"CLIPModel"
|
||||
],
|
||||
"initializer_factor": 1.0,
|
||||
"logit_scale_init_value": 2.6592,
|
||||
"model_type": "clip",
|
||||
"projection_dim": 768,
|
||||
"text_config": {
|
||||
"_name_or_path": "",
|
||||
"add_cross_attention": false,
|
||||
"architectures": null,
|
||||
"attention_dropout": 0.0,
|
||||
"bad_words_ids": null,
|
||||
"bos_token_id": 0,
|
||||
"chunk_size_feed_forward": 0,
|
||||
"cross_attention_hidden_size": null,
|
||||
"decoder_start_token_id": null,
|
||||
"diversity_penalty": 0.0,
|
||||
"do_sample": false,
|
||||
"dropout": 0.0,
|
||||
"early_stopping": false,
|
||||
"encoder_no_repeat_ngram_size": 0,
|
||||
"eos_token_id": 2,
|
||||
"finetuning_task": null,
|
||||
"forced_bos_token_id": null,
|
||||
"forced_eos_token_id": null,
|
||||
"hidden_act": "quick_gelu",
|
||||
"hidden_size": 768,
|
||||
"id2label": {
|
||||
"0": "LABEL_0",
|
||||
"1": "LABEL_1"
|
||||
},
|
||||
"initializer_factor": 1.0,
|
||||
"initializer_range": 0.02,
|
||||
"intermediate_size": 3072,
|
||||
"is_decoder": false,
|
||||
"is_encoder_decoder": false,
|
||||
"label2id": {
|
||||
"LABEL_0": 0,
|
||||
"LABEL_1": 1
|
||||
},
|
||||
"layer_norm_eps": 1e-05,
|
||||
"length_penalty": 1.0,
|
||||
"max_length": 20,
|
||||
"max_position_embeddings": 77,
|
||||
"min_length": 0,
|
||||
"model_type": "clip_text_model",
|
||||
"no_repeat_ngram_size": 0,
|
||||
"num_attention_heads": 12,
|
||||
"num_beam_groups": 1,
|
||||
"num_beams": 1,
|
||||
"num_hidden_layers": 12,
|
||||
"num_return_sequences": 1,
|
||||
"output_attentions": false,
|
||||
"output_hidden_states": false,
|
||||
"output_scores": false,
|
||||
"pad_token_id": 1,
|
||||
"prefix": null,
|
||||
"problem_type": null,
|
||||
"projection_dim" : 768,
|
||||
"pruned_heads": {},
|
||||
"remove_invalid_values": false,
|
||||
"repetition_penalty": 1.0,
|
||||
"return_dict": true,
|
||||
"return_dict_in_generate": false,
|
||||
"sep_token_id": null,
|
||||
"task_specific_params": null,
|
||||
"temperature": 1.0,
|
||||
"tie_encoder_decoder": false,
|
||||
"tie_word_embeddings": true,
|
||||
"tokenizer_class": null,
|
||||
"top_k": 50,
|
||||
"top_p": 1.0,
|
||||
"torch_dtype": null,
|
||||
"torchscript": false,
|
||||
"transformers_version": "4.16.0.dev0",
|
||||
"use_bfloat16": false,
|
||||
"vocab_size": 49408
|
||||
},
|
||||
"text_config_dict": {
|
||||
"hidden_size": 768,
|
||||
"intermediate_size": 3072,
|
||||
"num_attention_heads": 12,
|
||||
"num_hidden_layers": 12,
|
||||
"projection_dim": 768
|
||||
},
|
||||
"torch_dtype": "float32",
|
||||
"transformers_version": null,
|
||||
"vision_config": {
|
||||
"_name_or_path": "",
|
||||
"add_cross_attention": false,
|
||||
"architectures": null,
|
||||
"attention_dropout": 0.0,
|
||||
"bad_words_ids": null,
|
||||
"bos_token_id": null,
|
||||
"chunk_size_feed_forward": 0,
|
||||
"cross_attention_hidden_size": null,
|
||||
"decoder_start_token_id": null,
|
||||
"diversity_penalty": 0.0,
|
||||
"do_sample": false,
|
||||
"dropout": 0.0,
|
||||
"early_stopping": false,
|
||||
"encoder_no_repeat_ngram_size": 0,
|
||||
"eos_token_id": null,
|
||||
"finetuning_task": null,
|
||||
"forced_bos_token_id": null,
|
||||
"forced_eos_token_id": null,
|
||||
"hidden_act": "quick_gelu",
|
||||
"hidden_size": 1024,
|
||||
"id2label": {
|
||||
"0": "LABEL_0",
|
||||
"1": "LABEL_1"
|
||||
},
|
||||
"image_size": 224,
|
||||
"initializer_factor": 1.0,
|
||||
"initializer_range": 0.02,
|
||||
"intermediate_size": 4096,
|
||||
"is_decoder": false,
|
||||
"is_encoder_decoder": false,
|
||||
"label2id": {
|
||||
"LABEL_0": 0,
|
||||
"LABEL_1": 1
|
||||
},
|
||||
"layer_norm_eps": 1e-05,
|
||||
"length_penalty": 1.0,
|
||||
"max_length": 20,
|
||||
"min_length": 0,
|
||||
"model_type": "clip_vision_model",
|
||||
"no_repeat_ngram_size": 0,
|
||||
"num_attention_heads": 16,
|
||||
"num_beam_groups": 1,
|
||||
"num_beams": 1,
|
||||
"num_hidden_layers": 24,
|
||||
"num_return_sequences": 1,
|
||||
"output_attentions": false,
|
||||
"output_hidden_states": false,
|
||||
"output_scores": false,
|
||||
"pad_token_id": null,
|
||||
"patch_size": 14,
|
||||
"prefix": null,
|
||||
"problem_type": null,
|
||||
"projection_dim" : 768,
|
||||
"pruned_heads": {},
|
||||
"remove_invalid_values": false,
|
||||
"repetition_penalty": 1.0,
|
||||
"return_dict": true,
|
||||
"return_dict_in_generate": false,
|
||||
"sep_token_id": null,
|
||||
"task_specific_params": null,
|
||||
"temperature": 1.0,
|
||||
"tie_encoder_decoder": false,
|
||||
"tie_word_embeddings": true,
|
||||
"tokenizer_class": null,
|
||||
"top_k": 50,
|
||||
"top_p": 1.0,
|
||||
"torch_dtype": null,
|
||||
"torchscript": false,
|
||||
"transformers_version": "4.16.0.dev0",
|
||||
"use_bfloat16": false
|
||||
},
|
||||
"vision_config_dict": {
|
||||
"hidden_size": 1024,
|
||||
"intermediate_size": 4096,
|
||||
"num_attention_heads": 16,
|
||||
"num_hidden_layers": 24,
|
||||
"patch_size": 14,
|
||||
"projection_dim": 768
|
||||
}
|
||||
}
|
||||
+48895
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"crop_size": 224,
|
||||
"do_center_crop": true,
|
||||
"do_normalize": true,
|
||||
"do_resize": true,
|
||||
"feature_extractor_type": "CLIPFeatureExtractor",
|
||||
"image_mean": [
|
||||
0.48145466,
|
||||
0.4578275,
|
||||
0.40821073
|
||||
],
|
||||
"image_std": [
|
||||
0.26862954,
|
||||
0.26130258,
|
||||
0.27577711
|
||||
],
|
||||
"resample": 3,
|
||||
"size": 224
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"bos_token": {"content": "<|startoftext|>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": true}, "eos_token": {"content": "<|endoftext|>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": true}, "unk_token": {"content": "<|endoftext|>", "single_word": false, "lstrip": false, "rstrip": false, "normalized": true}, "pad_token": "<|endoftext|>"}
|
||||
+98393
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"unk_token": {
|
||||
"content": "<|endoftext|>",
|
||||
"single_word": false,
|
||||
"lstrip": false,
|
||||
"rstrip": false,
|
||||
"normalized": true,
|
||||
"__type": "AddedToken"
|
||||
},
|
||||
"bos_token": {
|
||||
"content": "<|startoftext|>",
|
||||
"single_word": false,
|
||||
"lstrip": false,
|
||||
"rstrip": false,
|
||||
"normalized": true,
|
||||
"__type": "AddedToken"
|
||||
},
|
||||
"eos_token": {
|
||||
"content": "<|endoftext|>",
|
||||
"single_word": false,
|
||||
"lstrip": false,
|
||||
"rstrip": false,
|
||||
"normalized": true,
|
||||
"__type": "AddedToken"
|
||||
},
|
||||
"pad_token": "<|endoftext|>",
|
||||
"add_prefix_space": false,
|
||||
"errors": "replace",
|
||||
"do_lower_case": true,
|
||||
"name_or_path": "openai/clip-vit-base-patch32",
|
||||
"model_max_length": 77,
|
||||
"special_tokens_map_file": "./special_tokens_map.json",
|
||||
"tokenizer_class": "CLIPTokenizer"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"models": {
|
||||
"denoiser": {
|
||||
"name": "ElasticSLatFlowModel",
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "ImageConditionedSLat",
|
||||
"args": {
|
||||
"latent_model": "dinov2_vitl14_reg_slat_enc_swin8_B_64l8_fp16",
|
||||
"min_aesthetic_score": 4.5,
|
||||
"max_num_voxels": 32768,
|
||||
"image_size": 518,
|
||||
"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
|
||||
]
|
||||
},
|
||||
"pretrained_slat_dec": "JeffreyXiang/TRELLIS-image-large/ckpts/slat_dec_gs_swin8_B_64l8gs32_fp16"
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "ImageConditionedSparseFlowMatchingCFGTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 8,
|
||||
"batch_split": 4,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 0.0001,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"elastic": {
|
||||
"name": "LinearMemoryController",
|
||||
"args": {
|
||||
"target_ratio": 0.75,
|
||||
"max_mem_ratio_start": 0.5
|
||||
}
|
||||
},
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"p_uncond": 0.1,
|
||||
"t_schedule": {
|
||||
"name": "logitNormal",
|
||||
"args": {
|
||||
"mean": 1.0,
|
||||
"std": 1.0
|
||||
}
|
||||
},
|
||||
"sigma_min": 1e-5,
|
||||
"image_cond_model": "dinov2_vitl14_reg"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"models": {
|
||||
"denoiser": {
|
||||
"name": "ElasticSLatFlowModel",
|
||||
"args": {
|
||||
"resolution": 64,
|
||||
"in_channels": 8,
|
||||
"out_channels": 8,
|
||||
"model_channels": 768,
|
||||
"cond_channels": 768,
|
||||
"num_blocks": 12,
|
||||
"num_heads": 12,
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "TextConditionedSLat",
|
||||
"args": {
|
||||
"latent_model": "dinov2_vitl14_reg_slat_enc_swin8_B_64l8_fp16",
|
||||
"min_aesthetic_score": 4.5,
|
||||
"max_num_voxels": 32768,
|
||||
"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
|
||||
]
|
||||
},
|
||||
"pretrained_slat_dec": "JeffreyXiang/TRELLIS-image-large/ckpts/slat_dec_gs_swin8_B_64l8gs32_fp16"
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "TextConditionedSparseFlowMatchingCFGTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 16,
|
||||
"batch_split": 4,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 0.0001,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"elastic": {
|
||||
"name": "LinearMemoryController",
|
||||
"args": {
|
||||
"target_ratio": 0.75,
|
||||
"max_mem_ratio_start": 0.5
|
||||
}
|
||||
},
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"p_uncond": 0.1,
|
||||
"t_schedule": {
|
||||
"name": "logitNormal",
|
||||
"args": {
|
||||
"mean": 1.0,
|
||||
"std": 1.0
|
||||
}
|
||||
},
|
||||
"sigma_min": 1e-5,
|
||||
"text_cond_model": "openai/clip-vit-large-patch14"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"models": {
|
||||
"denoiser": {
|
||||
"name": "ElasticSLatFlowModel",
|
||||
"args": {
|
||||
"resolution": 64,
|
||||
"in_channels": 8,
|
||||
"out_channels": 8,
|
||||
"model_channels": 1024,
|
||||
"cond_channels": 768,
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "TextConditionedSLat",
|
||||
"args": {
|
||||
"latent_model": "dinov2_vitl14_reg_slat_enc_swin8_B_64l8_fp16",
|
||||
"min_aesthetic_score": 4.5,
|
||||
"max_num_voxels": 32768,
|
||||
"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
|
||||
]
|
||||
},
|
||||
"pretrained_slat_dec": "JeffreyXiang/TRELLIS-image-large/ckpts/slat_dec_gs_swin8_B_64l8gs32_fp16"
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "TextConditionedSparseFlowMatchingCFGTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 8,
|
||||
"batch_split": 4,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 0.0001,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"elastic": {
|
||||
"name": "LinearMemoryController",
|
||||
"args": {
|
||||
"target_ratio": 0.75,
|
||||
"max_mem_ratio_start": 0.5
|
||||
}
|
||||
},
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"p_uncond": 0.1,
|
||||
"t_schedule": {
|
||||
"name": "logitNormal",
|
||||
"args": {
|
||||
"mean": 1.0,
|
||||
"std": 1.0
|
||||
}
|
||||
},
|
||||
"sigma_min": 1e-5,
|
||||
"text_cond_model": "openai/clip-vit-large-patch14"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"models": {
|
||||
"denoiser": {
|
||||
"name": "ElasticSLatFlowModel",
|
||||
"args": {
|
||||
"resolution": 64,
|
||||
"in_channels": 8,
|
||||
"out_channels": 8,
|
||||
"model_channels": 1280,
|
||||
"cond_channels": 768,
|
||||
"num_blocks": 28,
|
||||
"num_heads": 16,
|
||||
"mlp_ratio": 4,
|
||||
"patch_size": 2,
|
||||
"num_io_res_blocks": 3,
|
||||
"io_block_channels": [256],
|
||||
"pe_mode": "ape",
|
||||
"qk_rms_norm": true,
|
||||
"use_fp16": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "TextConditionedSLat",
|
||||
"args": {
|
||||
"latent_model": "dinov2_vitl14_reg_slat_enc_swin8_B_64l8_fp16",
|
||||
"min_aesthetic_score": 4.5,
|
||||
"max_num_voxels": 32768,
|
||||
"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
|
||||
]
|
||||
},
|
||||
"pretrained_slat_dec": "JeffreyXiang/TRELLIS-image-large/ckpts/slat_dec_gs_swin8_B_64l8gs32_fp16"
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "TextConditionedSparseFlowMatchingCFGTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 4,
|
||||
"batch_split": 4,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 0.0001,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"elastic": {
|
||||
"name": "LinearMemoryController",
|
||||
"args": {
|
||||
"target_ratio": 0.75,
|
||||
"max_mem_ratio_start": 0.5
|
||||
}
|
||||
},
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"p_uncond": 0.1,
|
||||
"t_schedule": {
|
||||
"name": "logitNormal",
|
||||
"args": {
|
||||
"mean": 1.0,
|
||||
"std": 1.0
|
||||
}
|
||||
},
|
||||
"sigma_min": 1e-5,
|
||||
"text_cond_model": "openai/clip-vit-large-patch14"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"models": {
|
||||
"denoiser": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "ImageConditionedSparseStructureLatent",
|
||||
"args": {
|
||||
"latent_model": "ss_enc_conv3d_16l8_fp16",
|
||||
"min_aesthetic_score": 4.5,
|
||||
"image_size": 518,
|
||||
"pretrained_ss_dec": "JeffreyXiang/TRELLIS-image-large/ckpts/ss_dec_conv3d_16l8_fp16"
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "ImageConditionedFlowMatchingCFGTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 8,
|
||||
"batch_split": 1,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 0.0001,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"p_uncond": 0.1,
|
||||
"t_schedule": {
|
||||
"name": "logitNormal",
|
||||
"args": {
|
||||
"mean": 1.0,
|
||||
"std": 1.0
|
||||
}
|
||||
},
|
||||
"sigma_min": 1e-5,
|
||||
"image_cond_model": "dinov2_vitl14_reg"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"models": {
|
||||
"denoiser": {
|
||||
"name": "SparseStructureFlowModel",
|
||||
"args": {
|
||||
"resolution": 16,
|
||||
"in_channels": 8,
|
||||
"out_channels": 8,
|
||||
"model_channels": 768,
|
||||
"cond_channels": 768,
|
||||
"num_blocks": 12,
|
||||
"num_heads": 12,
|
||||
"mlp_ratio": 4,
|
||||
"patch_size": 1,
|
||||
"pe_mode": "ape",
|
||||
"qk_rms_norm": true,
|
||||
"use_fp16": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "TextConditionedSparseStructureLatent",
|
||||
"args": {
|
||||
"latent_model": "ss_enc_conv3d_16l8_fp16",
|
||||
"min_aesthetic_score": 4.5,
|
||||
"pretrained_ss_dec": "JeffreyXiang/TRELLIS-image-large/ckpts/ss_dec_conv3d_16l8_fp16"
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "TextConditionedFlowMatchingCFGTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 16,
|
||||
"batch_split": 1,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 0.0001,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"p_uncond": 0.1,
|
||||
"t_schedule": {
|
||||
"name": "logitNormal",
|
||||
"args": {
|
||||
"mean": 1.0,
|
||||
"std": 1.0
|
||||
}
|
||||
},
|
||||
"sigma_min": 1e-5,
|
||||
"text_cond_model": "openai/clip-vit-large-patch14"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"models": {
|
||||
"denoiser": {
|
||||
"name": "SparseStructureFlowModel",
|
||||
"args": {
|
||||
"resolution": 16,
|
||||
"in_channels": 8,
|
||||
"out_channels": 8,
|
||||
"model_channels": 1024,
|
||||
"cond_channels": 768,
|
||||
"num_blocks": 24,
|
||||
"num_heads": 16,
|
||||
"mlp_ratio": 4,
|
||||
"patch_size": 1,
|
||||
"pe_mode": "ape",
|
||||
"qk_rms_norm": true,
|
||||
"use_fp16": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "TextConditionedSparseStructureLatent",
|
||||
"args": {
|
||||
"latent_model": "ss_enc_conv3d_16l8_fp16",
|
||||
"min_aesthetic_score": 4.5,
|
||||
"pretrained_ss_dec": "JeffreyXiang/TRELLIS-image-large/ckpts/ss_dec_conv3d_16l8_fp16"
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "TextConditionedFlowMatchingCFGTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 8,
|
||||
"batch_split": 1,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 0.0001,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"p_uncond": 0.1,
|
||||
"t_schedule": {
|
||||
"name": "logitNormal",
|
||||
"args": {
|
||||
"mean": 1.0,
|
||||
"std": 1.0
|
||||
}
|
||||
},
|
||||
"sigma_min": 1e-5,
|
||||
"text_cond_model": "openai/clip-vit-large-patch14"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"models": {
|
||||
"denoiser": {
|
||||
"name": "SparseStructureFlowModel",
|
||||
"args": {
|
||||
"resolution": 16,
|
||||
"in_channels": 8,
|
||||
"out_channels": 8,
|
||||
"model_channels": 1280,
|
||||
"cond_channels": 768,
|
||||
"num_blocks": 28,
|
||||
"num_heads": 16,
|
||||
"mlp_ratio": 4,
|
||||
"patch_size": 1,
|
||||
"pe_mode": "ape",
|
||||
"qk_rms_norm": true,
|
||||
"qk_rms_norm_cross": true,
|
||||
"use_fp16": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "TextConditionedSparseStructureLatent",
|
||||
"args": {
|
||||
"latent_model": "ss_enc_conv3d_16l8_fp16",
|
||||
"min_aesthetic_score": 4.5,
|
||||
"pretrained_ss_dec": "JeffreyXiang/TRELLIS-image-large/ckpts/ss_dec_conv3d_16l8_fp16"
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "TextConditionedFlowMatchingCFGTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 4,
|
||||
"batch_split": 1,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 0.0001,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"p_uncond": 0.1,
|
||||
"t_schedule": {
|
||||
"name": "logitNormal",
|
||||
"args": {
|
||||
"mean": 1.0,
|
||||
"std": 1.0
|
||||
}
|
||||
},
|
||||
"sigma_min": 1e-5,
|
||||
"text_cond_model": "openai/clip-vit-large-patch14"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"models": {
|
||||
"decoder": {
|
||||
"name": "ElasticSLatMeshDecoder",
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "Slat2RenderGeo",
|
||||
"args": {
|
||||
"image_size": 512,
|
||||
"latent_model": "dinov2_vitl14_reg_slat_enc_swin8_B_64l8_fp16",
|
||||
"min_aesthetic_score": 4.5,
|
||||
"max_num_voxels": 32768
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "SLatVaeMeshDecoderTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 4,
|
||||
"batch_split": 4,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 1e-4,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"elastic": {
|
||||
"name": "LinearMemoryController",
|
||||
"args": {
|
||||
"target_ratio": 0.75,
|
||||
"max_mem_ratio_start": 0.5
|
||||
}
|
||||
},
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"lambda_ssim": 0.2,
|
||||
"lambda_lpips": 0.2,
|
||||
"lambda_tsdf": 0.01,
|
||||
"lambda_depth": 10.0,
|
||||
"lambda_color": 0.1,
|
||||
"depth_loss_type": "smooth_l1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"models": {
|
||||
"decoder": {
|
||||
"name": "ElasticSLatRadianceFieldDecoder",
|
||||
"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": {
|
||||
"rank": 16,
|
||||
"dim": 8
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "SLat2Render",
|
||||
"args": {
|
||||
"image_size": 512,
|
||||
"latent_model": "dinov2_vitl14_reg_slat_enc_swin8_B_64l8_fp16",
|
||||
"min_aesthetic_score": 4.5,
|
||||
"max_num_voxels": 32768
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "SLatVaeRadianceFieldDecoderTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 4,
|
||||
"batch_split": 2,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 1e-4,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"elastic": {
|
||||
"name": "LinearMemoryController",
|
||||
"args": {
|
||||
"target_ratio": 0.75,
|
||||
"max_mem_ratio_start": 0.5
|
||||
}
|
||||
},
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"loss_type": "l1",
|
||||
"lambda_ssim": 0.2,
|
||||
"lambda_lpips": 0.2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"models": {
|
||||
"encoder": {
|
||||
"name": "ElasticSLatEncoder",
|
||||
"args": {
|
||||
"resolution": 64,
|
||||
"in_channels": 1024,
|
||||
"model_channels": 768,
|
||||
"latent_channels": 8,
|
||||
"num_blocks": 12,
|
||||
"num_heads": 12,
|
||||
"mlp_ratio": 4,
|
||||
"attn_mode": "swin",
|
||||
"window_size": 8,
|
||||
"use_fp16": true
|
||||
}
|
||||
},
|
||||
"decoder": {
|
||||
"name": "ElasticSLatGaussianDecoder",
|
||||
"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": {
|
||||
"lr": {
|
||||
"_xyz": 1.0,
|
||||
"_features_dc": 1.0,
|
||||
"_opacity": 1.0,
|
||||
"_scaling": 1.0,
|
||||
"_rotation": 0.1
|
||||
},
|
||||
"perturb_offset": true,
|
||||
"voxel_size": 1.5,
|
||||
"num_gaussians": 32,
|
||||
"2d_filter_kernel_size": 0.1,
|
||||
"3d_filter_kernel_size": 9e-4,
|
||||
"scaling_bias": 4e-3,
|
||||
"opacity_bias": 0.1,
|
||||
"scaling_activation": "softplus"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "SparseFeat2Render",
|
||||
"args": {
|
||||
"image_size": 512,
|
||||
"model": "dinov2_vitl14_reg",
|
||||
"resolution": 64,
|
||||
"min_aesthetic_score": 4.5,
|
||||
"max_num_voxels": 32768
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "SLatVaeGaussianTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 4,
|
||||
"batch_split": 2,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 1e-4,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"elastic": {
|
||||
"name": "LinearMemoryController",
|
||||
"args": {
|
||||
"target_ratio": 0.75,
|
||||
"max_mem_ratio_start": 0.5
|
||||
}
|
||||
},
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"loss_type": "l1",
|
||||
"lambda_ssim": 0.2,
|
||||
"lambda_lpips": 0.2,
|
||||
"lambda_kl": 1e-06,
|
||||
"regularizations": {
|
||||
"lambda_vol": 10000.0,
|
||||
"lambda_opacity": 0.001
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"models": {
|
||||
"encoder": {
|
||||
"name": "SparseStructureEncoder",
|
||||
"args": {
|
||||
"in_channels": 1,
|
||||
"latent_channels": 8,
|
||||
"num_res_blocks": 2,
|
||||
"num_res_blocks_middle": 2,
|
||||
"channels": [32, 128, 512],
|
||||
"use_fp16": true
|
||||
}
|
||||
},
|
||||
"decoder": {
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
"name": "SparseStructure",
|
||||
"args": {
|
||||
"resolution": 64,
|
||||
"min_aesthetic_score": 4.5
|
||||
}
|
||||
},
|
||||
"trainer": {
|
||||
"name": "SparseStructureVaeTrainer",
|
||||
"args": {
|
||||
"max_steps": 1000000,
|
||||
"batch_size_per_gpu": 4,
|
||||
"batch_split": 1,
|
||||
"optimizer": {
|
||||
"name": "AdamW",
|
||||
"args": {
|
||||
"lr": 1e-4,
|
||||
"weight_decay": 0.0
|
||||
}
|
||||
},
|
||||
"ema_rate": [
|
||||
0.9999
|
||||
],
|
||||
"fp16_mode": "inflat_all",
|
||||
"fp16_scale_growth": 0.001,
|
||||
"grad_clip": {
|
||||
"name": "AdaptiveGradClipper",
|
||||
"args": {
|
||||
"max_norm": 1.0,
|
||||
"clip_percentile": 95
|
||||
}
|
||||
},
|
||||
"i_log": 500,
|
||||
"i_sample": 10000,
|
||||
"i_save": 10000,
|
||||
"loss_type": "dice",
|
||||
"lambda_kl": 0.001
|
||||
}
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
import os
|
||||
# os.environ['ATTN_BACKEND'] = 'xformers' # Can be 'flash-attn' or 'xformers', default is 'flash-attn'
|
||||
os.environ['SPCONV_ALGO'] = 'native' # Can be 'native' or 'auto', default is 'auto'.
|
||||
# 'auto' is faster but will do benchmarking at the beginning.
|
||||
# Recommended to set to 'native' if run only once.
|
||||
|
||||
import imageio
|
||||
from PIL import Image
|
||||
from trellis.pipelines import TrellisImageTo3DPipeline
|
||||
from trellis.utils import render_utils, postprocessing_utils
|
||||
|
||||
# Load a pipeline from a model folder or a Hugging Face model hub.
|
||||
pipeline = TrellisImageTo3DPipeline.from_pretrained("JeffreyXiang/TRELLIS-image-large")
|
||||
pipeline.cuda()
|
||||
|
||||
# Load an image
|
||||
image = Image.open("assets/example_image/T.png")
|
||||
|
||||
# Run the pipeline
|
||||
outputs = pipeline.run(
|
||||
image,
|
||||
# Optional parameters
|
||||
seed=1,
|
||||
# sparse_structure_sampler_params={
|
||||
# "steps": 12,
|
||||
# "cfg_strength": 7.5,
|
||||
# },
|
||||
# slat_sampler_params={
|
||||
# "steps": 12,
|
||||
# "cfg_strength": 3,
|
||||
# },
|
||||
)
|
||||
# outputs is a dictionary containing generated 3D assets in different formats:
|
||||
# - outputs['gaussian']: a list of 3D Gaussians
|
||||
# - outputs['radiance_field']: a list of radiance fields
|
||||
# - outputs['mesh']: a list of meshes
|
||||
|
||||
# Render the outputs
|
||||
video = render_utils.render_video(outputs['gaussian'][0])['color']
|
||||
imageio.mimsave("sample_gs.mp4", video, fps=30)
|
||||
video = render_utils.render_video(outputs['radiance_field'][0])['color']
|
||||
imageio.mimsave("sample_rf.mp4", video, fps=30)
|
||||
video = render_utils.render_video(outputs['mesh'][0])['normal']
|
||||
imageio.mimsave("sample_mesh.mp4", video, fps=30)
|
||||
|
||||
# GLB files can be extracted from the outputs
|
||||
glb = postprocessing_utils.to_glb(
|
||||
outputs['gaussian'][0],
|
||||
outputs['mesh'][0],
|
||||
# Optional parameters
|
||||
simplify=0.95, # Ratio of triangles to remove in the simplification process
|
||||
texture_size=1024, # Size of the texture used for the GLB
|
||||
)
|
||||
glb.export("sample.glb")
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 364 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 301 KiB |
@@ -0,0 +1,58 @@
|
||||
import importlib
|
||||
|
||||
__attributes = {
|
||||
'SparseStructure': 'sparse_structure',
|
||||
|
||||
'SparseFeat2Render': 'sparse_feat2render',
|
||||
'SLat2Render':'structured_latent2render',
|
||||
'Slat2RenderGeo':'structured_latent2render',
|
||||
|
||||
'SparseStructureLatent': 'sparse_structure_latent',
|
||||
'TextConditionedSparseStructureLatent': 'sparse_structure_latent',
|
||||
'ImageConditionedSparseStructureLatent': 'sparse_structure_latent',
|
||||
|
||||
'SLat': 'structured_latent',
|
||||
'TextConditionedSLat': 'structured_latent',
|
||||
'ImageConditionedSLat': 'structured_latent',
|
||||
}
|
||||
|
||||
__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]
|
||||
|
||||
|
||||
# For Pylance
|
||||
if __name__ == '__main__':
|
||||
from .sparse_structure import SparseStructure
|
||||
|
||||
from .sparse_feat2render import SparseFeat2Render
|
||||
from .structured_latent2render import (
|
||||
SLat2Render,
|
||||
Slat2RenderGeo,
|
||||
)
|
||||
|
||||
from .sparse_structure_latent import (
|
||||
SparseStructureLatent,
|
||||
TextConditionedSparseStructureLatent,
|
||||
ImageConditionedSparseStructureLatent,
|
||||
)
|
||||
|
||||
from .structured_latent import (
|
||||
SLat,
|
||||
TextConditionedSLat,
|
||||
ImageConditionedSLat,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
from typing import *
|
||||
from abc import abstractmethod
|
||||
import os
|
||||
import json
|
||||
import torch
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from PIL import Image
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
|
||||
class StandardDatasetBase(Dataset):
|
||||
"""
|
||||
Base class for standard datasets.
|
||||
|
||||
Args:
|
||||
roots (str): paths to the dataset
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
roots: str,
|
||||
):
|
||||
super().__init__()
|
||||
self.roots = roots.split(',')
|
||||
self.instances = []
|
||||
self.metadata = pd.DataFrame()
|
||||
|
||||
self._stats = {}
|
||||
for root in self.roots:
|
||||
key = os.path.basename(root)
|
||||
self._stats[key] = {}
|
||||
metadata = pd.read_csv(os.path.join(root, 'metadata.csv'))
|
||||
self._stats[key]['Total'] = len(metadata)
|
||||
metadata, stats = self.filter_metadata(metadata)
|
||||
self._stats[key].update(stats)
|
||||
self.instances.extend([(root, sha256) for sha256 in metadata['sha256'].values])
|
||||
metadata.set_index('sha256', inplace=True)
|
||||
self.metadata = pd.concat([self.metadata, metadata])
|
||||
|
||||
@abstractmethod
|
||||
def filter_metadata(self, metadata: pd.DataFrame) -> Tuple[pd.DataFrame, Dict[str, int]]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_instance(self, root: str, instance: str) -> Dict[str, Any]:
|
||||
pass
|
||||
|
||||
def __len__(self):
|
||||
return len(self.instances)
|
||||
|
||||
def __getitem__(self, index) -> Dict[str, Any]:
|
||||
try:
|
||||
root, instance = self.instances[index]
|
||||
return self.get_instance(root, instance)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return self.__getitem__(np.random.randint(0, len(self)))
|
||||
|
||||
def __str__(self):
|
||||
lines = []
|
||||
lines.append(self.__class__.__name__)
|
||||
lines.append(f' - Total instances: {len(self)}')
|
||||
lines.append(f' - Sources:')
|
||||
for key, stats in self._stats.items():
|
||||
lines.append(f' - {key}:')
|
||||
for k, v in stats.items():
|
||||
lines.append(f' - {k}: {v}')
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
class TextConditionedMixin:
|
||||
def __init__(self, roots, **kwargs):
|
||||
super().__init__(roots, **kwargs)
|
||||
self.captions = {}
|
||||
for instance in self.instances:
|
||||
sha256 = instance[1]
|
||||
self.captions[sha256] = json.loads(self.metadata.loc[sha256]['captions'])
|
||||
|
||||
def filter_metadata(self, metadata):
|
||||
metadata, stats = super().filter_metadata(metadata)
|
||||
metadata = metadata[metadata['captions'].notna()]
|
||||
stats['With captions'] = len(metadata)
|
||||
return metadata, stats
|
||||
|
||||
def get_instance(self, root, instance):
|
||||
pack = super().get_instance(root, instance)
|
||||
text = np.random.choice(self.captions[instance])
|
||||
pack['cond'] = text
|
||||
return pack
|
||||
|
||||
|
||||
class ImageConditionedMixin:
|
||||
def __init__(self, roots, *, image_size=518, **kwargs):
|
||||
self.image_size = image_size
|
||||
super().__init__(roots, **kwargs)
|
||||
|
||||
def filter_metadata(self, metadata):
|
||||
metadata, stats = super().filter_metadata(metadata)
|
||||
metadata = metadata[metadata[f'cond_rendered']]
|
||||
stats['Cond rendered'] = len(metadata)
|
||||
return metadata, stats
|
||||
|
||||
def get_instance(self, root, instance):
|
||||
pack = super().get_instance(root, instance)
|
||||
|
||||
image_root = os.path.join(root, 'renders_cond', instance)
|
||||
with open(os.path.join(image_root, 'transforms.json')) as f:
|
||||
metadata = json.load(f)
|
||||
n_views = len(metadata['frames'])
|
||||
view = np.random.randint(n_views)
|
||||
metadata = metadata['frames'][view]
|
||||
|
||||
image_path = os.path.join(image_root, metadata['file_path'])
|
||||
image = Image.open(image_path)
|
||||
|
||||
alpha = np.array(image.getchannel(3))
|
||||
bbox = np.array(alpha).nonzero()
|
||||
bbox = [bbox[1].min(), bbox[0].min(), bbox[1].max(), bbox[0].max()]
|
||||
center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2]
|
||||
hsize = max(bbox[2] - bbox[0], bbox[3] - bbox[1]) / 2
|
||||
aug_size_ratio = 1.2
|
||||
aug_hsize = hsize * aug_size_ratio
|
||||
aug_center_offset = [0, 0]
|
||||
aug_center = [center[0] + aug_center_offset[0], center[1] + aug_center_offset[1]]
|
||||
aug_bbox = [int(aug_center[0] - aug_hsize), int(aug_center[1] - aug_hsize), int(aug_center[0] + aug_hsize), int(aug_center[1] + aug_hsize)]
|
||||
image = image.crop(aug_bbox)
|
||||
|
||||
image = image.resize((self.image_size, self.image_size), Image.Resampling.LANCZOS)
|
||||
alpha = image.getchannel(3)
|
||||
image = image.convert('RGB')
|
||||
image = torch.tensor(np.array(image)).permute(2, 0, 1).float() / 255.0
|
||||
alpha = torch.tensor(np.array(alpha)).float() / 255.0
|
||||
image = image * alpha.unsqueeze(0)
|
||||
pack['cond'] = image
|
||||
|
||||
return pack
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import os
|
||||
from PIL import Image
|
||||
import json
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import torch
|
||||
import utils3d.torch
|
||||
from ..modules.sparse.basic import SparseTensor
|
||||
from .components import StandardDatasetBase
|
||||
|
||||
|
||||
class SparseFeat2Render(StandardDatasetBase):
|
||||
"""
|
||||
SparseFeat2Render dataset.
|
||||
|
||||
Args:
|
||||
roots (str): paths to the dataset
|
||||
image_size (int): size of the image
|
||||
model (str): model name
|
||||
resolution (int): resolution of the data
|
||||
min_aesthetic_score (float): minimum aesthetic score
|
||||
max_num_voxels (int): maximum number of voxels
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
roots: str,
|
||||
image_size: int,
|
||||
model: str = 'dinov2_vitl14_reg',
|
||||
resolution: int = 64,
|
||||
min_aesthetic_score: float = 5.0,
|
||||
max_num_voxels: int = 32768,
|
||||
):
|
||||
self.image_size = image_size
|
||||
self.model = model
|
||||
self.resolution = resolution
|
||||
self.min_aesthetic_score = min_aesthetic_score
|
||||
self.max_num_voxels = max_num_voxels
|
||||
self.value_range = (0, 1)
|
||||
|
||||
super().__init__(roots)
|
||||
|
||||
def filter_metadata(self, metadata):
|
||||
stats = {}
|
||||
metadata = metadata[metadata[f'feature_{self.model}']]
|
||||
stats['With features'] = len(metadata)
|
||||
metadata = metadata[metadata['aesthetic_score'] >= self.min_aesthetic_score]
|
||||
stats[f'Aesthetic score >= {self.min_aesthetic_score}'] = len(metadata)
|
||||
metadata = metadata[metadata['num_voxels'] <= self.max_num_voxels]
|
||||
stats[f'Num voxels <= {self.max_num_voxels}'] = len(metadata)
|
||||
return metadata, stats
|
||||
|
||||
def _get_image(self, root, instance):
|
||||
with open(os.path.join(root, 'renders', instance, 'transforms.json')) as f:
|
||||
metadata = json.load(f)
|
||||
n_views = len(metadata['frames'])
|
||||
view = np.random.randint(n_views)
|
||||
metadata = metadata['frames'][view]
|
||||
fov = metadata['camera_angle_x']
|
||||
intrinsics = utils3d.torch.intrinsics_from_fov_xy(torch.tensor(fov), torch.tensor(fov))
|
||||
c2w = torch.tensor(metadata['transform_matrix'])
|
||||
c2w[:3, 1:3] *= -1
|
||||
extrinsics = torch.inverse(c2w)
|
||||
|
||||
image_path = os.path.join(root, 'renders', instance, metadata['file_path'])
|
||||
image = Image.open(image_path)
|
||||
alpha = image.getchannel(3)
|
||||
image = image.convert('RGB')
|
||||
image = image.resize((self.image_size, self.image_size), Image.Resampling.LANCZOS)
|
||||
alpha = alpha.resize((self.image_size, self.image_size), Image.Resampling.LANCZOS)
|
||||
image = torch.tensor(np.array(image)).permute(2, 0, 1).float() / 255.0
|
||||
alpha = torch.tensor(np.array(alpha)).float() / 255.0
|
||||
|
||||
return {
|
||||
'image': image,
|
||||
'alpha': alpha,
|
||||
'extrinsics': extrinsics,
|
||||
'intrinsics': intrinsics,
|
||||
}
|
||||
|
||||
def _get_feat(self, root, instance):
|
||||
DATA_RESOLUTION = 64
|
||||
feats_path = os.path.join(root, 'features', self.model, f'{instance}.npz')
|
||||
feats = np.load(feats_path, allow_pickle=True)
|
||||
coords = torch.tensor(feats['indices']).int()
|
||||
feats = torch.tensor(feats['patchtokens']).float()
|
||||
|
||||
if self.resolution != DATA_RESOLUTION:
|
||||
factor = DATA_RESOLUTION // self.resolution
|
||||
coords = coords // factor
|
||||
coords, idx = coords.unique(return_inverse=True, dim=0)
|
||||
feats = torch.scatter_reduce(
|
||||
torch.zeros(coords.shape[0], feats.shape[1], device=feats.device),
|
||||
dim=0,
|
||||
index=idx.unsqueeze(-1).expand(-1, feats.shape[1]),
|
||||
src=feats,
|
||||
reduce='mean'
|
||||
)
|
||||
|
||||
return {
|
||||
'coords': coords,
|
||||
'feats': feats,
|
||||
}
|
||||
|
||||
@torch.no_grad()
|
||||
def visualize_sample(self, sample: dict):
|
||||
return sample['image']
|
||||
|
||||
@staticmethod
|
||||
def collate_fn(batch):
|
||||
pack = {}
|
||||
coords = []
|
||||
for i, b in enumerate(batch):
|
||||
coords.append(torch.cat([torch.full((b['coords'].shape[0], 1), i, dtype=torch.int32), b['coords']], dim=-1))
|
||||
coords = torch.cat(coords)
|
||||
feats = torch.cat([b['feats'] for b in batch])
|
||||
pack['feats'] = SparseTensor(
|
||||
coords=coords,
|
||||
feats=feats,
|
||||
)
|
||||
|
||||
pack['image'] = torch.stack([b['image'] for b in batch])
|
||||
pack['alpha'] = torch.stack([b['alpha'] for b in batch])
|
||||
pack['extrinsics'] = torch.stack([b['extrinsics'] for b in batch])
|
||||
pack['intrinsics'] = torch.stack([b['intrinsics'] for b in batch])
|
||||
|
||||
return pack
|
||||
|
||||
def get_instance(self, root, instance):
|
||||
image = self._get_image(root, instance)
|
||||
feat = self._get_feat(root, instance)
|
||||
return {
|
||||
**image,
|
||||
**feat,
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import os
|
||||
import json
|
||||
from typing import Union
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import torch
|
||||
from torch.utils.data import Dataset
|
||||
import utils3d
|
||||
from .components import StandardDatasetBase
|
||||
from ..representations.octree import DfsOctree as Octree
|
||||
from ..renderers import OctreeRenderer
|
||||
|
||||
|
||||
class SparseStructure(StandardDatasetBase):
|
||||
"""
|
||||
Sparse structure dataset
|
||||
|
||||
Args:
|
||||
roots (str): path to the dataset
|
||||
resolution (int): resolution of the voxel grid
|
||||
min_aesthetic_score (float): minimum aesthetic score of the instances to be included in the dataset
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
roots,
|
||||
resolution: int = 64,
|
||||
min_aesthetic_score: float = 5.0,
|
||||
):
|
||||
self.resolution = resolution
|
||||
self.min_aesthetic_score = min_aesthetic_score
|
||||
self.value_range = (0, 1)
|
||||
|
||||
super().__init__(roots)
|
||||
|
||||
def filter_metadata(self, metadata):
|
||||
stats = {}
|
||||
metadata = metadata[metadata[f'voxelized']]
|
||||
stats['Voxelized'] = len(metadata)
|
||||
metadata = metadata[metadata['aesthetic_score'] >= self.min_aesthetic_score]
|
||||
stats[f'Aesthetic score >= {self.min_aesthetic_score}'] = len(metadata)
|
||||
return metadata, stats
|
||||
|
||||
def get_instance(self, root, instance):
|
||||
position = utils3d.io.read_ply(os.path.join(root, 'voxels', f'{instance}.ply'))[0]
|
||||
coords = ((torch.tensor(position) + 0.5) * self.resolution).int().contiguous()
|
||||
ss = torch.zeros(1, self.resolution, self.resolution, self.resolution, dtype=torch.long)
|
||||
ss[:, coords[:, 0], coords[:, 1], coords[:, 2]] = 1
|
||||
return {'ss': ss}
|
||||
|
||||
@torch.no_grad()
|
||||
def visualize_sample(self, ss: Union[torch.Tensor, dict]):
|
||||
ss = ss if isinstance(ss, torch.Tensor) else ss['ss']
|
||||
|
||||
renderer = OctreeRenderer()
|
||||
renderer.rendering_options.resolution = 512
|
||||
renderer.rendering_options.near = 0.8
|
||||
renderer.rendering_options.far = 1.6
|
||||
renderer.rendering_options.bg_color = (0, 0, 0)
|
||||
renderer.rendering_options.ssaa = 4
|
||||
renderer.pipe.primitive = 'voxel'
|
||||
|
||||
# Build camera
|
||||
yaws = [0, np.pi / 2, np.pi, 3 * np.pi / 2]
|
||||
yaws_offset = np.random.uniform(-np.pi / 4, np.pi / 4)
|
||||
yaws = [y + yaws_offset for y in yaws]
|
||||
pitch = [np.random.uniform(-np.pi / 4, np.pi / 4) for _ in range(4)]
|
||||
|
||||
exts = []
|
||||
ints = []
|
||||
for yaw, pitch in zip(yaws, pitch):
|
||||
orig = torch.tensor([
|
||||
np.sin(yaw) * np.cos(pitch),
|
||||
np.cos(yaw) * np.cos(pitch),
|
||||
np.sin(pitch),
|
||||
]).float().cuda() * 2
|
||||
fov = torch.deg2rad(torch.tensor(30)).cuda()
|
||||
extrinsics = utils3d.torch.extrinsics_look_at(orig, torch.tensor([0, 0, 0]).float().cuda(), torch.tensor([0, 0, 1]).float().cuda())
|
||||
intrinsics = utils3d.torch.intrinsics_from_fov_xy(fov, fov)
|
||||
exts.append(extrinsics)
|
||||
ints.append(intrinsics)
|
||||
|
||||
images = []
|
||||
|
||||
# Build each representation
|
||||
ss = ss.cuda()
|
||||
for i in range(ss.shape[0]):
|
||||
representation = Octree(
|
||||
depth=10,
|
||||
aabb=[-0.5, -0.5, -0.5, 1, 1, 1],
|
||||
device='cuda',
|
||||
primitive='voxel',
|
||||
sh_degree=0,
|
||||
primitive_config={'solid': True},
|
||||
)
|
||||
coords = torch.nonzero(ss[i, 0], as_tuple=False)
|
||||
representation.position = coords.float() / self.resolution
|
||||
representation.depth = torch.full((representation.position.shape[0], 1), int(np.log2(self.resolution)), dtype=torch.uint8, device='cuda')
|
||||
|
||||
image = torch.zeros(3, 1024, 1024).cuda()
|
||||
tile = [2, 2]
|
||||
for j, (ext, intr) in enumerate(zip(exts, ints)):
|
||||
res = renderer.render(representation, ext, intr, colors_overwrite=representation.position)
|
||||
image[:, 512 * (j // tile[1]):512 * (j // tile[1] + 1), 512 * (j % tile[1]):512 * (j % tile[1] + 1)] = res['color']
|
||||
images.append(image)
|
||||
|
||||
return torch.stack(images)
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import os
|
||||
import json
|
||||
from typing import *
|
||||
import numpy as np
|
||||
import torch
|
||||
import utils3d
|
||||
from ..representations.octree import DfsOctree as Octree
|
||||
from ..renderers import OctreeRenderer
|
||||
from .components import StandardDatasetBase, TextConditionedMixin, ImageConditionedMixin
|
||||
from .. import models
|
||||
from ..utils.dist_utils import read_file_dist
|
||||
|
||||
|
||||
class SparseStructureLatentVisMixin:
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
pretrained_ss_dec: str = 'JeffreyXiang/TRELLIS-image-large/ckpts/ss_dec_conv3d_16l8_fp16',
|
||||
ss_dec_path: Optional[str] = None,
|
||||
ss_dec_ckpt: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.ss_dec = None
|
||||
self.pretrained_ss_dec = pretrained_ss_dec
|
||||
self.ss_dec_path = ss_dec_path
|
||||
self.ss_dec_ckpt = ss_dec_ckpt
|
||||
|
||||
def _loading_ss_dec(self):
|
||||
if self.ss_dec is not None:
|
||||
return
|
||||
if self.ss_dec_path is not None:
|
||||
cfg = json.load(open(os.path.join(self.ss_dec_path, 'config.json'), 'r'))
|
||||
decoder = getattr(models, cfg['models']['decoder']['name'])(**cfg['models']['decoder']['args'])
|
||||
ckpt_path = os.path.join(self.ss_dec_path, 'ckpts', f'decoder_{self.ss_dec_ckpt}.pt')
|
||||
decoder.load_state_dict(torch.load(read_file_dist(ckpt_path), map_location='cpu', weights_only=True))
|
||||
else:
|
||||
decoder = models.from_pretrained(self.pretrained_ss_dec)
|
||||
self.ss_dec = decoder.cuda().eval()
|
||||
|
||||
def _delete_ss_dec(self):
|
||||
del self.ss_dec
|
||||
self.ss_dec = None
|
||||
|
||||
@torch.no_grad()
|
||||
def decode_latent(self, z, batch_size=4):
|
||||
self._loading_ss_dec()
|
||||
ss = []
|
||||
if self.normalization is not None:
|
||||
z = z * self.std.to(z.device) + self.mean.to(z.device)
|
||||
for i in range(0, z.shape[0], batch_size):
|
||||
ss.append(self.ss_dec(z[i:i+batch_size]))
|
||||
ss = torch.cat(ss, dim=0)
|
||||
self._delete_ss_dec()
|
||||
return ss
|
||||
|
||||
@torch.no_grad()
|
||||
def visualize_sample(self, x_0: Union[torch.Tensor, dict]):
|
||||
x_0 = x_0 if isinstance(x_0, torch.Tensor) else x_0['x_0']
|
||||
x_0 = self.decode_latent(x_0.cuda())
|
||||
|
||||
renderer = OctreeRenderer()
|
||||
renderer.rendering_options.resolution = 512
|
||||
renderer.rendering_options.near = 0.8
|
||||
renderer.rendering_options.far = 1.6
|
||||
renderer.rendering_options.bg_color = (0, 0, 0)
|
||||
renderer.rendering_options.ssaa = 4
|
||||
renderer.pipe.primitive = 'voxel'
|
||||
|
||||
# Build camera
|
||||
yaws = [0, np.pi / 2, np.pi, 3 * np.pi / 2]
|
||||
yaws_offset = np.random.uniform(-np.pi / 4, np.pi / 4)
|
||||
yaws = [y + yaws_offset for y in yaws]
|
||||
pitch = [np.random.uniform(-np.pi / 4, np.pi / 4) for _ in range(4)]
|
||||
|
||||
exts = []
|
||||
ints = []
|
||||
for yaw, pitch in zip(yaws, pitch):
|
||||
orig = torch.tensor([
|
||||
np.sin(yaw) * np.cos(pitch),
|
||||
np.cos(yaw) * np.cos(pitch),
|
||||
np.sin(pitch),
|
||||
]).float().cuda() * 2
|
||||
fov = torch.deg2rad(torch.tensor(30)).cuda()
|
||||
extrinsics = utils3d.torch.extrinsics_look_at(orig, torch.tensor([0, 0, 0]).float().cuda(), torch.tensor([0, 0, 1]).float().cuda())
|
||||
intrinsics = utils3d.torch.intrinsics_from_fov_xy(fov, fov)
|
||||
exts.append(extrinsics)
|
||||
ints.append(intrinsics)
|
||||
|
||||
images = []
|
||||
|
||||
# Build each representation
|
||||
x_0 = x_0.cuda()
|
||||
for i in range(x_0.shape[0]):
|
||||
representation = Octree(
|
||||
depth=10,
|
||||
aabb=[-0.5, -0.5, -0.5, 1, 1, 1],
|
||||
device='cuda',
|
||||
primitive='voxel',
|
||||
sh_degree=0,
|
||||
primitive_config={'solid': True},
|
||||
)
|
||||
coords = torch.nonzero(x_0[i, 0] > 0, as_tuple=False)
|
||||
resolution = x_0.shape[-1]
|
||||
representation.position = coords.float() / resolution
|
||||
representation.depth = torch.full((representation.position.shape[0], 1), int(np.log2(resolution)), dtype=torch.uint8, device='cuda')
|
||||
|
||||
image = torch.zeros(3, 1024, 1024).cuda()
|
||||
tile = [2, 2]
|
||||
for j, (ext, intr) in enumerate(zip(exts, ints)):
|
||||
res = renderer.render(representation, ext, intr, colors_overwrite=representation.position)
|
||||
image[:, 512 * (j // tile[1]):512 * (j // tile[1] + 1), 512 * (j % tile[1]):512 * (j % tile[1] + 1)] = res['color']
|
||||
images.append(image)
|
||||
|
||||
return torch.stack(images)
|
||||
|
||||
|
||||
class SparseStructureLatent(SparseStructureLatentVisMixin, StandardDatasetBase):
|
||||
"""
|
||||
Sparse structure latent dataset
|
||||
|
||||
Args:
|
||||
roots (str): path to the dataset
|
||||
latent_model (str): name of the latent model
|
||||
min_aesthetic_score (float): minimum aesthetic score
|
||||
normalization (dict): normalization stats
|
||||
pretrained_ss_dec (str): name of the pretrained sparse structure decoder
|
||||
ss_dec_path (str): path to the sparse structure decoder, if given, will override the pretrained_ss_dec
|
||||
ss_dec_ckpt (str): name of the sparse structure decoder checkpoint
|
||||
"""
|
||||
def __init__(self,
|
||||
roots: str,
|
||||
*,
|
||||
latent_model: str,
|
||||
min_aesthetic_score: float = 5.0,
|
||||
normalization: Optional[dict] = None,
|
||||
pretrained_ss_dec: str = 'JeffreyXiang/TRELLIS-image-large/ckpts/ss_dec_conv3d_16l8_fp16',
|
||||
ss_dec_path: Optional[str] = None,
|
||||
ss_dec_ckpt: Optional[str] = None,
|
||||
):
|
||||
self.latent_model = latent_model
|
||||
self.min_aesthetic_score = min_aesthetic_score
|
||||
self.normalization = normalization
|
||||
self.value_range = (0, 1)
|
||||
|
||||
super().__init__(
|
||||
roots,
|
||||
pretrained_ss_dec=pretrained_ss_dec,
|
||||
ss_dec_path=ss_dec_path,
|
||||
ss_dec_ckpt=ss_dec_ckpt,
|
||||
)
|
||||
|
||||
if self.normalization is not None:
|
||||
self.mean = torch.tensor(self.normalization['mean']).reshape(-1, 1, 1, 1)
|
||||
self.std = torch.tensor(self.normalization['std']).reshape(-1, 1, 1, 1)
|
||||
|
||||
def filter_metadata(self, metadata):
|
||||
stats = {}
|
||||
metadata = metadata[metadata[f'ss_latent_{self.latent_model}']]
|
||||
stats['With sparse structure latents'] = len(metadata)
|
||||
metadata = metadata[metadata['aesthetic_score'] >= self.min_aesthetic_score]
|
||||
stats[f'Aesthetic score >= {self.min_aesthetic_score}'] = len(metadata)
|
||||
return metadata, stats
|
||||
|
||||
def get_instance(self, root, instance):
|
||||
latent = np.load(os.path.join(root, 'ss_latents', self.latent_model, f'{instance}.npz'))
|
||||
z = torch.tensor(latent['mean']).float()
|
||||
if self.normalization is not None:
|
||||
z = (z - self.mean) / self.std
|
||||
|
||||
pack = {
|
||||
'x_0': z,
|
||||
}
|
||||
return pack
|
||||
|
||||
|
||||
class TextConditionedSparseStructureLatent(TextConditionedMixin, SparseStructureLatent):
|
||||
"""
|
||||
Text-conditioned sparse structure dataset
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ImageConditionedSparseStructureLatent(ImageConditionedMixin, SparseStructureLatent):
|
||||
"""
|
||||
Image-conditioned sparse structure dataset
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
import json
|
||||
import os
|
||||
from typing import *
|
||||
import numpy as np
|
||||
import torch
|
||||
import utils3d.torch
|
||||
from .components import StandardDatasetBase, TextConditionedMixin, ImageConditionedMixin
|
||||
from ..modules.sparse.basic import SparseTensor
|
||||
from .. import models
|
||||
from ..utils.render_utils import get_renderer
|
||||
from ..utils.dist_utils import read_file_dist
|
||||
from ..utils.data_utils import load_balanced_group_indices
|
||||
|
||||
|
||||
class SLatVisMixin:
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
pretrained_slat_dec: str = 'JeffreyXiang/TRELLIS-image-large/ckpts/slat_dec_gs_swin8_B_64l8gs32_fp16',
|
||||
slat_dec_path: Optional[str] = None,
|
||||
slat_dec_ckpt: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.slat_dec = None
|
||||
self.pretrained_slat_dec = pretrained_slat_dec
|
||||
self.slat_dec_path = slat_dec_path
|
||||
self.slat_dec_ckpt = slat_dec_ckpt
|
||||
|
||||
def _loading_slat_dec(self):
|
||||
if self.slat_dec is not None:
|
||||
return
|
||||
if self.slat_dec_path is not None:
|
||||
cfg = json.load(open(os.path.join(self.slat_dec_path, 'config.json'), 'r'))
|
||||
decoder = getattr(models, cfg['models']['decoder']['name'])(**cfg['models']['decoder']['args'])
|
||||
ckpt_path = os.path.join(self.slat_dec_path, 'ckpts', f'decoder_{self.slat_dec_ckpt}.pt')
|
||||
decoder.load_state_dict(torch.load(read_file_dist(ckpt_path), map_location='cpu', weights_only=True))
|
||||
else:
|
||||
decoder = models.from_pretrained(self.pretrained_slat_dec)
|
||||
self.slat_dec = decoder.cuda().eval()
|
||||
|
||||
def _delete_slat_dec(self):
|
||||
del self.slat_dec
|
||||
self.slat_dec = None
|
||||
|
||||
@torch.no_grad()
|
||||
def decode_latent(self, z, batch_size=4):
|
||||
self._loading_slat_dec()
|
||||
reps = []
|
||||
if self.normalization is not None:
|
||||
z = z * self.std.to(z.device) + self.mean.to(z.device)
|
||||
for i in range(0, z.shape[0], batch_size):
|
||||
reps.append(self.slat_dec(z[i:i+batch_size]))
|
||||
reps = sum(reps, [])
|
||||
self._delete_slat_dec()
|
||||
return reps
|
||||
|
||||
@torch.no_grad()
|
||||
def visualize_sample(self, x_0: Union[SparseTensor, dict]):
|
||||
x_0 = x_0 if isinstance(x_0, SparseTensor) else x_0['x_0']
|
||||
reps = self.decode_latent(x_0.cuda())
|
||||
|
||||
# Build camera
|
||||
yaws = [0, np.pi / 2, np.pi, 3 * np.pi / 2]
|
||||
yaws_offset = np.random.uniform(-np.pi / 4, np.pi / 4)
|
||||
yaws = [y + yaws_offset for y in yaws]
|
||||
pitch = [np.random.uniform(-np.pi / 4, np.pi / 4) for _ in range(4)]
|
||||
|
||||
exts = []
|
||||
ints = []
|
||||
for yaw, pitch in zip(yaws, pitch):
|
||||
orig = torch.tensor([
|
||||
np.sin(yaw) * np.cos(pitch),
|
||||
np.cos(yaw) * np.cos(pitch),
|
||||
np.sin(pitch),
|
||||
]).float().cuda() * 2
|
||||
fov = torch.deg2rad(torch.tensor(40)).cuda()
|
||||
extrinsics = utils3d.torch.extrinsics_look_at(orig, torch.tensor([0, 0, 0]).float().cuda(), torch.tensor([0, 0, 1]).float().cuda())
|
||||
intrinsics = utils3d.torch.intrinsics_from_fov_xy(fov, fov)
|
||||
exts.append(extrinsics)
|
||||
ints.append(intrinsics)
|
||||
|
||||
renderer = get_renderer(reps[0])
|
||||
images = []
|
||||
for representation in reps:
|
||||
image = torch.zeros(3, 1024, 1024).cuda()
|
||||
tile = [2, 2]
|
||||
for j, (ext, intr) in enumerate(zip(exts, ints)):
|
||||
res = renderer.render(representation, ext, intr)
|
||||
image[:, 512 * (j // tile[1]):512 * (j // tile[1] + 1), 512 * (j % tile[1]):512 * (j % tile[1] + 1)] = res['color']
|
||||
images.append(image)
|
||||
images = torch.stack(images)
|
||||
|
||||
return images
|
||||
|
||||
|
||||
class SLat(SLatVisMixin, StandardDatasetBase):
|
||||
"""
|
||||
structured latent dataset
|
||||
|
||||
Args:
|
||||
roots (str): path to the dataset
|
||||
latent_model (str): name of the latent model
|
||||
min_aesthetic_score (float): minimum aesthetic score
|
||||
max_num_voxels (int): maximum number of voxels
|
||||
normalization (dict): normalization stats
|
||||
pretrained_slat_dec (str): name of the pretrained slat decoder
|
||||
slat_dec_path (str): path to the slat decoder, if given, will override the pretrained_slat_dec
|
||||
slat_dec_ckpt (str): name of the slat decoder checkpoint
|
||||
"""
|
||||
def __init__(self,
|
||||
roots: str,
|
||||
*,
|
||||
latent_model: str,
|
||||
min_aesthetic_score: float = 5.0,
|
||||
max_num_voxels: int = 32768,
|
||||
normalization: Optional[dict] = None,
|
||||
pretrained_slat_dec: str = 'JeffreyXiang/TRELLIS-image-large/ckpts/slat_dec_gs_swin8_B_64l8gs32_fp16',
|
||||
slat_dec_path: Optional[str] = None,
|
||||
slat_dec_ckpt: Optional[str] = None,
|
||||
):
|
||||
self.normalization = normalization
|
||||
self.latent_model = latent_model
|
||||
self.min_aesthetic_score = min_aesthetic_score
|
||||
self.max_num_voxels = max_num_voxels
|
||||
self.value_range = (0, 1)
|
||||
|
||||
super().__init__(
|
||||
roots,
|
||||
pretrained_slat_dec=pretrained_slat_dec,
|
||||
slat_dec_path=slat_dec_path,
|
||||
slat_dec_ckpt=slat_dec_ckpt,
|
||||
)
|
||||
|
||||
self.loads = [self.metadata.loc[sha256, 'num_voxels'] for _, sha256 in self.instances]
|
||||
|
||||
if self.normalization is not None:
|
||||
self.mean = torch.tensor(self.normalization['mean']).reshape(1, -1)
|
||||
self.std = torch.tensor(self.normalization['std']).reshape(1, -1)
|
||||
|
||||
def filter_metadata(self, metadata):
|
||||
stats = {}
|
||||
metadata = metadata[metadata[f'latent_{self.latent_model}']]
|
||||
stats['With latent'] = len(metadata)
|
||||
metadata = metadata[metadata['aesthetic_score'] >= self.min_aesthetic_score]
|
||||
stats[f'Aesthetic score >= {self.min_aesthetic_score}'] = len(metadata)
|
||||
metadata = metadata[metadata['num_voxels'] <= self.max_num_voxels]
|
||||
stats[f'Num voxels <= {self.max_num_voxels}'] = len(metadata)
|
||||
return metadata, stats
|
||||
|
||||
def get_instance(self, root, instance):
|
||||
data = np.load(os.path.join(root, 'latents', self.latent_model, f'{instance}.npz'))
|
||||
coords = torch.tensor(data['coords']).int()
|
||||
feats = torch.tensor(data['feats']).float()
|
||||
if self.normalization is not None:
|
||||
feats = (feats - self.mean) / self.std
|
||||
return {
|
||||
'coords': coords,
|
||||
'feats': feats,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def collate_fn(batch, split_size=None):
|
||||
if split_size is None:
|
||||
group_idx = [list(range(len(batch)))]
|
||||
else:
|
||||
group_idx = load_balanced_group_indices([b['coords'].shape[0] for b in batch], split_size)
|
||||
packs = []
|
||||
for group in group_idx:
|
||||
sub_batch = [batch[i] for i in group]
|
||||
pack = {}
|
||||
coords = []
|
||||
feats = []
|
||||
layout = []
|
||||
start = 0
|
||||
for i, b in enumerate(sub_batch):
|
||||
coords.append(torch.cat([torch.full((b['coords'].shape[0], 1), i, dtype=torch.int32), b['coords']], dim=-1))
|
||||
feats.append(b['feats'])
|
||||
layout.append(slice(start, start + b['coords'].shape[0]))
|
||||
start += b['coords'].shape[0]
|
||||
coords = torch.cat(coords)
|
||||
feats = torch.cat(feats)
|
||||
pack['x_0'] = SparseTensor(
|
||||
coords=coords,
|
||||
feats=feats,
|
||||
)
|
||||
pack['x_0']._shape = torch.Size([len(group), *sub_batch[0]['feats'].shape[1:]])
|
||||
pack['x_0'].register_spatial_cache('layout', layout)
|
||||
|
||||
# collate other data
|
||||
keys = [k for k in sub_batch[0].keys() if k not in ['coords', 'feats']]
|
||||
for k in keys:
|
||||
if isinstance(sub_batch[0][k], torch.Tensor):
|
||||
pack[k] = torch.stack([b[k] for b in sub_batch])
|
||||
elif isinstance(sub_batch[0][k], list):
|
||||
pack[k] = sum([b[k] for b in sub_batch], [])
|
||||
else:
|
||||
pack[k] = [b[k] for b in sub_batch]
|
||||
|
||||
packs.append(pack)
|
||||
|
||||
if split_size is None:
|
||||
return packs[0]
|
||||
return packs
|
||||
|
||||
|
||||
class TextConditionedSLat(TextConditionedMixin, SLat):
|
||||
"""
|
||||
Text conditioned structured latent dataset
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ImageConditionedSLat(ImageConditionedMixin, SLat):
|
||||
"""
|
||||
Image conditioned structured latent dataset
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,160 @@
|
||||
import os
|
||||
from PIL import Image
|
||||
import json
|
||||
import numpy as np
|
||||
import torch
|
||||
import utils3d.torch
|
||||
from ..modules.sparse.basic import SparseTensor
|
||||
from .components import StandardDatasetBase
|
||||
|
||||
|
||||
class SLat2Render(StandardDatasetBase):
|
||||
"""
|
||||
Dataset for Structured Latent and rendered images.
|
||||
|
||||
Args:
|
||||
roots (str): paths to the dataset
|
||||
image_size (int): size of the image
|
||||
latent_model (str): latent model name
|
||||
min_aesthetic_score (float): minimum aesthetic score
|
||||
max_num_voxels (int): maximum number of voxels
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
roots: str,
|
||||
image_size: int,
|
||||
latent_model: str,
|
||||
min_aesthetic_score: float = 5.0,
|
||||
max_num_voxels: int = 32768,
|
||||
):
|
||||
self.image_size = image_size
|
||||
self.latent_model = latent_model
|
||||
self.min_aesthetic_score = min_aesthetic_score
|
||||
self.max_num_voxels = max_num_voxels
|
||||
self.value_range = (0, 1)
|
||||
|
||||
super().__init__(roots)
|
||||
|
||||
def filter_metadata(self, metadata):
|
||||
stats = {}
|
||||
metadata = metadata[metadata[f'latent_{self.latent_model}']]
|
||||
stats['With latent'] = len(metadata)
|
||||
metadata = metadata[metadata['aesthetic_score'] >= self.min_aesthetic_score]
|
||||
stats[f'Aesthetic score >= {self.min_aesthetic_score}'] = len(metadata)
|
||||
metadata = metadata[metadata['num_voxels'] <= self.max_num_voxels]
|
||||
stats[f'Num voxels <= {self.max_num_voxels}'] = len(metadata)
|
||||
return metadata, stats
|
||||
|
||||
def _get_image(self, root, instance):
|
||||
with open(os.path.join(root, 'renders', instance, 'transforms.json')) as f:
|
||||
metadata = json.load(f)
|
||||
n_views = len(metadata['frames'])
|
||||
view = np.random.randint(n_views)
|
||||
metadata = metadata['frames'][view]
|
||||
fov = metadata['camera_angle_x']
|
||||
intrinsics = utils3d.torch.intrinsics_from_fov_xy(torch.tensor(fov), torch.tensor(fov))
|
||||
c2w = torch.tensor(metadata['transform_matrix'])
|
||||
c2w[:3, 1:3] *= -1
|
||||
extrinsics = torch.inverse(c2w)
|
||||
|
||||
image_path = os.path.join(root, 'renders', instance, metadata['file_path'])
|
||||
image = Image.open(image_path)
|
||||
alpha = image.getchannel(3)
|
||||
image = image.convert('RGB')
|
||||
image = image.resize((self.image_size, self.image_size), Image.Resampling.LANCZOS)
|
||||
alpha = alpha.resize((self.image_size, self.image_size), Image.Resampling.LANCZOS)
|
||||
image = torch.tensor(np.array(image)).permute(2, 0, 1).float() / 255.0
|
||||
alpha = torch.tensor(np.array(alpha)).float() / 255.0
|
||||
|
||||
return {
|
||||
'image': image,
|
||||
'alpha': alpha,
|
||||
'extrinsics': extrinsics,
|
||||
'intrinsics': intrinsics,
|
||||
}
|
||||
|
||||
def _get_latent(self, root, instance):
|
||||
data = np.load(os.path.join(root, 'latents', self.latent_model, f'{instance}.npz'))
|
||||
coords = torch.tensor(data['coords']).int()
|
||||
feats = torch.tensor(data['feats']).float()
|
||||
return {
|
||||
'coords': coords,
|
||||
'feats': feats,
|
||||
}
|
||||
|
||||
@torch.no_grad()
|
||||
def visualize_sample(self, sample: dict):
|
||||
return sample['image']
|
||||
|
||||
@staticmethod
|
||||
def collate_fn(batch):
|
||||
pack = {}
|
||||
coords = []
|
||||
for i, b in enumerate(batch):
|
||||
coords.append(torch.cat([torch.full((b['coords'].shape[0], 1), i, dtype=torch.int32), b['coords']], dim=-1))
|
||||
coords = torch.cat(coords)
|
||||
feats = torch.cat([b['feats'] for b in batch])
|
||||
pack['latents'] = SparseTensor(
|
||||
coords=coords,
|
||||
feats=feats,
|
||||
)
|
||||
|
||||
# collate other data
|
||||
keys = [k for k in batch[0].keys() if k not in ['coords', 'feats']]
|
||||
for k in keys:
|
||||
if isinstance(batch[0][k], torch.Tensor):
|
||||
pack[k] = torch.stack([b[k] for b in batch])
|
||||
elif isinstance(batch[0][k], list):
|
||||
pack[k] = sum([b[k] for b in batch], [])
|
||||
else:
|
||||
pack[k] = [b[k] for b in batch]
|
||||
|
||||
return pack
|
||||
|
||||
def get_instance(self, root, instance):
|
||||
image = self._get_image(root, instance)
|
||||
latent = self._get_latent(root, instance)
|
||||
return {
|
||||
**image,
|
||||
**latent,
|
||||
}
|
||||
|
||||
|
||||
class Slat2RenderGeo(SLat2Render):
|
||||
def __init__(
|
||||
self,
|
||||
roots: str,
|
||||
image_size: int,
|
||||
latent_model: str,
|
||||
min_aesthetic_score: float = 5.0,
|
||||
max_num_voxels: int = 32768,
|
||||
):
|
||||
super().__init__(
|
||||
roots,
|
||||
image_size,
|
||||
latent_model,
|
||||
min_aesthetic_score,
|
||||
max_num_voxels,
|
||||
)
|
||||
|
||||
def _get_geo(self, root, instance):
|
||||
verts, face = utils3d.io.read_ply(os.path.join(root, 'renders', instance, 'mesh.ply'))
|
||||
mesh = {
|
||||
"vertices" : torch.from_numpy(verts),
|
||||
"faces" : torch.from_numpy(face),
|
||||
}
|
||||
return {
|
||||
"mesh" : mesh,
|
||||
}
|
||||
|
||||
def get_instance(self, root, instance):
|
||||
image = self._get_image(root, instance)
|
||||
latent = self._get_latent(root, instance)
|
||||
geo = self._get_geo(root, instance)
|
||||
return {
|
||||
**image,
|
||||
**latent,
|
||||
**geo,
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,20 @@ import importlib
|
||||
__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',
|
||||
'ElasticSLatEncoder': 'structured_latent_vae',
|
||||
'ElasticSLatGaussianDecoder': 'structured_latent_vae',
|
||||
'ElasticSLatRadianceFieldDecoder': 'structured_latent_vae',
|
||||
'ElasticSLatMeshDecoder': 'structured_latent_vae',
|
||||
|
||||
'SLatFlowModel': 'structured_latent_flow',
|
||||
'ElasticSLatFlowModel': 'structured_latent_flow',
|
||||
}
|
||||
|
||||
__submodules = []
|
||||
@@ -64,7 +72,25 @@ def from_pretrained(path: str, **kwargs):
|
||||
|
||||
# For Pylance
|
||||
if __name__ == '__main__':
|
||||
from .sparse_structure_vae import SparseStructureEncoder, SparseStructureDecoder
|
||||
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
|
||||
|
||||
from .structured_latent_vae import (
|
||||
SLatEncoder,
|
||||
SLatGaussianDecoder,
|
||||
SLatRadianceFieldDecoder,
|
||||
SLatMeshDecoder,
|
||||
ElasticSLatEncoder,
|
||||
ElasticSLatGaussianDecoder,
|
||||
ElasticSLatRadianceFieldDecoder,
|
||||
ElasticSLatMeshDecoder,
|
||||
)
|
||||
|
||||
from .structured_latent_flow import (
|
||||
SLatFlowModel,
|
||||
ElasticSLatFlowModel,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
from contextlib import contextmanager
|
||||
from typing import *
|
||||
import math
|
||||
from ..modules import sparse as sp
|
||||
from ..utils.elastic_utils import ElasticModuleMixin
|
||||
|
||||
|
||||
class SparseTransformerElasticMixin(ElasticModuleMixin):
|
||||
def _get_input_size(self, x: sp.SparseTensor, *args, **kwargs):
|
||||
return x.feats.shape[0]
|
||||
|
||||
@contextmanager
|
||||
def with_mem_ratio(self, mem_ratio=1.0):
|
||||
if mem_ratio == 1.0:
|
||||
yield 1.0
|
||||
return
|
||||
num_blocks = len(self.blocks)
|
||||
num_checkpoint_blocks = min(math.ceil((1 - mem_ratio) * num_blocks) + 1, num_blocks)
|
||||
exact_mem_ratio = 1 - (num_checkpoint_blocks - 1) / num_blocks
|
||||
for i in range(num_blocks):
|
||||
self.blocks[i].use_checkpoint = i < num_checkpoint_blocks
|
||||
yield exact_mem_ratio
|
||||
for i in range(num_blocks):
|
||||
self.blocks[i].use_checkpoint = False
|
||||
@@ -9,6 +9,7 @@ from ..modules.norm import LayerNorm32
|
||||
from ..modules import sparse as sp
|
||||
from ..modules.sparse.transformer import ModulatedSparseTransformerCrossBlock
|
||||
from .sparse_structure_flow import TimestepEmbedder
|
||||
from .sparse_elastic_mixin import SparseTransformerElasticMixin
|
||||
|
||||
|
||||
class SparseResBlock3d(nn.Module):
|
||||
@@ -109,8 +110,9 @@ class SLatFlowModel(nn.Module):
|
||||
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"
|
||||
if self.io_block_channels is not None:
|
||||
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:
|
||||
@@ -122,25 +124,27 @@ class SLatFlowModel(nn.Module):
|
||||
if pe_mode == "ape":
|
||||
self.pos_embedder = AbsolutePositionEmbedder(model_channels)
|
||||
|
||||
self.input_layer = sp.SparseLinear(in_channels, io_block_channels[0])
|
||||
self.input_layer = sp.SparseLinear(in_channels, model_channels if io_block_channels is None else 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,
|
||||
if io_block_channels is not None:
|
||||
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,
|
||||
)
|
||||
)
|
||||
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(
|
||||
@@ -159,24 +163,26 @@ class SLatFlowModel(nn.Module):
|
||||
])
|
||||
|
||||
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,
|
||||
if io_block_channels is not None:
|
||||
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.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(model_channels if io_block_channels is None else io_block_channels[0], out_channels)
|
||||
|
||||
self.initialize_weights()
|
||||
if use_fp16:
|
||||
@@ -260,3 +266,11 @@ class SLatFlowModel(nn.Module):
|
||||
h = h.replace(F.layer_norm(h.feats, h.feats.shape[-1:]))
|
||||
h = self.out_layer(h.type(x.dtype))
|
||||
return h
|
||||
|
||||
|
||||
class ElasticSLatFlowModel(SparseTransformerElasticMixin, SLatFlowModel):
|
||||
"""
|
||||
SLat Flow Model with elastic memory management.
|
||||
Used for training with low VRAM.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from .encoder import SLatEncoder
|
||||
from .decoder_gs import SLatGaussianDecoder
|
||||
from .decoder_rf import SLatRadianceFieldDecoder
|
||||
from .decoder_mesh import SLatMeshDecoder
|
||||
from .encoder import SLatEncoder, ElasticSLatEncoder
|
||||
from .decoder_gs import SLatGaussianDecoder, ElasticSLatGaussianDecoder
|
||||
from .decoder_rf import SLatRadianceFieldDecoder, ElasticSLatRadianceFieldDecoder
|
||||
from .decoder_mesh import SLatMeshDecoder, ElasticSLatMeshDecoder
|
||||
|
||||
@@ -6,6 +6,7 @@ from ...modules import sparse as sp
|
||||
from ...utils.random_utils import hammersley_sequence
|
||||
from .base import SparseTransformerBase
|
||||
from ...representations import Gaussian
|
||||
from ..sparse_elastic_mixin import SparseTransformerElasticMixin
|
||||
|
||||
|
||||
class SLatGaussianDecoder(SparseTransformerBase):
|
||||
@@ -120,3 +121,11 @@ class SLatGaussianDecoder(SparseTransformerBase):
|
||||
h = h.replace(F.layer_norm(h.feats, h.feats.shape[-1:]))
|
||||
h = self.out_layer(h)
|
||||
return self.to_representation(h)
|
||||
|
||||
|
||||
class ElasticSLatGaussianDecoder(SparseTransformerElasticMixin, SLatGaussianDecoder):
|
||||
"""
|
||||
Slat VAE Gaussian decoder with elastic memory management.
|
||||
Used for training with low VRAM.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -8,6 +8,7 @@ from ...modules import sparse as sp
|
||||
from .base import SparseTransformerBase
|
||||
from ...representations import MeshExtractResult
|
||||
from ...representations.mesh import SparseFeatures2Mesh
|
||||
from ..sparse_elastic_mixin import SparseTransformerElasticMixin
|
||||
|
||||
|
||||
class SparseSubdivideBlock3d(nn.Module):
|
||||
@@ -165,3 +166,11 @@ class SLatMeshDecoder(SparseTransformerBase):
|
||||
h = h.type(x.dtype)
|
||||
h = self.out_layer(h)
|
||||
return self.to_representation(h)
|
||||
|
||||
|
||||
class ElasticSLatMeshDecoder(SparseTransformerElasticMixin, SLatMeshDecoder):
|
||||
"""
|
||||
Slat VAE Mesh decoder with elastic memory management.
|
||||
Used for training with low VRAM.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -6,6 +6,7 @@ import numpy as np
|
||||
from ...modules import sparse as sp
|
||||
from .base import SparseTransformerBase
|
||||
from ...representations import Strivec
|
||||
from ..sparse_elastic_mixin import SparseTransformerElasticMixin
|
||||
|
||||
|
||||
class SLatRadianceFieldDecoder(SparseTransformerBase):
|
||||
@@ -102,3 +103,11 @@ class SLatRadianceFieldDecoder(SparseTransformerBase):
|
||||
h = h.replace(F.layer_norm(h.feats, h.feats.shape[-1:]))
|
||||
h = self.out_layer(h)
|
||||
return self.to_representation(h)
|
||||
|
||||
|
||||
class ElasticSLatRadianceFieldDecoder(SparseTransformerElasticMixin, SLatRadianceFieldDecoder):
|
||||
"""
|
||||
Slat VAE Radiance Field Decoder with elastic memory management.
|
||||
Used for training with low VRAM.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -4,6 +4,7 @@ import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from ...modules import sparse as sp
|
||||
from .base import SparseTransformerBase
|
||||
from ..sparse_elastic_mixin import SparseTransformerElasticMixin
|
||||
|
||||
|
||||
class SLatEncoder(SparseTransformerBase):
|
||||
@@ -70,3 +71,10 @@ class SLatEncoder(SparseTransformerBase):
|
||||
return z, mean, logvar
|
||||
else:
|
||||
return z
|
||||
|
||||
|
||||
class ElasticSLatEncoder(SparseTransformerElasticMixin, SLatEncoder):
|
||||
"""
|
||||
SLat VAE encoder with elastic memory management.
|
||||
Used for training with low VRAM.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from . import samplers
|
||||
from .trellis_image_to_3d import TrellisImageTo3DPipeline
|
||||
|
||||
from .trellis_text_to_3d import TrellisTextTo3DPipeline
|
||||
|
||||
def from_pretrained(path: str):
|
||||
"""
|
||||
|
||||
@@ -36,10 +36,12 @@ class Pipeline:
|
||||
with open(config_file, 'r') as f:
|
||||
args = json.load(f)['args']
|
||||
|
||||
_models = {
|
||||
k: models.from_pretrained(f"{path}/{v}")
|
||||
for k, v in args['models'].items()
|
||||
}
|
||||
_models = {}
|
||||
for k, v in args['models'].items():
|
||||
try:
|
||||
_models[k] = models.from_pretrained(f"{path}/{v}")
|
||||
except:
|
||||
_models[k] = models.from_pretrained(v)
|
||||
|
||||
new_pipeline = Pipeline(_models)
|
||||
new_pipeline._pretrained_args = args
|
||||
|
||||
@@ -37,6 +37,8 @@ class FlowEulerSampler(Sampler):
|
||||
|
||||
def _inference_model(self, model, x_t, t, cond=None, **kwargs):
|
||||
t = torch.tensor([1000 * t] * x_t.shape[0], device=x_t.device, dtype=torch.float32)
|
||||
if cond is not None and cond.shape[0] == 1 and x_t.shape[0] > 1:
|
||||
cond = cond.repeat(x_t.shape[0], *([1] * (len(cond.shape) - 1)))
|
||||
return model(x_t, t, cond, **kwargs)
|
||||
|
||||
def _get_model_prediction(self, model, x_t, t, cond=None, **kwargs):
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
from transformers import CLIPTextModel, AutoTokenizer,CLIPTextConfig
|
||||
from contextlib import nullcontext
|
||||
from safetensors.torch import load_file
|
||||
|
||||
import open3d as o3d
|
||||
from .base import Pipeline
|
||||
from . import samplers
|
||||
from ..modules import sparse as sp
|
||||
|
||||
|
||||
class TrellisTextTo3DPipeline(Pipeline):
|
||||
"""
|
||||
Pipeline for inferring Trellis text-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.
|
||||
text_cond_model (str): The name of the text 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,
|
||||
text_cond_model: str = None,
|
||||
clip: str = None,
|
||||
clip_moudel: str = "openai/clip-vit-large-patch14",
|
||||
):
|
||||
if models is None:
|
||||
return
|
||||
super().__init__(models)
|
||||
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._init_text_cond_model(text_cond_model)
|
||||
self.clip=clip
|
||||
self.clip_moudel=clip_moudel
|
||||
|
||||
@staticmethod
|
||||
def from_pretrained(path: str) -> "TrellisTextTo3DPipeline":
|
||||
"""
|
||||
Load a pretrained model.
|
||||
|
||||
Args:
|
||||
path (str): The path to the model. Can be either local path or a Hugging Face repository.
|
||||
"""
|
||||
pipeline = super(TrellisTextTo3DPipeline, TrellisTextTo3DPipeline).from_pretrained(path)
|
||||
new_pipeline = TrellisTextTo3DPipeline()
|
||||
new_pipeline.__dict__ = pipeline.__dict__
|
||||
args = pipeline._pretrained_args
|
||||
|
||||
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']
|
||||
|
||||
new_pipeline._init_text_cond_model(args['text_cond_model'])
|
||||
|
||||
return new_pipeline
|
||||
|
||||
def _init_text_cond_model(self, name: str):
|
||||
"""
|
||||
Initialize the text conditioning model.
|
||||
"""
|
||||
# load model
|
||||
#model = CLIPTextModel.from_pretrained(name)
|
||||
is_accelerate_available=False
|
||||
try:
|
||||
from accelerate import init_empty_weights
|
||||
from accelerate.utils import set_module_tensor_to_device
|
||||
is_accelerate_available = True
|
||||
except:
|
||||
pass
|
||||
ctx = init_empty_weights if is_accelerate_available else nullcontext
|
||||
with ctx():
|
||||
text_encoder_config=CLIPTextConfig.from_pretrained(self.clip_moudel, local_files_only=True)
|
||||
model = CLIPTextModel(text_encoder_config)
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.clip_moudel)
|
||||
|
||||
clip_dict=load_file(self.clip)
|
||||
model.load_state_dict(clip_dict,strict=False)
|
||||
del clip_dict
|
||||
torch.cuda.empty_cache()
|
||||
model.eval()
|
||||
self.clip_model = model.cuda()
|
||||
self.text_cond_model = {
|
||||
'model': self.clip_model,
|
||||
'tokenizer': tokenizer,
|
||||
}
|
||||
self.text_cond_model['null_cond'] = self.encode_text([''])
|
||||
|
||||
@torch.no_grad()
|
||||
def encode_text(self, text: List[str]) -> torch.Tensor:
|
||||
"""
|
||||
Encode the text.
|
||||
"""
|
||||
assert isinstance(text, list) and all(isinstance(t, str) for t in text), "text must be a list of strings"
|
||||
encoding = self.text_cond_model['tokenizer'](text, max_length=77, padding='max_length', truncation=True, return_tensors='pt')
|
||||
tokens = encoding['input_ids'].cuda()
|
||||
embeddings = self.text_cond_model['model'](input_ids=tokens).last_hidden_state
|
||||
|
||||
return embeddings
|
||||
|
||||
def get_cond(self, prompt: List[str]) -> dict:
|
||||
"""
|
||||
Get the conditioning information for the model.
|
||||
|
||||
Args:
|
||||
prompt (List[str]): The text prompt.
|
||||
|
||||
Returns:
|
||||
dict: The conditioning information
|
||||
"""
|
||||
cond = self.encode_text(prompt)
|
||||
neg_cond = self.text_cond_model['null_cond']
|
||||
self.clip_model.to('cpu')# unload clip
|
||||
return {
|
||||
'cond': cond,
|
||||
'neg_cond': neg_cond,
|
||||
}
|
||||
|
||||
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.
|
||||
"""
|
||||
# Sample occupancy latent
|
||||
flow_model = self.models['sparse_structure_flow_model']
|
||||
reso = flow_model.resolution
|
||||
noise = torch.randn(num_samples, flow_model.in_channels, reso, reso, reso).to(self.device)
|
||||
sampler_params = {**self.sparse_structure_sampler_params, **sampler_params}
|
||||
z_s = self.sparse_structure_sampler.sample(
|
||||
flow_model,
|
||||
noise,
|
||||
**cond,
|
||||
**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 = {}
|
||||
if 'mesh' in formats:
|
||||
ret['mesh'] = self.models['slat_decoder_mesh'](slat)
|
||||
if 'gaussian' in formats:
|
||||
ret['gaussian'] = self.models['slat_decoder_gs'](slat)
|
||||
if 'radiance_field' in formats:
|
||||
ret['radiance_field'] = self.models['slat_decoder_rf'](slat)
|
||||
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.
|
||||
"""
|
||||
# Sample structured latent
|
||||
flow_model = self.models['slat_flow_model']
|
||||
noise = sp.SparseTensor(
|
||||
feats=torch.randn(coords.shape[0], flow_model.in_channels).to(self.device),
|
||||
coords=coords,
|
||||
)
|
||||
sampler_params = {**self.slat_sampler_params, **sampler_params}
|
||||
slat = self.slat_sampler.sample(
|
||||
flow_model,
|
||||
noise,
|
||||
**cond,
|
||||
**sampler_params,
|
||||
verbose=True
|
||||
).samples
|
||||
|
||||
std = torch.tensor(self.slat_normalization['std'])[None].to(slat.device)
|
||||
mean = torch.tensor(self.slat_normalization['mean'])[None].to(slat.device)
|
||||
slat = slat * std + mean
|
||||
|
||||
return slat
|
||||
|
||||
@torch.no_grad()
|
||||
def run(
|
||||
self,
|
||||
prompt: str,
|
||||
num_samples: int = 1,
|
||||
seed: int = 42,
|
||||
sparse_structure_sampler_params: dict = {},
|
||||
slat_sampler_params: dict = {},
|
||||
formats: List[str] = ['mesh', 'gaussian', 'radiance_field'],
|
||||
) -> dict:
|
||||
"""
|
||||
Run the pipeline.
|
||||
|
||||
Args:
|
||||
prompt (str): The text prompt.
|
||||
num_samples (int): The number of samples to generate.
|
||||
seed (int): The random seed.
|
||||
sparse_structure_sampler_params (dict): Additional parameters for the sparse structure sampler.
|
||||
slat_sampler_params (dict): Additional parameters for the structured latent sampler.
|
||||
formats (List[str]): The formats to decode the structured latent to.
|
||||
"""
|
||||
cond = self.get_cond([prompt])
|
||||
torch.manual_seed(seed)
|
||||
coords = self.sample_sparse_structure(cond, num_samples, sparse_structure_sampler_params)
|
||||
slat = self.sample_slat(cond, coords, slat_sampler_params)
|
||||
return self.decode_slat(slat, formats)
|
||||
|
||||
def voxelize(self, mesh: o3d.geometry.TriangleMesh) -> torch.Tensor:
|
||||
"""
|
||||
Voxelize a mesh.
|
||||
|
||||
Args:
|
||||
mesh (o3d.geometry.TriangleMesh): The mesh to voxelize.
|
||||
sha256 (str): The SHA256 hash of the mesh.
|
||||
output_dir (str): The output directory.
|
||||
"""
|
||||
vertices = np.asarray(mesh.vertices)
|
||||
aabb = np.stack([vertices.min(0), vertices.max(0)])
|
||||
center = (aabb[0] + aabb[1]) / 2
|
||||
scale = (aabb[1] - aabb[0]).max()
|
||||
vertices = (vertices - center) / scale
|
||||
vertices = np.clip(vertices, -0.5 + 1e-6, 0.5 - 1e-6)
|
||||
mesh.vertices = o3d.utility.Vector3dVector(vertices)
|
||||
voxel_grid = o3d.geometry.VoxelGrid.create_from_triangle_mesh_within_bounds(mesh, voxel_size=1/64, min_bound=(-0.5, -0.5, -0.5), max_bound=(0.5, 0.5, 0.5))
|
||||
vertices = np.array([voxel.grid_index for voxel in voxel_grid.get_voxels()])
|
||||
return torch.tensor(vertices).int().cuda()
|
||||
|
||||
@torch.no_grad()
|
||||
def run_variant(
|
||||
self,
|
||||
mesh: o3d.geometry.TriangleMesh,
|
||||
prompt: str,
|
||||
num_samples: int = 1,
|
||||
seed: int = 42,
|
||||
slat_sampler_params: dict = {},
|
||||
formats: List[str] = ['mesh', 'gaussian', 'radiance_field'],
|
||||
) -> dict:
|
||||
"""
|
||||
Run the pipeline for making variants of an asset.
|
||||
|
||||
Args:
|
||||
mesh (o3d.geometry.TriangleMesh): The base mesh.
|
||||
prompt (str): The text prompt.
|
||||
num_samples (int): The number of samples to generate.
|
||||
seed (int): The random seed
|
||||
slat_sampler_params (dict): Additional parameters for the structured latent sampler.
|
||||
formats (List[str]): The formats to decode the structured latent to.
|
||||
"""
|
||||
cond = self.get_cond([prompt])
|
||||
coords = self.voxelize(mesh)
|
||||
coords = torch.cat([
|
||||
torch.arange(num_samples).repeat_interleave(coords.shape[0], 0)[:, None].int().cuda(),
|
||||
coords.repeat(num_samples, 1)
|
||||
], 1)
|
||||
torch.manual_seed(seed)
|
||||
slat = self.sample_slat(cond, coords, slat_sampler_params)
|
||||
return self.decode_slat(slat, formats)
|
||||
@@ -3,21 +3,6 @@ import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
DEFAULT_TRIVEC_CONFIG = {
|
||||
'dim': 8,
|
||||
'rank': 8,
|
||||
}
|
||||
|
||||
DEFAULT_VOXEL_CONFIG = {
|
||||
'solid': False,
|
||||
}
|
||||
|
||||
DEFAULT_DECOPOLY_CONFIG = {
|
||||
'degree': 8,
|
||||
'rank': 16,
|
||||
}
|
||||
|
||||
|
||||
class DfsOctree:
|
||||
"""
|
||||
Sparse Voxel Octree (SVO) implementation for PyTorch.
|
||||
@@ -145,8 +130,8 @@ class DfsOctree:
|
||||
|
||||
@property
|
||||
def get_density(self):
|
||||
if self.primitive == 'voxel' and self.voxel_config['solid']:
|
||||
return torch.full((self.position.shape[0], 1), 1000, dtype=torch.float32, device=self.device)
|
||||
if self.primitive == 'voxel' and self.primitive_config.get('solid', False):
|
||||
return torch.full((self.position.shape[0], 1), torch.finfo(torch.float32).max, dtype=torch.float32, device=self.device)
|
||||
return self.density_activation(self.density)
|
||||
|
||||
@property
|
||||
@@ -172,7 +157,7 @@ class DfsOctree:
|
||||
return torch.cat([self.features_dc, self.features_ac], dim=-2)
|
||||
|
||||
def state_dict(self):
|
||||
ret = {'structure': self.structure, 'position': self.position, 'depth': self.depth, 'sh_degree': self.sh_degree, 'active_sh_degree': self.active_sh_degree, 'trivec_config': self.trivec_config, 'voxel_config': self.voxel_config, 'primitive': self.primitive}
|
||||
ret = {'structure': self.structure, 'position': self.position, 'depth': self.depth, 'sh_degree': self.sh_degree, 'active_sh_degree': self.active_sh_degree, 'primitive_config': self.primitive_config, 'primitive': self.primitive}
|
||||
if hasattr(self, 'density_shift'):
|
||||
ret['density_shift'] = self.density_shift
|
||||
for data in set(self.data + self.param_names):
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import importlib
|
||||
|
||||
__attributes = {
|
||||
'BasicTrainer': 'basic',
|
||||
|
||||
'SparseStructureVaeTrainer': 'vae.sparse_structure_vae',
|
||||
|
||||
'SLatVaeGaussianTrainer': 'vae.structured_latent_vae_gaussian',
|
||||
'SLatVaeRadianceFieldDecoderTrainer': 'vae.structured_latent_vae_rf_dec',
|
||||
'SLatVaeMeshDecoderTrainer': 'vae.structured_latent_vae_mesh_dec',
|
||||
|
||||
'FlowMatchingTrainer': 'flow_matching.flow_matching',
|
||||
'FlowMatchingCFGTrainer': 'flow_matching.flow_matching',
|
||||
'TextConditionedFlowMatchingCFGTrainer': 'flow_matching.flow_matching',
|
||||
'ImageConditionedFlowMatchingCFGTrainer': 'flow_matching.flow_matching',
|
||||
|
||||
'SparseFlowMatchingTrainer': 'flow_matching.sparse_flow_matching',
|
||||
'SparseFlowMatchingCFGTrainer': 'flow_matching.sparse_flow_matching',
|
||||
'TextConditionedSparseFlowMatchingCFGTrainer': 'flow_matching.sparse_flow_matching',
|
||||
'ImageConditionedSparseFlowMatchingCFGTrainer': 'flow_matching.sparse_flow_matching',
|
||||
}
|
||||
|
||||
__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]
|
||||
|
||||
|
||||
# For Pylance
|
||||
if __name__ == '__main__':
|
||||
from .basic import BasicTrainer
|
||||
|
||||
from .vae.sparse_structure_vae import SparseStructureVaeTrainer
|
||||
|
||||
from .vae.structured_latent_vae_gaussian import SLatVaeGaussianTrainer
|
||||
from .vae.structured_latent_vae_rf_dec import SLatVaeRadianceFieldDecoderTrainer
|
||||
from .vae.structured_latent_vae_mesh_dec import SLatVaeMeshDecoderTrainer
|
||||
|
||||
from .flow_matching.flow_matching import (
|
||||
FlowMatchingTrainer,
|
||||
FlowMatchingCFGTrainer,
|
||||
TextConditionedFlowMatchingCFGTrainer,
|
||||
ImageConditionedFlowMatchingCFGTrainer,
|
||||
)
|
||||
|
||||
from .flow_matching.sparse_flow_matching import (
|
||||
SparseFlowMatchingTrainer,
|
||||
SparseFlowMatchingCFGTrainer,
|
||||
TextConditionedSparseFlowMatchingCFGTrainer,
|
||||
ImageConditionedSparseFlowMatchingCFGTrainer,
|
||||
)
|
||||
@@ -0,0 +1,451 @@
|
||||
from abc import abstractmethod
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from torch.utils.data import DataLoader
|
||||
import numpy as np
|
||||
|
||||
from torchvision import utils
|
||||
from torch.utils.tensorboard import SummaryWriter
|
||||
|
||||
from .utils import *
|
||||
from ..utils.general_utils import *
|
||||
from ..utils.data_utils import recursive_to_device, cycle, ResumableSampler
|
||||
|
||||
|
||||
class Trainer:
|
||||
"""
|
||||
Base class for training.
|
||||
"""
|
||||
def __init__(self,
|
||||
models,
|
||||
dataset,
|
||||
*,
|
||||
output_dir,
|
||||
load_dir,
|
||||
step,
|
||||
max_steps,
|
||||
batch_size=None,
|
||||
batch_size_per_gpu=None,
|
||||
batch_split=None,
|
||||
optimizer={},
|
||||
lr_scheduler=None,
|
||||
elastic=None,
|
||||
grad_clip=None,
|
||||
ema_rate=0.9999,
|
||||
fp16_mode='inflat_all',
|
||||
fp16_scale_growth=1e-3,
|
||||
finetune_ckpt=None,
|
||||
log_param_stats=False,
|
||||
prefetch_data=True,
|
||||
i_print=1000,
|
||||
i_log=500,
|
||||
i_sample=10000,
|
||||
i_save=10000,
|
||||
i_ddpcheck=10000,
|
||||
**kwargs
|
||||
):
|
||||
assert batch_size is not None or batch_size_per_gpu is not None, 'Either batch_size or batch_size_per_gpu must be specified.'
|
||||
|
||||
self.models = models
|
||||
self.dataset = dataset
|
||||
self.batch_split = batch_split if batch_split is not None else 1
|
||||
self.max_steps = max_steps
|
||||
self.optimizer_config = optimizer
|
||||
self.lr_scheduler_config = lr_scheduler
|
||||
self.elastic_controller_config = elastic
|
||||
self.grad_clip = grad_clip
|
||||
self.ema_rate = [ema_rate] if isinstance(ema_rate, float) else ema_rate
|
||||
self.fp16_mode = fp16_mode
|
||||
self.fp16_scale_growth = fp16_scale_growth
|
||||
self.log_param_stats = log_param_stats
|
||||
self.prefetch_data = prefetch_data
|
||||
if self.prefetch_data:
|
||||
self._data_prefetched = None
|
||||
|
||||
self.output_dir = output_dir
|
||||
self.i_print = i_print
|
||||
self.i_log = i_log
|
||||
self.i_sample = i_sample
|
||||
self.i_save = i_save
|
||||
self.i_ddpcheck = i_ddpcheck
|
||||
|
||||
if dist.is_initialized():
|
||||
# Multi-GPU params
|
||||
self.world_size = dist.get_world_size()
|
||||
self.rank = dist.get_rank()
|
||||
self.local_rank = dist.get_rank() % torch.cuda.device_count()
|
||||
self.is_master = self.rank == 0
|
||||
else:
|
||||
# Single-GPU params
|
||||
self.world_size = 1
|
||||
self.rank = 0
|
||||
self.local_rank = 0
|
||||
self.is_master = True
|
||||
|
||||
self.batch_size = batch_size if batch_size_per_gpu is None else batch_size_per_gpu * self.world_size
|
||||
self.batch_size_per_gpu = batch_size_per_gpu if batch_size_per_gpu is not None else batch_size // self.world_size
|
||||
assert self.batch_size % self.world_size == 0, 'Batch size must be divisible by the number of GPUs.'
|
||||
assert self.batch_size_per_gpu % self.batch_split == 0, 'Batch size per GPU must be divisible by batch split.'
|
||||
|
||||
self.init_models_and_more(**kwargs)
|
||||
self.prepare_dataloader(**kwargs)
|
||||
|
||||
# Load checkpoint
|
||||
self.step = 0
|
||||
if load_dir is not None and step is not None:
|
||||
self.load(load_dir, step)
|
||||
elif finetune_ckpt is not None:
|
||||
self.finetune_from(finetune_ckpt)
|
||||
|
||||
if self.is_master:
|
||||
os.makedirs(os.path.join(self.output_dir, 'ckpts'), exist_ok=True)
|
||||
os.makedirs(os.path.join(self.output_dir, 'samples'), exist_ok=True)
|
||||
self.writer = SummaryWriter(os.path.join(self.output_dir, 'tb_logs'))
|
||||
|
||||
if self.world_size > 1:
|
||||
self.check_ddp()
|
||||
|
||||
if self.is_master:
|
||||
print('\n\nTrainer initialized.')
|
||||
print(self)
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
for _, model in self.models.items():
|
||||
if hasattr(model, 'device'):
|
||||
return model.device
|
||||
return next(list(self.models.values())[0].parameters()).device
|
||||
|
||||
@abstractmethod
|
||||
def init_models_and_more(self, **kwargs):
|
||||
"""
|
||||
Initialize models and more.
|
||||
"""
|
||||
pass
|
||||
|
||||
def prepare_dataloader(self, **kwargs):
|
||||
"""
|
||||
Prepare dataloader.
|
||||
"""
|
||||
self.data_sampler = ResumableSampler(
|
||||
self.dataset,
|
||||
shuffle=True,
|
||||
)
|
||||
self.dataloader = DataLoader(
|
||||
self.dataset,
|
||||
batch_size=self.batch_size_per_gpu,
|
||||
num_workers=int(np.ceil(os.cpu_count() / torch.cuda.device_count())),
|
||||
pin_memory=True,
|
||||
drop_last=True,
|
||||
persistent_workers=True,
|
||||
collate_fn=self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else None,
|
||||
sampler=self.data_sampler,
|
||||
)
|
||||
self.data_iterator = cycle(self.dataloader)
|
||||
|
||||
@abstractmethod
|
||||
def load(self, load_dir, step=0):
|
||||
"""
|
||||
Load a checkpoint.
|
||||
Should be called by all processes.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def save(self):
|
||||
"""
|
||||
Save a checkpoint.
|
||||
Should be called only by the rank 0 process.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def finetune_from(self, finetune_ckpt):
|
||||
"""
|
||||
Finetune from a checkpoint.
|
||||
Should be called by all processes.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def run_snapshot(self, num_samples, batch_size=4, verbose=False, **kwargs):
|
||||
"""
|
||||
Run a snapshot of the model.
|
||||
"""
|
||||
pass
|
||||
|
||||
@torch.no_grad()
|
||||
def visualize_sample(self, sample):
|
||||
"""
|
||||
Convert a sample to an image.
|
||||
"""
|
||||
if hasattr(self.dataset, 'visualize_sample'):
|
||||
return self.dataset.visualize_sample(sample)
|
||||
else:
|
||||
return sample
|
||||
|
||||
@torch.no_grad()
|
||||
def snapshot_dataset(self, num_samples=100):
|
||||
"""
|
||||
Sample images from the dataset.
|
||||
"""
|
||||
dataloader = torch.utils.data.DataLoader(
|
||||
self.dataset,
|
||||
batch_size=num_samples,
|
||||
num_workers=0,
|
||||
shuffle=True,
|
||||
collate_fn=self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else None,
|
||||
)
|
||||
data = next(iter(dataloader))
|
||||
data = recursive_to_device(data, self.device)
|
||||
vis = self.visualize_sample(data)
|
||||
if isinstance(vis, dict):
|
||||
save_cfg = [(f'dataset_{k}', v) for k, v in vis.items()]
|
||||
else:
|
||||
save_cfg = [('dataset', vis)]
|
||||
for name, image in save_cfg:
|
||||
utils.save_image(
|
||||
image,
|
||||
os.path.join(self.output_dir, 'samples', f'{name}.jpg'),
|
||||
nrow=int(np.sqrt(num_samples)),
|
||||
normalize=True,
|
||||
value_range=self.dataset.value_range,
|
||||
)
|
||||
|
||||
@torch.no_grad()
|
||||
def snapshot(self, suffix=None, num_samples=64, batch_size=4, verbose=False):
|
||||
"""
|
||||
Sample images from the model.
|
||||
NOTE: This function should be called by all processes.
|
||||
"""
|
||||
if self.is_master:
|
||||
print(f'\nSampling {num_samples} images...', end='')
|
||||
|
||||
if suffix is None:
|
||||
suffix = f'step{self.step:07d}'
|
||||
|
||||
# Assign tasks
|
||||
num_samples_per_process = int(np.ceil(num_samples / self.world_size))
|
||||
samples = self.run_snapshot(num_samples_per_process, batch_size=batch_size, verbose=verbose)
|
||||
|
||||
# Preprocess images
|
||||
for key in list(samples.keys()):
|
||||
if samples[key]['type'] == 'sample':
|
||||
vis = self.visualize_sample(samples[key]['value'])
|
||||
if isinstance(vis, dict):
|
||||
for k, v in vis.items():
|
||||
samples[f'{key}_{k}'] = {'value': v, 'type': 'image'}
|
||||
del samples[key]
|
||||
else:
|
||||
samples[key] = {'value': vis, 'type': 'image'}
|
||||
|
||||
# Gather results
|
||||
if self.world_size > 1:
|
||||
for key in samples.keys():
|
||||
samples[key]['value'] = samples[key]['value'].contiguous()
|
||||
if self.is_master:
|
||||
all_images = [torch.empty_like(samples[key]['value']) for _ in range(self.world_size)]
|
||||
else:
|
||||
all_images = []
|
||||
dist.gather(samples[key]['value'], all_images, dst=0)
|
||||
if self.is_master:
|
||||
samples[key]['value'] = torch.cat(all_images, dim=0)[:num_samples]
|
||||
|
||||
# Save images
|
||||
if self.is_master:
|
||||
os.makedirs(os.path.join(self.output_dir, 'samples', suffix), exist_ok=True)
|
||||
for key in samples.keys():
|
||||
if samples[key]['type'] == 'image':
|
||||
utils.save_image(
|
||||
samples[key]['value'],
|
||||
os.path.join(self.output_dir, 'samples', suffix, f'{key}_{suffix}.jpg'),
|
||||
nrow=int(np.sqrt(num_samples)),
|
||||
normalize=True,
|
||||
value_range=self.dataset.value_range,
|
||||
)
|
||||
elif samples[key]['type'] == 'number':
|
||||
min = samples[key]['value'].min()
|
||||
max = samples[key]['value'].max()
|
||||
images = (samples[key]['value'] - min) / (max - min)
|
||||
images = utils.make_grid(
|
||||
images,
|
||||
nrow=int(np.sqrt(num_samples)),
|
||||
normalize=False,
|
||||
)
|
||||
save_image_with_notes(
|
||||
images,
|
||||
os.path.join(self.output_dir, 'samples', suffix, f'{key}_{suffix}.jpg'),
|
||||
notes=f'{key} min: {min}, max: {max}',
|
||||
)
|
||||
|
||||
if self.is_master:
|
||||
print(' Done.')
|
||||
|
||||
@abstractmethod
|
||||
def update_ema(self):
|
||||
"""
|
||||
Update exponential moving average.
|
||||
Should only be called by the rank 0 process.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def check_ddp(self):
|
||||
"""
|
||||
Check if DDP is working properly.
|
||||
Should be called by all process.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def training_losses(**mb_data):
|
||||
"""
|
||||
Compute training losses.
|
||||
"""
|
||||
pass
|
||||
|
||||
def load_data(self):
|
||||
"""
|
||||
Load data.
|
||||
"""
|
||||
if self.prefetch_data:
|
||||
if self._data_prefetched is None:
|
||||
self._data_prefetched = recursive_to_device(next(self.data_iterator), self.device, non_blocking=True)
|
||||
data = self._data_prefetched
|
||||
self._data_prefetched = recursive_to_device(next(self.data_iterator), self.device, non_blocking=True)
|
||||
else:
|
||||
data = recursive_to_device(next(self.data_iterator), self.device, non_blocking=True)
|
||||
|
||||
# if the data is a dict, we need to split it into multiple dicts with batch_size_per_gpu
|
||||
if isinstance(data, dict):
|
||||
if self.batch_split == 1:
|
||||
data_list = [data]
|
||||
else:
|
||||
batch_size = list(data.values())[0].shape[0]
|
||||
data_list = [
|
||||
{k: v[i * batch_size // self.batch_split:(i + 1) * batch_size // self.batch_split] for k, v in data.items()}
|
||||
for i in range(self.batch_split)
|
||||
]
|
||||
elif isinstance(data, list):
|
||||
data_list = data
|
||||
else:
|
||||
raise ValueError('Data must be a dict or a list of dicts.')
|
||||
|
||||
return data_list
|
||||
|
||||
@abstractmethod
|
||||
def run_step(self, data_list):
|
||||
"""
|
||||
Run a training step.
|
||||
"""
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run training.
|
||||
"""
|
||||
if self.is_master:
|
||||
print('\nStarting training...')
|
||||
self.snapshot_dataset()
|
||||
if self.step == 0:
|
||||
self.snapshot(suffix='init')
|
||||
else: # resume
|
||||
self.snapshot(suffix=f'resume_step{self.step:07d}')
|
||||
|
||||
log = []
|
||||
time_last_print = 0.0
|
||||
time_elapsed = 0.0
|
||||
while self.step < self.max_steps:
|
||||
time_start = time.time()
|
||||
|
||||
data_list = self.load_data()
|
||||
step_log = self.run_step(data_list)
|
||||
|
||||
time_end = time.time()
|
||||
time_elapsed += time_end - time_start
|
||||
|
||||
self.step += 1
|
||||
|
||||
# Print progress
|
||||
if self.is_master and self.step % self.i_print == 0:
|
||||
speed = self.i_print / (time_elapsed - time_last_print) * 3600
|
||||
columns = [
|
||||
f'Step: {self.step}/{self.max_steps} ({self.step / self.max_steps * 100:.2f}%)',
|
||||
f'Elapsed: {time_elapsed / 3600:.2f} h',
|
||||
f'Speed: {speed:.2f} steps/h',
|
||||
f'ETA: {(self.max_steps - self.step) / speed:.2f} h',
|
||||
]
|
||||
print(' | '.join([c.ljust(25) for c in columns]), flush=True)
|
||||
time_last_print = time_elapsed
|
||||
|
||||
# Check ddp
|
||||
if self.world_size > 1 and self.i_ddpcheck is not None and self.step % self.i_ddpcheck == 0:
|
||||
self.check_ddp()
|
||||
|
||||
# Sample images
|
||||
if self.step % self.i_sample == 0:
|
||||
self.snapshot()
|
||||
|
||||
if self.is_master:
|
||||
log.append((self.step, {}))
|
||||
|
||||
# Log time
|
||||
log[-1][1]['time'] = {
|
||||
'step': time_end - time_start,
|
||||
'elapsed': time_elapsed,
|
||||
}
|
||||
|
||||
# Log losses
|
||||
if step_log is not None:
|
||||
log[-1][1].update(step_log)
|
||||
|
||||
# Log scale
|
||||
if self.fp16_mode == 'amp':
|
||||
log[-1][1]['scale'] = self.scaler.get_scale()
|
||||
elif self.fp16_mode == 'inflat_all':
|
||||
log[-1][1]['log_scale'] = self.log_scale
|
||||
|
||||
# Save log
|
||||
if self.step % self.i_log == 0:
|
||||
## save to log file
|
||||
log_str = '\n'.join([
|
||||
f'{step}: {json.dumps(log)}' for step, log in log
|
||||
])
|
||||
with open(os.path.join(self.output_dir, 'log.txt'), 'a') as log_file:
|
||||
log_file.write(log_str + '\n')
|
||||
|
||||
# show with mlflow
|
||||
log_show = [l for _, l in log if not dict_any(l, lambda x: np.isnan(x))]
|
||||
log_show = dict_reduce(log_show, lambda x: np.mean(x))
|
||||
log_show = dict_flatten(log_show, sep='/')
|
||||
for key, value in log_show.items():
|
||||
self.writer.add_scalar(key, value, self.step)
|
||||
log = []
|
||||
|
||||
# Save checkpoint
|
||||
if self.step % self.i_save == 0:
|
||||
self.save()
|
||||
|
||||
if self.is_master:
|
||||
self.snapshot(suffix='final')
|
||||
self.writer.close()
|
||||
print('Training finished.')
|
||||
|
||||
def profile(self, wait=2, warmup=3, active=5):
|
||||
"""
|
||||
Profile the training loop.
|
||||
"""
|
||||
with torch.profiler.profile(
|
||||
schedule=torch.profiler.schedule(wait=wait, warmup=warmup, active=active, repeat=1),
|
||||
on_trace_ready=torch.profiler.tensorboard_trace_handler(os.path.join(self.output_dir, 'profile')),
|
||||
profile_memory=True,
|
||||
with_stack=True,
|
||||
) as prof:
|
||||
for _ in range(wait + warmup + active):
|
||||
self.run_step()
|
||||
prof.step()
|
||||
|
||||
@@ -0,0 +1,438 @@
|
||||
import os
|
||||
import copy
|
||||
from functools import partial
|
||||
from contextlib import nullcontext
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from torch.nn.parallel import DistributedDataParallel as DDP
|
||||
import numpy as np
|
||||
|
||||
from .utils import *
|
||||
from .base import Trainer
|
||||
from ..utils.general_utils import *
|
||||
from ..utils.dist_utils import *
|
||||
from ..utils import grad_clip_utils, elastic_utils
|
||||
|
||||
|
||||
class BasicTrainer(Trainer):
|
||||
"""
|
||||
Trainer for basic training loop.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
lines = []
|
||||
lines.append(self.__class__.__name__)
|
||||
lines.append(f' - Models:')
|
||||
for name, model in self.models.items():
|
||||
lines.append(f' - {name}: {model.__class__.__name__}')
|
||||
lines.append(f' - Dataset: {indent(str(self.dataset), 2)}')
|
||||
lines.append(f' - Dataloader:')
|
||||
lines.append(f' - Sampler: {self.dataloader.sampler.__class__.__name__}')
|
||||
lines.append(f' - Num workers: {self.dataloader.num_workers}')
|
||||
lines.append(f' - Number of steps: {self.max_steps}')
|
||||
lines.append(f' - Number of GPUs: {self.world_size}')
|
||||
lines.append(f' - Batch size: {self.batch_size}')
|
||||
lines.append(f' - Batch size per GPU: {self.batch_size_per_gpu}')
|
||||
lines.append(f' - Batch split: {self.batch_split}')
|
||||
lines.append(f' - Optimizer: {self.optimizer.__class__.__name__}')
|
||||
lines.append(f' - Learning rate: {self.optimizer.param_groups[0]["lr"]}')
|
||||
if self.lr_scheduler_config is not None:
|
||||
lines.append(f' - LR scheduler: {self.lr_scheduler.__class__.__name__}')
|
||||
if self.elastic_controller_config is not None:
|
||||
lines.append(f' - Elastic memory: {indent(str(self.elastic_controller), 2)}')
|
||||
if self.grad_clip is not None:
|
||||
lines.append(f' - Gradient clip: {indent(str(self.grad_clip), 2)}')
|
||||
lines.append(f' - EMA rate: {self.ema_rate}')
|
||||
lines.append(f' - FP16 mode: {self.fp16_mode}')
|
||||
return '\n'.join(lines)
|
||||
|
||||
def init_models_and_more(self, **kwargs):
|
||||
"""
|
||||
Initialize models and more.
|
||||
"""
|
||||
if self.world_size > 1:
|
||||
# Prepare distributed data parallel
|
||||
self.training_models = {
|
||||
name: DDP(
|
||||
model,
|
||||
device_ids=[self.local_rank],
|
||||
output_device=self.local_rank,
|
||||
bucket_cap_mb=128,
|
||||
find_unused_parameters=False
|
||||
)
|
||||
for name, model in self.models.items()
|
||||
}
|
||||
else:
|
||||
self.training_models = self.models
|
||||
|
||||
# Build master params
|
||||
self.model_params = sum(
|
||||
[[p for p in model.parameters() if p.requires_grad] for model in self.models.values()]
|
||||
, [])
|
||||
if self.fp16_mode == 'amp':
|
||||
self.master_params = self.model_params
|
||||
self.scaler = torch.GradScaler() if self.fp16_mode == 'amp' else None
|
||||
elif self.fp16_mode == 'inflat_all':
|
||||
self.master_params = make_master_params(self.model_params)
|
||||
self.fp16_scale_growth = self.fp16_scale_growth
|
||||
self.log_scale = 20.0
|
||||
elif self.fp16_mode is None:
|
||||
self.master_params = self.model_params
|
||||
else:
|
||||
raise NotImplementedError(f'FP16 mode {self.fp16_mode} is not implemented.')
|
||||
|
||||
# Build EMA params
|
||||
if self.is_master:
|
||||
self.ema_params = [copy.deepcopy(self.master_params) for _ in self.ema_rate]
|
||||
|
||||
# Initialize optimizer
|
||||
if hasattr(torch.optim, self.optimizer_config['name']):
|
||||
self.optimizer = getattr(torch.optim, self.optimizer_config['name'])(self.master_params, **self.optimizer_config['args'])
|
||||
else:
|
||||
self.optimizer = globals()[self.optimizer_config['name']](self.master_params, **self.optimizer_config['args'])
|
||||
|
||||
# Initalize learning rate scheduler
|
||||
if self.lr_scheduler_config is not None:
|
||||
if hasattr(torch.optim.lr_scheduler, self.lr_scheduler_config['name']):
|
||||
self.lr_scheduler = getattr(torch.optim.lr_scheduler, self.lr_scheduler_config['name'])(self.optimizer, **self.lr_scheduler_config['args'])
|
||||
else:
|
||||
self.lr_scheduler = globals()[self.lr_scheduler_config['name']](self.optimizer, **self.lr_scheduler_config['args'])
|
||||
|
||||
# Initialize elastic memory controller
|
||||
if self.elastic_controller_config is not None:
|
||||
assert any([isinstance(model, (elastic_utils.ElasticModule, elastic_utils.ElasticModuleMixin)) for model in self.models.values()]), \
|
||||
'No elastic module found in models, please inherit from ElasticModule or ElasticModuleMixin'
|
||||
self.elastic_controller = getattr(elastic_utils, self.elastic_controller_config['name'])(**self.elastic_controller_config['args'])
|
||||
for model in self.models.values():
|
||||
if isinstance(model, (elastic_utils.ElasticModule, elastic_utils.ElasticModuleMixin)):
|
||||
model.register_memory_controller(self.elastic_controller)
|
||||
|
||||
# Initialize gradient clipper
|
||||
if self.grad_clip is not None:
|
||||
if isinstance(self.grad_clip, (float, int)):
|
||||
self.grad_clip = float(self.grad_clip)
|
||||
else:
|
||||
self.grad_clip = getattr(grad_clip_utils, self.grad_clip['name'])(**self.grad_clip['args'])
|
||||
|
||||
def _master_params_to_state_dicts(self, master_params):
|
||||
"""
|
||||
Convert master params to dict of state_dicts.
|
||||
"""
|
||||
if self.fp16_mode == 'inflat_all':
|
||||
master_params = unflatten_master_params(self.model_params, master_params)
|
||||
state_dicts = {name: model.state_dict() for name, model in self.models.items()}
|
||||
master_params_names = sum(
|
||||
[[(name, n) for n, p in model.named_parameters() if p.requires_grad] for name, model in self.models.items()]
|
||||
, [])
|
||||
for i, (model_name, param_name) in enumerate(master_params_names):
|
||||
state_dicts[model_name][param_name] = master_params[i]
|
||||
return state_dicts
|
||||
|
||||
def _state_dicts_to_master_params(self, master_params, state_dicts):
|
||||
"""
|
||||
Convert a state_dict to master params.
|
||||
"""
|
||||
master_params_names = sum(
|
||||
[[(name, n) for n, p in model.named_parameters() if p.requires_grad] for name, model in self.models.items()]
|
||||
, [])
|
||||
params = [state_dicts[name][param_name] for name, param_name in master_params_names]
|
||||
if self.fp16_mode == 'inflat_all':
|
||||
model_params_to_master_params(params, master_params)
|
||||
else:
|
||||
for i, param in enumerate(params):
|
||||
master_params[i].data.copy_(param.data)
|
||||
|
||||
def load(self, load_dir, step=0):
|
||||
"""
|
||||
Load a checkpoint.
|
||||
Should be called by all processes.
|
||||
"""
|
||||
if self.is_master:
|
||||
print(f'\nLoading checkpoint from step {step}...', end='')
|
||||
|
||||
model_ckpts = {}
|
||||
for name, model in self.models.items():
|
||||
model_ckpt = torch.load(read_file_dist(os.path.join(load_dir, 'ckpts', f'{name}_step{step:07d}.pt')), map_location=self.device, weights_only=True)
|
||||
model_ckpts[name] = model_ckpt
|
||||
model.load_state_dict(model_ckpt)
|
||||
if self.fp16_mode == 'inflat_all':
|
||||
model.convert_to_fp16()
|
||||
self._state_dicts_to_master_params(self.master_params, model_ckpts)
|
||||
del model_ckpts
|
||||
|
||||
if self.is_master:
|
||||
for i, ema_rate in enumerate(self.ema_rate):
|
||||
ema_ckpts = {}
|
||||
for name, model in self.models.items():
|
||||
ema_ckpt = torch.load(os.path.join(load_dir, 'ckpts', f'{name}_ema{ema_rate}_step{step:07d}.pt'), map_location=self.device, weights_only=True)
|
||||
ema_ckpts[name] = ema_ckpt
|
||||
self._state_dicts_to_master_params(self.ema_params[i], ema_ckpts)
|
||||
del ema_ckpts
|
||||
|
||||
misc_ckpt = torch.load(read_file_dist(os.path.join(load_dir, 'ckpts', f'misc_step{step:07d}.pt')), map_location=torch.device('cpu'), weights_only=False)
|
||||
self.optimizer.load_state_dict(misc_ckpt['optimizer'])
|
||||
self.step = misc_ckpt['step']
|
||||
self.data_sampler.load_state_dict(misc_ckpt['data_sampler'])
|
||||
if self.fp16_mode == 'amp':
|
||||
self.scaler.load_state_dict(misc_ckpt['scaler'])
|
||||
elif self.fp16_mode == 'inflat_all':
|
||||
self.log_scale = misc_ckpt['log_scale']
|
||||
if self.lr_scheduler_config is not None:
|
||||
self.lr_scheduler.load_state_dict(misc_ckpt['lr_scheduler'])
|
||||
if self.elastic_controller_config is not None:
|
||||
self.elastic_controller.load_state_dict(misc_ckpt['elastic_controller'])
|
||||
if self.grad_clip is not None and not isinstance(self.grad_clip, float):
|
||||
self.grad_clip.load_state_dict(misc_ckpt['grad_clip'])
|
||||
del misc_ckpt
|
||||
|
||||
if self.world_size > 1:
|
||||
dist.barrier()
|
||||
if self.is_master:
|
||||
print(' Done.')
|
||||
|
||||
if self.world_size > 1:
|
||||
self.check_ddp()
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save a checkpoint.
|
||||
Should be called only by the rank 0 process.
|
||||
"""
|
||||
assert self.is_master, 'save() should be called only by the rank 0 process.'
|
||||
print(f'\nSaving checkpoint at step {self.step}...', end='')
|
||||
|
||||
model_ckpts = self._master_params_to_state_dicts(self.master_params)
|
||||
for name, model_ckpt in model_ckpts.items():
|
||||
torch.save(model_ckpt, os.path.join(self.output_dir, 'ckpts', f'{name}_step{self.step:07d}.pt'))
|
||||
|
||||
for i, ema_rate in enumerate(self.ema_rate):
|
||||
ema_ckpts = self._master_params_to_state_dicts(self.ema_params[i])
|
||||
for name, ema_ckpt in ema_ckpts.items():
|
||||
torch.save(ema_ckpt, os.path.join(self.output_dir, 'ckpts', f'{name}_ema{ema_rate}_step{self.step:07d}.pt'))
|
||||
|
||||
misc_ckpt = {
|
||||
'optimizer': self.optimizer.state_dict(),
|
||||
'step': self.step,
|
||||
'data_sampler': self.data_sampler.state_dict(),
|
||||
}
|
||||
if self.fp16_mode == 'amp':
|
||||
misc_ckpt['scaler'] = self.scaler.state_dict()
|
||||
elif self.fp16_mode == 'inflat_all':
|
||||
misc_ckpt['log_scale'] = self.log_scale
|
||||
if self.lr_scheduler_config is not None:
|
||||
misc_ckpt['lr_scheduler'] = self.lr_scheduler.state_dict()
|
||||
if self.elastic_controller_config is not None:
|
||||
misc_ckpt['elastic_controller'] = self.elastic_controller.state_dict()
|
||||
if self.grad_clip is not None and not isinstance(self.grad_clip, float):
|
||||
misc_ckpt['grad_clip'] = self.grad_clip.state_dict()
|
||||
torch.save(misc_ckpt, os.path.join(self.output_dir, 'ckpts', f'misc_step{self.step:07d}.pt'))
|
||||
print(' Done.')
|
||||
|
||||
def finetune_from(self, finetune_ckpt):
|
||||
"""
|
||||
Finetune from a checkpoint.
|
||||
Should be called by all processes.
|
||||
"""
|
||||
if self.is_master:
|
||||
print('\nFinetuning from:')
|
||||
for name, path in finetune_ckpt.items():
|
||||
print(f' - {name}: {path}')
|
||||
|
||||
model_ckpts = {}
|
||||
for name, model in self.models.items():
|
||||
model_state_dict = model.state_dict()
|
||||
if name in finetune_ckpt:
|
||||
model_ckpt = torch.load(read_file_dist(finetune_ckpt[name]), map_location=self.device, weights_only=True)
|
||||
for k, v in model_ckpt.items():
|
||||
if model_ckpt[k].shape != model_state_dict[k].shape:
|
||||
if self.is_master:
|
||||
print(f'Warning: {k} shape mismatch, {model_ckpt[k].shape} vs {model_state_dict[k].shape}, skipped.')
|
||||
model_ckpt[k] = model_state_dict[k]
|
||||
model_ckpts[name] = model_ckpt
|
||||
model.load_state_dict(model_ckpt)
|
||||
if self.fp16_mode == 'inflat_all':
|
||||
model.convert_to_fp16()
|
||||
else:
|
||||
if self.is_master:
|
||||
print(f'Warning: {name} not found in finetune_ckpt, skipped.')
|
||||
model_ckpts[name] = model_state_dict
|
||||
self._state_dicts_to_master_params(self.master_params, model_ckpts)
|
||||
del model_ckpts
|
||||
|
||||
if self.world_size > 1:
|
||||
dist.barrier()
|
||||
if self.is_master:
|
||||
print('Done.')
|
||||
|
||||
if self.world_size > 1:
|
||||
self.check_ddp()
|
||||
|
||||
def update_ema(self):
|
||||
"""
|
||||
Update exponential moving average.
|
||||
Should only be called by the rank 0 process.
|
||||
"""
|
||||
assert self.is_master, 'update_ema() should be called only by the rank 0 process.'
|
||||
for i, ema_rate in enumerate(self.ema_rate):
|
||||
for master_param, ema_param in zip(self.master_params, self.ema_params[i]):
|
||||
ema_param.detach().mul_(ema_rate).add_(master_param, alpha=1.0 - ema_rate)
|
||||
|
||||
def check_ddp(self):
|
||||
"""
|
||||
Check if DDP is working properly.
|
||||
Should be called by all process.
|
||||
"""
|
||||
if self.is_master:
|
||||
print('\nPerforming DDP check...')
|
||||
|
||||
if self.is_master:
|
||||
print('Checking if parameters are consistent across processes...')
|
||||
dist.barrier()
|
||||
try:
|
||||
for p in self.master_params:
|
||||
# split to avoid OOM
|
||||
for i in range(0, p.numel(), 10000000):
|
||||
sub_size = min(10000000, p.numel() - i)
|
||||
sub_p = p.detach().view(-1)[i:i+sub_size]
|
||||
# gather from all processes
|
||||
sub_p_gather = [torch.empty_like(sub_p) for _ in range(self.world_size)]
|
||||
dist.all_gather(sub_p_gather, sub_p)
|
||||
# check if equal
|
||||
assert all([torch.equal(sub_p, sub_p_gather[i]) for i in range(self.world_size)]), 'parameters are not consistent across processes'
|
||||
except AssertionError as e:
|
||||
if self.is_master:
|
||||
print(f'\n\033[91mError: {e}\033[0m')
|
||||
print('DDP check failed.')
|
||||
raise e
|
||||
|
||||
dist.barrier()
|
||||
if self.is_master:
|
||||
print('Done.')
|
||||
|
||||
def run_step(self, data_list):
|
||||
"""
|
||||
Run a training step.
|
||||
"""
|
||||
step_log = {'loss': {}, 'status': {}}
|
||||
amp_context = partial(torch.autocast, device_type='cuda') if self.fp16_mode == 'amp' else nullcontext
|
||||
elastic_controller_context = self.elastic_controller.record if self.elastic_controller_config is not None else nullcontext
|
||||
|
||||
# Train
|
||||
losses = []
|
||||
statuses = []
|
||||
elastic_controller_logs = []
|
||||
zero_grad(self.model_params)
|
||||
for i, mb_data in enumerate(data_list):
|
||||
## sync at the end of each batch split
|
||||
sync_contexts = [self.training_models[name].no_sync for name in self.training_models] if i != len(data_list) - 1 and self.world_size > 1 else [nullcontext]
|
||||
with nested_contexts(*sync_contexts), elastic_controller_context():
|
||||
with amp_context():
|
||||
loss, status = self.training_losses(**mb_data)
|
||||
l = loss['loss'] / len(data_list)
|
||||
## backward
|
||||
if self.fp16_mode == 'amp':
|
||||
self.scaler.scale(l).backward()
|
||||
elif self.fp16_mode == 'inflat_all':
|
||||
scaled_l = l * (2 ** self.log_scale)
|
||||
scaled_l.backward()
|
||||
else:
|
||||
l.backward()
|
||||
## log
|
||||
losses.append(dict_foreach(loss, lambda x: x.item() if isinstance(x, torch.Tensor) else x))
|
||||
statuses.append(dict_foreach(status, lambda x: x.item() if isinstance(x, torch.Tensor) else x))
|
||||
if self.elastic_controller_config is not None:
|
||||
elastic_controller_logs.append(self.elastic_controller.log())
|
||||
## gradient clip
|
||||
if self.grad_clip is not None:
|
||||
if self.fp16_mode == 'amp':
|
||||
self.scaler.unscale_(self.optimizer)
|
||||
elif self.fp16_mode == 'inflat_all':
|
||||
model_grads_to_master_grads(self.model_params, self.master_params)
|
||||
self.master_params[0].grad.mul_(1.0 / (2 ** self.log_scale))
|
||||
if isinstance(self.grad_clip, float):
|
||||
grad_norm = torch.nn.utils.clip_grad_norm_(self.master_params, self.grad_clip)
|
||||
else:
|
||||
grad_norm = self.grad_clip(self.master_params)
|
||||
if torch.isfinite(grad_norm):
|
||||
statuses[-1]['grad_norm'] = grad_norm.item()
|
||||
## step
|
||||
if self.fp16_mode == 'amp':
|
||||
prev_scale = self.scaler.get_scale()
|
||||
self.scaler.step(self.optimizer)
|
||||
self.scaler.update()
|
||||
elif self.fp16_mode == 'inflat_all':
|
||||
prev_scale = 2 ** self.log_scale
|
||||
if not any(not p.grad.isfinite().all() for p in self.model_params):
|
||||
if self.grad_clip is None:
|
||||
model_grads_to_master_grads(self.model_params, self.master_params)
|
||||
self.master_params[0].grad.mul_(1.0 / (2 ** self.log_scale))
|
||||
self.optimizer.step()
|
||||
master_params_to_model_params(self.model_params, self.master_params)
|
||||
self.log_scale += self.fp16_scale_growth
|
||||
else:
|
||||
self.log_scale -= 1
|
||||
else:
|
||||
prev_scale = 1.0
|
||||
if not any(not p.grad.isfinite().all() for p in self.model_params):
|
||||
self.optimizer.step()
|
||||
else:
|
||||
print('\n\033[93mWarning: NaN detected in gradients. Skipping update.\033[0m')
|
||||
## adjust learning rate
|
||||
if self.lr_scheduler_config is not None:
|
||||
statuses[-1]['lr'] = self.lr_scheduler.get_last_lr()[0]
|
||||
self.lr_scheduler.step()
|
||||
|
||||
# Logs
|
||||
step_log['loss'] = dict_reduce(losses, lambda x: np.mean(x))
|
||||
step_log['status'] = dict_reduce(statuses, lambda x: np.mean(x), special_func={'min': lambda x: np.min(x), 'max': lambda x: np.max(x)})
|
||||
if self.elastic_controller_config is not None:
|
||||
step_log['elastic'] = dict_reduce(elastic_controller_logs, lambda x: np.mean(x))
|
||||
if self.grad_clip is not None:
|
||||
step_log['grad_clip'] = self.grad_clip if isinstance(self.grad_clip, float) else self.grad_clip.log()
|
||||
|
||||
# Check grad and norm of each param
|
||||
if self.log_param_stats:
|
||||
param_norms = {}
|
||||
param_grads = {}
|
||||
for name, param in self.backbone.named_parameters():
|
||||
if param.requires_grad:
|
||||
param_norms[name] = param.norm().item()
|
||||
if param.grad is not None and torch.isfinite(param.grad).all():
|
||||
param_grads[name] = param.grad.norm().item() / prev_scale
|
||||
step_log['param_norms'] = param_norms
|
||||
step_log['param_grads'] = param_grads
|
||||
|
||||
# Update exponential moving average
|
||||
if self.is_master:
|
||||
self.update_ema()
|
||||
|
||||
return step_log
|
||||
@@ -0,0 +1,353 @@
|
||||
from typing import *
|
||||
import copy
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch.utils.data import DataLoader
|
||||
import numpy as np
|
||||
from easydict import EasyDict as edict
|
||||
|
||||
from ..basic import BasicTrainer
|
||||
from ...pipelines import samplers
|
||||
from ...utils.general_utils import dict_reduce
|
||||
from .mixins.classifier_free_guidance import ClassifierFreeGuidanceMixin
|
||||
from .mixins.text_conditioned import TextConditionedMixin
|
||||
from .mixins.image_conditioned import ImageConditionedMixin
|
||||
|
||||
|
||||
class FlowMatchingTrainer(BasicTrainer):
|
||||
"""
|
||||
Trainer for diffusion model with flow matching objective.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
t_schedule (dict): Time schedule for flow matching.
|
||||
sigma_min (float): Minimum noise level.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
t_schedule: dict = {
|
||||
'name': 'logitNormal',
|
||||
'args': {
|
||||
'mean': 0.0,
|
||||
'std': 1.0,
|
||||
}
|
||||
},
|
||||
sigma_min: float = 1e-5,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.t_schedule = t_schedule
|
||||
self.sigma_min = sigma_min
|
||||
|
||||
def diffuse(self, x_0: torch.Tensor, t: torch.Tensor, noise: Optional[torch.Tensor] = None) -> torch.Tensor:
|
||||
"""
|
||||
Diffuse the data for a given number of diffusion steps.
|
||||
In other words, sample from q(x_t | x_0).
|
||||
|
||||
Args:
|
||||
x_0: The [N x C x ...] tensor of noiseless inputs.
|
||||
t: The [N] tensor of diffusion steps [0-1].
|
||||
noise: If specified, use this noise instead of generating new noise.
|
||||
|
||||
Returns:
|
||||
x_t, the noisy version of x_0 under timestep t.
|
||||
"""
|
||||
if noise is None:
|
||||
noise = torch.randn_like(x_0)
|
||||
assert noise.shape == x_0.shape, "noise must have same shape as x_0"
|
||||
|
||||
t = t.view(-1, *[1 for _ in range(len(x_0.shape) - 1)])
|
||||
x_t = (1 - t) * x_0 + (self.sigma_min + (1 - self.sigma_min) * t) * noise
|
||||
|
||||
return x_t
|
||||
|
||||
def reverse_diffuse(self, x_t: torch.Tensor, t: torch.Tensor, noise: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Get original image from noisy version under timestep t.
|
||||
"""
|
||||
assert noise.shape == x_t.shape, "noise must have same shape as x_t"
|
||||
t = t.view(-1, *[1 for _ in range(len(x_t.shape) - 1)])
|
||||
x_0 = (x_t - (self.sigma_min + (1 - self.sigma_min) * t) * noise) / (1 - t)
|
||||
return x_0
|
||||
|
||||
def get_v(self, x_0: torch.Tensor, noise: torch.Tensor, t: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Compute the velocity of the diffusion process at time t.
|
||||
"""
|
||||
return (1 - self.sigma_min) * noise - x_0
|
||||
|
||||
def get_cond(self, cond, **kwargs):
|
||||
"""
|
||||
Get the conditioning data.
|
||||
"""
|
||||
return cond
|
||||
|
||||
def get_inference_cond(self, cond, **kwargs):
|
||||
"""
|
||||
Get the conditioning data for inference.
|
||||
"""
|
||||
return {'cond': cond, **kwargs}
|
||||
|
||||
def get_sampler(self, **kwargs) -> samplers.FlowEulerSampler:
|
||||
"""
|
||||
Get the sampler for the diffusion process.
|
||||
"""
|
||||
return samplers.FlowEulerSampler(self.sigma_min)
|
||||
|
||||
def vis_cond(self, **kwargs):
|
||||
"""
|
||||
Visualize the conditioning data.
|
||||
"""
|
||||
return {}
|
||||
|
||||
def sample_t(self, batch_size: int) -> torch.Tensor:
|
||||
"""
|
||||
Sample timesteps.
|
||||
"""
|
||||
if self.t_schedule['name'] == 'uniform':
|
||||
t = torch.rand(batch_size)
|
||||
elif self.t_schedule['name'] == 'logitNormal':
|
||||
mean = self.t_schedule['args']['mean']
|
||||
std = self.t_schedule['args']['std']
|
||||
t = torch.sigmoid(torch.randn(batch_size) * std + mean)
|
||||
else:
|
||||
raise ValueError(f"Unknown t_schedule: {self.t_schedule['name']}")
|
||||
return t
|
||||
|
||||
def training_losses(
|
||||
self,
|
||||
x_0: torch.Tensor,
|
||||
cond=None,
|
||||
**kwargs
|
||||
) -> Tuple[Dict, Dict]:
|
||||
"""
|
||||
Compute training losses for a single timestep.
|
||||
|
||||
Args:
|
||||
x_0: The [N x C x ...] tensor of noiseless inputs.
|
||||
cond: The [N x ...] tensor of additional conditions.
|
||||
kwargs: Additional arguments to pass to the backbone.
|
||||
|
||||
Returns:
|
||||
a dict with the key "loss" containing a tensor of shape [N].
|
||||
may also contain other keys for different terms.
|
||||
"""
|
||||
noise = torch.randn_like(x_0)
|
||||
t = self.sample_t(x_0.shape[0]).to(x_0.device).float()
|
||||
x_t = self.diffuse(x_0, t, noise=noise)
|
||||
cond = self.get_cond(cond, **kwargs)
|
||||
|
||||
pred = self.training_models['denoiser'](x_t, t * 1000, cond, **kwargs)
|
||||
assert pred.shape == noise.shape == x_0.shape
|
||||
target = self.get_v(x_0, noise, t)
|
||||
terms = edict()
|
||||
terms["mse"] = F.mse_loss(pred, target)
|
||||
terms["loss"] = terms["mse"]
|
||||
|
||||
# log loss with time bins
|
||||
mse_per_instance = np.array([
|
||||
F.mse_loss(pred[i], target[i]).item()
|
||||
for i in range(x_0.shape[0])
|
||||
])
|
||||
time_bin = np.digitize(t.cpu().numpy(), np.linspace(0, 1, 11)) - 1
|
||||
for i in range(10):
|
||||
if (time_bin == i).sum() != 0:
|
||||
terms[f"bin_{i}"] = {"mse": mse_per_instance[time_bin == i].mean()}
|
||||
|
||||
return terms, {}
|
||||
|
||||
@torch.no_grad()
|
||||
def run_snapshot(
|
||||
self,
|
||||
num_samples: int,
|
||||
batch_size: int,
|
||||
verbose: bool = False,
|
||||
) -> Dict:
|
||||
dataloader = DataLoader(
|
||||
copy.deepcopy(self.dataset),
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
num_workers=0,
|
||||
collate_fn=self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else None,
|
||||
)
|
||||
|
||||
# inference
|
||||
sampler = self.get_sampler()
|
||||
sample_gt = []
|
||||
sample = []
|
||||
cond_vis = []
|
||||
for i in range(0, num_samples, batch_size):
|
||||
batch = min(batch_size, num_samples - i)
|
||||
data = next(iter(dataloader))
|
||||
data = {k: v[:batch].cuda() if isinstance(v, torch.Tensor) else v[:batch] for k, v in data.items()}
|
||||
noise = torch.randn_like(data['x_0'])
|
||||
sample_gt.append(data['x_0'])
|
||||
cond_vis.append(self.vis_cond(**data))
|
||||
del data['x_0']
|
||||
args = self.get_inference_cond(**data)
|
||||
res = sampler.sample(
|
||||
self.models['denoiser'],
|
||||
noise=noise,
|
||||
**args,
|
||||
steps=50, cfg_strength=3.0, verbose=verbose,
|
||||
)
|
||||
sample.append(res.samples)
|
||||
|
||||
sample_gt = torch.cat(sample_gt, dim=0)
|
||||
sample = torch.cat(sample, dim=0)
|
||||
sample_dict = {
|
||||
'sample_gt': {'value': sample_gt, 'type': 'sample'},
|
||||
'sample': {'value': sample, 'type': 'sample'},
|
||||
}
|
||||
sample_dict.update(dict_reduce(cond_vis, None, {
|
||||
'value': lambda x: torch.cat(x, dim=0),
|
||||
'type': lambda x: x[0],
|
||||
}))
|
||||
|
||||
return sample_dict
|
||||
|
||||
|
||||
class FlowMatchingCFGTrainer(ClassifierFreeGuidanceMixin, FlowMatchingTrainer):
|
||||
"""
|
||||
Trainer for diffusion model with flow matching objective and classifier-free guidance.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
t_schedule (dict): Time schedule for flow matching.
|
||||
sigma_min (float): Minimum noise level.
|
||||
p_uncond (float): Probability of dropping conditions.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TextConditionedFlowMatchingCFGTrainer(TextConditionedMixin, FlowMatchingCFGTrainer):
|
||||
"""
|
||||
Trainer for text-conditioned diffusion model with flow matching objective and classifier-free guidance.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
t_schedule (dict): Time schedule for flow matching.
|
||||
sigma_min (float): Minimum noise level.
|
||||
p_uncond (float): Probability of dropping conditions.
|
||||
text_cond_model(str): Text conditioning model.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ImageConditionedFlowMatchingCFGTrainer(ImageConditionedMixin, FlowMatchingCFGTrainer):
|
||||
"""
|
||||
Trainer for image-conditioned diffusion model with flow matching objective and classifier-free guidance.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
t_schedule (dict): Time schedule for flow matching.
|
||||
sigma_min (float): Minimum noise level.
|
||||
p_uncond (float): Probability of dropping conditions.
|
||||
image_cond_model (str): Image conditioning model.
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,59 @@
|
||||
import torch
|
||||
import numpy as np
|
||||
from ....utils.general_utils import dict_foreach
|
||||
from ....pipelines import samplers
|
||||
|
||||
|
||||
class ClassifierFreeGuidanceMixin:
|
||||
def __init__(self, *args, p_uncond: float = 0.1, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.p_uncond = p_uncond
|
||||
|
||||
def get_cond(self, cond, neg_cond=None, **kwargs):
|
||||
"""
|
||||
Get the conditioning data.
|
||||
"""
|
||||
assert neg_cond is not None, "neg_cond must be provided for classifier-free guidance"
|
||||
|
||||
if self.p_uncond > 0:
|
||||
# randomly drop the class label
|
||||
def get_batch_size(cond):
|
||||
if isinstance(cond, torch.Tensor):
|
||||
return cond.shape[0]
|
||||
elif isinstance(cond, list):
|
||||
return len(cond)
|
||||
else:
|
||||
raise ValueError(f"Unsupported type of cond: {type(cond)}")
|
||||
|
||||
ref_cond = cond if not isinstance(cond, dict) else cond[list(cond.keys())[0]]
|
||||
B = get_batch_size(ref_cond)
|
||||
|
||||
def select(cond, neg_cond, mask):
|
||||
if isinstance(cond, torch.Tensor):
|
||||
mask = torch.tensor(mask, device=cond.device).reshape(-1, *[1] * (cond.ndim - 1))
|
||||
return torch.where(mask, neg_cond, cond)
|
||||
elif isinstance(cond, list):
|
||||
return [nc if m else c for c, nc, m in zip(cond, neg_cond, mask)]
|
||||
else:
|
||||
raise ValueError(f"Unsupported type of cond: {type(cond)}")
|
||||
|
||||
mask = list(np.random.rand(B) < self.p_uncond)
|
||||
if not isinstance(cond, dict):
|
||||
cond = select(cond, neg_cond, mask)
|
||||
else:
|
||||
cond = dict_foreach([cond, neg_cond], lambda x: select(x[0], x[1], mask))
|
||||
|
||||
return cond
|
||||
|
||||
def get_inference_cond(self, cond, neg_cond=None, **kwargs):
|
||||
"""
|
||||
Get the conditioning data for inference.
|
||||
"""
|
||||
assert neg_cond is not None, "neg_cond must be provided for classifier-free guidance"
|
||||
return {'cond': cond, 'neg_cond': neg_cond, **kwargs}
|
||||
|
||||
def get_sampler(self, **kwargs) -> samplers.FlowEulerCfgSampler:
|
||||
"""
|
||||
Get the sampler for the diffusion process.
|
||||
"""
|
||||
return samplers.FlowEulerCfgSampler(self.sigma_min)
|
||||
@@ -0,0 +1,93 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torchvision import transforms
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from ....utils import dist_utils
|
||||
|
||||
|
||||
class ImageConditionedMixin:
|
||||
"""
|
||||
Mixin for image-conditioned models.
|
||||
|
||||
Args:
|
||||
image_cond_model: The image conditioning model.
|
||||
"""
|
||||
def __init__(self, *args, image_cond_model: str = 'dinov2_vitl14_reg', **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.image_cond_model_name = image_cond_model
|
||||
self.image_cond_model = None # the model is init lazily
|
||||
|
||||
@staticmethod
|
||||
def prepare_for_training(image_cond_model: str, **kwargs):
|
||||
"""
|
||||
Prepare for training.
|
||||
"""
|
||||
if hasattr(super(ImageConditionedMixin, ImageConditionedMixin), 'prepare_for_training'):
|
||||
super(ImageConditionedMixin, ImageConditionedMixin).prepare_for_training(**kwargs)
|
||||
# download the model
|
||||
torch.hub.load('facebookresearch/dinov2', image_cond_model, pretrained=True)
|
||||
|
||||
def _init_image_cond_model(self):
|
||||
"""
|
||||
Initialize the image conditioning model.
|
||||
"""
|
||||
with dist_utils.local_master_first():
|
||||
dinov2_model = torch.hub.load('facebookresearch/dinov2', self.image_cond_model_name, pretrained=True)
|
||||
dinov2_model.eval().cuda()
|
||||
transform = transforms.Compose([
|
||||
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
|
||||
])
|
||||
self.image_cond_model = {
|
||||
'model': dinov2_model,
|
||||
'transform': transform,
|
||||
}
|
||||
|
||||
@torch.no_grad()
|
||||
def encode_image(self, image: Union[torch.Tensor, List[Image.Image]]) -> torch.Tensor:
|
||||
"""
|
||||
Encode the image.
|
||||
"""
|
||||
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).cuda()
|
||||
else:
|
||||
raise ValueError(f"Unsupported type of image: {type(image)}")
|
||||
|
||||
if self.image_cond_model is None:
|
||||
self._init_image_cond_model()
|
||||
image = self.image_cond_model['transform'](image).cuda()
|
||||
features = self.image_cond_model['model'](image, is_training=True)['x_prenorm']
|
||||
patchtokens = F.layer_norm(features, features.shape[-1:])
|
||||
return patchtokens
|
||||
|
||||
def get_cond(self, cond, **kwargs):
|
||||
"""
|
||||
Get the conditioning data.
|
||||
"""
|
||||
cond = self.encode_image(cond)
|
||||
kwargs['neg_cond'] = torch.zeros_like(cond)
|
||||
cond = super().get_cond(cond, **kwargs)
|
||||
return cond
|
||||
|
||||
def get_inference_cond(self, cond, **kwargs):
|
||||
"""
|
||||
Get the conditioning data for inference.
|
||||
"""
|
||||
cond = self.encode_image(cond)
|
||||
kwargs['neg_cond'] = torch.zeros_like(cond)
|
||||
cond = super().get_inference_cond(cond, **kwargs)
|
||||
return cond
|
||||
|
||||
def vis_cond(self, cond, **kwargs):
|
||||
"""
|
||||
Visualize the conditioning data.
|
||||
"""
|
||||
return {'image': {'value': cond, 'type': 'image'}}
|
||||
@@ -0,0 +1,68 @@
|
||||
from typing import *
|
||||
import os
|
||||
os.environ['TOKENIZERS_PARALLELISM'] = 'true'
|
||||
import torch
|
||||
from transformers import AutoTokenizer, CLIPTextModel
|
||||
|
||||
from ....utils import dist_utils
|
||||
|
||||
|
||||
class TextConditionedMixin:
|
||||
"""
|
||||
Mixin for text-conditioned models.
|
||||
|
||||
Args:
|
||||
text_cond_model: The text conditioning model.
|
||||
"""
|
||||
def __init__(self, *args, text_cond_model: str = 'openai/clip-vit-large-patch14', **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.text_cond_model_name = text_cond_model
|
||||
self.text_cond_model = None # the model is init lazily
|
||||
|
||||
def _init_text_cond_model(self):
|
||||
"""
|
||||
Initialize the text conditioning model.
|
||||
"""
|
||||
# load model
|
||||
with dist_utils.local_master_first():
|
||||
model = CLIPTextModel.from_pretrained(self.text_cond_model_name)
|
||||
tokenizer = AutoTokenizer.from_pretrained(self.text_cond_model_name)
|
||||
model.eval()
|
||||
model = model.cuda()
|
||||
self.text_cond_model = {
|
||||
'model': model,
|
||||
'tokenizer': tokenizer,
|
||||
}
|
||||
self.text_cond_model['null_cond'] = self.encode_text([''])
|
||||
|
||||
@torch.no_grad()
|
||||
def encode_text(self, text: List[str]) -> torch.Tensor:
|
||||
"""
|
||||
Encode the text.
|
||||
"""
|
||||
assert isinstance(text, list) and isinstance(text[0], str), "TextConditionedMixin only supports list of strings as cond"
|
||||
if self.text_cond_model is None:
|
||||
self._init_text_cond_model()
|
||||
encoding = self.text_cond_model['tokenizer'](text, max_length=77, padding='max_length', truncation=True, return_tensors='pt')
|
||||
tokens = encoding['input_ids'].cuda()
|
||||
embeddings = self.text_cond_model['model'](input_ids=tokens).last_hidden_state
|
||||
|
||||
return embeddings
|
||||
|
||||
def get_cond(self, cond, **kwargs):
|
||||
"""
|
||||
Get the conditioning data.
|
||||
"""
|
||||
cond = self.encode_text(cond)
|
||||
kwargs['neg_cond'] = self.text_cond_model['null_cond'].repeat(cond.shape[0], 1, 1)
|
||||
cond = super().get_cond(cond, **kwargs)
|
||||
return cond
|
||||
|
||||
def get_inference_cond(self, cond, **kwargs):
|
||||
"""
|
||||
Get the conditioning data for inference.
|
||||
"""
|
||||
cond = self.encode_text(cond)
|
||||
kwargs['neg_cond'] = self.text_cond_model['null_cond'].repeat(cond.shape[0], 1, 1)
|
||||
cond = super().get_inference_cond(cond, **kwargs)
|
||||
return cond
|
||||
@@ -0,0 +1,286 @@
|
||||
from typing import *
|
||||
import os
|
||||
import copy
|
||||
import functools
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch.utils.data import DataLoader
|
||||
import numpy as np
|
||||
from easydict import EasyDict as edict
|
||||
|
||||
from ...modules import sparse as sp
|
||||
from ...utils.general_utils import dict_reduce
|
||||
from ...utils.data_utils import cycle, BalancedResumableSampler
|
||||
from .flow_matching import FlowMatchingTrainer
|
||||
from .mixins.classifier_free_guidance import ClassifierFreeGuidanceMixin
|
||||
from .mixins.text_conditioned import TextConditionedMixin
|
||||
from .mixins.image_conditioned import ImageConditionedMixin
|
||||
|
||||
|
||||
class SparseFlowMatchingTrainer(FlowMatchingTrainer):
|
||||
"""
|
||||
Trainer for sparse diffusion model with flow matching objective.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
t_schedule (dict): Time schedule for flow matching.
|
||||
sigma_min (float): Minimum noise level.
|
||||
"""
|
||||
|
||||
def prepare_dataloader(self, **kwargs):
|
||||
"""
|
||||
Prepare dataloader.
|
||||
"""
|
||||
self.data_sampler = BalancedResumableSampler(
|
||||
self.dataset,
|
||||
shuffle=True,
|
||||
batch_size=self.batch_size_per_gpu,
|
||||
)
|
||||
self.dataloader = DataLoader(
|
||||
self.dataset,
|
||||
batch_size=self.batch_size_per_gpu,
|
||||
num_workers=int(np.ceil(os.cpu_count() / torch.cuda.device_count())),
|
||||
pin_memory=True,
|
||||
drop_last=True,
|
||||
persistent_workers=True,
|
||||
collate_fn=functools.partial(self.dataset.collate_fn, split_size=self.batch_split),
|
||||
sampler=self.data_sampler,
|
||||
)
|
||||
self.data_iterator = cycle(self.dataloader)
|
||||
|
||||
def training_losses(
|
||||
self,
|
||||
x_0: sp.SparseTensor,
|
||||
cond=None,
|
||||
**kwargs
|
||||
) -> Tuple[Dict, Dict]:
|
||||
"""
|
||||
Compute training losses for a single timestep.
|
||||
|
||||
Args:
|
||||
x_0: The [N x ... x C] sparse tensor of the inputs.
|
||||
cond: The [N x ...] tensor of additional conditions.
|
||||
kwargs: Additional arguments to pass to the backbone.
|
||||
|
||||
Returns:
|
||||
a dict with the key "loss" containing a tensor of shape [N].
|
||||
may also contain other keys for different terms.
|
||||
"""
|
||||
noise = x_0.replace(torch.randn_like(x_0.feats))
|
||||
t = self.sample_t(x_0.shape[0]).to(x_0.device).float()
|
||||
x_t = self.diffuse(x_0, t, noise=noise)
|
||||
cond = self.get_cond(cond, **kwargs)
|
||||
|
||||
pred = self.training_models['denoiser'](x_t, t * 1000, cond, **kwargs)
|
||||
assert pred.shape == noise.shape == x_0.shape
|
||||
target = self.get_v(x_0, noise, t)
|
||||
terms = edict()
|
||||
terms["mse"] = F.mse_loss(pred.feats, target.feats)
|
||||
terms["loss"] = terms["mse"]
|
||||
|
||||
# log loss with time bins
|
||||
mse_per_instance = np.array([
|
||||
F.mse_loss(pred.feats[x_0.layout[i]], target.feats[x_0.layout[i]]).item()
|
||||
for i in range(x_0.shape[0])
|
||||
])
|
||||
time_bin = np.digitize(t.cpu().numpy(), np.linspace(0, 1, 11)) - 1
|
||||
for i in range(10):
|
||||
if (time_bin == i).sum() != 0:
|
||||
terms[f"bin_{i}"] = {"mse": mse_per_instance[time_bin == i].mean()}
|
||||
|
||||
return terms, {}
|
||||
|
||||
@torch.no_grad()
|
||||
def run_snapshot(
|
||||
self,
|
||||
num_samples: int,
|
||||
batch_size: int,
|
||||
verbose: bool = False,
|
||||
) -> Dict:
|
||||
dataloader = DataLoader(
|
||||
copy.deepcopy(self.dataset),
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
num_workers=0,
|
||||
collate_fn=self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else None,
|
||||
)
|
||||
|
||||
# inference
|
||||
sampler = self.get_sampler()
|
||||
sample_gt = []
|
||||
sample = []
|
||||
cond_vis = []
|
||||
for i in range(0, num_samples, batch_size):
|
||||
batch = min(batch_size, num_samples - i)
|
||||
data = next(iter(dataloader))
|
||||
data = {k: v[:batch].cuda() if not isinstance(v, list) else v[:batch] for k, v in data.items()}
|
||||
noise = data['x_0'].replace(torch.randn_like(data['x_0'].feats))
|
||||
sample_gt.append(data['x_0'])
|
||||
cond_vis.append(self.vis_cond(**data))
|
||||
del data['x_0']
|
||||
args = self.get_inference_cond(**data)
|
||||
res = sampler.sample(
|
||||
self.models['denoiser'],
|
||||
noise=noise,
|
||||
**args,
|
||||
steps=50, cfg_strength=3.0, verbose=verbose,
|
||||
)
|
||||
sample.append(res.samples)
|
||||
|
||||
sample_gt = sp.sparse_cat(sample_gt)
|
||||
sample = sp.sparse_cat(sample)
|
||||
sample_dict = {
|
||||
'sample_gt': {'value': sample_gt, 'type': 'sample'},
|
||||
'sample': {'value': sample, 'type': 'sample'},
|
||||
}
|
||||
sample_dict.update(dict_reduce(cond_vis, None, {
|
||||
'value': lambda x: torch.cat(x, dim=0),
|
||||
'type': lambda x: x[0],
|
||||
}))
|
||||
|
||||
return sample_dict
|
||||
|
||||
|
||||
class SparseFlowMatchingCFGTrainer(ClassifierFreeGuidanceMixin, SparseFlowMatchingTrainer):
|
||||
"""
|
||||
Trainer for sparse diffusion model with flow matching objective and classifier-free guidance.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
t_schedule (dict): Time schedule for flow matching.
|
||||
sigma_min (float): Minimum noise level.
|
||||
p_uncond (float): Probability of dropping conditions.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TextConditionedSparseFlowMatchingCFGTrainer(TextConditionedMixin, SparseFlowMatchingCFGTrainer):
|
||||
"""
|
||||
Trainer for sparse text-conditioned diffusion model with flow matching objective and classifier-free guidance.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
t_schedule (dict): Time schedule for flow matching.
|
||||
sigma_min (float): Minimum noise level.
|
||||
p_uncond (float): Probability of dropping conditions.
|
||||
text_cond_model(str): Text conditioning model.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ImageConditionedSparseFlowMatchingCFGTrainer(ImageConditionedMixin, SparseFlowMatchingCFGTrainer):
|
||||
"""
|
||||
Trainer for sparse image-conditioned diffusion model with flow matching objective and classifier-free guidance.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
t_schedule (dict): Time schedule for flow matching.
|
||||
sigma_min (float): Minimum noise level.
|
||||
p_uncond (float): Probability of dropping conditions.
|
||||
image_cond_model (str): Image conditioning model.
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,77 @@
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
# FP16 utils
|
||||
from torch._utils import _flatten_dense_tensors, _unflatten_dense_tensors
|
||||
|
||||
def make_master_params(model_params):
|
||||
"""
|
||||
Copy model parameters into a inflated tensor of full-precision parameters.
|
||||
"""
|
||||
master_params = _flatten_dense_tensors(
|
||||
[param.detach().float() for param in model_params]
|
||||
)
|
||||
master_params = nn.Parameter(master_params)
|
||||
master_params.requires_grad = True
|
||||
return [master_params]
|
||||
|
||||
|
||||
def unflatten_master_params(model_params, master_params):
|
||||
"""
|
||||
Unflatten the master parameters to look like model_params.
|
||||
"""
|
||||
return _unflatten_dense_tensors(master_params[0].detach(), model_params)
|
||||
|
||||
|
||||
def model_params_to_master_params(model_params, master_params):
|
||||
"""
|
||||
Copy the model parameter data into the master parameters.
|
||||
"""
|
||||
master_params[0].detach().copy_(
|
||||
_flatten_dense_tensors([param.detach().float() for param in model_params])
|
||||
)
|
||||
|
||||
|
||||
def master_params_to_model_params(model_params, master_params):
|
||||
"""
|
||||
Copy the master parameter data back into the model parameters.
|
||||
"""
|
||||
for param, master_param in zip(
|
||||
model_params, _unflatten_dense_tensors(master_params[0].detach(), model_params)
|
||||
):
|
||||
param.detach().copy_(master_param)
|
||||
|
||||
|
||||
def model_grads_to_master_grads(model_params, master_params):
|
||||
"""
|
||||
Copy the gradients from the model parameters into the master parameters
|
||||
from make_master_params().
|
||||
"""
|
||||
master_params[0].grad = _flatten_dense_tensors(
|
||||
[param.grad.data.detach().float() for param in model_params]
|
||||
)
|
||||
|
||||
|
||||
def zero_grad(model_params):
|
||||
for param in model_params:
|
||||
if param.grad is not None:
|
||||
if param.grad.grad_fn is not None:
|
||||
param.grad.detach_()
|
||||
else:
|
||||
param.grad.requires_grad_(False)
|
||||
param.grad.zero_()
|
||||
|
||||
|
||||
# LR Schedulers
|
||||
from torch.optim.lr_scheduler import LambdaLR
|
||||
|
||||
class LinearWarmupLRScheduler(LambdaLR):
|
||||
def __init__(self, optimizer, warmup_steps, last_epoch=-1):
|
||||
self.warmup_steps = warmup_steps
|
||||
super(LinearWarmupLRScheduler, self).__init__(optimizer, self.lr_lambda, last_epoch=last_epoch)
|
||||
|
||||
def lr_lambda(self, current_step):
|
||||
if current_step < self.warmup_steps:
|
||||
return float(current_step + 1) / self.warmup_steps
|
||||
return 1.0
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
from typing import *
|
||||
import copy
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch.utils.data import DataLoader
|
||||
from easydict import EasyDict as edict
|
||||
|
||||
from ..basic import BasicTrainer
|
||||
|
||||
|
||||
class SparseStructureVaeTrainer(BasicTrainer):
|
||||
"""
|
||||
Trainer for Sparse Structure VAE.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
loss_type (str): Loss type. 'bce' for binary cross entropy, 'l1' for L1 loss, 'dice' for Dice loss.
|
||||
lambda_kl (float): KL divergence loss weight.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
loss_type='bce',
|
||||
lambda_kl=1e-6,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.loss_type = loss_type
|
||||
self.lambda_kl = lambda_kl
|
||||
|
||||
def training_losses(
|
||||
self,
|
||||
ss: torch.Tensor,
|
||||
**kwargs
|
||||
) -> Tuple[Dict, Dict]:
|
||||
"""
|
||||
Compute training losses.
|
||||
|
||||
Args:
|
||||
ss: The [N x 1 x H x W x D] tensor of binary sparse structure.
|
||||
|
||||
Returns:
|
||||
a dict with the key "loss" containing a scalar tensor.
|
||||
may also contain other keys for different terms.
|
||||
"""
|
||||
z, mean, logvar = self.training_models['encoder'](ss.float(), sample_posterior=True, return_raw=True)
|
||||
logits = self.training_models['decoder'](z)
|
||||
|
||||
terms = edict(loss = 0.0)
|
||||
if self.loss_type == 'bce':
|
||||
terms["bce"] = F.binary_cross_entropy_with_logits(logits, ss.float(), reduction='mean')
|
||||
terms["loss"] = terms["loss"] + terms["bce"]
|
||||
elif self.loss_type == 'l1':
|
||||
terms["l1"] = F.l1_loss(F.sigmoid(logits), ss.float(), reduction='mean')
|
||||
terms["loss"] = terms["loss"] + terms["l1"]
|
||||
elif self.loss_type == 'dice':
|
||||
logits = F.sigmoid(logits)
|
||||
terms["dice"] = 1 - (2 * (logits * ss.float()).sum() + 1) / (logits.sum() + ss.float().sum() + 1)
|
||||
terms["loss"] = terms["loss"] + terms["dice"]
|
||||
else:
|
||||
raise ValueError(f'Invalid loss type {self.loss_type}')
|
||||
terms["kl"] = 0.5 * torch.mean(mean.pow(2) + logvar.exp() - logvar - 1)
|
||||
terms["loss"] = terms["loss"] + self.lambda_kl * terms["kl"]
|
||||
|
||||
return terms, {}
|
||||
|
||||
@torch.no_grad()
|
||||
def snapshot(self, suffix=None, num_samples=64, batch_size=1, verbose=False):
|
||||
super().snapshot(suffix=suffix, num_samples=num_samples, batch_size=batch_size, verbose=verbose)
|
||||
|
||||
@torch.no_grad()
|
||||
def run_snapshot(
|
||||
self,
|
||||
num_samples: int,
|
||||
batch_size: int,
|
||||
verbose: bool = False,
|
||||
) -> Dict:
|
||||
dataloader = DataLoader(
|
||||
copy.deepcopy(self.dataset),
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
num_workers=0,
|
||||
collate_fn=self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else None,
|
||||
)
|
||||
|
||||
# inference
|
||||
gts = []
|
||||
recons = []
|
||||
for i in range(0, num_samples, batch_size):
|
||||
batch = min(batch_size, num_samples - i)
|
||||
data = next(iter(dataloader))
|
||||
args = {k: v[:batch].cuda() if isinstance(v, torch.Tensor) else v[:batch] for k, v in data.items()}
|
||||
z = self.models['encoder'](args['ss'].float(), sample_posterior=False)
|
||||
logits = self.models['decoder'](z)
|
||||
recon = (logits > 0).long()
|
||||
gts.append(args['ss'])
|
||||
recons.append(recon)
|
||||
|
||||
sample_dict = {
|
||||
'gt': {'value': torch.cat(gts, dim=0), 'type': 'sample'},
|
||||
'recon': {'value': torch.cat(recons, dim=0), 'type': 'sample'},
|
||||
}
|
||||
return sample_dict
|
||||
@@ -0,0 +1,275 @@
|
||||
from typing import *
|
||||
import copy
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
import numpy as np
|
||||
from easydict import EasyDict as edict
|
||||
import utils3d.torch
|
||||
|
||||
from ..basic import BasicTrainer
|
||||
from ...representations import Gaussian
|
||||
from ...renderers import GaussianRenderer
|
||||
from ...modules.sparse import SparseTensor
|
||||
from ...utils.loss_utils import l1_loss, l2_loss, ssim, lpips
|
||||
|
||||
|
||||
class SLatVaeGaussianTrainer(BasicTrainer):
|
||||
"""
|
||||
Trainer for structured latent VAE.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
loss_type (str): Loss type. Can be 'l1', 'l2'
|
||||
lambda_ssim (float): SSIM loss weight.
|
||||
lambda_lpips (float): LPIPS loss weight.
|
||||
lambda_kl (float): KL loss weight.
|
||||
regularizations (dict): Regularization config.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
loss_type: str = 'l1',
|
||||
lambda_ssim: float = 0.2,
|
||||
lambda_lpips: float = 0.2,
|
||||
lambda_kl: float = 1e-6,
|
||||
regularizations: Dict = {},
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.loss_type = loss_type
|
||||
self.lambda_ssim = lambda_ssim
|
||||
self.lambda_lpips = lambda_lpips
|
||||
self.lambda_kl = lambda_kl
|
||||
self.regularizations = regularizations
|
||||
|
||||
self._init_renderer()
|
||||
|
||||
def _init_renderer(self):
|
||||
rendering_options = {"near" : 0.8,
|
||||
"far" : 1.6,
|
||||
"bg_color" : 'random'}
|
||||
self.renderer = GaussianRenderer(rendering_options)
|
||||
self.renderer.pipe.kernel_size = self.models['decoder'].rep_config['2d_filter_kernel_size']
|
||||
|
||||
def _render_batch(self, reps: List[Gaussian], extrinsics: torch.Tensor, intrinsics: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Render a batch of representations.
|
||||
|
||||
Args:
|
||||
reps: The dictionary of lists of representations.
|
||||
extrinsics: The [N x 4 x 4] tensor of extrinsics.
|
||||
intrinsics: The [N x 3 x 3] tensor of intrinsics.
|
||||
"""
|
||||
ret = None
|
||||
for i, representation in enumerate(reps):
|
||||
render_pack = self.renderer.render(representation, extrinsics[i], intrinsics[i])
|
||||
if ret is None:
|
||||
ret = {k: [] for k in list(render_pack.keys()) + ['bg_color']}
|
||||
for k, v in render_pack.items():
|
||||
ret[k].append(v)
|
||||
ret['bg_color'].append(self.renderer.bg_color)
|
||||
for k, v in ret.items():
|
||||
ret[k] = torch.stack(v, dim=0)
|
||||
return ret
|
||||
|
||||
@torch.no_grad()
|
||||
def _get_status(self, z: SparseTensor, reps: List[Gaussian]) -> Dict:
|
||||
xyz = torch.cat([g.get_xyz for g in reps], dim=0)
|
||||
xyz_base = (z.coords[:, 1:].float() + 0.5) / self.models['decoder'].resolution - 0.5
|
||||
offset = xyz - xyz_base.unsqueeze(1).expand(-1, self.models['decoder'].rep_config['num_gaussians'], -1).reshape(-1, 3)
|
||||
status = {
|
||||
'xyz': xyz,
|
||||
'offset': offset,
|
||||
'scale': torch.cat([g.get_scaling for g in reps], dim=0),
|
||||
'opacity': torch.cat([g.get_opacity for g in reps], dim=0),
|
||||
}
|
||||
|
||||
for k in list(status.keys()):
|
||||
status[k] = {
|
||||
'mean': status[k].mean().item(),
|
||||
'max': status[k].max().item(),
|
||||
'min': status[k].min().item(),
|
||||
}
|
||||
|
||||
return status
|
||||
|
||||
def _get_regularization_loss(self, reps: List[Gaussian]) -> Tuple[torch.Tensor, Dict]:
|
||||
loss = 0.0
|
||||
terms = {}
|
||||
if 'lambda_vol' in self.regularizations:
|
||||
scales = torch.cat([g.get_scaling for g in reps], dim=0) # [N x 3]
|
||||
volume = torch.prod(scales, dim=1) # [N]
|
||||
terms[f'reg_vol'] = volume.mean()
|
||||
loss = loss + self.regularizations['lambda_vol'] * terms[f'reg_vol']
|
||||
if 'lambda_opacity' in self.regularizations:
|
||||
opacity = torch.cat([g.get_opacity for g in reps], dim=0)
|
||||
terms[f'reg_opacity'] = (opacity - 1).pow(2).mean()
|
||||
loss = loss + self.regularizations['lambda_opacity'] * terms[f'reg_opacity']
|
||||
return loss, terms
|
||||
|
||||
def training_losses(
|
||||
self,
|
||||
feats: SparseTensor,
|
||||
image: torch.Tensor,
|
||||
alpha: torch.Tensor,
|
||||
extrinsics: torch.Tensor,
|
||||
intrinsics: torch.Tensor,
|
||||
return_aux: bool = False,
|
||||
**kwargs
|
||||
) -> Tuple[Dict, Dict]:
|
||||
"""
|
||||
Compute training losses.
|
||||
|
||||
Args:
|
||||
feats: The [N x * x C] sparse tensor of features.
|
||||
image: The [N x 3 x H x W] tensor of images.
|
||||
alpha: The [N x H x W] tensor of alpha channels.
|
||||
extrinsics: The [N x 4 x 4] tensor of extrinsics.
|
||||
intrinsics: The [N x 3 x 3] tensor of intrinsics.
|
||||
return_aux: Whether to return auxiliary information.
|
||||
|
||||
Returns:
|
||||
a dict with the key "loss" containing a scalar tensor.
|
||||
may also contain other keys for different terms.
|
||||
"""
|
||||
z, mean, logvar = self.training_models['encoder'](feats, sample_posterior=True, return_raw=True)
|
||||
reps = self.training_models['decoder'](z)
|
||||
self.renderer.rendering_options.resolution = image.shape[-1]
|
||||
render_results = self._render_batch(reps, extrinsics, intrinsics)
|
||||
|
||||
terms = edict(loss = 0.0, rec = 0.0)
|
||||
|
||||
rec_image = render_results['color']
|
||||
gt_image = image * alpha[:, None] + (1 - alpha[:, None]) * render_results['bg_color'][..., None, None]
|
||||
|
||||
if self.loss_type == 'l1':
|
||||
terms["l1"] = l1_loss(rec_image, gt_image)
|
||||
terms["rec"] = terms["rec"] + terms["l1"]
|
||||
elif self.loss_type == 'l2':
|
||||
terms["l2"] = l2_loss(rec_image, gt_image)
|
||||
terms["rec"] = terms["rec"] + terms["l2"]
|
||||
else:
|
||||
raise ValueError(f"Invalid loss type: {self.loss_type}")
|
||||
if self.lambda_ssim > 0:
|
||||
terms["ssim"] = 1 - ssim(rec_image, gt_image)
|
||||
terms["rec"] = terms["rec"] + self.lambda_ssim * terms["ssim"]
|
||||
if self.lambda_lpips > 0:
|
||||
terms["lpips"] = lpips(rec_image, gt_image)
|
||||
terms["rec"] = terms["rec"] + self.lambda_lpips * terms["lpips"]
|
||||
terms["loss"] = terms["loss"] + terms["rec"]
|
||||
|
||||
terms["kl"] = 0.5 * torch.mean(mean.pow(2) + logvar.exp() - logvar - 1)
|
||||
terms["loss"] = terms["loss"] + self.lambda_kl * terms["kl"]
|
||||
|
||||
reg_loss, reg_terms = self._get_regularization_loss(reps)
|
||||
terms.update(reg_terms)
|
||||
terms["loss"] = terms["loss"] + reg_loss
|
||||
|
||||
status = self._get_status(z, reps)
|
||||
|
||||
if return_aux:
|
||||
return terms, status, {'rec_image': rec_image, 'gt_image': gt_image}
|
||||
return terms, status
|
||||
|
||||
@torch.no_grad()
|
||||
def run_snapshot(
|
||||
self,
|
||||
num_samples: int,
|
||||
batch_size: int,
|
||||
verbose: bool = False,
|
||||
) -> Dict:
|
||||
dataloader = DataLoader(
|
||||
copy.deepcopy(self.dataset),
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
num_workers=0,
|
||||
collate_fn=self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else None,
|
||||
)
|
||||
|
||||
# inference
|
||||
ret_dict = {}
|
||||
gt_images = []
|
||||
exts = []
|
||||
ints = []
|
||||
reps = []
|
||||
for i in range(0, num_samples, batch_size):
|
||||
batch = min(batch_size, num_samples - i)
|
||||
data = next(iter(dataloader))
|
||||
args = {k: v[:batch].cuda() for k, v in data.items()}
|
||||
gt_images.append(args['image'] * args['alpha'][:, None])
|
||||
exts.append(args['extrinsics'])
|
||||
ints.append(args['intrinsics'])
|
||||
z = self.models['encoder'](args['feats'], sample_posterior=True, return_raw=False)
|
||||
reps.extend(self.models['decoder'](z))
|
||||
gt_images = torch.cat(gt_images, dim=0)
|
||||
ret_dict.update({f'gt_image': {'value': gt_images, 'type': 'image'}})
|
||||
|
||||
# render single view
|
||||
exts = torch.cat(exts, dim=0)
|
||||
ints = torch.cat(ints, dim=0)
|
||||
self.renderer.rendering_options.bg_color = (0, 0, 0)
|
||||
self.renderer.rendering_options.resolution = gt_images.shape[-1]
|
||||
render_results = self._render_batch(reps, exts, ints)
|
||||
ret_dict.update({f'rec_image': {'value': render_results['color'], 'type': 'image'}})
|
||||
|
||||
# render multiview
|
||||
self.renderer.rendering_options.resolution = 512
|
||||
## Build camera
|
||||
yaws = [0, np.pi / 2, np.pi, 3 * np.pi / 2]
|
||||
yaws_offset = np.random.uniform(-np.pi / 4, np.pi / 4)
|
||||
yaws = [y + yaws_offset for y in yaws]
|
||||
pitch = [np.random.uniform(-np.pi / 4, np.pi / 4) for _ in range(4)]
|
||||
|
||||
## render each view
|
||||
miltiview_images = []
|
||||
for yaw, pitch in zip(yaws, pitch):
|
||||
orig = torch.tensor([
|
||||
np.sin(yaw) * np.cos(pitch),
|
||||
np.cos(yaw) * np.cos(pitch),
|
||||
np.sin(pitch),
|
||||
]).float().cuda() * 2
|
||||
fov = torch.deg2rad(torch.tensor(30)).cuda()
|
||||
extrinsics = utils3d.torch.extrinsics_look_at(orig, torch.tensor([0, 0, 0]).float().cuda(), torch.tensor([0, 0, 1]).float().cuda())
|
||||
intrinsics = utils3d.torch.intrinsics_from_fov_xy(fov, fov)
|
||||
extrinsics = extrinsics.unsqueeze(0).expand(num_samples, -1, -1)
|
||||
intrinsics = intrinsics.unsqueeze(0).expand(num_samples, -1, -1)
|
||||
render_results = self._render_batch(reps, extrinsics, intrinsics)
|
||||
miltiview_images.append(render_results['color'])
|
||||
|
||||
## Concatenate views
|
||||
miltiview_images = torch.cat([
|
||||
torch.cat(miltiview_images[:2], dim=-2),
|
||||
torch.cat(miltiview_images[2:], dim=-2),
|
||||
], dim=-1)
|
||||
ret_dict.update({f'miltiview_image': {'value': miltiview_images, 'type': 'image'}})
|
||||
|
||||
self.renderer.rendering_options.bg_color = 'random'
|
||||
|
||||
return ret_dict
|
||||
@@ -0,0 +1,382 @@
|
||||
from typing import *
|
||||
import copy
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
import numpy as np
|
||||
from easydict import EasyDict as edict
|
||||
import utils3d.torch
|
||||
|
||||
from ..basic import BasicTrainer
|
||||
from ...representations import MeshExtractResult
|
||||
from ...renderers import MeshRenderer
|
||||
from ...modules.sparse import SparseTensor
|
||||
from ...utils.loss_utils import l1_loss, smooth_l1_loss, ssim, lpips
|
||||
from ...utils.data_utils import recursive_to_device
|
||||
|
||||
|
||||
class SLatVaeMeshDecoderTrainer(BasicTrainer):
|
||||
"""
|
||||
Trainer for structured latent VAE Mesh Decoder.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
loss_type (str): Loss type. Can be 'l1', 'l2'
|
||||
lambda_ssim (float): SSIM loss weight.
|
||||
lambda_lpips (float): LPIPS loss weight.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
depth_loss_type: str = 'l1',
|
||||
lambda_depth: int = 1,
|
||||
lambda_ssim: float = 0.2,
|
||||
lambda_lpips: float = 0.2,
|
||||
lambda_tsdf: float = 0.01,
|
||||
lambda_color: float = 0.1,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.depth_loss_type = depth_loss_type
|
||||
self.lambda_depth = lambda_depth
|
||||
self.lambda_ssim = lambda_ssim
|
||||
self.lambda_lpips = lambda_lpips
|
||||
self.lambda_tsdf = lambda_tsdf
|
||||
self.lambda_color = lambda_color
|
||||
self.use_color = self.lambda_color > 0
|
||||
|
||||
self._init_renderer()
|
||||
|
||||
def _init_renderer(self):
|
||||
rendering_options = {"near" : 1,
|
||||
"far" : 3}
|
||||
self.renderer = MeshRenderer(rendering_options, device=self.device)
|
||||
|
||||
def _render_batch(self, reps: List[MeshExtractResult], extrinsics: torch.Tensor, intrinsics: torch.Tensor,
|
||||
return_types=['mask', 'normal', 'depth']) -> Dict[str, torch.Tensor]:
|
||||
"""
|
||||
Render a batch of representations.
|
||||
|
||||
Args:
|
||||
reps: The dictionary of lists of representations.
|
||||
extrinsics: The [N x 4 x 4] tensor of extrinsics.
|
||||
intrinsics: The [N x 3 x 3] tensor of intrinsics.
|
||||
return_types: vary in ['mask', 'normal', 'depth', 'normal_map', 'color']
|
||||
|
||||
Returns:
|
||||
a dict with
|
||||
reg_loss : [N] tensor of regularization losses
|
||||
mask : [N x 1 x H x W] tensor of rendered masks
|
||||
normal : [N x 3 x H x W] tensor of rendered normals
|
||||
depth : [N x 1 x H x W] tensor of rendered depths
|
||||
"""
|
||||
ret = {k : [] for k in return_types}
|
||||
for i, rep in enumerate(reps):
|
||||
out_dict = self.renderer.render(rep, extrinsics[i], intrinsics[i], return_types=return_types)
|
||||
for k in out_dict:
|
||||
ret[k].append(out_dict[k][None] if k in ['mask', 'depth'] else out_dict[k])
|
||||
for k in ret:
|
||||
ret[k] = torch.stack(ret[k])
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def _tsdf_reg_loss(rep: MeshExtractResult, depth_map: torch.Tensor, extrinsics: torch.Tensor, intrinsics: torch.Tensor) -> torch.Tensor:
|
||||
# Calculate tsdf
|
||||
with torch.no_grad():
|
||||
# Project points to camera and calculate pseudo-sdf as difference between gt depth and projected depth
|
||||
projected_pts, pts_depth = utils3d.torch.project_cv(extrinsics=extrinsics, intrinsics=intrinsics, points=rep.tsdf_v)
|
||||
projected_pts = (projected_pts - 0.5) * 2.0
|
||||
depth_map_res = depth_map.shape[1]
|
||||
gt_depth = torch.nn.functional.grid_sample(depth_map.reshape(1, 1, depth_map_res, depth_map_res),
|
||||
projected_pts.reshape(1, 1, -1, 2), mode='bilinear', padding_mode='border', align_corners=True)
|
||||
pseudo_sdf = gt_depth.flatten() - pts_depth.flatten()
|
||||
# Truncate pseudo-sdf
|
||||
delta = 1 / rep.res * 3.0
|
||||
trunc_mask = pseudo_sdf > -delta
|
||||
|
||||
# Loss
|
||||
gt_tsdf = pseudo_sdf[trunc_mask]
|
||||
tsdf = rep.tsdf_s.flatten()[trunc_mask]
|
||||
gt_tsdf = torch.clamp(gt_tsdf, -delta, delta)
|
||||
return torch.mean((tsdf - gt_tsdf) ** 2)
|
||||
|
||||
def _calc_tsdf_loss(self, reps : list[MeshExtractResult], depth_maps, extrinsics, intrinsics) -> torch.Tensor:
|
||||
tsdf_loss = 0.0
|
||||
for i, rep in enumerate(reps):
|
||||
tsdf_loss += self._tsdf_reg_loss(rep, depth_maps[i], extrinsics[i], intrinsics[i])
|
||||
return tsdf_loss / len(reps)
|
||||
|
||||
@torch.no_grad()
|
||||
def _flip_normal(self, normal: torch.Tensor, extrinsics: torch.Tensor, intrinsics: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Flip normal to align with camera.
|
||||
"""
|
||||
normal = normal * 2.0 - 1.0
|
||||
R = torch.zeros_like(extrinsics)
|
||||
R[:, :3, :3] = extrinsics[:, :3, :3]
|
||||
R[:, 3, 3] = 1.0
|
||||
view_dir = utils3d.torch.unproject_cv(
|
||||
utils3d.torch.image_uv(*normal.shape[-2:], device=self.device).reshape(1, -1, 2),
|
||||
torch.ones(*normal.shape[-2:], device=self.device).reshape(1, -1),
|
||||
R, intrinsics
|
||||
).reshape(-1, *normal.shape[-2:], 3).permute(0, 3, 1, 2)
|
||||
unflip = (normal * view_dir).sum(1, keepdim=True) < 0
|
||||
normal *= unflip * 2.0 - 1.0
|
||||
return (normal + 1.0) / 2.0
|
||||
|
||||
def _perceptual_loss(self, gt: torch.Tensor, pred: torch.Tensor, name: str) -> Dict[str, torch.Tensor]:
|
||||
"""
|
||||
Combination of L1, SSIM, and LPIPS loss.
|
||||
"""
|
||||
if gt.shape[1] != 3:
|
||||
assert gt.shape[-1] == 3
|
||||
gt = gt.permute(0, 3, 1, 2)
|
||||
if pred.shape[1] != 3:
|
||||
assert pred.shape[-1] == 3
|
||||
pred = pred.permute(0, 3, 1, 2)
|
||||
terms = {
|
||||
f"{name}_loss" : l1_loss(gt, pred),
|
||||
f"{name}_loss_ssim" : 1 - ssim(gt, pred),
|
||||
f"{name}_loss_lpips" : lpips(gt, pred)
|
||||
}
|
||||
terms[f"{name}_loss_perceptual"] = terms[f"{name}_loss"] + terms[f"{name}_loss_ssim"] * self.lambda_ssim + terms[f"{name}_loss_lpips"] * self.lambda_lpips
|
||||
return terms
|
||||
|
||||
def geometry_losses(
|
||||
self,
|
||||
reps: List[MeshExtractResult],
|
||||
mesh: List[Dict],
|
||||
normal_map: torch.Tensor,
|
||||
extrinsics: torch.Tensor,
|
||||
intrinsics: torch.Tensor,
|
||||
):
|
||||
with torch.no_grad():
|
||||
gt_meshes = []
|
||||
for i in range(len(reps)):
|
||||
gt_mesh = MeshExtractResult(mesh[i]['vertices'].to(self.device), mesh[i]['faces'].to(self.device))
|
||||
gt_meshes.append(gt_mesh)
|
||||
target = self._render_batch(gt_meshes, extrinsics, intrinsics, return_types=['mask', 'depth', 'normal'])
|
||||
target['normal'] = self._flip_normal(target['normal'], extrinsics, intrinsics)
|
||||
|
||||
terms = edict(geo_loss = 0.0)
|
||||
if self.lambda_tsdf > 0:
|
||||
tsdf_loss = self._calc_tsdf_loss(reps, target['depth'], extrinsics, intrinsics)
|
||||
terms['tsdf_loss'] = tsdf_loss
|
||||
terms['geo_loss'] += tsdf_loss * self.lambda_tsdf
|
||||
|
||||
return_types = ['mask', 'depth', 'normal', 'normal_map'] if self.use_color else ['mask', 'depth', 'normal']
|
||||
buffer = self._render_batch(reps, extrinsics, intrinsics, return_types=return_types)
|
||||
|
||||
success_mask = torch.tensor([rep.success for rep in reps], device=self.device)
|
||||
if success_mask.sum() != 0:
|
||||
for k, v in buffer.items():
|
||||
buffer[k] = v[success_mask]
|
||||
for k, v in target.items():
|
||||
target[k] = v[success_mask]
|
||||
|
||||
terms['mask_loss'] = l1_loss(buffer['mask'], target['mask'])
|
||||
if self.depth_loss_type == 'l1':
|
||||
terms['depth_loss'] = l1_loss(buffer['depth'] * target['mask'], target['depth'] * target['mask'])
|
||||
elif self.depth_loss_type == 'smooth_l1':
|
||||
terms['depth_loss'] = smooth_l1_loss(buffer['depth'] * target['mask'], target['depth'] * target['mask'], beta=1.0 / (2 * reps[0].res))
|
||||
else:
|
||||
raise ValueError(f"Unsupported depth loss type: {self.depth_loss_type}")
|
||||
terms.update(self._perceptual_loss(buffer['normal'] * target['mask'], target['normal'] * target['mask'], 'normal'))
|
||||
terms['geo_loss'] = terms['geo_loss'] + terms['mask_loss'] + terms['depth_loss'] * self.lambda_depth + terms['normal_loss_perceptual']
|
||||
if self.use_color and normal_map is not None:
|
||||
terms.update(self._perceptual_loss(normal_map[success_mask], buffer['normal_map'], 'normal_map'))
|
||||
terms['geo_loss'] = terms['geo_loss'] + terms['normal_map_loss_perceptual'] * self.lambda_color
|
||||
|
||||
return terms
|
||||
|
||||
def color_losses(self, reps, image, alpha, extrinsics, intrinsics):
|
||||
terms = edict(color_loss = torch.tensor(0.0, device=self.device))
|
||||
buffer = self._render_batch(reps, extrinsics, intrinsics, return_types=['color'])
|
||||
success_mask = torch.tensor([rep.success for rep in reps], device=self.device)
|
||||
if success_mask.sum() != 0:
|
||||
terms.update(self._perceptual_loss(image * alpha[:, None][success_mask], buffer['color'][success_mask], 'color'))
|
||||
terms['color_loss'] = terms['color_loss'] + terms['color_loss_perceptual'] * self.lambda_color
|
||||
return terms
|
||||
|
||||
def training_losses(
|
||||
self,
|
||||
latents: SparseTensor,
|
||||
image: torch.Tensor,
|
||||
alpha: torch.Tensor,
|
||||
mesh: List[Dict],
|
||||
extrinsics: torch.Tensor,
|
||||
intrinsics: torch.Tensor,
|
||||
normal_map: torch.Tensor = None,
|
||||
) -> Tuple[Dict, Dict]:
|
||||
"""
|
||||
Compute training losses.
|
||||
|
||||
Args:
|
||||
latents: The [N x * x C] sparse latents
|
||||
image: The [N x 3 x H x W] tensor of images.
|
||||
alpha: The [N x H x W] tensor of alpha channels.
|
||||
mesh: The list of dictionaries of meshes.
|
||||
extrinsics: The [N x 4 x 4] tensor of extrinsics.
|
||||
intrinsics: The [N x 3 x 3] tensor of intrinsics.
|
||||
|
||||
Returns:
|
||||
a dict with the key "loss" containing a scalar tensor.
|
||||
may also contain other keys for different terms.
|
||||
"""
|
||||
reps = self.training_models['decoder'](latents)
|
||||
self.renderer.rendering_options.resolution = image.shape[-1]
|
||||
|
||||
terms = edict(loss = 0.0, rec = 0.0)
|
||||
|
||||
terms['reg_loss'] = sum([rep.reg_loss for rep in reps]) / len(reps)
|
||||
terms['loss'] = terms['loss'] + terms['reg_loss']
|
||||
|
||||
geo_terms = self.geometry_losses(reps, mesh, normal_map, extrinsics, intrinsics)
|
||||
terms.update(geo_terms)
|
||||
terms['loss'] = terms['loss'] + terms['geo_loss']
|
||||
|
||||
if self.use_color:
|
||||
color_terms = self.color_losses(reps, image, alpha, extrinsics, intrinsics)
|
||||
terms.update(color_terms)
|
||||
terms['loss'] = terms['loss'] + terms['color_loss']
|
||||
|
||||
return terms, {}
|
||||
|
||||
@torch.no_grad()
|
||||
def run_snapshot(
|
||||
self,
|
||||
num_samples: int,
|
||||
batch_size: int,
|
||||
verbose: bool = False,
|
||||
) -> Dict:
|
||||
dataloader = DataLoader(
|
||||
copy.deepcopy(self.dataset),
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
num_workers=0,
|
||||
collate_fn=self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else None,
|
||||
)
|
||||
|
||||
# inference
|
||||
ret_dict = {}
|
||||
gt_images = []
|
||||
gt_normal_maps = []
|
||||
gt_meshes = []
|
||||
exts = []
|
||||
ints = []
|
||||
reps = []
|
||||
for i in range(0, num_samples, batch_size):
|
||||
batch = min(batch_size, num_samples - i)
|
||||
data = next(iter(dataloader))
|
||||
args = recursive_to_device(data, 'cuda')
|
||||
gt_images.append(args['image'] * args['alpha'][:, None])
|
||||
if self.use_color and 'normal_map' in data:
|
||||
gt_normal_maps.append(args['normal_map'])
|
||||
gt_meshes.extend(args['mesh'])
|
||||
exts.append(args['extrinsics'])
|
||||
ints.append(args['intrinsics'])
|
||||
reps.extend(self.models['decoder'](args['latents']))
|
||||
gt_images = torch.cat(gt_images, dim=0)
|
||||
ret_dict.update({f'gt_image': {'value': gt_images, 'type': 'image'}})
|
||||
if self.use_color and gt_normal_maps:
|
||||
gt_normal_maps = torch.cat(gt_normal_maps, dim=0)
|
||||
ret_dict.update({f'gt_normal_map': {'value': gt_normal_maps, 'type': 'image'}})
|
||||
|
||||
# render single view
|
||||
exts = torch.cat(exts, dim=0)
|
||||
ints = torch.cat(ints, dim=0)
|
||||
self.renderer.rendering_options.bg_color = (0, 0, 0)
|
||||
self.renderer.rendering_options.resolution = gt_images.shape[-1]
|
||||
gt_render_results = self._render_batch([
|
||||
MeshExtractResult(vertices=mesh['vertices'].to(self.device), faces=mesh['faces'].to(self.device))
|
||||
for mesh in gt_meshes
|
||||
], exts, ints, return_types=['normal'])
|
||||
ret_dict.update({f'gt_normal': {'value': self._flip_normal(gt_render_results['normal'], exts, ints), 'type': 'image'}})
|
||||
return_types = ['normal']
|
||||
if self.use_color:
|
||||
return_types.append('color')
|
||||
if 'normal_map' in data:
|
||||
return_types.append('normal_map')
|
||||
render_results = self._render_batch(reps, exts, ints, return_types=return_types)
|
||||
ret_dict.update({f'rec_normal': {'value': render_results['normal'], 'type': 'image'}})
|
||||
if 'color' in return_types:
|
||||
ret_dict.update({f'rec_image': {'value': render_results['color'], 'type': 'image'}})
|
||||
if 'normal_map' in return_types:
|
||||
ret_dict.update({f'rec_normal_map': {'value': render_results['normal_map'], 'type': 'image'}})
|
||||
|
||||
# render multiview
|
||||
self.renderer.rendering_options.resolution = 512
|
||||
## Build camera
|
||||
yaws = [0, np.pi / 2, np.pi, 3 * np.pi / 2]
|
||||
yaws_offset = np.random.uniform(-np.pi / 4, np.pi / 4)
|
||||
yaws = [y + yaws_offset for y in yaws]
|
||||
pitch = [np.random.uniform(-np.pi / 4, np.pi / 4) for _ in range(4)]
|
||||
|
||||
## render each view
|
||||
multiview_normals = []
|
||||
multiview_normal_maps = []
|
||||
miltiview_images = []
|
||||
for yaw, pitch in zip(yaws, pitch):
|
||||
orig = torch.tensor([
|
||||
np.sin(yaw) * np.cos(pitch),
|
||||
np.cos(yaw) * np.cos(pitch),
|
||||
np.sin(pitch),
|
||||
]).float().cuda() * 2
|
||||
fov = torch.deg2rad(torch.tensor(30)).cuda()
|
||||
extrinsics = utils3d.torch.extrinsics_look_at(orig, torch.tensor([0, 0, 0]).float().cuda(), torch.tensor([0, 0, 1]).float().cuda())
|
||||
intrinsics = utils3d.torch.intrinsics_from_fov_xy(fov, fov)
|
||||
extrinsics = extrinsics.unsqueeze(0).expand(num_samples, -1, -1)
|
||||
intrinsics = intrinsics.unsqueeze(0).expand(num_samples, -1, -1)
|
||||
render_results = self._render_batch(reps, extrinsics, intrinsics, return_types=return_types)
|
||||
multiview_normals.append(render_results['normal'])
|
||||
if 'color' in return_types:
|
||||
miltiview_images.append(render_results['color'])
|
||||
if 'normal_map' in return_types:
|
||||
multiview_normal_maps.append(render_results['normal_map'])
|
||||
|
||||
## Concatenate views
|
||||
multiview_normals = torch.cat([
|
||||
torch.cat(multiview_normals[:2], dim=-2),
|
||||
torch.cat(multiview_normals[2:], dim=-2),
|
||||
], dim=-1)
|
||||
ret_dict.update({f'multiview_normal': {'value': multiview_normals, 'type': 'image'}})
|
||||
if 'color' in return_types:
|
||||
miltiview_images = torch.cat([
|
||||
torch.cat(miltiview_images[:2], dim=-2),
|
||||
torch.cat(miltiview_images[2:], dim=-2),
|
||||
], dim=-1)
|
||||
ret_dict.update({f'multiview_image': {'value': miltiview_images, 'type': 'image'}})
|
||||
if 'normal_map' in return_types:
|
||||
multiview_normal_maps = torch.cat([
|
||||
torch.cat(multiview_normal_maps[:2], dim=-2),
|
||||
torch.cat(multiview_normal_maps[2:], dim=-2),
|
||||
], dim=-1)
|
||||
ret_dict.update({f'multiview_normal_map': {'value': multiview_normal_maps, 'type': 'image'}})
|
||||
|
||||
return ret_dict
|
||||
@@ -0,0 +1,223 @@
|
||||
from typing import *
|
||||
import copy
|
||||
import torch
|
||||
from torch.utils.data import DataLoader
|
||||
import numpy as np
|
||||
from easydict import EasyDict as edict
|
||||
import utils3d.torch
|
||||
|
||||
from ..basic import BasicTrainer
|
||||
from ...representations import Strivec
|
||||
from ...renderers import OctreeRenderer
|
||||
from ...modules.sparse import SparseTensor
|
||||
from ...utils.loss_utils import l1_loss, l2_loss, ssim, lpips
|
||||
|
||||
|
||||
class SLatVaeRadianceFieldDecoderTrainer(BasicTrainer):
|
||||
"""
|
||||
Trainer for structured latent VAE Radiance Field Decoder.
|
||||
|
||||
Args:
|
||||
models (dict[str, nn.Module]): Models to train.
|
||||
dataset (torch.utils.data.Dataset): Dataset.
|
||||
output_dir (str): Output directory.
|
||||
load_dir (str): Load directory.
|
||||
step (int): Step to load.
|
||||
batch_size (int): Batch size.
|
||||
batch_size_per_gpu (int): Batch size per GPU. If specified, batch_size will be ignored.
|
||||
batch_split (int): Split batch with gradient accumulation.
|
||||
max_steps (int): Max steps.
|
||||
optimizer (dict): Optimizer config.
|
||||
lr_scheduler (dict): Learning rate scheduler config.
|
||||
elastic (dict): Elastic memory management config.
|
||||
grad_clip (float or dict): Gradient clip config.
|
||||
ema_rate (float or list): Exponential moving average rates.
|
||||
fp16_mode (str): FP16 mode.
|
||||
- None: No FP16.
|
||||
- 'inflat_all': Hold a inflated fp32 master param for all params.
|
||||
- 'amp': Automatic mixed precision.
|
||||
fp16_scale_growth (float): Scale growth for FP16 gradient backpropagation.
|
||||
finetune_ckpt (dict): Finetune checkpoint.
|
||||
log_param_stats (bool): Log parameter stats.
|
||||
i_print (int): Print interval.
|
||||
i_log (int): Log interval.
|
||||
i_sample (int): Sample interval.
|
||||
i_save (int): Save interval.
|
||||
i_ddpcheck (int): DDP check interval.
|
||||
|
||||
loss_type (str): Loss type. Can be 'l1', 'l2'
|
||||
lambda_ssim (float): SSIM loss weight.
|
||||
lambda_lpips (float): LPIPS loss weight.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
loss_type: str = 'l1',
|
||||
lambda_ssim: float = 0.2,
|
||||
lambda_lpips: float = 0.2,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.loss_type = loss_type
|
||||
self.lambda_ssim = lambda_ssim
|
||||
self.lambda_lpips = lambda_lpips
|
||||
|
||||
self._init_renderer()
|
||||
|
||||
def _init_renderer(self):
|
||||
rendering_options = {"near" : 0.8,
|
||||
"far" : 1.6,
|
||||
"bg_color" : 'random'}
|
||||
self.renderer = OctreeRenderer(rendering_options)
|
||||
self.renderer.pipe.primitive = 'trivec'
|
||||
|
||||
def _render_batch(self, reps: List[Strivec], extrinsics: torch.Tensor, intrinsics: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Render a batch of representations.
|
||||
|
||||
Args:
|
||||
reps: The dictionary of lists of representations.
|
||||
extrinsics: The [N x 4 x 4] tensor of extrinsics.
|
||||
intrinsics: The [N x 3 x 3] tensor of intrinsics.
|
||||
"""
|
||||
ret = None
|
||||
for i, representation in enumerate(reps):
|
||||
render_pack = self.renderer.render(representation, extrinsics[i], intrinsics[i])
|
||||
if ret is None:
|
||||
ret = {k: [] for k in list(render_pack.keys()) + ['bg_color']}
|
||||
for k, v in render_pack.items():
|
||||
ret[k].append(v)
|
||||
ret['bg_color'].append(self.renderer.bg_color)
|
||||
for k, v in ret.items():
|
||||
ret[k] = torch.stack(v, dim=0)
|
||||
return ret
|
||||
|
||||
def training_losses(
|
||||
self,
|
||||
latents: SparseTensor,
|
||||
image: torch.Tensor,
|
||||
alpha: torch.Tensor,
|
||||
extrinsics: torch.Tensor,
|
||||
intrinsics: torch.Tensor,
|
||||
return_aux: bool = False,
|
||||
**kwargs
|
||||
) -> Tuple[Dict, Dict]:
|
||||
"""
|
||||
Compute training losses.
|
||||
|
||||
Args:
|
||||
latents: The [N x * x C] sparse latents
|
||||
image: The [N x 3 x H x W] tensor of images.
|
||||
alpha: The [N x H x W] tensor of alpha channels.
|
||||
extrinsics: The [N x 4 x 4] tensor of extrinsics.
|
||||
intrinsics: The [N x 3 x 3] tensor of intrinsics.
|
||||
return_aux: Whether to return auxiliary information.
|
||||
|
||||
Returns:
|
||||
a dict with the key "loss" containing a scalar tensor.
|
||||
may also contain other keys for different terms.
|
||||
"""
|
||||
reps = self.training_models['decoder'](latents)
|
||||
self.renderer.rendering_options.resolution = image.shape[-1]
|
||||
render_results = self._render_batch(reps, extrinsics, intrinsics)
|
||||
|
||||
terms = edict(loss = 0.0, rec = 0.0)
|
||||
|
||||
rec_image = render_results['color']
|
||||
gt_image = image * alpha[:, None] + (1 - alpha[:, None]) * render_results['bg_color'][..., None, None]
|
||||
|
||||
if self.loss_type == 'l1':
|
||||
terms["l1"] = l1_loss(rec_image, gt_image)
|
||||
terms["rec"] = terms["rec"] + terms["l1"]
|
||||
elif self.loss_type == 'l2':
|
||||
terms["l2"] = l2_loss(rec_image, gt_image)
|
||||
terms["rec"] = terms["rec"] + terms["l2"]
|
||||
else:
|
||||
raise ValueError(f"Invalid loss type: {self.loss_type}")
|
||||
if self.lambda_ssim > 0:
|
||||
terms["ssim"] = 1 - ssim(rec_image, gt_image)
|
||||
terms["rec"] = terms["rec"] + self.lambda_ssim * terms["ssim"]
|
||||
if self.lambda_lpips > 0:
|
||||
terms["lpips"] = lpips(rec_image, gt_image)
|
||||
terms["rec"] = terms["rec"] + self.lambda_lpips * terms["lpips"]
|
||||
terms["loss"] = terms["loss"] + terms["rec"]
|
||||
|
||||
if return_aux:
|
||||
return terms, {}, {'rec_image': rec_image, 'gt_image': gt_image}
|
||||
return terms, {}
|
||||
|
||||
@torch.no_grad()
|
||||
def run_snapshot(
|
||||
self,
|
||||
num_samples: int,
|
||||
batch_size: int,
|
||||
verbose: bool = False,
|
||||
) -> Dict:
|
||||
dataloader = DataLoader(
|
||||
copy.deepcopy(self.dataset),
|
||||
batch_size=batch_size,
|
||||
shuffle=True,
|
||||
num_workers=0,
|
||||
collate_fn=self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else None,
|
||||
)
|
||||
|
||||
# inference
|
||||
ret_dict = {}
|
||||
gt_images = []
|
||||
exts = []
|
||||
ints = []
|
||||
reps = []
|
||||
for i in range(0, num_samples, batch_size):
|
||||
batch = min(batch_size, num_samples - i)
|
||||
data = next(iter(dataloader))
|
||||
args = {k: v[:batch].cuda() for k, v in data.items()}
|
||||
gt_images.append(args['image'] * args['alpha'][:, None])
|
||||
exts.append(args['extrinsics'])
|
||||
ints.append(args['intrinsics'])
|
||||
reps.extend(self.models['decoder'](args['latents']))
|
||||
gt_images = torch.cat(gt_images, dim=0)
|
||||
ret_dict.update({f'gt_image': {'value': gt_images, 'type': 'image'}})
|
||||
|
||||
# render single view
|
||||
exts = torch.cat(exts, dim=0)
|
||||
ints = torch.cat(ints, dim=0)
|
||||
self.renderer.rendering_options.bg_color = (0, 0, 0)
|
||||
self.renderer.rendering_options.resolution = gt_images.shape[-1]
|
||||
render_results = self._render_batch(reps, exts, ints)
|
||||
ret_dict.update({f'rec_image': {'value': render_results['color'], 'type': 'image'}})
|
||||
|
||||
# render multiview
|
||||
self.renderer.rendering_options.resolution = 512
|
||||
## Build camera
|
||||
yaws = [0, np.pi / 2, np.pi, 3 * np.pi / 2]
|
||||
yaws_offset = np.random.uniform(-np.pi / 4, np.pi / 4)
|
||||
yaws = [y + yaws_offset for y in yaws]
|
||||
pitch = [np.random.uniform(-np.pi / 4, np.pi / 4) for _ in range(4)]
|
||||
|
||||
## render each view
|
||||
miltiview_images = []
|
||||
for yaw, pitch in zip(yaws, pitch):
|
||||
orig = torch.tensor([
|
||||
np.sin(yaw) * np.cos(pitch),
|
||||
np.cos(yaw) * np.cos(pitch),
|
||||
np.sin(pitch),
|
||||
]).float().cuda() * 2
|
||||
fov = torch.deg2rad(torch.tensor(30)).cuda()
|
||||
extrinsics = utils3d.torch.extrinsics_look_at(orig, torch.tensor([0, 0, 0]).float().cuda(), torch.tensor([0, 0, 1]).float().cuda())
|
||||
intrinsics = utils3d.torch.intrinsics_from_fov_xy(fov, fov)
|
||||
extrinsics = extrinsics.unsqueeze(0).expand(num_samples, -1, -1)
|
||||
intrinsics = intrinsics.unsqueeze(0).expand(num_samples, -1, -1)
|
||||
render_results = self._render_batch(reps, extrinsics, intrinsics)
|
||||
miltiview_images.append(render_results['color'])
|
||||
|
||||
## Concatenate views
|
||||
miltiview_images = torch.cat([
|
||||
torch.cat(miltiview_images[:2], dim=-2),
|
||||
torch.cat(miltiview_images[2:], dim=-2),
|
||||
], dim=-1)
|
||||
ret_dict.update({f'miltiview_image': {'value': miltiview_images, 'type': 'image'}})
|
||||
|
||||
self.renderer.rendering_options.bg_color = 'random'
|
||||
|
||||
return ret_dict
|
||||
@@ -0,0 +1,226 @@
|
||||
from typing import *
|
||||
import math
|
||||
import torch
|
||||
import numpy as np
|
||||
from torch.utils.data import Sampler, Dataset, DataLoader, DistributedSampler
|
||||
import torch.distributed as dist
|
||||
|
||||
|
||||
def recursive_to_device(
|
||||
data: Any,
|
||||
device: torch.device,
|
||||
non_blocking: bool = False,
|
||||
) -> Any:
|
||||
"""
|
||||
Recursively move all tensors in a data structure to a device.
|
||||
"""
|
||||
if hasattr(data, "to"):
|
||||
return data.to(device, non_blocking=non_blocking)
|
||||
elif isinstance(data, (list, tuple)):
|
||||
return type(data)(recursive_to_device(d, device, non_blocking) for d in data)
|
||||
elif isinstance(data, dict):
|
||||
return {k: recursive_to_device(v, device, non_blocking) for k, v in data.items()}
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
def load_balanced_group_indices(
|
||||
load: List[int],
|
||||
num_groups: int,
|
||||
equal_size: bool = False,
|
||||
) -> List[List[int]]:
|
||||
"""
|
||||
Split indices into groups with balanced load.
|
||||
"""
|
||||
if equal_size:
|
||||
group_size = len(load) // num_groups
|
||||
indices = np.argsort(load)[::-1]
|
||||
groups = [[] for _ in range(num_groups)]
|
||||
group_load = np.zeros(num_groups)
|
||||
for idx in indices:
|
||||
min_group_idx = np.argmin(group_load)
|
||||
groups[min_group_idx].append(idx)
|
||||
if equal_size and len(groups[min_group_idx]) == group_size:
|
||||
group_load[min_group_idx] = float('inf')
|
||||
else:
|
||||
group_load[min_group_idx] += load[idx]
|
||||
return groups
|
||||
|
||||
|
||||
def cycle(data_loader: DataLoader) -> Iterator:
|
||||
while True:
|
||||
for data in data_loader:
|
||||
if isinstance(data_loader.sampler, ResumableSampler):
|
||||
data_loader.sampler.idx += data_loader.batch_size # type: ignore[attr-defined]
|
||||
yield data
|
||||
if isinstance(data_loader.sampler, DistributedSampler):
|
||||
data_loader.sampler.epoch += 1
|
||||
if isinstance(data_loader.sampler, ResumableSampler):
|
||||
data_loader.sampler.epoch += 1
|
||||
data_loader.sampler.idx = 0
|
||||
|
||||
|
||||
class ResumableSampler(Sampler):
|
||||
"""
|
||||
Distributed sampler that is resumable.
|
||||
|
||||
Args:
|
||||
dataset: Dataset used for sampling.
|
||||
rank (int, optional): Rank of the current process within :attr:`num_replicas`.
|
||||
By default, :attr:`rank` is retrieved from the current distributed
|
||||
group.
|
||||
shuffle (bool, optional): If ``True`` (default), sampler will shuffle the
|
||||
indices.
|
||||
seed (int, optional): random seed used to shuffle the sampler if
|
||||
:attr:`shuffle=True`. This number should be identical across all
|
||||
processes in the distributed group. Default: ``0``.
|
||||
drop_last (bool, optional): if ``True``, then the sampler will drop the
|
||||
tail of the data to make it evenly divisible across the number of
|
||||
replicas. If ``False``, the sampler will add extra indices to make
|
||||
the data evenly divisible across the replicas. Default: ``False``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dataset: Dataset,
|
||||
shuffle: bool = True,
|
||||
seed: int = 0,
|
||||
drop_last: bool = False,
|
||||
) -> None:
|
||||
self.dataset = dataset
|
||||
self.epoch = 0
|
||||
self.idx = 0
|
||||
self.drop_last = drop_last
|
||||
self.world_size = dist.get_world_size() if dist.is_initialized() else 1
|
||||
self.rank = dist.get_rank() if dist.is_initialized() else 0
|
||||
# If the dataset length is evenly divisible by # of replicas, then there
|
||||
# is no need to drop any data, since the dataset will be split equally.
|
||||
if self.drop_last and len(self.dataset) % self.world_size != 0: # type: ignore[arg-type]
|
||||
# Split to nearest available length that is evenly divisible.
|
||||
# This is to ensure each rank receives the same amount of data when
|
||||
# using this Sampler.
|
||||
self.num_samples = math.ceil(
|
||||
(len(self.dataset) - self.world_size) / self.world_size # type: ignore[arg-type]
|
||||
)
|
||||
else:
|
||||
self.num_samples = math.ceil(len(self.dataset) / self.world_size) # type: ignore[arg-type]
|
||||
self.total_size = self.num_samples * self.world_size
|
||||
self.shuffle = shuffle
|
||||
self.seed = seed
|
||||
|
||||
def __iter__(self) -> Iterator:
|
||||
if self.shuffle:
|
||||
# deterministically shuffle based on epoch and seed
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.seed + self.epoch)
|
||||
indices = torch.randperm(len(self.dataset), generator=g).tolist() # type: ignore[arg-type]
|
||||
else:
|
||||
indices = list(range(len(self.dataset))) # type: ignore[arg-type]
|
||||
|
||||
if not self.drop_last:
|
||||
# add extra samples to make it evenly divisible
|
||||
padding_size = self.total_size - len(indices)
|
||||
if padding_size <= len(indices):
|
||||
indices += indices[:padding_size]
|
||||
else:
|
||||
indices += (indices * math.ceil(padding_size / len(indices)))[
|
||||
:padding_size
|
||||
]
|
||||
else:
|
||||
# remove tail of data to make it evenly divisible.
|
||||
indices = indices[: self.total_size]
|
||||
assert len(indices) == self.total_size
|
||||
|
||||
# subsample
|
||||
indices = indices[self.rank : self.total_size : self.world_size]
|
||||
|
||||
# resume from previous state
|
||||
indices = indices[self.idx:]
|
||||
|
||||
return iter(indices)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self.num_samples
|
||||
|
||||
def state_dict(self) -> dict[str, int]:
|
||||
return {
|
||||
'epoch': self.epoch,
|
||||
'idx': self.idx,
|
||||
}
|
||||
|
||||
def load_state_dict(self, state_dict):
|
||||
self.epoch = state_dict['epoch']
|
||||
self.idx = state_dict['idx']
|
||||
|
||||
|
||||
class BalancedResumableSampler(ResumableSampler):
|
||||
"""
|
||||
Distributed sampler that is resumable and balances the load among the processes.
|
||||
|
||||
Args:
|
||||
dataset: Dataset used for sampling.
|
||||
rank (int, optional): Rank of the current process within :attr:`num_replicas`.
|
||||
By default, :attr:`rank` is retrieved from the current distributed
|
||||
group.
|
||||
shuffle (bool, optional): If ``True`` (default), sampler will shuffle the
|
||||
indices.
|
||||
seed (int, optional): random seed used to shuffle the sampler if
|
||||
:attr:`shuffle=True`. This number should be identical across all
|
||||
processes in the distributed group. Default: ``0``.
|
||||
drop_last (bool, optional): if ``True``, then the sampler will drop the
|
||||
tail of the data to make it evenly divisible across the number of
|
||||
replicas. If ``False``, the sampler will add extra indices to make
|
||||
the data evenly divisible across the replicas. Default: ``False``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dataset: Dataset,
|
||||
shuffle: bool = True,
|
||||
seed: int = 0,
|
||||
drop_last: bool = False,
|
||||
batch_size: int = 1,
|
||||
) -> None:
|
||||
assert hasattr(dataset, 'loads'), 'Dataset must have "loads" attribute to use BalancedResumableSampler'
|
||||
super().__init__(dataset, shuffle, seed, drop_last)
|
||||
self.batch_size = batch_size
|
||||
self.loads = dataset.loads
|
||||
|
||||
def __iter__(self) -> Iterator:
|
||||
if self.shuffle:
|
||||
# deterministically shuffle based on epoch and seed
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.seed + self.epoch)
|
||||
indices = torch.randperm(len(self.dataset), generator=g).tolist() # type: ignore[arg-type]
|
||||
else:
|
||||
indices = list(range(len(self.dataset))) # type: ignore[arg-type]
|
||||
|
||||
if not self.drop_last:
|
||||
# add extra samples to make it evenly divisible
|
||||
padding_size = self.total_size - len(indices)
|
||||
if padding_size <= len(indices):
|
||||
indices += indices[:padding_size]
|
||||
else:
|
||||
indices += (indices * math.ceil(padding_size / len(indices)))[
|
||||
:padding_size
|
||||
]
|
||||
else:
|
||||
# remove tail of data to make it evenly divisible.
|
||||
indices = indices[: self.total_size]
|
||||
assert len(indices) == self.total_size
|
||||
|
||||
# balance load among processes
|
||||
num_batches = len(indices) // (self.batch_size * self.world_size)
|
||||
balanced_indices = []
|
||||
for i in range(num_batches):
|
||||
start_idx = i * self.batch_size * self.world_size
|
||||
end_idx = (i + 1) * self.batch_size * self.world_size
|
||||
batch_indices = indices[start_idx:end_idx]
|
||||
batch_loads = [self.loads[idx] for idx in batch_indices]
|
||||
groups = load_balanced_group_indices(batch_loads, self.world_size, equal_size=True)
|
||||
balanced_indices.extend([batch_indices[j] for j in groups[self.rank]])
|
||||
|
||||
# resume from previous state
|
||||
indices = balanced_indices[self.idx:]
|
||||
|
||||
return iter(indices)
|
||||
@@ -0,0 +1,93 @@
|
||||
import os
|
||||
import io
|
||||
from contextlib import contextmanager
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from torch.nn.parallel import DistributedDataParallel as DDP
|
||||
|
||||
|
||||
def setup_dist(rank, local_rank, world_size, master_addr, master_port):
|
||||
os.environ['MASTER_ADDR'] = master_addr
|
||||
os.environ['MASTER_PORT'] = master_port
|
||||
os.environ['WORLD_SIZE'] = str(world_size)
|
||||
os.environ['RANK'] = str(rank)
|
||||
os.environ['LOCAL_RANK'] = str(local_rank)
|
||||
torch.cuda.set_device(local_rank)
|
||||
dist.init_process_group('nccl', rank=rank, world_size=world_size)
|
||||
|
||||
|
||||
def read_file_dist(path):
|
||||
"""
|
||||
Read the binary file distributedly.
|
||||
File is only read once by the rank 0 process and broadcasted to other processes.
|
||||
|
||||
Returns:
|
||||
data (io.BytesIO): The binary data read from the file.
|
||||
"""
|
||||
if dist.is_initialized() and dist.get_world_size() > 1:
|
||||
# read file
|
||||
size = torch.LongTensor(1).cuda()
|
||||
if dist.get_rank() == 0:
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
data = torch.ByteTensor(
|
||||
torch.UntypedStorage.from_buffer(data, dtype=torch.uint8)
|
||||
).cuda()
|
||||
size[0] = data.shape[0]
|
||||
# broadcast size
|
||||
dist.broadcast(size, src=0)
|
||||
if dist.get_rank() != 0:
|
||||
data = torch.ByteTensor(size[0].item()).cuda()
|
||||
# broadcast data
|
||||
dist.broadcast(data, src=0)
|
||||
# convert to io.BytesIO
|
||||
data = data.cpu().numpy().tobytes()
|
||||
data = io.BytesIO(data)
|
||||
return data
|
||||
else:
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
data = io.BytesIO(data)
|
||||
return data
|
||||
|
||||
|
||||
def unwrap_dist(model):
|
||||
"""
|
||||
Unwrap the model from distributed training.
|
||||
"""
|
||||
if isinstance(model, DDP):
|
||||
return model.module
|
||||
return model
|
||||
|
||||
|
||||
@contextmanager
|
||||
def master_first():
|
||||
"""
|
||||
A context manager that ensures master process executes first.
|
||||
"""
|
||||
if not dist.is_initialized():
|
||||
yield
|
||||
else:
|
||||
if dist.get_rank() == 0:
|
||||
yield
|
||||
dist.barrier()
|
||||
else:
|
||||
dist.barrier()
|
||||
yield
|
||||
|
||||
|
||||
@contextmanager
|
||||
def local_master_first():
|
||||
"""
|
||||
A context manager that ensures local master process executes first.
|
||||
"""
|
||||
if not dist.is_initialized():
|
||||
yield
|
||||
else:
|
||||
if dist.get_rank() % torch.cuda.device_count() == 0:
|
||||
yield
|
||||
dist.barrier()
|
||||
else:
|
||||
dist.barrier()
|
||||
yield
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
from abc import abstractmethod
|
||||
from contextlib import contextmanager
|
||||
from typing import Tuple
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
|
||||
|
||||
class MemoryController:
|
||||
"""
|
||||
Base class for memory management during training.
|
||||
"""
|
||||
|
||||
_last_input_size = None
|
||||
_last_mem_ratio = []
|
||||
|
||||
@contextmanager
|
||||
def record(self):
|
||||
pass
|
||||
|
||||
def update_run_states(self, input_size=None, mem_ratio=None):
|
||||
if self._last_input_size is None:
|
||||
self._last_input_size = input_size
|
||||
elif self._last_input_size!= input_size:
|
||||
raise ValueError(f'Input size should not change for different ElasticModules.')
|
||||
self._last_mem_ratio.append(mem_ratio)
|
||||
|
||||
@abstractmethod
|
||||
def get_mem_ratio(self, input_size):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def state_dict(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def log(self):
|
||||
pass
|
||||
|
||||
|
||||
class LinearMemoryController(MemoryController):
|
||||
"""
|
||||
A simple controller for memory management during training.
|
||||
The memory usage is modeled as a linear function of:
|
||||
- the number of input parameters
|
||||
- the ratio of memory the model use compared to the maximum usage (with no checkpointing)
|
||||
memory_usage = k * input_size * mem_ratio + b
|
||||
The controller keeps track of the memory usage and gives the
|
||||
expected memory ratio to keep the memory usage under a target
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
buffer_size=1000,
|
||||
update_every=500,
|
||||
target_ratio=0.8,
|
||||
available_memory=None,
|
||||
max_mem_ratio_start=0.1,
|
||||
params=None,
|
||||
device=None
|
||||
):
|
||||
self.buffer_size = buffer_size
|
||||
self.update_every = update_every
|
||||
self.target_ratio = target_ratio
|
||||
self.device = device or torch.cuda.current_device()
|
||||
self.available_memory = available_memory or torch.cuda.get_device_properties(self.device).total_memory / 1024**3
|
||||
|
||||
self._memory = np.zeros(buffer_size, dtype=np.float32)
|
||||
self._input_size = np.zeros(buffer_size, dtype=np.float32)
|
||||
self._mem_ratio = np.zeros(buffer_size, dtype=np.float32)
|
||||
self._buffer_ptr = 0
|
||||
self._buffer_length = 0
|
||||
self._params = tuple(params) if params is not None else (0.0, 0.0)
|
||||
self._max_mem_ratio = max_mem_ratio_start
|
||||
self.step = 0
|
||||
|
||||
def __repr__(self):
|
||||
return f'LinearMemoryController(target_ratio={self.target_ratio}, available_memory={self.available_memory})'
|
||||
|
||||
def _add_sample(self, memory, input_size, mem_ratio):
|
||||
self._memory[self._buffer_ptr] = memory
|
||||
self._input_size[self._buffer_ptr] = input_size
|
||||
self._mem_ratio[self._buffer_ptr] = mem_ratio
|
||||
self._buffer_ptr = (self._buffer_ptr + 1) % self.buffer_size
|
||||
self._buffer_length = min(self._buffer_length + 1, self.buffer_size)
|
||||
|
||||
@contextmanager
|
||||
def record(self):
|
||||
torch.cuda.reset_peak_memory_stats(self.device)
|
||||
self._last_input_size = None
|
||||
self._last_mem_ratio = []
|
||||
yield
|
||||
self._last_memory = torch.cuda.max_memory_allocated(self.device) / 1024**3
|
||||
self._last_mem_ratio = sum(self._last_mem_ratio) / len(self._last_mem_ratio)
|
||||
self._add_sample(self._last_memory, self._last_input_size, self._last_mem_ratio)
|
||||
self.step += 1
|
||||
if self.step % self.update_every == 0:
|
||||
self._max_mem_ratio = min(1.0, self._max_mem_ratio + 0.1)
|
||||
self._fit_params()
|
||||
|
||||
def _fit_params(self):
|
||||
memory_usage = self._memory[:self._buffer_length]
|
||||
input_size = self._input_size[:self._buffer_length]
|
||||
mem_ratio = self._mem_ratio[:self._buffer_length]
|
||||
|
||||
x = input_size * mem_ratio
|
||||
y = memory_usage
|
||||
k, b = np.polyfit(x, y, 1)
|
||||
self._params = (k, b)
|
||||
# self._visualize()
|
||||
|
||||
def _visualize(self):
|
||||
import matplotlib.pyplot as plt
|
||||
memory_usage = self._memory[:self._buffer_length]
|
||||
input_size = self._input_size[:self._buffer_length]
|
||||
mem_ratio = self._mem_ratio[:self._buffer_length]
|
||||
k, b = self._params
|
||||
|
||||
plt.scatter(input_size * mem_ratio, memory_usage, c=mem_ratio, cmap='viridis')
|
||||
x = np.array([0.0, 20000.0])
|
||||
plt.plot(x, k * x + b, c='r')
|
||||
plt.savefig(f'linear_memory_controller_{self.step}.png')
|
||||
plt.cla()
|
||||
|
||||
def get_mem_ratio(self, input_size):
|
||||
k, b = self._params
|
||||
if k == 0: return np.random.rand() * self._max_mem_ratio
|
||||
pred = (self.available_memory * self.target_ratio - b) / (k * input_size)
|
||||
return min(self._max_mem_ratio, max(0.0, pred))
|
||||
|
||||
def state_dict(self):
|
||||
return {
|
||||
'params': self._params,
|
||||
}
|
||||
|
||||
def load_state_dict(self, state_dict):
|
||||
self._params = tuple(state_dict['params'])
|
||||
|
||||
def log(self):
|
||||
return {
|
||||
'params/k': self._params[0],
|
||||
'params/b': self._params[1],
|
||||
'memory': self._last_memory,
|
||||
'input_size': self._last_input_size,
|
||||
'mem_ratio': self._last_mem_ratio,
|
||||
}
|
||||
|
||||
|
||||
class ElasticModule(nn.Module):
|
||||
"""
|
||||
Module for training with elastic memory management.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._memory_controller: MemoryController = None
|
||||
|
||||
@abstractmethod
|
||||
def _get_input_size(self, *args, **kwargs) -> int:
|
||||
"""
|
||||
Get the size of the input data.
|
||||
|
||||
Returns:
|
||||
int: The size of the input data.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _forward_with_mem_ratio(self, *args, mem_ratio=0.0, **kwargs) -> Tuple[float, Tuple]:
|
||||
"""
|
||||
Forward with a given memory ratio.
|
||||
"""
|
||||
pass
|
||||
|
||||
def register_memory_controller(self, memory_controller: MemoryController):
|
||||
self._memory_controller = memory_controller
|
||||
|
||||
def forward(self, *args, **kwargs):
|
||||
if self._memory_controller is None or not torch.is_grad_enabled() or not self.training:
|
||||
_, ret = self._forward_with_mem_ratio(*args, **kwargs)
|
||||
else:
|
||||
input_size = self._get_input_size(*args, **kwargs)
|
||||
mem_ratio = self._memory_controller.get_mem_ratio(input_size)
|
||||
mem_ratio, ret = self._forward_with_mem_ratio(*args, mem_ratio=mem_ratio, **kwargs)
|
||||
self._memory_controller.update_run_states(input_size, mem_ratio)
|
||||
return ret
|
||||
|
||||
|
||||
class ElasticModuleMixin:
|
||||
"""
|
||||
Mixin for training with elastic memory management.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._memory_controller: MemoryController = None
|
||||
|
||||
@abstractmethod
|
||||
def _get_input_size(self, *args, **kwargs) -> int:
|
||||
"""
|
||||
Get the size of the input data.
|
||||
|
||||
Returns:
|
||||
int: The size of the input data.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@contextmanager
|
||||
def with_mem_ratio(self, mem_ratio=1.0) -> float:
|
||||
"""
|
||||
Context manager for training with a reduced memory ratio compared to the full memory usage.
|
||||
|
||||
Returns:
|
||||
float: The exact memory ratio used during the forward pass.
|
||||
"""
|
||||
pass
|
||||
|
||||
def register_memory_controller(self, memory_controller: MemoryController):
|
||||
self._memory_controller = memory_controller
|
||||
|
||||
def forward(self, *args, **kwargs):
|
||||
if self._memory_controller is None or not torch.is_grad_enabled() or not self.training:
|
||||
ret = super().forward(*args, **kwargs)
|
||||
else:
|
||||
input_size = self._get_input_size(*args, **kwargs)
|
||||
mem_ratio = self._memory_controller.get_mem_ratio(input_size)
|
||||
with self.with_mem_ratio(mem_ratio) as exact_mem_ratio:
|
||||
ret = super().forward(*args, **kwargs)
|
||||
self._memory_controller.update_run_states(input_size, exact_mem_ratio)
|
||||
return ret
|
||||
@@ -0,0 +1,81 @@
|
||||
from typing import *
|
||||
import torch
|
||||
import numpy as np
|
||||
import torch.utils
|
||||
|
||||
|
||||
class AdaptiveGradClipper:
|
||||
"""
|
||||
Adaptive gradient clipping for training.
|
||||
"""
|
||||
def __init__(
|
||||
self,
|
||||
max_norm=None,
|
||||
clip_percentile=95.0,
|
||||
buffer_size=1000,
|
||||
):
|
||||
self.max_norm = max_norm
|
||||
self.clip_percentile = clip_percentile
|
||||
self.buffer_size = buffer_size
|
||||
|
||||
self._grad_norm = np.zeros(buffer_size, dtype=np.float32)
|
||||
self._max_norm = max_norm
|
||||
self._buffer_ptr = 0
|
||||
self._buffer_length = 0
|
||||
|
||||
def __repr__(self):
|
||||
return f'AdaptiveGradClipper(max_norm={self.max_norm}, clip_percentile={self.clip_percentile})'
|
||||
|
||||
def state_dict(self):
|
||||
return {
|
||||
'grad_norm': self._grad_norm,
|
||||
'max_norm': self._max_norm,
|
||||
'buffer_ptr': self._buffer_ptr,
|
||||
'buffer_length': self._buffer_length,
|
||||
}
|
||||
|
||||
def load_state_dict(self, state_dict):
|
||||
self._grad_norm = state_dict['grad_norm']
|
||||
self._max_norm = state_dict['max_norm']
|
||||
self._buffer_ptr = state_dict['buffer_ptr']
|
||||
self._buffer_length = state_dict['buffer_length']
|
||||
|
||||
def log(self):
|
||||
return {
|
||||
'max_norm': self._max_norm,
|
||||
}
|
||||
|
||||
def __call__(self, parameters, norm_type=2.0, error_if_nonfinite=False, foreach=None):
|
||||
"""Clip the gradient norm of an iterable of parameters.
|
||||
|
||||
The norm is computed over all gradients together, as if they were
|
||||
concatenated into a single vector. Gradients are modified in-place.
|
||||
|
||||
Args:
|
||||
parameters (Iterable[Tensor] or Tensor): an iterable of Tensors or a
|
||||
single Tensor that will have gradients normalized
|
||||
norm_type (float): type of the used p-norm. Can be ``'inf'`` for
|
||||
infinity norm.
|
||||
error_if_nonfinite (bool): if True, an error is thrown if the total
|
||||
norm of the gradients from :attr:`parameters` is ``nan``,
|
||||
``inf``, or ``-inf``. Default: False (will switch to True in the future)
|
||||
foreach (bool): use the faster foreach-based implementation.
|
||||
If ``None``, use the foreach implementation for CUDA and CPU native tensors and silently
|
||||
fall back to the slow implementation for other device types.
|
||||
Default: ``None``
|
||||
|
||||
Returns:
|
||||
Total norm of the parameter gradients (viewed as a single vector).
|
||||
"""
|
||||
max_norm = self._max_norm if self._max_norm is not None else float('inf')
|
||||
grad_norm = torch.nn.utils.clip_grad_norm_(parameters, max_norm=max_norm, norm_type=norm_type, error_if_nonfinite=error_if_nonfinite, foreach=foreach)
|
||||
|
||||
if torch.isfinite(grad_norm):
|
||||
self._grad_norm[self._buffer_ptr] = grad_norm
|
||||
self._buffer_ptr = (self._buffer_ptr + 1) % self.buffer_size
|
||||
self._buffer_length = min(self._buffer_length + 1, self.buffer_size)
|
||||
if self._buffer_length == self.buffer_size:
|
||||
self._max_norm = np.percentile(self._grad_norm, self.clip_percentile)
|
||||
self._max_norm = min(self._max_norm, self.max_norm) if self.max_norm is not None else self._max_norm
|
||||
|
||||
return grad_norm
|
||||
@@ -0,0 +1,92 @@
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch.autograd import Variable
|
||||
from math import exp
|
||||
from lpips import LPIPS
|
||||
|
||||
|
||||
def smooth_l1_loss(pred, target, beta=1.0):
|
||||
diff = torch.abs(pred - target)
|
||||
loss = torch.where(diff < beta, 0.5 * diff ** 2 / beta, diff - 0.5 * beta)
|
||||
return loss.mean()
|
||||
|
||||
|
||||
def l1_loss(network_output, gt):
|
||||
return torch.abs((network_output - gt)).mean()
|
||||
|
||||
|
||||
def l2_loss(network_output, gt):
|
||||
return ((network_output - gt) ** 2).mean()
|
||||
|
||||
|
||||
def gaussian(window_size, sigma):
|
||||
gauss = torch.Tensor([exp(-(x - window_size // 2) ** 2 / float(2 * sigma ** 2)) for x in range(window_size)])
|
||||
return gauss / gauss.sum()
|
||||
|
||||
|
||||
def create_window(window_size, channel):
|
||||
_1D_window = gaussian(window_size, 1.5).unsqueeze(1)
|
||||
_2D_window = _1D_window.mm(_1D_window.t()).float().unsqueeze(0).unsqueeze(0)
|
||||
window = Variable(_2D_window.expand(channel, 1, window_size, window_size).contiguous())
|
||||
return window
|
||||
|
||||
|
||||
def psnr(img1, img2, max_val=1.0):
|
||||
mse = F.mse_loss(img1, img2)
|
||||
return 20 * torch.log10(max_val / torch.sqrt(mse))
|
||||
|
||||
|
||||
def ssim(img1, img2, window_size=11, size_average=True):
|
||||
channel = img1.size(-3)
|
||||
window = create_window(window_size, channel)
|
||||
|
||||
if img1.is_cuda:
|
||||
window = window.cuda(img1.get_device())
|
||||
window = window.type_as(img1)
|
||||
|
||||
return _ssim(img1, img2, window, window_size, channel, size_average)
|
||||
|
||||
def _ssim(img1, img2, window, window_size, channel, size_average=True):
|
||||
mu1 = F.conv2d(img1, window, padding=window_size // 2, groups=channel)
|
||||
mu2 = F.conv2d(img2, window, padding=window_size // 2, groups=channel)
|
||||
|
||||
mu1_sq = mu1.pow(2)
|
||||
mu2_sq = mu2.pow(2)
|
||||
mu1_mu2 = mu1 * mu2
|
||||
|
||||
sigma1_sq = F.conv2d(img1 * img1, window, padding=window_size // 2, groups=channel) - mu1_sq
|
||||
sigma2_sq = F.conv2d(img2 * img2, window, padding=window_size // 2, groups=channel) - mu2_sq
|
||||
sigma12 = F.conv2d(img1 * img2, window, padding=window_size // 2, groups=channel) - mu1_mu2
|
||||
|
||||
C1 = 0.01 ** 2
|
||||
C2 = 0.03 ** 2
|
||||
|
||||
ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2))
|
||||
|
||||
if size_average:
|
||||
return ssim_map.mean()
|
||||
else:
|
||||
return ssim_map.mean(1).mean(1).mean(1)
|
||||
|
||||
|
||||
loss_fn_vgg = None
|
||||
def lpips(img1, img2, value_range=(0, 1)):
|
||||
global loss_fn_vgg
|
||||
if loss_fn_vgg is None:
|
||||
loss_fn_vgg = LPIPS(net='vgg').cuda().eval()
|
||||
# normalize to [-1, 1]
|
||||
img1 = (img1 - value_range[0]) / (value_range[1] - value_range[0]) * 2 - 1
|
||||
img2 = (img2 - value_range[0]) / (value_range[1] - value_range[0]) * 2 - 1
|
||||
return loss_fn_vgg(img1, img2).mean()
|
||||
|
||||
|
||||
def normal_angle(pred, gt):
|
||||
pred = pred * 2.0 - 1.0
|
||||
gt = gt * 2.0 - 1.0
|
||||
norms = pred.norm(dim=-1) * gt.norm(dim=-1)
|
||||
cos_sim = (pred * gt).sum(-1) / (norms + 1e-9)
|
||||
cos_sim = torch.clamp(cos_sim, -1.0, 1.0)
|
||||
ang = torch.rad2deg(torch.acos(cos_sim[norms > 1e-9])).mean()
|
||||
if ang.isnan():
|
||||
return -1
|
||||
return ang
|
||||
@@ -4,10 +4,7 @@ from tqdm import tqdm
|
||||
import utils3d
|
||||
from PIL import Image
|
||||
|
||||
from ..renderers import OctreeRenderer, MeshRenderer,GaussianRenderer
|
||||
from ..renderers.gaussian_render import GaussianRenderer
|
||||
# from ..renderers.mesh_renderer import MeshRenderer
|
||||
# from ..renderers.octree_renderer import OctreeRenderer
|
||||
from ..renderers import OctreeRenderer, GaussianRenderer, MeshRenderer
|
||||
from ..representations import Octree, Gaussian, MeshExtractResult
|
||||
from ..modules import sparse as sp
|
||||
from .random_utils import sphere_hammersley_sequence
|
||||
@@ -43,36 +40,44 @@ def yaw_pitch_r_fov_to_extrinsics_intrinsics(yaws, pitchs, rs, fovs):
|
||||
return extrinsics, intrinsics
|
||||
|
||||
|
||||
def render_frames(sample, extrinsics, intrinsics, options={}, colors_overwrite=None, verbose=True, **kwargs):
|
||||
def get_renderer(sample, **kwargs):
|
||||
if isinstance(sample, Octree):
|
||||
renderer = OctreeRenderer()
|
||||
renderer.rendering_options.resolution = options.get('resolution', 512)
|
||||
renderer.rendering_options.near = options.get('near', 0.8)
|
||||
renderer.rendering_options.far = options.get('far', 1.6)
|
||||
renderer.rendering_options.bg_color = options.get('bg_color', (0, 0, 0))
|
||||
renderer.rendering_options.ssaa = options.get('ssaa', 4)
|
||||
renderer.rendering_options.resolution = kwargs.get('resolution', 512)
|
||||
renderer.rendering_options.near = kwargs.get('near', 0.8)
|
||||
renderer.rendering_options.far = kwargs.get('far', 1.6)
|
||||
renderer.rendering_options.bg_color = kwargs.get('bg_color', (0, 0, 0))
|
||||
renderer.rendering_options.ssaa = kwargs.get('ssaa', 4)
|
||||
renderer.pipe.primitive = sample.primitive
|
||||
elif isinstance(sample, Gaussian):
|
||||
renderer = GaussianRenderer()
|
||||
renderer.rendering_options.resolution = options.get('resolution', 512)
|
||||
renderer.rendering_options.near = options.get('near', 0.8)
|
||||
renderer.rendering_options.far = options.get('far', 1.6)
|
||||
renderer.rendering_options.bg_color = options.get('bg_color', (0, 0, 0))
|
||||
renderer.rendering_options.ssaa = options.get('ssaa', 1)
|
||||
renderer.rendering_options.resolution = kwargs.get('resolution', 512)
|
||||
renderer.rendering_options.near = kwargs.get('near', 0.8)
|
||||
renderer.rendering_options.far = kwargs.get('far', 1.6)
|
||||
renderer.rendering_options.bg_color = kwargs.get('bg_color', (0, 0, 0))
|
||||
renderer.rendering_options.ssaa = kwargs.get('ssaa', 1)
|
||||
renderer.pipe.kernel_size = kwargs.get('kernel_size', 0.1)
|
||||
renderer.pipe.use_mip_gaussian = True
|
||||
elif isinstance(sample, MeshExtractResult):
|
||||
renderer = MeshRenderer()
|
||||
renderer.rendering_options.resolution = options.get('resolution', 512)
|
||||
renderer.rendering_options.near = options.get('near', 1)
|
||||
renderer.rendering_options.far = options.get('far', 100)
|
||||
renderer.rendering_options.ssaa = options.get('ssaa', 4)
|
||||
renderer.rendering_options.resolution = kwargs.get('resolution', 512)
|
||||
renderer.rendering_options.near = kwargs.get('near', 1)
|
||||
renderer.rendering_options.far = kwargs.get('far', 100)
|
||||
renderer.rendering_options.ssaa = kwargs.get('ssaa', 4)
|
||||
else:
|
||||
raise ValueError(f'Unsupported sample type: {type(sample)}')
|
||||
|
||||
return renderer
|
||||
|
||||
|
||||
def render_frames(sample, extrinsics, intrinsics, options={}, colors_overwrite=None, verbose=True, **kwargs):
|
||||
renderer = get_renderer(sample, **options)
|
||||
rets = {}
|
||||
for j, (extr, intr) in tqdm(enumerate(zip(extrinsics, intrinsics)), desc='Rendering', disable=not verbose):
|
||||
if not isinstance(sample, MeshExtractResult):
|
||||
if isinstance(sample, MeshExtractResult):
|
||||
res = renderer.render(sample, extr, intr)
|
||||
if 'normal' not in rets: rets['normal'] = []
|
||||
rets['normal'].append(np.clip(res['normal'].detach().cpu().numpy().transpose(1, 2, 0) * 255, 0, 255).astype(np.uint8))
|
||||
else:
|
||||
res = renderer.render(sample, extr, intr, colors_overwrite=colors_overwrite)
|
||||
if 'color' not in rets: rets['color'] = []
|
||||
if 'depth' not in rets: rets['depth'] = []
|
||||
@@ -83,10 +88,6 @@ def render_frames(sample, extrinsics, intrinsics, options={}, colors_overwrite=N
|
||||
rets['depth'].append(res['depth'].detach().cpu().numpy())
|
||||
else:
|
||||
rets['depth'].append(None)
|
||||
else:
|
||||
res = renderer.render(sample, extr, intr)
|
||||
if 'normal' not in rets: rets['normal'] = []
|
||||
rets['normal'].append(np.clip(res['normal'].detach().cpu().numpy().transpose(1, 2, 0) * 255, 0, 255).astype(np.uint8))
|
||||
return rets
|
||||
|
||||
|
||||
@@ -96,18 +97,17 @@ def render_video(sample, resolution=512, bg_color=(0, 0, 0), num_frames=300, r=2
|
||||
yaws = yaws.tolist()
|
||||
pitch = pitch.tolist()
|
||||
extrinsics, intrinsics = yaw_pitch_r_fov_to_extrinsics_intrinsics(yaws, pitch, r, fov)
|
||||
|
||||
return render_frames(sample, extrinsics, intrinsics, {'resolution': resolution, 'bg_color': bg_color}, **kwargs)
|
||||
|
||||
def render_multiview(sample, resolution=512, nviews=30,**kwargs):
|
||||
|
||||
def render_multiview(sample, resolution=512, nviews=30):
|
||||
r = 2
|
||||
fov = 40
|
||||
bg_color=(0, 0, 0)
|
||||
cams = [sphere_hammersley_sequence(i, nviews) for i in range(nviews)]
|
||||
yaws = [cam[0] for cam in cams]
|
||||
pitchs = [cam[1] for cam in cams]
|
||||
extrinsics, intrinsics = yaw_pitch_r_fov_to_extrinsics_intrinsics(yaws, pitchs, r, fov) #计算相机内外参
|
||||
res = render_frames(sample, extrinsics, intrinsics, {'resolution': resolution, 'bg_color': bg_color}, **kwargs)
|
||||
extrinsics, intrinsics = yaw_pitch_r_fov_to_extrinsics_intrinsics(yaws, pitchs, r, fov)
|
||||
res = render_frames(sample, extrinsics, intrinsics, {'resolution': resolution, 'bg_color': (0, 0, 0)})
|
||||
return res['color'], extrinsics, intrinsics
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,39 @@ import trimesh
|
||||
|
||||
from comfy.utils import common_upscale,ProgressBar
|
||||
import folder_paths
|
||||
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
def convert_clip2diffuser(clip):
|
||||
|
||||
from transformers import (
|
||||
CLIPTextModel,
|
||||
CLIPTextConfig,
|
||||
)
|
||||
from contextlib import nullcontext
|
||||
|
||||
try:
|
||||
from accelerate import init_empty_weights
|
||||
from accelerate.utils import set_module_tensor_to_device
|
||||
is_accelerate_available = True
|
||||
except:
|
||||
pass
|
||||
|
||||
import comfy.model_management as mm
|
||||
|
||||
offload_device=mm.unet_offload_device()
|
||||
|
||||
text_encoder_config=CLIPTextConfig.from_pretrained(os.path.join(current_path, 'text_encoder'), local_files_only=True)
|
||||
ctx = init_empty_weights if is_accelerate_available else nullcontext
|
||||
with ctx():
|
||||
text_encoder = CLIPTextModel(text_encoder_config)
|
||||
text_encoder_sd = clip.get_sd()
|
||||
|
||||
|
||||
from diffusers.pipelines.stable_diffusion.convert_from_ckpt import convert_open_clip_checkpoint,convert_ldm_clip_checkpoint
|
||||
text_encoder = convert_ldm_clip_checkpoint(text_encoder_sd, text_encoder=text_encoder)
|
||||
|
||||
|
||||
return text_encoder,text_encoder_2
|
||||
#https://github.com/Abecid/realtime-3d-stylization-drawing/blob/97a687f148cec04e107f7fec8986c8b5615af1ec/gradio_app.py#L33
|
||||
def glb2obj_(glb_path, obj_path):
|
||||
print('Converting glb to obj')
|
||||
|
||||
Reference in New Issue
Block a user