Integrated TripoSG plus Scribble model

This commit is contained in:
Mr. For Example
2025-04-29 16:50:36 -06:00
parent 979d770c56
commit 0768ba7a75
43 changed files with 102700 additions and 8 deletions
@@ -0,0 +1,35 @@
*.7z filter=lfs diff=lfs merge=lfs -text
*.arrow filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text
*.bz2 filter=lfs diff=lfs merge=lfs -text
*.ckpt filter=lfs diff=lfs merge=lfs -text
*.ftz filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
*.h5 filter=lfs diff=lfs merge=lfs -text
*.joblib filter=lfs diff=lfs merge=lfs -text
*.lfs.* filter=lfs diff=lfs merge=lfs -text
*.mlmodel filter=lfs diff=lfs merge=lfs -text
*.model filter=lfs diff=lfs merge=lfs -text
*.msgpack filter=lfs diff=lfs merge=lfs -text
*.npy filter=lfs diff=lfs merge=lfs -text
*.npz filter=lfs diff=lfs merge=lfs -text
*.onnx filter=lfs diff=lfs merge=lfs -text
*.ot filter=lfs diff=lfs merge=lfs -text
*.parquet filter=lfs diff=lfs merge=lfs -text
*.pb filter=lfs diff=lfs merge=lfs -text
*.pickle filter=lfs diff=lfs merge=lfs -text
*.pkl filter=lfs diff=lfs merge=lfs -text
*.pt filter=lfs diff=lfs merge=lfs -text
*.pth filter=lfs diff=lfs merge=lfs -text
*.rar filter=lfs diff=lfs merge=lfs -text
*.safetensors filter=lfs diff=lfs merge=lfs -text
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
*.tar.* filter=lfs diff=lfs merge=lfs -text
*.tar filter=lfs diff=lfs merge=lfs -text
*.tflite filter=lfs diff=lfs merge=lfs -text
*.tgz filter=lfs diff=lfs merge=lfs -text
*.wasm filter=lfs diff=lfs merge=lfs -text
*.xz filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text
*tfevents* filter=lfs diff=lfs merge=lfs -text
@@ -0,0 +1,49 @@
---
license: mit
pipeline_tag: image-to-3d
tags:
- triposg
- 3d-generation
- rectified-flow
---
# TripoSG-scribble - Fast 3D Shape Prototyping with Scribble and Prompt
TripoSG-scribble converts a scribble image and a text prompt to a 3D shape. TripoSG-scribble is a variant of TripoSG. TripoSG is a state-of-the-art image-to-3D generation foundation model that leverages large-scale rectified flow transformers to produce high-fidelity 3D shapes from single images.
## Model Description
### Model Architecture
TripoSG utilizes a novel architecture combining:
- Rectified Flow (RF) based Transformer for stable, linear trajectory modeling
- Advanced VAE with SDF-based representation and hybrid geometric supervision
- Cross-attention mechanism for image feature condition
- 1.5B parameters operating on 2048 latent tokens
For inference efficiency, TripoSG-scribble is different from TripoSG in:
- TripoSG-scribble is a CFG-distilled model and should be used with CFG=0
- TripoSG-scribble is trained with 512 latent tokens
## Intended Uses
This model is designed for:
- Converting scribble image and text prompt to high-quality 3D meshes
- Creative and design applications
- Gaming and VFX asset creation
- Prototyping and visualization
## Requirements
- CUDA-capable GPU (>8GB VRAM)
## Usage
For detailed usage instructions, please visit our [GitHub repository](https://github.com/VAST-AI-Research/TripoSG).
## About
TripoSG-scribble is developed by [Tripo](https://www.tripo3d.ai), [VAST AI Research](https://github.com/orgs/VAST-AI-Research), pushing the boundaries of 3D Generative AI.
For more information:
- [GitHub Repository](https://github.com/VAST-AI-Research/TripoSG)
- [Paper](https://arxiv.org/abs/2502.06608)
- [Gradio Demo](https://huggingface.co/spaces/VAST-AI/TripoSG-scribble)
@@ -0,0 +1,27 @@
{
"crop_size": {
"height": 224,
"width": 224
},
"do_center_crop": true,
"do_convert_rgb": true,
"do_normalize": true,
"do_rescale": true,
"do_resize": true,
"image_mean": [
0.485,
0.456,
0.406
],
"image_processor_type": "BitImageProcessor",
"image_std": [
0.229,
0.224,
0.225
],
"resample": 3,
"rescale_factor": 0.00392156862745098,
"size": {
"shortest_edge": 256
}
}
@@ -0,0 +1,60 @@
{
"apply_layernorm": true,
"architectures": [
"Dinov2Model"
],
"attention_probs_dropout_prob": 0.0,
"drop_path_rate": 0.0,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.0,
"hidden_size": 1024,
"image_size": 518,
"initializer_range": 0.02,
"layer_norm_eps": 1e-06,
"layerscale_value": 1.0,
"mlp_ratio": 4,
"model_type": "dinov2",
"num_attention_heads": 16,
"num_channels": 3,
"num_hidden_layers": 24,
"out_features": [
"stage24"
],
"out_indices": [
24
],
"patch_size": 14,
"qkv_bias": true,
"reshape_hidden_states": true,
"stage_names": [
"stem",
"stage1",
"stage2",
"stage3",
"stage4",
"stage5",
"stage6",
"stage7",
"stage8",
"stage9",
"stage10",
"stage11",
"stage12",
"stage13",
"stage14",
"stage15",
"stage16",
"stage17",
"stage18",
"stage19",
"stage20",
"stage21",
"stage22",
"stage23",
"stage24"
],
"torch_dtype": "float16",
"transformers_version": "4.50.3",
"use_mask_token": true,
"use_swiglu_ffn": false
}
@@ -0,0 +1,32 @@
{
"_class_name": "TripoSGScribblePipeline",
"_diffusers_version": "0.33.1",
"feature_extractor_dinov2": [
"transformers",
"BitImageProcessor"
],
"image_encoder_dinov2": [
"transformers",
"Dinov2Model"
],
"scheduler": [
"TripoSG.schedulers.scheduling_rectified_flow",
"RectifiedFlowScheduler"
],
"text_encoder": [
"transformers",
"CLIPTextModelWithProjection"
],
"tokenizer": [
"transformers",
"CLIPTokenizer"
],
"transformer": [
"TripoSG.models.transformers.triposg_transformer",
"TripoSGDiTModel"
],
"vae": [
"TripoSG.models.autoencoders.autoencoder_kl_triposg",
"TripoSGVAEModel"
]
}
@@ -0,0 +1,7 @@
{
"_class_name": "RectifiedFlowScheduler",
"_diffusers_version": "0.33.1",
"num_train_timesteps": 1000,
"shift": 1,
"use_dynamic_shifting": false
}
@@ -0,0 +1,24 @@
{
"architectures": [
"CLIPTextModelWithProjection"
],
"attention_dropout": 0.0,
"bos_token_id": 0,
"dropout": 0.0,
"eos_token_id": 2,
"hidden_act": "quick_gelu",
"hidden_size": 768,
"initializer_factor": 1.0,
"initializer_range": 0.02,
"intermediate_size": 3072,
"layer_norm_eps": 1e-05,
"max_position_embeddings": 77,
"model_type": "clip_text_model",
"num_attention_heads": 12,
"num_hidden_layers": 12,
"pad_token_id": 1,
"projection_dim": 768,
"torch_dtype": "float16",
"transformers_version": "4.50.3",
"vocab_size": 49408
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,30 @@
{
"bos_token": {
"content": "<|startoftext|>",
"lstrip": false,
"normalized": true,
"rstrip": false,
"single_word": false
},
"eos_token": {
"content": "<|endoftext|>",
"lstrip": false,
"normalized": true,
"rstrip": false,
"single_word": false
},
"pad_token": {
"content": "<|endoftext|>",
"lstrip": false,
"normalized": true,
"rstrip": false,
"single_word": false
},
"unk_token": {
"content": "<|endoftext|>",
"lstrip": false,
"normalized": true,
"rstrip": false,
"single_word": false
}
}
@@ -0,0 +1,31 @@
{
"add_prefix_space": false,
"added_tokens_decoder": {
"49406": {
"content": "<|startoftext|>",
"lstrip": false,
"normalized": true,
"rstrip": false,
"single_word": false,
"special": true
},
"49407": {
"content": "<|endoftext|>",
"lstrip": false,
"normalized": true,
"rstrip": false,
"single_word": false,
"special": true
}
},
"bos_token": "<|startoftext|>",
"clean_up_tokenization_spaces": false,
"do_lower_case": true,
"eos_token": "<|endoftext|>",
"errors": "replace",
"extra_special_tokens": {},
"model_max_length": 77,
"pad_token": "<|endoftext|>",
"tokenizer_class": "CLIPTokenizer",
"unk_token": "<|endoftext|>"
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
{
"_class_name": "TripoSGDiTModel",
"_diffusers_version": "0.33.1",
"cross_attention_2_dim": 1024,
"cross_attention_dim": 768,
"in_channels": 64,
"num_attention_heads": 16,
"num_layers": 21,
"use_cross_attention_2": true,
"width": 2048
}
@@ -0,0 +1,14 @@
{
"_class_name": "TripoSGVAEModel",
"_diffusers_version": "0.33.1",
"embed_frequency": 8,
"embed_include_pi": false,
"embedding_type": "frequency",
"in_channels": 3,
"latent_channels": 64,
"num_attention_heads": 8,
"num_layers_decoder": 16,
"num_layers_encoder": 8,
"width_decoder": 1024,
"width_encoder": 512
}
@@ -0,0 +1,36 @@
*.7z filter=lfs diff=lfs merge=lfs -text
*.arrow filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text
*.bz2 filter=lfs diff=lfs merge=lfs -text
*.ckpt filter=lfs diff=lfs merge=lfs -text
*.ftz filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
*.h5 filter=lfs diff=lfs merge=lfs -text
*.joblib filter=lfs diff=lfs merge=lfs -text
*.lfs.* filter=lfs diff=lfs merge=lfs -text
*.mlmodel filter=lfs diff=lfs merge=lfs -text
*.model filter=lfs diff=lfs merge=lfs -text
*.msgpack filter=lfs diff=lfs merge=lfs -text
*.npy filter=lfs diff=lfs merge=lfs -text
*.npz filter=lfs diff=lfs merge=lfs -text
*.onnx filter=lfs diff=lfs merge=lfs -text
*.ot filter=lfs diff=lfs merge=lfs -text
*.parquet filter=lfs diff=lfs merge=lfs -text
*.pb filter=lfs diff=lfs merge=lfs -text
*.pickle filter=lfs diff=lfs merge=lfs -text
*.pkl filter=lfs diff=lfs merge=lfs -text
*.pt filter=lfs diff=lfs merge=lfs -text
*.pth filter=lfs diff=lfs merge=lfs -text
*.rar filter=lfs diff=lfs merge=lfs -text
*.safetensors filter=lfs diff=lfs merge=lfs -text
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
*.tar.* filter=lfs diff=lfs merge=lfs -text
*.tar filter=lfs diff=lfs merge=lfs -text
*.tflite filter=lfs diff=lfs merge=lfs -text
*.tgz filter=lfs diff=lfs merge=lfs -text
*.wasm filter=lfs diff=lfs merge=lfs -text
*.xz filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text
*tfevents* filter=lfs diff=lfs merge=lfs -text
*.tar.gz filter=lfs diff=lfs merge=lfs -text
@@ -0,0 +1,45 @@
---
license: mit
pipeline_tag: image-to-3d
tags:
- triposg
- 3d-generation
- rectified-flow
---
# TripoSG - High-Fidelity 3D Shape Synthesis using Large-Scale Rectified Flow Models
TripoSG is a state-of-the-art image-to-3D generation foundation model that leverages large-scale rectified flow transformers to produce high-fidelity 3D shapes from single images.
## Model Description
### Model Architecture
TripoSG utilizes a novel architecture combining:
- Rectified Flow (RF) based Transformer for stable, linear trajectory modeling
- Advanced VAE with SDF-based representation and hybrid geometric supervision
- Cross-attention mechanism for image feature condition
- 1.5B parameters operating on 2048 latent tokens
## Intended Uses
This model is designed for:
- Converting single images to high-quality 3D meshes
- Creative and design applications
- Gaming and VFX asset creation
- Prototyping and visualization
## Requirements
- CUDA-capable GPU (>8GB VRAM)
## Usage
For detailed usage instructions, please visit our [GitHub repository](https://github.com/VAST-AI-Research/TripoSG).
## About
TripoSG is developed by [Tripo](https://www.tripo3d.ai), [VAST AI Research](https://github.com/orgs/VAST-AI-Research), pushing the boundaries of 3D Generative AI.
For more information:
- [GitHub Repository](https://github.com/VAST-AI-Research/TripoSG)
- [Paper](https://arxiv.org/abs/2502.06608)
- [Gradio Demo](https://huggingface.co/spaces/VAST-AI/TripoSG)
@@ -0,0 +1,27 @@
{
"crop_size": {
"height": 224,
"width": 224
},
"do_center_crop": true,
"do_convert_rgb": true,
"do_normalize": true,
"do_rescale": true,
"do_resize": true,
"image_mean": [
0.485,
0.456,
0.406
],
"image_processor_type": "BitImageProcessor",
"image_std": [
0.229,
0.224,
0.225
],
"resample": 3,
"rescale_factor": 0.00392156862745098,
"size": {
"shortest_edge": 256
}
}
@@ -0,0 +1,60 @@
{
"_name_or_path": "facebook/dinov2-large",
"apply_layernorm": true,
"architectures": [
"Dinov2Model"
],
"attention_probs_dropout_prob": 0.0,
"drop_path_rate": 0.0,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.0,
"hidden_size": 1024,
"image_size": 518,
"initializer_range": 0.02,
"layer_norm_eps": 1e-06,
"layerscale_value": 1.0,
"mlp_ratio": 4,
"model_type": "dinov2",
"num_attention_heads": 16,
"num_channels": 3,
"num_hidden_layers": 24,
"out_features": [
"stage24"
],
"out_indices": [
24
],
"patch_size": 14,
"qkv_bias": true,
"reshape_hidden_states": true,
"stage_names": [
"stem",
"stage1",
"stage2",
"stage3",
"stage4",
"stage5",
"stage6",
"stage7",
"stage8",
"stage9",
"stage10",
"stage11",
"stage12",
"stage13",
"stage14",
"stage15",
"stage16",
"stage17",
"stage18",
"stage19",
"stage20",
"stage21",
"stage22",
"stage23",
"stage24"
],
"torch_dtype": "float32",
"transformers_version": "4.45.2",
"use_swiglu_ffn": false
}
@@ -0,0 +1,24 @@
{
"_class_name": "TripoSGPipeline",
"_diffusers_version": "0.30.3",
"feature_extractor_dinov2": [
"transformers",
"BitImageProcessor"
],
"image_encoder_dinov2": [
"transformers",
"Dinov2Model"
],
"scheduler": [
"TripoSG.schedulers.scheduling_rectified_flow",
"RectifiedFlowScheduler"
],
"transformer": [
"TripoSG.models.transformers.triposg_transformer",
"TripoSGDiTModel"
],
"vae": [
"TripoSG.models.autoencoders.autoencoder_kl_triposg",
"TripoSGVAEModel"
]
}
@@ -0,0 +1,7 @@
{
"_class_name": "RectifiedFlowScheduler",
"_diffusers_version": "0.30.3",
"num_train_timesteps": 1000,
"shift": 1,
"use_dynamic_shifting": false
}
@@ -0,0 +1,9 @@
{
"_class_name": "TripoSGDiTModel",
"_diffusers_version": "0.30.3",
"cross_attention_dim": 1024,
"in_channels": 64,
"num_attention_heads": 16,
"num_layers": 21,
"width": 2048
}
@@ -0,0 +1,14 @@
{
"_class_name": "Tripo2VAEModel",
"_diffusers_version": "0.30.3",
"embed_frequency": 8,
"embed_include_pi": false,
"embedding_type": "frequency",
"in_channels": 3,
"latent_channels": 64,
"num_attention_heads": 8,
"num_layers_decoder": 16,
"num_layers_encoder": 8,
"width_decoder": 1024,
"width_encoder": 512
}
+80
View File
@@ -0,0 +1,80 @@
TENCENT HUNYUAN FLASHVDM COMMUNITY LICENSE AGREEMENT
Tencent Hunyuan FlashVDM Release Date: March 19, 2025
THIS LICENSE AGREEMENT DOES NOT APPLY IN THE EUROPEAN UNION, UNITED KINGDOM AND SOUTH KORE AND IS EXPRESSLY LIMITED TO THE TERRITORY, AS DEFINED BELOW.
By clicking to agree or by using, reproducing, modifying, distributing, performing or displaying any portion or element of the Tencent Hunyuan FlashVDM Works, including via any Hosted Service, You will be deemed to have recognized and accepted the content of this Agreement, which is effective immediately.
1. DEFINITIONS.
a. “Acceptable Use Policy” shall mean the policy made available by Tencent as set forth in the Exhibit A.
b. “Agreement” shall mean the terms and conditions for use, reproduction, distribution, modification, performance and displaying of Tencent Hunyuan FlashVDM Works or any portion or element thereof set forth herein.
c. “Documentation” shall mean the specifications, manuals and documentation for Tencent Hunyuan FlashVDM made publicly available by Tencent.
d. “Hosted Service” shall mean a hosted service offered via an application programming interface (API), web access, or any other electronic or remote means.
e. “Licensee,” “You” or “Your” shall mean a natural person or legal entity exercising the rights granted by this Agreement and/or using the Tencent Hunyuan FlashVDM Works for any purpose and in any field of use.
f. “Materials” shall mean, collectively, Tencent’s proprietary Tencent Hunyuan FlashVDM and Documentation (and any portion thereof) as made available by Tencent under this Agreement.
g. “Model Derivatives” shall mean all: (i) modifications to Tencent Hunyuan FlashVDM or any Model Derivative of Tencent Hunyuan FlashVDM; (ii) works based on Tencent Hunyuan FlashVDM or any Model Derivative of Tencent Hunyuan FlashVDM; or (iii) any other machine learning model which is created by transfer of patterns of the weights, parameters, operations, or Output of Tencent Hunyuan FlashVDM or any Model Derivative of Tencent Hunyuan FlashVDM, to that model in order to cause that model to perform similarly to Tencent Hunyuan FlashVDM or a Model Derivative of Tencent Hunyuan FlashVDM, including distillation methods, methods that use intermediate data representations, or methods based on the generation of synthetic data Outputs by Tencent Hunyuan FlashVDM or a Model Derivative of Tencent Hunyuan FlashVDM for training that model. For clarity, Outputs by themselves are not deemed Model Derivatives.
h. “Output” shall mean the information and/or content output of Tencent Hunyuan FlashVDM or a Model Derivative that results from operating or otherwise using Tencent Hunyuan FlashVDM or a Model Derivative, including via a Hosted Service.
i. “Tencent,” “We” or “Us” shall mean THL A29 Limited.
j. “Tencent Hunyuan FlashVDM” shall mean the 3D generation models and their software and algorithms, including trained model weights, parameters (including optimizer states), machine-learning model code, inference-enabling code, training-enabling code, fine-tuning enabling code and other elements of the foregoing made publicly available by Us at [ https://github.com/Tencent/FlashVDM ].
k. “Tencent Hunyuan FlashVDM Works” shall mean: (i) the Materials; (ii) Model Derivatives; and (iii) all derivative works thereof.
l. “Territory” shall mean the worldwide territory, excluding the territory of the European Union, United Kingdom and South Korea.
m. “Third Party” or “Third Parties” shall mean individuals or legal entities that are not under common control with Us or You.
n. “including” shall mean including but not limited to.
2. GRANT OF RIGHTS.
We grant You, for the Territory only, a non-exclusive, non-transferable and royalty-free limited license under Tencent’s intellectual property or other rights owned by Us embodied in or utilized by the Materials to use, reproduce, distribute, create derivative works of (including Model Derivatives), and make modifications to the Materials, only in accordance with the terms of this Agreement and the Acceptable Use Policy, and You must not violate (or encourage or permit anyone else to violate) any term of this Agreement or the Acceptable Use Policy.
3. DISTRIBUTION.
You may, subject to Your compliance with this Agreement, distribute or make available to Third Parties the Tencent Hunyuan FlashVDM Works, exclusively in the Territory, provided that You meet all of the following conditions:
a. You must provide all such Third Party recipients of the Tencent Hunyuan FlashVDM Works or products or services using them a copy of this Agreement;
b. You must cause any modified files to carry prominent notices stating that You changed the files;
c. You are encouraged to: (i) publish at least one technology introduction blogpost or one public statement expressing Your experience of using the Tencent Hunyuan FlashVDM Works; and (ii) mark the products or services developed by using the Tencent Hunyuan FlashVDM Works to indicate that the product/service is “Powered by Tencent Hunyuan ”; and
d. All distributions to Third Parties (other than through a Hosted Service) must be accompanied by a “Notice” text file that contains the following notice: “Tencent Hunyuan FlashVDM is licensed under the Tencent Hunyuan FlashVDM Community License Agreement, Copyright © 2025 Tencent. All Rights Reserved. The trademark rights of “Tencent Hunyuan” are owned by Tencent or its affiliate.”
You may add Your own copyright statement to Your modifications and, except as set forth in this Section and in Section 5, may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Model Derivatives as a whole, provided Your use, reproduction, modification, distribution, performance and display of the work otherwise complies with the terms and conditions of this Agreement (including as regards the Territory). If You receive Tencent Hunyuan FlashVDM Works from a Licensee as part of an integrated end user product, then this Section 3 of this Agreement will not apply to You.
4. ADDITIONAL COMMERCIAL TERMS.
If, on the Tencent Hunyuan FlashVDM version release date, the monthly active users of all products or services made available by or for Licensee is greater than 1 million monthly active users in the preceding calendar month, You must request a license from Tencent, which Tencent may grant to You in its sole discretion, and You are not authorized to exercise any of the rights under this Agreement unless or until Tencent otherwise expressly grants You such rights.
Subject to Tencent's written approval, you may request a license for the use of Tencent Hunyuan FlashVDM by submitting the following information to hunyuan3d@tencent.com:
a. Your company’s name and associated business sector that plans to use Tencent Hunyuan FlashVDM.
b. Your intended use case and the purpose of using Tencent Hunyuan FlashVDM.
5. RULES OF USE.
a. Your use of the Tencent Hunyuan FlashVDM Works must comply with applicable laws and regulations (including trade compliance laws and regulations) and adhere to the Acceptable Use Policy for the Tencent Hunyuan FlashVDM Works, which is hereby incorporated by reference into this Agreement. You must include the use restrictions referenced in these Sections 5(a) and 5(b) as an enforceable provision in any agreement (e.g., license agreement, terms of use, etc.) governing the use and/or distribution of Tencent Hunyuan FlashVDM Works and You must provide notice to subsequent users to whom You distribute that Tencent Hunyuan FlashVDM Works are subject to the use restrictions in these Sections 5(a) and 5(b).
b. You must not use the Tencent Hunyuan FlashVDM Works or any Output or results of the Tencent Hunyuan FlashVDM Works to improve any other AI model (other than Tencent Hunyuan FlashVDM or Model Derivatives thereof).
c. You must not use, reproduce, modify, distribute, or display the Tencent Hunyuan FlashVDM Works, Output or results of the Tencent Hunyuan FlashVDM Works outside the Territory. Any such use outside the Territory is unlicensed and unauthorized under this Agreement.
6. INTELLECTUAL PROPERTY.
a. Subject to Tencent’s ownership of Tencent Hunyuan FlashVDM Works made by or for Tencent and intellectual property rights therein, conditioned upon Your compliance with the terms and conditions of this Agreement, as between You and Tencent, You will be the owner of any derivative works and modifications of the Materials and any Model Derivatives that are made by or for You.
b. No trademark licenses are granted under this Agreement, and in connection with the Tencent Hunyuan FlashVDM Works, Licensee may not use any name or mark owned by or associated with Tencent or any of its affiliates, except as required for reasonable and customary use in describing and distributing the Tencent Hunyuan FlashVDM Works. Tencent hereby grants You a license to use “Tencent Hunyuan” (the “Mark”) in the Territory solely as required to comply with the provisions of Section 3(c), provided that You comply with any applicable laws related to trademark protection. All goodwill arising out of Your use of the Mark will inure to the benefit of Tencent.
c. If You commence a lawsuit or other proceedings (including a cross-claim or counterclaim in a lawsuit) against Us or any person or entity alleging that the Materials or any Output, or any portion of any of the foregoing, infringe any intellectual property or other right owned or licensable by You, then all licenses granted to You under this Agreement shall terminate as of the date such lawsuit or other proceeding is filed. You will defend, indemnify and hold harmless Us from and against any claim by any Third Party arising out of or related to Your or the Third Party’s use or distribution of the Tencent Hunyuan FlashVDM Works.
d. Tencent claims no rights in Outputs You generate. You and Your users are solely responsible for Outputs and their subsequent uses.
7. DISCLAIMERS OF WARRANTY AND LIMITATIONS OF LIABILITY.
a. We are not obligated to support, update, provide training for, or develop any further version of the Tencent Hunyuan FlashVDM Works or to grant any license thereto.
b. UNLESS AND ONLY TO THE EXTENT REQUIRED BY APPLICABLE LAW, THE TENCENT HUNYUAN FLASHVDM WORKS AND ANY OUTPUT AND RESULTS THEREFROM ARE PROVIDED “AS IS” WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES OF ANY KIND INCLUDING ANY WARRANTIES OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, COURSE OF DEALING, USAGE OF TRADE, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING, REPRODUCING, MODIFYING, PERFORMING, DISPLAYING OR DISTRIBUTING ANY OF THE TENCENT HUNYUAN FLASHVDM WORKS OR OUTPUTS AND ASSUME ANY AND ALL RISKS ASSOCIATED WITH YOUR OR A THIRD PARTY’S USE OR DISTRIBUTION OF ANY OF THE TENCENT HUNYUAN FLASHVDM WORKS OR OUTPUTS AND YOUR EXERCISE OF RIGHTS AND PERMISSIONS UNDER THIS AGREEMENT.
c. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL TENCENT OR ITS AFFILIATES BE LIABLE UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, TORT, NEGLIGENCE, PRODUCTS LIABILITY, OR OTHERWISE, FOR ANY DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY, CONSEQUENTIAL OR PUNITIVE DAMAGES, OR LOST PROFITS OF ANY KIND ARISING FROM THIS AGREEMENT OR RELATED TO ANY OF THE TENCENT HUNYUAN FLASHVDM WORKS OR OUTPUTS, EVEN IF TENCENT OR ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF ANY OF THE FOREGOING.
8. SURVIVAL AND TERMINATION.
a. The term of this Agreement shall commence upon Your acceptance of this Agreement or access to the Materials and will continue in full force and effect until terminated in accordance with the terms and conditions herein.
b. We may terminate this Agreement if You breach any of the terms or conditions of this Agreement. Upon termination of this Agreement, You must promptly delete and cease use of the Tencent Hunyuan FlashVDM Works. Sections 6(a), 6(c), 7 and 9 shall survive the termination of this Agreement.
9. GOVERNING LAW AND JURISDICTION.
a. This Agreement and any dispute arising out of or relating to it will be governed by the laws of the Hong Kong Special Administrative Region of the People’s Republic of China, without regard to conflict of law principles, and the UN Convention on Contracts for the International Sale of Goods does not apply to this Agreement.
b. Exclusive jurisdiction and venue for any dispute arising out of or relating to this Agreement will be a court of competent jurisdiction in the Hong Kong Special Administrative Region of the People’s Republic of China, and Tencent and Licensee consent to the exclusive jurisdiction of such court with respect to any such dispute.
 
EXHIBIT A
ACCEPTABLE USE POLICY
Tencent reserves the right to update this Acceptable Use Policy from time to time.
Last modified: November 5, 2024
Tencent endeavors to promote safe and fair use of its tools and features, including Tencent Hunyuan FlashVDM. You agree not to use Tencent Hunyuan FlashVDM or Model Derivatives:
1. Outside the Territory;
2. In any way that violates any applicable national, federal, state, local, international or any other law or regulation;
3. To harm Yourself or others;
4. To repurpose or distribute output from Tencent Hunyuan FlashVDM or any Model Derivatives to harm Yourself or others;
5. To override or circumvent the safety guardrails and safeguards We have put in place;
6. For the purpose of exploiting, harming or attempting to exploit or harm minors in any way;
7. To generate or disseminate verifiably false information and/or content with the purpose of harming others or influencing elections;
8. To generate or facilitate false online engagement, including fake reviews and other means of fake online engagement;
9. To intentionally defame, disparage or otherwise harass others;
10. To generate and/or disseminate malware (including ransomware) or any other content to be used for the purpose of harming electronic systems;
11. To generate or disseminate personal identifiable information with the purpose of harming others;
12. To generate or disseminate information (including images, code, posts, articles), and place the information in any public context (including –through the use of bot generated tweets), without expressly and conspicuously identifying that the information and/or content is machine generated;
13. To impersonate another individual without consent, authorization, or legal right;
14. To make high-stakes automated decisions in domains that affect an individual’s safety, rights or wellbeing (e.g., law enforcement, migration, medicine/health, management of critical infrastructure, safety components of products, essential services, credit, employment, housing, education, social scoring, or insurance);
15. In a manner that violates or disrespects the social ethics and moral standards of other countries or regions;
16. To perform, facilitate, threaten, incite, plan, promote or encourage violent extremism or terrorism;
17. For any use intended to discriminate against or harm individuals or groups based on protected characteristics or categories, online or offline social behavior or known or predicted personal or personality characteristics;
18. To intentionally exploit any of the vulnerabilities of a specific group of persons based on their age, social, physical or mental characteristics, in order to materially distort the behavior of a person pertaining to that group in a manner that causes or is likely to cause that person or another person physical or psychological harm;
19. For military purposes;
20. To engage in the unauthorized or unlicensed practice of any profession including, but not limited to, financial, legal, medical/health, or other professional practices.
+479
View File
@@ -0,0 +1,479 @@
import numpy as np
import torch
import torch.nn as nn
import scipy.ndimage
from skimage import measure
from einops import repeat
from diso import DiffDMC
import torch.nn.functional as F
from TripoSG.utils.typing import *
def generate_dense_grid_points_gpu(bbox_min: torch.Tensor,
bbox_max: torch.Tensor,
octree_depth: int,
indexing: str = "ij"):
length = bbox_max - bbox_min
num_cells = 2 ** octree_depth
device = bbox_min.device
x = torch.linspace(bbox_min[0], bbox_max[0], int(num_cells), dtype=torch.float16, device=device)
y = torch.linspace(bbox_min[1], bbox_max[1], int(num_cells), dtype=torch.float16, device=device)
z = torch.linspace(bbox_min[2], bbox_max[2], int(num_cells), dtype=torch.float16, device=device)
xs, ys, zs = torch.meshgrid(x, y, z, indexing=indexing)
xyz = torch.stack((xs, ys, zs), dim=-1)
xyz = xyz.view(-1, 3)
grid_size = [int(num_cells), int(num_cells), int(num_cells)]
return xyz, grid_size, length
def find_mesh_grid_coordinates_fast_gpu(occupancy_grid, n_limits=-1):
core_grid = occupancy_grid[1:-1, 1:-1, 1:-1]
occupied = core_grid > 0
neighbors_unoccupied = (
(occupancy_grid[:-2, :-2, :-2] < 0)
| (occupancy_grid[:-2, :-2, 1:-1] < 0)
| (occupancy_grid[:-2, :-2, 2:] < 0) # x-1, y-1, z-1/0/1
| (occupancy_grid[:-2, 1:-1, :-2] < 0)
| (occupancy_grid[:-2, 1:-1, 1:-1] < 0)
| (occupancy_grid[:-2, 1:-1, 2:] < 0) # x-1, y0, z-1/0/1
| (occupancy_grid[:-2, 2:, :-2] < 0)
| (occupancy_grid[:-2, 2:, 1:-1] < 0)
| (occupancy_grid[:-2, 2:, 2:] < 0) # x-1, y+1, z-1/0/1
| (occupancy_grid[1:-1, :-2, :-2] < 0)
| (occupancy_grid[1:-1, :-2, 1:-1] < 0)
| (occupancy_grid[1:-1, :-2, 2:] < 0) # x0, y-1, z-1/0/1
| (occupancy_grid[1:-1, 1:-1, :-2] < 0)
| (occupancy_grid[1:-1, 1:-1, 2:] < 0) # x0, y0, z-1/1
| (occupancy_grid[1:-1, 2:, :-2] < 0)
| (occupancy_grid[1:-1, 2:, 1:-1] < 0)
| (occupancy_grid[1:-1, 2:, 2:] < 0) # x0, y+1, z-1/0/1
| (occupancy_grid[2:, :-2, :-2] < 0)
| (occupancy_grid[2:, :-2, 1:-1] < 0)
| (occupancy_grid[2:, :-2, 2:] < 0) # x+1, y-1, z-1/0/1
| (occupancy_grid[2:, 1:-1, :-2] < 0)
| (occupancy_grid[2:, 1:-1, 1:-1] < 0)
| (occupancy_grid[2:, 1:-1, 2:] < 0) # x+1, y0, z-1/0/1
| (occupancy_grid[2:, 2:, :-2] < 0)
| (occupancy_grid[2:, 2:, 1:-1] < 0)
| (occupancy_grid[2:, 2:, 2:] < 0) # x+1, y+1, z-1/0/1
)
core_mesh_coords = torch.nonzero(occupied & neighbors_unoccupied, as_tuple=False) + 1
if n_limits != -1 and core_mesh_coords.shape[0] > n_limits:
print(f"core mesh coords {core_mesh_coords.shape[0]} is too large, limited to {n_limits}")
ind = np.random.choice(core_mesh_coords.shape[0], n_limits, True)
core_mesh_coords = core_mesh_coords[ind]
return core_mesh_coords
def find_candidates_band(occupancy_grid: torch.Tensor, band_threshold: float, n_limits: int = -1) -> torch.Tensor:
"""
Returns the coordinates of all voxels in the occupancy_grid where |value| < band_threshold.
Args:
occupancy_grid (torch.Tensor): A 3D tensor of SDF values.
band_threshold (float): The threshold below which |SDF| must be to include the voxel.
n_limits (int): Maximum number of points to return (-1 for no limit)
Returns:
torch.Tensor: A 2D tensor of coordinates (N x 3) where each row is [x, y, z].
"""
core_grid = occupancy_grid[1:-1, 1:-1, 1:-1]
# logits to sdf
core_grid = torch.sigmoid(core_grid) * 2 - 1
# Create a boolean mask for all cells in the band
in_band = torch.abs(core_grid) < band_threshold
# Get coordinates of all voxels in the band
core_mesh_coords = torch.nonzero(in_band, as_tuple=False) + 1
if n_limits != -1 and core_mesh_coords.shape[0] > n_limits:
print(f"core mesh coords {core_mesh_coords.shape[0]} is too large, limited to {n_limits}")
ind = np.random.choice(core_mesh_coords.shape[0], n_limits, True)
core_mesh_coords = core_mesh_coords[ind]
return core_mesh_coords
def expand_edge_region_fast(edge_coords, grid_size):
expanded_tensor = torch.zeros(grid_size, grid_size, grid_size, device='cuda', dtype=torch.float16, requires_grad=False)
expanded_tensor[edge_coords[:, 0], edge_coords[:, 1], edge_coords[:, 2]] = 1
if grid_size < 512:
kernel_size = 5
pooled_tensor = torch.nn.functional.max_pool3d(expanded_tensor.unsqueeze(0).unsqueeze(0), kernel_size=kernel_size, stride=1, padding=2).squeeze()
else:
kernel_size = 3
pooled_tensor = torch.nn.functional.max_pool3d(expanded_tensor.unsqueeze(0).unsqueeze(0), kernel_size=kernel_size, stride=1, padding=1).squeeze()
expanded_coords_low_res = torch.nonzero(pooled_tensor, as_tuple=False).to(torch.int16)
expanded_coords_high_res = torch.stack([
torch.cat((expanded_coords_low_res[:, 0] * 2, expanded_coords_low_res[:, 0] * 2, expanded_coords_low_res[:, 0] * 2, expanded_coords_low_res[:, 0] * 2, expanded_coords_low_res[:, 0] * 2 + 1, expanded_coords_low_res[:, 0] * 2 + 1, expanded_coords_low_res[:, 0] * 2 + 1, expanded_coords_low_res[:, 0] * 2 + 1)),
torch.cat((expanded_coords_low_res[:, 1] * 2, expanded_coords_low_res[:, 1] * 2, expanded_coords_low_res[:, 1] * 2+1, expanded_coords_low_res[:, 1] * 2 + 1, expanded_coords_low_res[:, 1] * 2, expanded_coords_low_res[:, 1] * 2, expanded_coords_low_res[:, 1] * 2 + 1, expanded_coords_low_res[:, 1] * 2 + 1)),
torch.cat((expanded_coords_low_res[:, 2] * 2, expanded_coords_low_res[:, 2] * 2+1, expanded_coords_low_res[:, 2] * 2, expanded_coords_low_res[:, 2] * 2 + 1, expanded_coords_low_res[:, 2] * 2, expanded_coords_low_res[:, 2] * 2+1, expanded_coords_low_res[:, 2] * 2, expanded_coords_low_res[:, 2] * 2 + 1))
], dim=1)
return expanded_coords_high_res
def zoom_block(block, scale_factor, order=3):
block = block.astype(np.float32)
return scipy.ndimage.zoom(block, scale_factor, order=order)
def parallel_zoom(occupancy_grid, scale_factor):
result = torch.nn.functional.interpolate(occupancy_grid.unsqueeze(0).unsqueeze(0), scale_factor=scale_factor)
return result.squeeze(0).squeeze(0)
@torch.no_grad()
def hierarchical_extract_geometry(geometric_func: Callable,
device: torch.device,
bounds: Union[Tuple[float], List[float], float] = (-1.25, -1.25, -1.25, 1.25, 1.25, 1.25),
dense_octree_depth: int = 8,
hierarchical_octree_depth: int = 9,
):
"""
Args:
geometric_func:
device:
bounds:
dense_octree_depth:
hierarchical_octree_depth:
Returns:
"""
if isinstance(bounds, float):
bounds = [-bounds, -bounds, -bounds, bounds, bounds, bounds]
bbox_min = torch.tensor(bounds[0:3]).to(device)
bbox_max = torch.tensor(bounds[3:6]).to(device)
bbox_size = bbox_max - bbox_min
xyz_samples, grid_size, length = generate_dense_grid_points_gpu(
bbox_min=bbox_min,
bbox_max=bbox_max,
octree_depth=dense_octree_depth,
indexing="ij"
)
print(f'step 1 query num: {xyz_samples.shape[0]}')
grid_logits = geometric_func(xyz_samples.unsqueeze(0)).to(torch.float16).view(grid_size[0], grid_size[1], grid_size[2])
# print(f'step 1 grid_logits shape: {grid_logits.shape}')
for i in range(hierarchical_octree_depth - dense_octree_depth):
curr_octree_depth = dense_octree_depth + i + 1
# upsample
grid_size = 2**curr_octree_depth
normalize_offset = grid_size / 2
high_res_occupancy = parallel_zoom(grid_logits, 2)
band_threshold = 1.0
edge_coords = find_candidates_band(grid_logits, band_threshold)
expanded_coords = expand_edge_region_fast(edge_coords, grid_size=int(grid_size/2)).to(torch.float16)
print(f'step {i+2} query num: {len(expanded_coords)}')
expanded_coords_norm = (expanded_coords - normalize_offset) * (abs(bounds[0]) / normalize_offset)
all_logits = None
all_logits = geometric_func(expanded_coords_norm.unsqueeze(0)).to(torch.float16)
all_logits = torch.cat([expanded_coords_norm, all_logits[0]], dim=1)
# print("all logits shape = ", all_logits.shape)
indices = all_logits[..., :3]
indices = indices * (normalize_offset / abs(bounds[0])) + normalize_offset
indices = indices.type(torch.IntTensor)
values = all_logits[:, 3]
# breakpoint()
high_res_occupancy[indices[:, 0], indices[:, 1], indices[:, 2]] = values
grid_logits = high_res_occupancy
torch.cuda.empty_cache()
mesh_v_f = []
try:
print("final grids shape = ", grid_logits.shape)
vertices, faces, normals, _ = measure.marching_cubes(grid_logits.float().cpu().numpy(), 0, method="lewiner")
vertices = vertices / (2**hierarchical_octree_depth) * bbox_size.cpu().numpy() + bbox_min.cpu().numpy()
mesh_v_f = (vertices.astype(np.float32), np.ascontiguousarray(faces))
except Exception as e:
print(e)
torch.cuda.empty_cache()
mesh_v_f = (None, None)
return [mesh_v_f]
def extract_near_surface_volume_fn(input_tensor: torch.Tensor, alpha: float):
"""
Args:
input_tensor: shape [D, D, D], torch.float16
alpha: isosurface offset
Returns:
mask: shape [D, D, D], torch.int32
"""
device = input_tensor.device
D = input_tensor.shape[0]
signed_val = 0.0
# add isosurface offset and exclude invalid value
val = input_tensor + alpha
valid_mask = val > -9000
# obtain neighbors
def get_neighbor(t, shift, axis):
if shift == 0:
return t.clone()
pad_dims = [0, 0, 0, 0, 0, 0] # [x_front,x_back,y_front,y_back,z_front,z_back]
if axis == 0: # x axis
pad_idx = 0 if shift > 0 else 1
pad_dims[pad_idx] = abs(shift)
elif axis == 1: # y axis
pad_idx = 2 if shift > 0 else 3
pad_dims[pad_idx] = abs(shift)
elif axis == 2: # z axis
pad_idx = 4 if shift > 0 else 5
pad_dims[pad_idx] = abs(shift)
# Apply padding with replication at boundaries
padded = F.pad(t.unsqueeze(0).unsqueeze(0), pad_dims[::-1], mode='replicate')
# Create dynamic slicing indices
slice_dims = [slice(None)] * 3
if axis == 0: # x axis
if shift > 0:
slice_dims[0] = slice(shift, None)
else:
slice_dims[0] = slice(None, shift)
elif axis == 1: # y axis
if shift > 0:
slice_dims[1] = slice(shift, None)
else:
slice_dims[1] = slice(None, shift)
elif axis == 2: # z axis
if shift > 0:
slice_dims[2] = slice(shift, None)
else:
slice_dims[2] = slice(None, shift)
# Apply slicing and restore dimensions
padded = padded.squeeze(0).squeeze(0)
sliced = padded[slice_dims]
return sliced
# Get neighbors in all directions
left = get_neighbor(val, 1, axis=0) # x axis
right = get_neighbor(val, -1, axis=0)
back = get_neighbor(val, 1, axis=1) # y axis
front = get_neighbor(val, -1, axis=1)
down = get_neighbor(val, 1, axis=2) # z axis
up = get_neighbor(val, -1, axis=2)
# Handle invalid boundary values
def safe_where(neighbor):
return torch.where(neighbor > -9000, neighbor, val)
left = safe_where(left)
right = safe_where(right)
back = safe_where(back)
front = safe_where(front)
down = safe_where(down)
up = safe_where(up)
# Calculate sign consistency
sign = torch.sign(val.to(torch.float32))
neighbors_sign = torch.stack([
torch.sign(left.to(torch.float32)),
torch.sign(right.to(torch.float32)),
torch.sign(back.to(torch.float32)),
torch.sign(front.to(torch.float32)),
torch.sign(down.to(torch.float32)),
torch.sign(up.to(torch.float32))
], dim=0)
# Check if all signs are consistent
same_sign = torch.all(neighbors_sign == sign, dim=0)
# Generate final mask
mask = (~same_sign).to(torch.int32)
return mask * valid_mask.to(torch.int32)
def generate_dense_grid_points_2(
bbox_min: np.ndarray,
bbox_max: np.ndarray,
octree_resolution: int,
indexing: str = "ij",
):
length = bbox_max - bbox_min
num_cells = octree_resolution
x = np.linspace(bbox_min[0], bbox_max[0], int(num_cells) + 1, dtype=np.float32)
y = np.linspace(bbox_min[1], bbox_max[1], int(num_cells) + 1, dtype=np.float32)
z = np.linspace(bbox_min[2], bbox_max[2], int(num_cells) + 1, dtype=np.float32)
[xs, ys, zs] = np.meshgrid(x, y, z, indexing=indexing)
xyz = np.stack((xs, ys, zs), axis=-1)
grid_size = [int(num_cells) + 1, int(num_cells) + 1, int(num_cells) + 1]
return xyz, grid_size, length
@torch.no_grad()
def flash_extract_geometry(
latents: torch.FloatTensor,
vae: Callable,
bounds: Union[Tuple[float], List[float], float] = 1.01,
num_chunks: int = 10000,
mc_level: float = 0.0,
octree_depth: int = 9,
min_resolution: int = 63,
mini_grid_num: int = 4,
**kwargs,
):
geo_decoder = vae.decoder
device = latents.device
dtype = latents.dtype
# resolution to depth
octree_resolution = 2 ** octree_depth
resolutions = []
if octree_resolution < min_resolution:
resolutions.append(octree_resolution)
while octree_resolution >= min_resolution:
resolutions.append(octree_resolution)
octree_resolution = octree_resolution // 2
resolutions.reverse()
resolutions[0] = round(resolutions[0] / mini_grid_num) * mini_grid_num - 1
for i, resolution in enumerate(resolutions[1:]):
resolutions[i + 1] = resolutions[0] * 2 ** (i + 1)
# 1. generate query points
if isinstance(bounds, float):
bounds = [-bounds, -bounds, -bounds, bounds, bounds, bounds]
bbox_min = np.array(bounds[0:3])
bbox_max = np.array(bounds[3:6])
bbox_size = bbox_max - bbox_min
xyz_samples, grid_size, length = generate_dense_grid_points_2(
bbox_min=bbox_min,
bbox_max=bbox_max,
octree_resolution=resolutions[0],
indexing="ij"
)
dilate = nn.Conv3d(1, 1, 3, padding=1, bias=False, device=device, dtype=dtype)
dilate.weight = torch.nn.Parameter(torch.ones(dilate.weight.shape, dtype=dtype, device=device))
grid_size = np.array(grid_size)
# 2. latents to 3d volume
xyz_samples = torch.from_numpy(xyz_samples).to(device, dtype=dtype)
batch_size = latents.shape[0]
mini_grid_size = xyz_samples.shape[0] // mini_grid_num
xyz_samples = xyz_samples.view(
mini_grid_num, mini_grid_size,
mini_grid_num, mini_grid_size,
mini_grid_num, mini_grid_size, 3
).permute(
0, 2, 4, 1, 3, 5, 6
).reshape(
-1, mini_grid_size * mini_grid_size * mini_grid_size, 3
)
batch_logits = []
num_batchs = max(num_chunks // xyz_samples.shape[1], 1)
for start in range(0, xyz_samples.shape[0], num_batchs):
queries = xyz_samples[start: start + num_batchs, :]
batch = queries.shape[0]
batch_latents = repeat(latents.squeeze(0), "p c -> b p c", b=batch)
# geo_decoder.set_topk(True)
geo_decoder.set_topk(False)
logits = vae.decode(batch_latents, queries).sample
batch_logits.append(logits)
grid_logits = torch.cat(batch_logits, dim=0).reshape(
mini_grid_num, mini_grid_num, mini_grid_num,
mini_grid_size, mini_grid_size,
mini_grid_size
).permute(0, 3, 1, 4, 2, 5).contiguous().view(
(batch_size, grid_size[0], grid_size[1], grid_size[2])
)
for octree_depth_now in resolutions[1:]:
grid_size = np.array([octree_depth_now + 1] * 3)
resolution = bbox_size / octree_depth_now
next_index = torch.zeros(tuple(grid_size), dtype=dtype, device=device)
next_logits = torch.full(next_index.shape, -10000., dtype=dtype, device=device)
curr_points = extract_near_surface_volume_fn(grid_logits.squeeze(0), mc_level)
curr_points += grid_logits.squeeze(0).abs() < 0.95
if octree_depth_now == resolutions[-1]:
expand_num = 0
else:
expand_num = 1
for i in range(expand_num):
curr_points = dilate(curr_points.unsqueeze(0).to(dtype)).squeeze(0)
curr_points = dilate(curr_points.unsqueeze(0).to(dtype)).squeeze(0)
(cidx_x, cidx_y, cidx_z) = torch.where(curr_points > 0)
next_index[cidx_x * 2, cidx_y * 2, cidx_z * 2] = 1
for i in range(2 - expand_num):
next_index = dilate(next_index.unsqueeze(0)).squeeze(0)
nidx = torch.where(next_index > 0)
next_points = torch.stack(nidx, dim=1)
next_points = (next_points * torch.tensor(resolution, dtype=torch.float32, device=device) +
torch.tensor(bbox_min, dtype=torch.float32, device=device))
query_grid_num = 6
min_val = next_points.min(axis=0).values
max_val = next_points.max(axis=0).values
vol_queries_index = (next_points - min_val) / (max_val - min_val) * (query_grid_num - 0.001)
index = torch.floor(vol_queries_index).long()
index = index[..., 0] * (query_grid_num ** 2) + index[..., 1] * query_grid_num + index[..., 2]
index = index.sort()
next_points = next_points[index.indices].unsqueeze(0).contiguous()
unique_values = torch.unique(index.values, return_counts=True)
grid_logits = torch.zeros((next_points.shape[1]), dtype=latents.dtype, device=latents.device)
input_grid = [[], []]
logits_grid_list = []
start_num = 0
sum_num = 0
for grid_index, count in zip(unique_values[0].cpu().tolist(), unique_values[1].cpu().tolist()):
if sum_num + count < num_chunks or sum_num == 0:
sum_num += count
input_grid[0].append(grid_index)
input_grid[1].append(count)
else:
# geo_decoder.set_topk(input_grid)
geo_decoder.set_topk(False)
logits_grid = vae.decode(latents,next_points[:, start_num:start_num + sum_num]).sample
start_num = start_num + sum_num
logits_grid_list.append(logits_grid)
input_grid = [[grid_index], [count]]
sum_num = count
if sum_num > 0:
# geo_decoder.set_topk(input_grid)
geo_decoder.set_topk(False)
logits_grid = vae.decode(latents,next_points[:, start_num:start_num + sum_num]).sample
logits_grid_list.append(logits_grid)
logits_grid = torch.cat(logits_grid_list, dim=1)
grid_logits[index.indices] = logits_grid.squeeze(0).squeeze(-1)
next_logits[nidx] = grid_logits
grid_logits = next_logits.unsqueeze(0)
grid_logits[grid_logits == -10000.] = float('nan')
torch.cuda.empty_cache()
mesh_v_f = []
grid_logits = grid_logits[0]
try:
print("final grids shape = ", grid_logits.shape)
dmc = DiffDMC(dtype=torch.float32).to(grid_logits.device)
sdf = -grid_logits / octree_resolution
sdf = sdf.to(torch.float32).contiguous()
vertices, faces = dmc(sdf, deform=None, return_quads=False, normalize=False)
vertices = vertices.detach().cpu().numpy()
faces = faces.detach().cpu().numpy()[:, ::-1]
vertices = vertices / (2 ** octree_depth) * bbox_size + bbox_min
mesh_v_f = (vertices.astype(np.float32), np.ascontiguousarray(faces))
except Exception as e:
print(e)
torch.cuda.empty_cache()
mesh_v_f = (None, None)
return [mesh_v_f]
@@ -0,0 +1,420 @@
from typing import Callable, List, Optional, Tuple, Union
import torch
import torch.nn.functional as F
from diffusers.models.attention_processor import Attention
from diffusers.models.embeddings import apply_rotary_emb
from diffusers.utils import logging
from einops import rearrange
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
class FlashTripoSGAttnProcessor2_0:
r"""
Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0). This is
used in the Tripo2DiT model. It applies a s normalization layer and rotary embedding on query and key vector.
"""
def __init__(self, topk=True):
if not hasattr(F, "scaled_dot_product_attention"):
raise ImportError(
"AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0."
)
self.topk = topk
def qkv(self, attn, q, k, v, attn_mask, dropout_p, is_causal):
if k.shape[-2] == 3072:
topk = 1024
elif k.shape[-2] == 512:
topk = 256
else:
topk = k.shape[-2] // 3
if self.topk is True:
q1 = q[:, :, ::100, :]
sim = q1 @ k.transpose(-1, -2)
sim = torch.mean(sim, -2)
topk_ind = torch.topk(sim, dim=-1, k=topk).indices.squeeze(-2).unsqueeze(-1)
topk_ind = topk_ind.expand(-1, -1, -1, v.shape[-1])
v0 = torch.gather(v, dim=-2, index=topk_ind)
k0 = torch.gather(k, dim=-2, index=topk_ind)
out = F.scaled_dot_product_attention(q, k0, v0)
elif self.topk is False:
out = F.scaled_dot_product_attention(q, k, v)
else:
idx, counts = self.topk
start = 0
outs = []
for grid_coord, count in zip(idx, counts):
end = start + count
q_chunk = q[:, :, start:end, :]
q1 = q_chunk[:, :, ::50, :]
sim = q1 @ k.transpose(-1, -2)
sim = torch.mean(sim, -2)
topk_ind = torch.topk(sim, dim=-1, k=topk).indices.squeeze(-2).unsqueeze(-1)
topk_ind = topk_ind.expand(-1, -1, -1, v.shape[-1])
v0 = torch.gather(v, dim=-2, index=topk_ind)
k0 = torch.gather(k, dim=-2, index=topk_ind)
out = F.scaled_dot_product_attention(q_chunk, k0, v0)
outs.append(out)
start += count
out = torch.cat(outs, dim=-2)
self.topk = False
return out
def __call__(
self,
attn: Attention,
hidden_states: torch.Tensor,
encoder_hidden_states: Optional[torch.Tensor] = None,
attention_mask: Optional[torch.Tensor] = None,
temb: Optional[torch.Tensor] = None,
image_rotary_emb: Optional[torch.Tensor] = None,
**kwargs,
) -> torch.Tensor:
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
input_ndim = hidden_states.ndim
if input_ndim == 4:
batch_size, channel, height, width = hidden_states.shape
hidden_states = hidden_states.view(
batch_size, channel, height * width
).transpose(1, 2)
batch_size, sequence_length, _ = (
hidden_states.shape
if encoder_hidden_states is None
else encoder_hidden_states.shape
)
if attention_mask is not None:
attention_mask = attn.prepare_attention_mask(
attention_mask, sequence_length, batch_size
)
# scaled_dot_product_attention expects attention_mask shape to be
# (batch, heads, source_length, target_length)
attention_mask = attention_mask.view(
batch_size, attn.heads, -1, attention_mask.shape[-1]
)
if attn.group_norm is not None:
hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(
1, 2
)
query = attn.to_q(hidden_states)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(
encoder_hidden_states
)
key = attn.to_k(encoder_hidden_states)
value = attn.to_v(encoder_hidden_states)
# NOTE that tripo2 split heads first then split qkv or kv, like .view(..., attn.heads, 3, dim)
# instead of .view(..., 3, attn.heads, dim). So we need to re-split here.
if not attn.is_cross_attention:
qkv = torch.cat((query, key, value), dim=-1)
split_size = qkv.shape[-1] // attn.heads // 3
qkv = qkv.view(batch_size, -1, attn.heads, split_size * 3)
query, key, value = torch.split(qkv, split_size, dim=-1)
else:
kv = torch.cat((key, value), dim=-1)
split_size = kv.shape[-1] // attn.heads // 2
kv = kv.view(batch_size, -1, attn.heads, split_size * 2)
key, value = torch.split(kv, split_size, dim=-1)
head_dim = key.shape[-1]
query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
if attn.norm_q is not None:
query = attn.norm_q(query)
if attn.norm_k is not None:
key = attn.norm_k(key)
# Apply RoPE if needed
if image_rotary_emb is not None:
query = apply_rotary_emb(query, image_rotary_emb)
if not attn.is_cross_attention:
key = apply_rotary_emb(key, image_rotary_emb)
# flashvdm topk
hidden_states = self.qkv(attn, query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False)
hidden_states = hidden_states.transpose(1, 2).reshape(
batch_size, -1, attn.heads * head_dim
)
hidden_states = hidden_states.to(query.dtype)
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
if input_ndim == 4:
hidden_states = hidden_states.transpose(-1, -2).reshape(
batch_size, channel, height, width
)
if attn.residual_connection:
hidden_states = hidden_states + residual
hidden_states = hidden_states / attn.rescale_output_factor
return hidden_states
class TripoSGAttnProcessor2_0:
r"""
Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0). This is
used in the TripoSG model. It applies a s normalization layer and rotary embedding on query and key vector.
"""
def __init__(self):
if not hasattr(F, "scaled_dot_product_attention"):
raise ImportError(
"AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0."
)
def __call__(
self,
attn: Attention,
hidden_states: torch.Tensor,
encoder_hidden_states: Optional[torch.Tensor] = None,
attention_mask: Optional[torch.Tensor] = None,
temb: Optional[torch.Tensor] = None,
image_rotary_emb: Optional[torch.Tensor] = None,
) -> torch.Tensor:
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
input_ndim = hidden_states.ndim
if input_ndim == 4:
batch_size, channel, height, width = hidden_states.shape
hidden_states = hidden_states.view(
batch_size, channel, height * width
).transpose(1, 2)
batch_size, sequence_length, _ = (
hidden_states.shape
if encoder_hidden_states is None
else encoder_hidden_states.shape
)
if attention_mask is not None:
attention_mask = attn.prepare_attention_mask(
attention_mask, sequence_length, batch_size
)
# scaled_dot_product_attention expects attention_mask shape to be
# (batch, heads, source_length, target_length)
attention_mask = attention_mask.view(
batch_size, attn.heads, -1, attention_mask.shape[-1]
)
if attn.group_norm is not None:
hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(
1, 2
)
query = attn.to_q(hidden_states)
if encoder_hidden_states is None:
encoder_hidden_states = hidden_states
elif attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(
encoder_hidden_states
)
key = attn.to_k(encoder_hidden_states)
value = attn.to_v(encoder_hidden_states)
# NOTE that pre-trained models split heads first then split qkv or kv, like .view(..., attn.heads, 3, dim)
# instead of .view(..., 3, attn.heads, dim). So we need to re-split here.
if not attn.is_cross_attention:
qkv = torch.cat((query, key, value), dim=-1)
split_size = qkv.shape[-1] // attn.heads // 3
qkv = qkv.view(batch_size, -1, attn.heads, split_size * 3)
query, key, value = torch.split(qkv, split_size, dim=-1)
else:
kv = torch.cat((key, value), dim=-1)
split_size = kv.shape[-1] // attn.heads // 2
kv = kv.view(batch_size, -1, attn.heads, split_size * 2)
key, value = torch.split(kv, split_size, dim=-1)
head_dim = key.shape[-1]
query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
if attn.norm_q is not None:
query = attn.norm_q(query)
if attn.norm_k is not None:
key = attn.norm_k(key)
# Apply RoPE if needed
if image_rotary_emb is not None:
query = apply_rotary_emb(query, image_rotary_emb)
if not attn.is_cross_attention:
key = apply_rotary_emb(key, image_rotary_emb)
# the output of sdp = (batch, num_heads, seq_len, head_dim)
# TODO: add support for attn.scale when we move to Torch 2.1
hidden_states = F.scaled_dot_product_attention(
query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False
)
hidden_states = hidden_states.transpose(1, 2).reshape(
batch_size, -1, attn.heads * head_dim
)
hidden_states = hidden_states.to(query.dtype)
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
if input_ndim == 4:
hidden_states = hidden_states.transpose(-1, -2).reshape(
batch_size, channel, height, width
)
if attn.residual_connection:
hidden_states = hidden_states + residual
hidden_states = hidden_states / attn.rescale_output_factor
return hidden_states
class FusedTripoSGAttnProcessor2_0:
r"""
Processor for implementing scaled dot-product attention (enabled by default if you're using PyTorch 2.0) with fused
projection layers. This is used in the HunyuanDiT model. It applies a s normalization layer and rotary embedding on
query and key vector.
"""
def __init__(self):
if not hasattr(F, "scaled_dot_product_attention"):
raise ImportError(
"FusedTripoSGAttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0."
)
def __call__(
self,
attn: Attention,
hidden_states: torch.Tensor,
encoder_hidden_states: Optional[torch.Tensor] = None,
attention_mask: Optional[torch.Tensor] = None,
temb: Optional[torch.Tensor] = None,
image_rotary_emb: Optional[torch.Tensor] = None,
) -> torch.Tensor:
residual = hidden_states
if attn.spatial_norm is not None:
hidden_states = attn.spatial_norm(hidden_states, temb)
input_ndim = hidden_states.ndim
if input_ndim == 4:
batch_size, channel, height, width = hidden_states.shape
hidden_states = hidden_states.view(
batch_size, channel, height * width
).transpose(1, 2)
batch_size, sequence_length, _ = (
hidden_states.shape
if encoder_hidden_states is None
else encoder_hidden_states.shape
)
if attention_mask is not None:
attention_mask = attn.prepare_attention_mask(
attention_mask, sequence_length, batch_size
)
# scaled_dot_product_attention expects attention_mask shape to be
# (batch, heads, source_length, target_length)
attention_mask = attention_mask.view(
batch_size, attn.heads, -1, attention_mask.shape[-1]
)
if attn.group_norm is not None:
hidden_states = attn.group_norm(hidden_states.transpose(1, 2)).transpose(
1, 2
)
# NOTE that pre-trained split heads first, then split qkv
if encoder_hidden_states is None:
qkv = attn.to_qkv(hidden_states)
split_size = qkv.shape[-1] // attn.heads // 3
qkv = qkv.view(batch_size, -1, attn.heads, split_size * 3)
query, key, value = torch.split(qkv, split_size, dim=-1)
else:
if attn.norm_cross:
encoder_hidden_states = attn.norm_encoder_hidden_states(
encoder_hidden_states
)
query = attn.to_q(hidden_states)
kv = attn.to_kv(encoder_hidden_states)
split_size = kv.shape[-1] // attn.heads // 2
kv = kv.view(batch_size, -1, attn.heads, split_size * 2)
key, value = torch.split(kv, split_size, dim=-1)
head_dim = key.shape[-1]
query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
if attn.norm_q is not None:
query = attn.norm_q(query)
if attn.norm_k is not None:
key = attn.norm_k(key)
# Apply RoPE if needed
if image_rotary_emb is not None:
query = apply_rotary_emb(query, image_rotary_emb)
if not attn.is_cross_attention:
key = apply_rotary_emb(key, image_rotary_emb)
# the output of sdp = (batch, num_heads, seq_len, head_dim)
# TODO: add support for attn.scale when we move to Torch 2.1
hidden_states = F.scaled_dot_product_attention(
query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False
)
hidden_states = hidden_states.transpose(1, 2).reshape(
batch_size, -1, attn.heads * head_dim
)
hidden_states = hidden_states.to(query.dtype)
# linear proj
hidden_states = attn.to_out[0](hidden_states)
# dropout
hidden_states = attn.to_out[1](hidden_states)
if input_ndim == 4:
hidden_states = hidden_states.transpose(-1, -2).reshape(
batch_size, channel, height, width
)
if attn.residual_connection:
hidden_states = hidden_states + residual
hidden_states = hidden_states / attn.rescale_output_factor
return hidden_states
@@ -0,0 +1 @@
from .autoencoder_kl_triposg import TripoSGVAEModel
@@ -0,0 +1,536 @@
from typing import Dict, List, Optional, Tuple, Union
import numpy as np
import torch
import torch.nn as nn
from diffusers.configuration_utils import ConfigMixin, register_to_config
from diffusers.models.attention_processor import Attention, AttentionProcessor
from diffusers.models.autoencoders.vae import DecoderOutput
from diffusers.models.modeling_outputs import AutoencoderKLOutput
from diffusers.models.modeling_utils import ModelMixin
from diffusers.models.normalization import FP32LayerNorm, LayerNorm
from diffusers.utils import logging
from diffusers.utils.accelerate_utils import apply_forward_hook
from einops import repeat
# from torch_cluster import fps
from tqdm import tqdm
from ..attention_processor import FusedTripoSGAttnProcessor2_0, TripoSGAttnProcessor2_0, FlashTripoSGAttnProcessor2_0
from ..embeddings import FrequencyPositionalEmbedding
from ..transformers.triposg_transformer import DiTBlock
from .vae import DiagonalGaussianDistribution
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
class TripoSGEncoder(nn.Module):
def __init__(
self,
in_channels: int = 3,
dim: int = 512,
num_attention_heads: int = 8,
num_layers: int = 8,
):
super().__init__()
self.proj_in = nn.Linear(in_channels, dim, bias=True)
self.blocks = nn.ModuleList(
[
DiTBlock(
dim=dim,
num_attention_heads=num_attention_heads,
use_self_attention=False,
use_cross_attention=True,
cross_attention_dim=dim,
cross_attention_norm_type="layer_norm",
activation_fn="gelu",
norm_type="fp32_layer_norm",
norm_eps=1e-5,
qk_norm=False,
qkv_bias=False,
) # cross attention
]
+ [
DiTBlock(
dim=dim,
num_attention_heads=num_attention_heads,
use_self_attention=True,
self_attention_norm_type="fp32_layer_norm",
use_cross_attention=False,
activation_fn="gelu",
norm_type="fp32_layer_norm",
norm_eps=1e-5,
qk_norm=False,
qkv_bias=False,
)
for _ in range(num_layers) # self attention
]
)
self.norm_out = LayerNorm(dim)
def forward(self, sample_1: torch.Tensor, sample_2: torch.Tensor):
hidden_states = self.proj_in(sample_1)
encoder_hidden_states = self.proj_in(sample_2)
for layer, block in enumerate(self.blocks):
if layer == 0:
hidden_states = block(
hidden_states, encoder_hidden_states=encoder_hidden_states
)
else:
hidden_states = block(hidden_states)
hidden_states = self.norm_out(hidden_states)
return hidden_states
class TripoSGDecoder(nn.Module):
def __init__(
self,
in_channels: int = 3,
out_channels: int = 1,
dim: int = 512,
num_attention_heads: int = 8,
num_layers: int = 16,
grad_type: str = "analytical",
grad_interval: float = 0.001,
):
super().__init__()
if grad_type not in ["numerical", "analytical"]:
raise ValueError(f"grad_type must be one of ['numerical', 'analytical']")
self.grad_type = grad_type
self.grad_interval = grad_interval
self.blocks = nn.ModuleList(
[
DiTBlock(
dim=dim,
num_attention_heads=num_attention_heads,
use_self_attention=True,
self_attention_norm_type="fp32_layer_norm",
use_cross_attention=False,
activation_fn="gelu",
norm_type="fp32_layer_norm",
norm_eps=1e-5,
qk_norm=False,
qkv_bias=False,
)
for _ in range(num_layers) # self attention
]
+ [
DiTBlock(
dim=dim,
num_attention_heads=num_attention_heads,
use_self_attention=False,
use_cross_attention=True,
cross_attention_dim=dim,
cross_attention_norm_type="layer_norm",
activation_fn="gelu",
norm_type="fp32_layer_norm",
norm_eps=1e-5,
qk_norm=False,
qkv_bias=False,
) # cross attention
]
)
self.proj_query = nn.Linear(in_channels, dim, bias=True)
self.norm_out = LayerNorm(dim)
self.proj_out = nn.Linear(dim, out_channels, bias=True)
def set_topk(self, topk):
self.blocks[-1].set_topk(topk)
def set_flash_processor(self, processor):
self.blocks[-1].set_flash_processor(processor)
def query_geometry(
self,
model_fn: callable,
queries: torch.Tensor,
sample: torch.Tensor,
grad: bool = False,
):
logits = model_fn(queries, sample)
if grad:
with torch.autocast(device_type="cuda", dtype=torch.float32):
if self.grad_type == "numerical":
interval = self.grad_interval
grad_value = []
for offset in [
(interval, 0, 0),
(0, interval, 0),
(0, 0, interval),
]:
offset_tensor = torch.tensor(offset, device=queries.device)[
None, :
]
res_p = model_fn(queries + offset_tensor, sample)[..., 0]
res_n = model_fn(queries - offset_tensor, sample)[..., 0]
grad_value.append((res_p - res_n) / (2 * interval))
grad_value = torch.stack(grad_value, dim=-1)
else:
queries_d = torch.clone(queries)
queries_d.requires_grad = True
with torch.enable_grad():
res_d = model_fn(queries_d, sample)
grad_value = torch.autograd.grad(
res_d,
[queries_d],
grad_outputs=torch.ones_like(res_d),
create_graph=self.training,
)[0]
else:
grad_value = None
return logits, grad_value
def forward(
self,
sample: torch.Tensor,
queries: torch.Tensor,
kv_cache: Optional[torch.Tensor] = None,
):
if kv_cache is None:
hidden_states = sample
for _, block in enumerate(self.blocks[:-1]):
hidden_states = block(hidden_states)
kv_cache = hidden_states
# query grid logits by cross attention
def query_fn(q, kv):
q = self.proj_query(q)
l = self.blocks[-1](q, encoder_hidden_states=kv)
return self.proj_out(self.norm_out(l))
logits, grad = self.query_geometry(
query_fn, queries, kv_cache, grad=self.training
)
logits = logits * -1 if not isinstance(logits, Tuple) else logits[0] * -1
return logits, kv_cache
class TripoSGVAEModel(ModelMixin, ConfigMixin):
@register_to_config
def __init__(
self,
in_channels: int = 3, # NOTE xyz instead of feature dim
latent_channels: int = 64,
num_attention_heads: int = 8,
width_encoder: int = 512,
width_decoder: int = 1024,
num_layers_encoder: int = 8,
num_layers_decoder: int = 16,
embedding_type: str = "frequency",
embed_frequency: int = 8,
embed_include_pi: bool = False,
):
super().__init__()
self.out_channels = 1
if embedding_type == "frequency":
self.embedder = FrequencyPositionalEmbedding(
num_freqs=embed_frequency,
logspace=True,
input_dim=in_channels,
include_pi=embed_include_pi,
)
else:
raise NotImplementedError(
f"Embedding type {embedding_type} is not supported."
)
self.encoder = TripoSGEncoder(
in_channels=in_channels + self.embedder.out_dim,
dim=width_encoder,
num_attention_heads=num_attention_heads,
num_layers=num_layers_encoder,
)
self.decoder = TripoSGDecoder(
in_channels=self.embedder.out_dim,
out_channels=self.out_channels,
dim=width_decoder,
num_attention_heads=num_attention_heads,
num_layers=num_layers_decoder,
)
self.quant = nn.Linear(width_encoder, latent_channels * 2, bias=True)
self.post_quant = nn.Linear(latent_channels, width_decoder, bias=True)
self.use_slicing = False
self.slicing_length = 1
def set_flash_decoder(self):
self.decoder.set_flash_processor(FlashTripoSGAttnProcessor2_0())
# Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.fuse_qkv_projections with FusedAttnProcessor2_0->FusedTripoSGAttnProcessor2_0
def fuse_qkv_projections(self):
"""
Enables fused QKV projections. For self-attention modules, all projection matrices (i.e., query, key, value)
are fused. For cross-attention modules, key and value projection matrices are fused.
<Tip warning={true}>
This API is 🧪 experimental.
</Tip>
"""
self.original_attn_processors = None
for _, attn_processor in self.attn_processors.items():
if "Added" in str(attn_processor.__class__.__name__):
raise ValueError(
"`fuse_qkv_projections()` is not supported for models having added KV projections."
)
self.original_attn_processors = self.attn_processors
for module in self.modules():
if isinstance(module, Attention):
module.fuse_projections(fuse=True)
self.set_attn_processor(FusedTripoSGAttnProcessor2_0())
# Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.unfuse_qkv_projections
def unfuse_qkv_projections(self):
"""Disables the fused QKV projection if enabled.
<Tip warning={true}>
This API is 🧪 experimental.
</Tip>
"""
if self.original_attn_processors is not None:
self.set_attn_processor(self.original_attn_processors)
@property
# Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.attn_processors
def attn_processors(self) -> Dict[str, AttentionProcessor]:
r"""
Returns:
`dict` of attention processors: A dictionary containing all attention processors used in the model with
indexed by its weight name.
"""
# set recursively
processors = {}
def fn_recursive_add_processors(
name: str,
module: torch.nn.Module,
processors: Dict[str, AttentionProcessor],
):
if hasattr(module, "get_processor"):
processors[f"{name}.processor"] = module.get_processor()
for sub_name, child in module.named_children():
fn_recursive_add_processors(f"{name}.{sub_name}", child, processors)
return processors
for name, module in self.named_children():
fn_recursive_add_processors(name, module, processors)
return processors
# Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.set_attn_processor
def set_attn_processor(
self, processor: Union[AttentionProcessor, Dict[str, AttentionProcessor]]
):
r"""
Sets the attention processor to use to compute attention.
Parameters:
processor (`dict` of `AttentionProcessor` or only `AttentionProcessor`):
The instantiated processor class or a dictionary of processor classes that will be set as the processor
for **all** `Attention` layers.
If `processor` is a dict, the key needs to define the path to the corresponding cross attention
processor. This is strongly recommended when setting trainable attention processors.
"""
count = len(self.attn_processors.keys())
if isinstance(processor, dict) and len(processor) != count:
raise ValueError(
f"A dict of processors was passed, but the number of processors {len(processor)} does not match the"
f" number of attention layers: {count}. Please make sure to pass {count} processor classes."
)
def fn_recursive_attn_processor(name: str, module: torch.nn.Module, processor):
if hasattr(module, "set_processor"):
if not isinstance(processor, dict):
module.set_processor(processor)
else:
module.set_processor(processor.pop(f"{name}.processor"))
for sub_name, child in module.named_children():
fn_recursive_attn_processor(f"{name}.{sub_name}", child, processor)
for name, module in self.named_children():
fn_recursive_attn_processor(name, module, processor)
def set_default_attn_processor(self):
"""
Disables custom attention processors and sets the default attention implementation.
"""
self.set_attn_processor(TripoSGAttnProcessor2_0())
def enable_slicing(self, slicing_length: int = 1) -> None:
r"""
Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to
compute decoding in several steps. This is useful to save some memory and allow larger batch sizes.
"""
self.use_slicing = True
self.slicing_length = slicing_length
def disable_slicing(self) -> None:
r"""
Disable sliced VAE decoding. If `enable_slicing` was previously enabled, this method will go back to computing
decoding in one step.
"""
self.use_slicing = False
def _sample_features(
self, x: torch.Tensor, num_tokens: int = 2048, seed: Optional[int] = None
):
"""
Sample points from features of the input point cloud.
Args:
x (torch.Tensor): The input point cloud. shape: (B, N, C)
num_tokens (int, optional): The number of points to sample. Defaults to 2048.
seed (Optional[int], optional): The random seed. Defaults to None.
"""
rng = np.random.default_rng(seed)
indices = rng.choice(
x.shape[1], num_tokens * 4, replace=num_tokens * 4 > x.shape[1]
)
selected_points = x[:, indices]
batch_size, num_points, num_channels = selected_points.shape
flattened_points = selected_points.view(batch_size * num_points, num_channels)
batch_indices = (
torch.arange(batch_size).to(x.device).repeat_interleave(num_points)
)
# fps sampling
sampling_ratio = 1.0 / 4
sampled_indices = fps(
flattened_points[:, :3],
batch_indices,
ratio=sampling_ratio,
random_start=self.training,
)
sampled_points = flattened_points[sampled_indices].view(
batch_size, -1, num_channels
)
return sampled_points
def _encode(
self, x: torch.Tensor, num_tokens: int = 2048, seed: Optional[int] = None
):
position_channels = self.config.in_channels
positions, features = x[..., :position_channels], x[..., position_channels:]
x_kv = torch.cat([self.embedder(positions), features], dim=-1)
sampled_x = self._sample_features(x, num_tokens, seed)
positions, features = (
sampled_x[..., :position_channels],
sampled_x[..., position_channels:],
)
x_q = torch.cat([self.embedder(positions), features], dim=-1)
x = self.encoder(x_q, x_kv)
x = self.quant(x)
return x
@apply_forward_hook
def encode(
self, x: torch.Tensor, return_dict: bool = True, **kwargs
) -> Union[AutoencoderKLOutput, Tuple[DiagonalGaussianDistribution]]:
"""
Encode a batch of point features into latents.
"""
if self.use_slicing and x.shape[0] > 1:
encoded_slices = [
self._encode(x_slice, **kwargs)
for x_slice in x.split(self.slicing_length)
]
h = torch.cat(encoded_slices)
else:
h = self._encode(x, **kwargs)
posterior = DiagonalGaussianDistribution(h, feature_dim=-1)
if not return_dict:
return (posterior,)
return AutoencoderKLOutput(latent_dist=posterior)
def _decode(
self,
z: torch.Tensor,
sampled_points: torch.Tensor,
num_chunks: int = 50000,
to_cpu: bool = False,
return_dict: bool = True,
) -> Union[DecoderOutput, torch.Tensor]:
xyz_samples = sampled_points
z = self.post_quant(z)
num_points = xyz_samples.shape[1]
kv_cache = None
dec = []
for i in range(0, num_points, num_chunks):
queries = xyz_samples[:, i : i + num_chunks, :].to(z.device, dtype=z.dtype)
queries = self.embedder(queries)
z_, kv_cache = self.decoder(z, queries, kv_cache)
dec.append(z_ if not to_cpu else z_.cpu())
z = torch.cat(dec, dim=1)
if not return_dict:
return (z,)
return DecoderOutput(sample=z)
@apply_forward_hook
def decode(
self,
z: torch.Tensor,
sampled_points: torch.Tensor,
return_dict: bool = True,
**kwargs,
) -> Union[DecoderOutput, torch.Tensor]:
if self.use_slicing and z.shape[0] > 1:
decoded_slices = [
self._decode(z_slice, p_slice, **kwargs).sample
for z_slice, p_slice in zip(
z.split(self.slicing_length),
sampled_points.split(self.slicing_length),
)
]
decoded = torch.cat(decoded_slices)
else:
decoded = self._decode(z, sampled_points, **kwargs).sample
if not return_dict:
return (decoded,)
return DecoderOutput(sample=decoded)
def forward(self, x: torch.Tensor):
pass
@@ -0,0 +1,69 @@
from typing import Optional, Tuple
import numpy as np
import torch
from diffusers.utils.torch_utils import randn_tensor
class DiagonalGaussianDistribution(object):
def __init__(
self,
parameters: torch.Tensor,
deterministic: bool = False,
feature_dim: int = 1,
):
self.parameters = parameters
self.feature_dim = feature_dim
self.mean, self.logvar = torch.chunk(parameters, 2, dim=feature_dim)
self.logvar = torch.clamp(self.logvar, -30.0, 20.0)
self.deterministic = deterministic
self.std = torch.exp(0.5 * self.logvar)
self.var = torch.exp(self.logvar)
if self.deterministic:
self.var = self.std = torch.zeros_like(
self.mean, device=self.parameters.device, dtype=self.parameters.dtype
)
def sample(self, generator: Optional[torch.Generator] = None) -> torch.Tensor:
# make sure sample is on the same device as the parameters and has same dtype
sample = randn_tensor(
self.mean.shape,
generator=generator,
device=self.parameters.device,
dtype=self.parameters.dtype,
)
x = self.mean + self.std * sample
return x
def kl(self, other: "DiagonalGaussianDistribution" = None) -> torch.Tensor:
if self.deterministic:
return torch.Tensor([0.0])
else:
if other is None:
return 0.5 * torch.sum(
torch.pow(self.mean, 2) + self.var - 1.0 - self.logvar,
dim=[1, 2, 3],
)
else:
return 0.5 * torch.sum(
torch.pow(self.mean - other.mean, 2) / other.var
+ self.var / other.var
- 1.0
- self.logvar
+ other.logvar,
dim=[1, 2, 3],
)
def nll(
self, sample: torch.Tensor, dims: Tuple[int, ...] = [1, 2, 3]
) -> torch.Tensor:
if self.deterministic:
return torch.Tensor([0.0])
logtwopi = np.log(2.0 * np.pi)
return 0.5 * torch.sum(
logtwopi + self.logvar + torch.pow(sample - self.mean, 2) / self.var,
dim=dims,
)
def mode(self) -> torch.Tensor:
return self.mean
@@ -0,0 +1,96 @@
import torch
import torch.nn as nn
class FrequencyPositionalEmbedding(nn.Module):
"""The sin/cosine positional embedding. Given an input tensor `x` of shape [n_batch, ..., c_dim], it converts
each feature dimension of `x[..., i]` into:
[
sin(x[..., i]),
sin(f_1*x[..., i]),
sin(f_2*x[..., i]),
...
sin(f_N * x[..., i]),
cos(x[..., i]),
cos(f_1*x[..., i]),
cos(f_2*x[..., i]),
...
cos(f_N * x[..., i]),
x[..., i] # only present if include_input is True.
], here f_i is the frequency.
Denote the space is [0 / num_freqs, 1 / num_freqs, 2 / num_freqs, 3 / num_freqs, ..., (num_freqs - 1) / num_freqs].
If logspace is True, then the frequency f_i is [2^(0 / num_freqs), ..., 2^(i / num_freqs), ...];
Otherwise, the frequencies are linearly spaced between [1.0, 2^(num_freqs - 1)].
Args:
num_freqs (int): the number of frequencies, default is 6;
logspace (bool): If logspace is True, then the frequency f_i is [..., 2^(i / num_freqs), ...],
otherwise, the frequencies are linearly spaced between [1.0, 2^(num_freqs - 1)];
input_dim (int): the input dimension, default is 3;
include_input (bool): include the input tensor or not, default is True.
Attributes:
frequencies (torch.Tensor): If logspace is True, then the frequency f_i is [..., 2^(i / num_freqs), ...],
otherwise, the frequencies are linearly spaced between [1.0, 2^(num_freqs - 1);
out_dim (int): the embedding size, if include_input is True, it is input_dim * (num_freqs * 2 + 1),
otherwise, it is input_dim * num_freqs * 2.
"""
def __init__(
self,
num_freqs: int = 6,
logspace: bool = True,
input_dim: int = 3,
include_input: bool = True,
include_pi: bool = True,
) -> None:
"""The initialization"""
super().__init__()
if logspace:
frequencies = 2.0 ** torch.arange(num_freqs, dtype=torch.float32)
else:
frequencies = torch.linspace(
1.0, 2.0 ** (num_freqs - 1), num_freqs, dtype=torch.float32
)
if include_pi:
frequencies *= torch.pi
self.register_buffer("frequencies", frequencies, persistent=False)
self.include_input = include_input
self.num_freqs = num_freqs
self.out_dim = self.get_dims(input_dim)
def get_dims(self, input_dim):
temp = 1 if self.include_input or self.num_freqs == 0 else 0
out_dim = input_dim * (self.num_freqs * 2 + temp)
return out_dim
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""Forward process.
Args:
x: tensor of shape [..., dim]
Returns:
embedding: an embedding of `x` of shape [..., dim * (num_freqs * 2 + temp)]
where temp is 1 if include_input is True and 0 otherwise.
"""
if self.num_freqs > 0:
embed = (x[..., None].contiguous() * self.frequencies).view(
*x.shape[:-1], -1
)
if self.include_input:
return torch.cat((x, embed.sin(), embed.cos()), dim=-1)
else:
return torch.cat((embed.sin(), embed.cos()), dim=-1)
else:
return x
@@ -0,0 +1,61 @@
from typing import Callable, Optional
from .triposg_transformer import TripoSGDiTModel
def default_set_attn_proc_func(
name: str,
hidden_size: int,
cross_attention_dim: Optional[int],
ori_attn_proc: object,
) -> object:
return ori_attn_proc
def set_transformer_attn_processor(
transformer: TripoSGDiTModel,
set_self_attn_proc_func: Callable = default_set_attn_proc_func,
set_cross_attn_1_proc_func: Callable = default_set_attn_proc_func,
set_cross_attn_2_proc_func: Callable = default_set_attn_proc_func,
set_self_attn_module_names: Optional[list[str]] = None,
set_cross_attn_1_module_names: Optional[list[str]] = None,
set_cross_attn_2_module_names: Optional[list[str]] = None,
) -> None:
do_set_processor = lambda name, module_names: (
any([name.startswith(module_name) for module_name in module_names])
if module_names is not None
else True
) # prefix match
attn_procs = {}
for name, attn_processor in transformer.attn_processors.items():
hidden_size = transformer.config.width
if name.endswith("attn1.processor"):
# self attention
attn_procs[name] = (
set_self_attn_proc_func(name, hidden_size, None, attn_processor)
if do_set_processor(name, set_self_attn_module_names)
else attn_processor
)
elif name.endswith("attn2.processor"):
# cross attention
cross_attention_dim = transformer.config.cross_attention_dim
attn_procs[name] = (
set_cross_attn_1_proc_func(
name, hidden_size, cross_attention_dim, attn_processor
)
if do_set_processor(name, set_cross_attn_1_module_names)
else attn_processor
)
elif name.endswith("attn2_2.processor"):
# cross attention 2
cross_attention_dim = transformer.config.cross_attention_2_dim
attn_procs[name] = (
set_cross_attn_2_proc_func(
name, hidden_size, cross_attention_dim, attn_processor
)
if do_set_processor(name, set_cross_attn_2_module_names)
else attn_processor
)
transformer.set_attn_processor(attn_procs)
@@ -0,0 +1,8 @@
from dataclasses import dataclass
import torch
@dataclass
class Transformer1DModelOutput:
sample: torch.FloatTensor
@@ -0,0 +1,775 @@
# Copyright (c) 2025 VAST-AI-Research and contributors
# This code is based on Tencent HunyuanDiT (https://huggingface.co/Tencent-Hunyuan/HunyuanDiT),
# which is licensed under the Tencent Hunyuan Community License Agreement.
# Portions of this code are copied or adapted from HunyuanDiT.
# See the original license below:
# ---- Start of Tencent Hunyuan Community License Agreement ----
# TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT
# Tencent Hunyuan DiT Release Date: 14 May 2024
# THIS LICENSE AGREEMENT DOES NOT APPLY IN THE EUROPEAN UNION AND IS EXPRESSLY LIMITED TO THE TERRITORY, AS DEFINED BELOW.
# By clicking to agree or by using, reproducing, modifying, distributing, performing or displaying any portion or element of the Tencent Hunyuan Works, including via any Hosted Service, You will be deemed to have recognized and accepted the content of this Agreement, which is effective immediately.
# 1. DEFINITIONS.
# a. “Acceptable Use Policy” shall mean the policy made available by Tencent as set forth in the Exhibit A.
# b. “Agreement” shall mean the terms and conditions for use, reproduction, distribution, modification, performance and displaying of Tencent Hunyuan Works or any portion or element thereof set forth herein.
# c. “Documentation” shall mean the specifications, manuals and documentation for Tencent Hunyuan made publicly available by Tencent.
# d. “Hosted Service” shall mean a hosted service offered via an application programming interface (API), web access, or any other electronic or remote means.
# e. “Licensee,” “You” or “Your” shall mean a natural person or legal entity exercising the rights granted by this Agreement and/or using the Tencent Hunyuan Works for any purpose and in any field of use.
# f. “Materials” shall mean, collectively, Tencent’s proprietary Tencent Hunyuan and Documentation (and any portion thereof) as made available by Tencent under this Agreement.
# g. “Model Derivatives” shall mean all: (i) modifications to Tencent Hunyuan or any Model Derivative of Tencent Hunyuan; (ii) works based on Tencent Hunyuan or any Model Derivative of Tencent Hunyuan; or (iii) any other machine learning model which is created by transfer of patterns of the weights, parameters, operations, or Output of Tencent Hunyuan or any Model Derivative of Tencent Hunyuan, to that model in order to cause that model to perform similarly to Tencent Hunyuan or a Model Derivative of Tencent Hunyuan, including distillation methods, methods that use intermediate data representations, or methods based on the generation of synthetic data Outputs by Tencent Hunyuan or a Model Derivative of Tencent Hunyuan for training that model. For clarity, Outputs by themselves are not deemed Model Derivatives.
# h. “Output” shall mean the information and/or content output of Tencent Hunyuan or a Model Derivative that results from operating or otherwise using Tencent Hunyuan or a Model Derivative, including via a Hosted Service.
# i. “Tencent,” “We” or “Us” shall mean THL A29 Limited.
# j. “Tencent Hunyuan” shall mean the large language models, text/image/video/audio/3D generation models, and multimodal large language models and their software and algorithms, including trained model weights, parameters (including optimizer states), machine-learning model code, inference-enabling code, training-enabling code, fine-tuning enabling code and other elements of the foregoing made publicly available by Us, including, without limitation to, Tencent Hunyuan DiT released at https://huggingface.co/Tencent-Hunyuan/HunyuanDiT.
# k. “Tencent Hunyuan Works” shall mean: (i) the Materials; (ii) Model Derivatives; and (iii) all derivative works thereof.
# l. “Territory” shall mean the worldwide territory, excluding the territory of the European Union.
# m. “Third Party” or “Third Parties” shall mean individuals or legal entities that are not under common control with Us or You.
# n. “including” shall mean including but not limited to.
# 2. GRANT OF RIGHTS.
# We grant You, for the Territory only, a non-exclusive, non-transferable and royalty-free limited license under Tencent’s intellectual property or other rights owned by Us embodied in or utilized by the Materials to use, reproduce, distribute, create derivative works of (including Model Derivatives), and make modifications to the Materials, only in accordance with the terms of this Agreement and the Acceptable Use Policy, and You must not violate (or encourage or permit anyone else to violate) any term of this Agreement or the Acceptable Use Policy.
# 3. DISTRIBUTION.
# You may, subject to Your compliance with this Agreement, distribute or make available to Third Parties the Tencent Hunyuan Works, exclusively in the Territory, provided that You meet all of the following conditions:
# a. You must provide all such Third Party recipients of the Tencent Hunyuan Works or products or services using them a copy of this Agreement;
# b. You must cause any modified files to carry prominent notices stating that You changed the files;
# c. You are encouraged to: (i) publish at least one technology introduction blogpost or one public statement expressing Your experience of using the Tencent Hunyuan Works; and (ii) mark the products or services developed by using the Tencent Hunyuan Works to indicate that the product/service is “Powered by Tencent Hunyuan”; and
# d. All distributions to Third Parties (other than through a Hosted Service) must be accompanied by a “Notice” text file that contains the following notice: “Tencent Hunyuan is licensed under the Tencent Hunyuan Community License Agreement, Copyright © 2024 Tencent. All Rights Reserved. The trademark rights of “Tencent Hunyuan” are owned by Tencent or its affiliate.”
# You may add Your own copyright statement to Your modifications and, except as set forth in this Section and in Section 5, may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Model Derivatives as a whole, provided Your use, reproduction, modification, distribution, performance and display of the work otherwise complies with the terms and conditions of this Agreement (including as regards the Territory). If You receive Tencent Hunyuan Works from a Licensee as part of an integrated end user product, then this Section 3 of this Agreement will not apply to You.
# 4. ADDITIONAL COMMERCIAL TERMS.
# If, on the Tencent Hunyuan version release date, the monthly active users of all products or services made available by or for Licensee is greater than 100 million monthly active users in the preceding calendar month, You must request a license from Tencent, which Tencent may grant to You in its sole discretion, and You are not authorized to exercise any of the rights under this Agreement unless or until Tencent otherwise expressly grants You such rights.
# 5. RULES OF USE.
# a. Your use of the Tencent Hunyuan Works must comply with applicable laws and regulations (including trade compliance laws and regulations) and adhere to the Acceptable Use Policy for the Tencent Hunyuan Works, which is hereby incorporated by reference into this Agreement. You must include the use restrictions referenced in these Sections 5(a) and 5(b) as an enforceable provision in any agreement (e.g., license agreement, terms of use, etc.) governing the use and/or distribution of Tencent Hunyuan Works and You must provide notice to subsequent users to whom You distribute that Tencent Hunyuan Works are subject to the use restrictions in these Sections 5(a) and 5(b).
# b. You must not use the Tencent Hunyuan Works or any Output or results of the Tencent Hunyuan Works to improve any other large language model (other than Tencent Hunyuan or Model Derivatives thereof).
# c. You must not use, reproduce, modify, distribute, or display the Tencent Hunyuan Works, Output or results of the Tencent Hunyuan Works outside the Territory. Any such use outside the Territory is unlicensed and unauthorized under this Agreement.
# 6. INTELLECTUAL PROPERTY.
# a. Subject to Tencent’s ownership of Tencent Hunyuan Works made by or for Tencent and intellectual property rights therein, conditioned upon Your compliance with the terms and conditions of this Agreement, as between You and Tencent, You will be the owner of any derivative works and modifications of the Materials and any Model Derivatives that are made by or for You.
# b. No trademark licenses are granted under this Agreement, and in connection with the Tencent Hunyuan Works, Licensee may not use any name or mark owned by or associated with Tencent or any of its affiliates, except as required for reasonable and customary use in describing and distributing the Tencent Hunyuan Works. Tencent hereby grants You a license to use “Tencent Hunyuan” (the “Mark”) in the Territory solely as required to comply with the provisions of Section 3(c), provided that You comply with any applicable laws related to trademark protection. All goodwill arising out of Your use of the Mark will inure to the benefit of Tencent.
# c. If You commence a lawsuit or other proceedings (including a cross-claim or counterclaim in a lawsuit) against Us or any person or entity alleging that the Materials or any Output, or any portion of any of the foregoing, infringe any intellectual property or other right owned or licensable by You, then all licenses granted to You under this Agreement shall terminate as of the date such lawsuit or other proceeding is filed. You will defend, indemnify and hold harmless Us from and against any claim by any Third Party arising out of or related to Your or the Third Party’s use or distribution of the Tencent Hunyuan Works.
# d. Tencent claims no rights in Outputs You generate. You and Your users are solely responsible for Outputs and their subsequent uses.
# 7. DISCLAIMERS OF WARRANTY AND LIMITATIONS OF LIABILITY.
# a. We are not obligated to support, update, provide training for, or develop any further version of the Tencent Hunyuan Works or to grant any license thereto.
# b. UNLESS AND ONLY TO THE EXTENT REQUIRED BY APPLICABLE LAW, THE TENCENT HUNYUAN WORKS AND ANY OUTPUT AND RESULTS THEREFROM ARE PROVIDED “AS IS” WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES OF ANY KIND INCLUDING ANY WARRANTIES OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, COURSE OF DEALING, USAGE OF TRADE, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING, REPRODUCING, MODIFYING, PERFORMING, DISPLAYING OR DISTRIBUTING ANY OF THE TENCENT HUNYUAN WORKS OR OUTPUTS AND ASSUME ANY AND ALL RISKS ASSOCIATED WITH YOUR OR A THIRD PARTY’S USE OR DISTRIBUTION OF ANY OF THE TENCENT HUNYUAN WORKS OR OUTPUTS AND YOUR EXERCISE OF RIGHTS AND PERMISSIONS UNDER THIS AGREEMENT.
# c. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL TENCENT OR ITS AFFILIATES BE LIABLE UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, TORT, NEGLIGENCE, PRODUCTS LIABILITY, OR OTHERWISE, FOR ANY DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY, CONSEQUENTIAL OR PUNITIVE DAMAGES, OR LOST PROFITS OF ANY KIND ARISING FROM THIS AGREEMENT OR RELATED TO ANY OF THE TENCENT HUNYUAN WORKS OR OUTPUTS, EVEN IF TENCENT OR ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF ANY OF THE FOREGOING.
# 8. SURVIVAL AND TERMINATION.
# a. The term of this Agreement shall commence upon Your acceptance of this Agreement or access to the Materials and will continue in full force and effect until terminated in accordance with the terms and conditions herein.
# b. We may terminate this Agreement if You breach any of the terms or conditions of this Agreement. Upon termination of this Agreement, You must promptly delete and cease use of the Tencent Hunyuan Works. Sections 6(a), 6(c), 7 and 9 shall survive the termination of this Agreement.
# 9. GOVERNING LAW AND JURISDICTION.
# a. This Agreement and any dispute arising out of or relating to it will be governed by the laws of the Hong Kong Special Administrative Region of the People’s Republic of China, without regard to conflict of law principles, and the UN Convention on Contracts for the International Sale of Goods does not apply to this Agreement.
# b. Exclusive jurisdiction and venue for any dispute arising out of or relating to this Agreement will be a court of competent jurisdiction in the Hong Kong Special Administrative Region of the People’s Republic of China, and Tencent and Licensee consent to the exclusive jurisdiction of such court with respect to any such dispute.
#
# EXHIBIT A
# ACCEPTABLE USE POLICY
# Tencent reserves the right to update this Acceptable Use Policy from time to time.
# Last modified: [insert date]
# Tencent endeavors to promote safe and fair use of its tools and features, including Tencent Hunyuan. You agree not to use Tencent Hunyuan or Model Derivatives:
# 1. Outside the Territory;
# 2. In any way that violates any applicable national, federal, state, local, international or any other law or regulation;
# 3. To harm Yourself or others;
# 4. To repurpose or distribute output from Tencent Hunyuan or any Model Derivatives to harm Yourself or others;
# 5. To override or circumvent the safety guardrails and safeguards We have put in place;
# 6. For the purpose of exploiting, harming or attempting to exploit or harm minors in any way;
# 7. To generate or disseminate verifiably false information and/or content with the purpose of harming others or influencing elections;
# 8. To generate or facilitate false online engagement, including fake reviews and other means of fake online engagement;
# 9. To intentionally defame, disparage or otherwise harass others;
# 10. To generate and/or disseminate malware (including ransomware) or any other content to be used for the purpose of harming electronic systems;
# 11. To generate or disseminate personal identifiable information with the purpose of harming others;
# 12. To generate or disseminate information (including images, code, posts, articles), and place the information in any public context (including –through the use of bot generated tweets), without expressly and conspicuously identifying that the information and/or content is machine generated;
# 13. To impersonate another individual without consent, authorization, or legal right;
# 14. To make high-stakes automated decisions in domains that affect an individual’s safety, rights or wellbeing (e.g., law enforcement, migration, medicine/health, management of critical infrastructure, safety components of products, essential services, credit, employment, housing, education, social scoring, or insurance);
# 15. In a manner that violates or disrespects the social ethics and moral standards of other countries or regions;
# 16. To perform, facilitate, threaten, incite, plan, promote or encourage violent extremism or terrorism;
# 17. For any use intended to discriminate against or harm individuals or groups based on protected characteristics or categories, online or offline social behavior or known or predicted personal or personality characteristics;
# 18. To intentionally exploit any of the vulnerabilities of a specific group of persons based on their age, social, physical or mental characteristics, in order to materially distort the behavior of a person pertaining to that group in a manner that causes or is likely to cause that person or another person physical or psychological harm;
# 19. For military purposes;
# 20. To engage in the unauthorized or unlicensed practice of any profession including, but not limited to, financial, legal, medical/health, or other professional practices.
# ---- End of Tencent Hunyuan Community License Agreement ----
# Please note that the use of this code is subject to the terms and conditions
# of the Tencent Hunyuan Community License Agreement, including the Acceptable Use Policy.
from typing import Any, Dict, Optional, Tuple, Union
import torch
import torch.utils.checkpoint
from diffusers.configuration_utils import ConfigMixin, register_to_config
from diffusers.loaders import PeftAdapterMixin
from diffusers.models.attention import FeedForward
from diffusers.models.attention_processor import Attention, AttentionProcessor
from diffusers.models.embeddings import (
GaussianFourierProjection,
TimestepEmbedding,
Timesteps,
)
from diffusers.models.modeling_utils import ModelMixin
from diffusers.models.normalization import (
AdaLayerNormContinuous,
FP32LayerNorm,
LayerNorm,
)
from diffusers.utils import (
USE_PEFT_BACKEND,
is_torch_version,
logging,
scale_lora_layers,
unscale_lora_layers,
)
from diffusers.utils.torch_utils import maybe_allow_in_graph
from torch import nn
from ..attention_processor import FusedTripoSGAttnProcessor2_0, TripoSGAttnProcessor2_0
from .modeling_outputs import Transformer1DModelOutput
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
@maybe_allow_in_graph
class DiTBlock(nn.Module):
r"""
Transformer block used in Hunyuan-DiT model (https://github.com/Tencent/HunyuanDiT). Allow skip connection and
QKNorm
Parameters:
dim (`int`):
The number of channels in the input and output.
num_attention_heads (`int`):
The number of headsto use for multi-head attention.
cross_attention_dim (`int`,*optional*):
The size of the encoder_hidden_states vector for cross attention.
dropout(`float`, *optional*, defaults to 0.0):
The dropout probability to use.
activation_fn (`str`,*optional*, defaults to `"geglu"`):
Activation function to be used in feed-forward. .
norm_elementwise_affine (`bool`, *optional*, defaults to `True`):
Whether to use learnable elementwise affine parameters for normalization.
norm_eps (`float`, *optional*, defaults to 1e-6):
A small constant added to the denominator in normalization layers to prevent division by zero.
final_dropout (`bool` *optional*, defaults to False):
Whether to apply a final dropout after the last feed-forward layer.
ff_inner_dim (`int`, *optional*):
The size of the hidden layer in the feed-forward block. Defaults to `None`.
ff_bias (`bool`, *optional*, defaults to `True`):
Whether to use bias in the feed-forward block.
skip (`bool`, *optional*, defaults to `False`):
Whether to use skip connection. Defaults to `False` for down-blocks and mid-blocks.
qk_norm (`bool`, *optional*, defaults to `True`):
Whether to use normalization in QK calculation. Defaults to `True`.
"""
def __init__(
self,
dim: int,
num_attention_heads: int,
use_self_attention: bool = True,
self_attention_norm_type: Optional[str] = None,
use_cross_attention: bool = True, # ada layer norm
cross_attention_dim: Optional[int] = None,
cross_attention_norm_type: Optional[str] = "fp32_layer_norm",
use_cross_attention_2: bool = False,
cross_attention_2_dim: Optional[int] = None,
cross_attention_2_norm_type: Optional[str] = None,
dropout=0.0,
activation_fn: str = "gelu",
norm_type: str = "fp32_layer_norm", # TODO
norm_elementwise_affine: bool = True,
norm_eps: float = 1e-5,
final_dropout: bool = False,
ff_inner_dim: Optional[int] = None, # int(dim * 4) if None
ff_bias: bool = True,
skip: bool = False,
skip_concat_front: bool = False, # [x, skip] or [skip, x]
skip_norm_last: bool = False, # this is an error
qk_norm: bool = True,
qkv_bias: bool = True,
):
super().__init__()
self.use_self_attention = use_self_attention
self.use_cross_attention = use_cross_attention
self.use_cross_attention_2 = use_cross_attention_2
self.skip_concat_front = skip_concat_front
self.skip_norm_last = skip_norm_last
# Define 3 blocks. Each block has its own normalization layer.
# NOTE: when new version comes, check norm2 and norm 3
# 1. Self-Attn
if use_self_attention:
if (
self_attention_norm_type == "fp32_layer_norm"
or self_attention_norm_type is None
):
self.norm1 = FP32LayerNorm(dim, norm_eps, norm_elementwise_affine)
else:
raise NotImplementedError
self.attn1 = Attention(
query_dim=dim,
cross_attention_dim=None,
dim_head=dim // num_attention_heads,
heads=num_attention_heads,
qk_norm="rms_norm" if qk_norm else None,
eps=1e-6,
bias=qkv_bias,
processor=TripoSGAttnProcessor2_0(),
)
# 2. Cross-Attn
if use_cross_attention:
assert cross_attention_dim is not None
self.norm2 = FP32LayerNorm(dim, norm_eps, norm_elementwise_affine)
self.attn2 = Attention(
query_dim=dim,
cross_attention_dim=cross_attention_dim,
dim_head=dim // num_attention_heads,
heads=num_attention_heads,
qk_norm="rms_norm" if qk_norm else None,
cross_attention_norm=cross_attention_norm_type,
eps=1e-6,
bias=qkv_bias,
processor=TripoSGAttnProcessor2_0(),
)
if use_cross_attention_2:
assert cross_attention_2_dim is not None
self.norm2_2 = FP32LayerNorm(dim, norm_eps, norm_elementwise_affine)
self.attn2_2 = Attention(
query_dim=dim,
cross_attention_dim=cross_attention_2_dim,
dim_head=dim // num_attention_heads,
heads=num_attention_heads,
qk_norm="rms_norm" if qk_norm else None,
cross_attention_norm=cross_attention_2_norm_type,
eps=1e-6,
bias=qkv_bias,
processor=TripoSGAttnProcessor2_0(),
)
# 3. Feed-forward
self.norm3 = FP32LayerNorm(dim, norm_eps, norm_elementwise_affine)
self.ff = FeedForward(
dim,
dropout=dropout, ### 0.0
activation_fn=activation_fn, ### approx GeLU
final_dropout=final_dropout, ### 0.0
inner_dim=ff_inner_dim, ### int(dim * mlp_ratio)
bias=ff_bias,
)
# 4. Skip Connection
if skip:
self.skip_norm = FP32LayerNorm(dim, norm_eps, elementwise_affine=True)
self.skip_linear = nn.Linear(2 * dim, dim)
else:
self.skip_linear = None
# let chunk size default to None
self._chunk_size = None
self._chunk_dim = 0
def set_topk(self, topk):
self.flash_processor.topk = topk
def set_flash_processor(self, flash_processor):
self.flash_processor = flash_processor
self.attn2.processor = self.flash_processor
# Copied from diffusers.models.attention.BasicTransformerBlock.set_chunk_feed_forward
def set_chunk_feed_forward(self, chunk_size: Optional[int], dim: int = 0):
# Sets chunk feed-forward
self._chunk_size = chunk_size
self._chunk_dim = dim
def forward(
self,
hidden_states: torch.Tensor,
encoder_hidden_states: Optional[torch.Tensor] = None,
encoder_hidden_states_2: Optional[torch.Tensor] = None,
temb: Optional[torch.Tensor] = None,
image_rotary_emb: Optional[torch.Tensor] = None,
skip: Optional[torch.Tensor] = None,
attention_kwargs: Optional[Dict[str, Any]] = None,
) -> torch.Tensor:
# Prepare attention kwargs
attention_kwargs = attention_kwargs.copy() if attention_kwargs is not None else {}
cross_attention_scale = attention_kwargs.pop("cross_attention_scale", 1.0)
cross_attention_2_scale = attention_kwargs.pop("cross_attention_2_scale", 1.0)
# Notice that normalization is always applied before the real computation in the following blocks.
# 0. Long Skip Connection
if self.skip_linear is not None:
cat = torch.cat(
(
[skip, hidden_states]
if self.skip_concat_front
else [hidden_states, skip]
),
dim=-1,
)
if self.skip_norm_last:
# don't do this
hidden_states = self.skip_linear(cat)
hidden_states = self.skip_norm(hidden_states)
else:
cat = self.skip_norm(cat)
hidden_states = self.skip_linear(cat)
# 1. Self-Attention
if self.use_self_attention:
norm_hidden_states = self.norm1(hidden_states)
attn_output = self.attn1(
norm_hidden_states,
image_rotary_emb=image_rotary_emb,
**attention_kwargs,
)
hidden_states = hidden_states + attn_output
# 2. Cross-Attention
if self.use_cross_attention:
if self.use_cross_attention_2:
hidden_states = (
hidden_states
+ self.attn2(
self.norm2(hidden_states),
encoder_hidden_states=encoder_hidden_states,
image_rotary_emb=image_rotary_emb,
**attention_kwargs,
) * cross_attention_scale
+ self.attn2_2(
self.norm2_2(hidden_states),
encoder_hidden_states=encoder_hidden_states_2,
image_rotary_emb=image_rotary_emb,
**attention_kwargs,
) * cross_attention_2_scale
)
else:
hidden_states = hidden_states + self.attn2(
self.norm2(hidden_states),
encoder_hidden_states=encoder_hidden_states,
image_rotary_emb=image_rotary_emb,
**attention_kwargs,
) * cross_attention_scale
# FFN Layer ### TODO: switch norm2 and norm3 in the state dict
mlp_inputs = self.norm3(hidden_states)
hidden_states = hidden_states + self.ff(mlp_inputs)
return hidden_states
class TripoSGDiTModel(ModelMixin, ConfigMixin, PeftAdapterMixin):
"""
TripoSG: Diffusion model with a Transformer backbone.
Inherit ModelMixin and ConfigMixin to be compatible with the sampler StableDiffusionPipeline of diffusers.
Parameters:
num_attention_heads (`int`, *optional*, defaults to 16):
The number of heads to use for multi-head attention.
attention_head_dim (`int`, *optional*, defaults to 88):
The number of channels in each head.
in_channels (`int`, *optional*):
The number of channels in the input and output (specify if the input is **continuous**).
patch_size (`int`, *optional*):
The size of the patch to use for the input.
activation_fn (`str`, *optional*, defaults to `"geglu"`):
Activation function to use in feed-forward.
sample_size (`int`, *optional*):
The width of the latent images. This is fixed during training since it is used to learn a number of
position embeddings.
dropout (`float`, *optional*, defaults to 0.0):
The dropout probability to use.
cross_attention_dim (`int`, *optional*):
The number of dimension in the clip text embedding.
hidden_size (`int`, *optional*):
The size of hidden layer in the conditioning embedding layers.
num_layers (`int`, *optional*, defaults to 1):
The number of layers of Transformer blocks to use.
mlp_ratio (`float`, *optional*, defaults to 4.0):
The ratio of the hidden layer size to the input size.
learn_sigma (`bool`, *optional*, defaults to `True`):
Whether to predict variance.
cross_attention_dim_t5 (`int`, *optional*):
The number dimensions in t5 text embedding.
pooled_projection_dim (`int`, *optional*):
The size of the pooled projection.
text_len (`int`, *optional*):
The length of the clip text embedding.
text_len_t5 (`int`, *optional*):
The length of the T5 text embedding.
use_style_cond_and_image_meta_size (`bool`, *optional*):
Whether or not to use style condition and image meta size. True for version <=1.1, False for version >= 1.2
"""
_supports_gradient_checkpointing = True
@register_to_config
def __init__(
self,
num_attention_heads: int = 16,
width: int = 2048,
in_channels: int = 64,
num_layers: int = 21,
cross_attention_dim: int = 1024,
use_cross_attention_2: bool = False,
cross_attention_2_dim: Optional[int] = None
):
super().__init__()
self.out_channels = in_channels
self.num_heads = num_attention_heads
self.inner_dim = width
self.mlp_ratio = 4.0
time_embed_dim, timestep_input_dim = self._set_time_proj(
"positional",
inner_dim=self.inner_dim,
flip_sin_to_cos=False,
freq_shift=0,
time_embedding_dim=None,
)
self.time_proj = TimestepEmbedding(
timestep_input_dim, time_embed_dim, act_fn="gelu", out_dim=self.inner_dim
)
self.proj_in = nn.Linear(self.config.in_channels, self.inner_dim, bias=True)
self.blocks = nn.ModuleList(
[
DiTBlock(
dim=self.inner_dim,
num_attention_heads=self.config.num_attention_heads,
use_self_attention=True,
self_attention_norm_type="fp32_layer_norm",
use_cross_attention=True,
cross_attention_dim=cross_attention_dim,
cross_attention_norm_type=None,
use_cross_attention_2=use_cross_attention_2,
cross_attention_2_dim=cross_attention_2_dim,
cross_attention_2_norm_type=None,
activation_fn="gelu",
norm_type="fp32_layer_norm", # TODO
norm_eps=1e-5,
ff_inner_dim=int(self.inner_dim * self.mlp_ratio),
skip=layer > num_layers // 2,
skip_concat_front=True,
skip_norm_last=True, # this is an error
qk_norm=True, # See http://arxiv.org/abs/2302.05442 for details.
qkv_bias=False,
)
for layer in range(num_layers)
]
)
self.norm_out = LayerNorm(self.inner_dim)
self.proj_out = nn.Linear(self.inner_dim, self.out_channels, bias=True)
self.gradient_checkpointing = False
def _set_gradient_checkpointing(self, module, value=False):
self.gradient_checkpointing = value
def _set_time_proj(
self,
time_embedding_type: str,
inner_dim: int,
flip_sin_to_cos: bool,
freq_shift: float,
time_embedding_dim: int,
) -> Tuple[int, int]:
if time_embedding_type == "fourier":
time_embed_dim = time_embedding_dim or inner_dim * 2
if time_embed_dim % 2 != 0:
raise ValueError(
f"`time_embed_dim` should be divisible by 2, but is {time_embed_dim}."
)
self.time_embed = GaussianFourierProjection(
time_embed_dim // 2,
set_W_to_weight=False,
log=False,
flip_sin_to_cos=flip_sin_to_cos,
)
timestep_input_dim = time_embed_dim
elif time_embedding_type == "positional":
time_embed_dim = time_embedding_dim or inner_dim * 4
self.time_embed = Timesteps(inner_dim, flip_sin_to_cos, freq_shift)
timestep_input_dim = inner_dim
else:
raise ValueError(
f"{time_embedding_type} does not exist. Please make sure to use one of `fourier` or `positional`."
)
return time_embed_dim, timestep_input_dim
# Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.fuse_qkv_projections with FusedAttnProcessor2_0->FusedTripoSGAttnProcessor2_0
def fuse_qkv_projections(self):
"""
Enables fused QKV projections. For self-attention modules, all projection matrices (i.e., query, key, value)
are fused. For cross-attention modules, key and value projection matrices are fused.
<Tip warning={true}>
This API is 🧪 experimental.
</Tip>
"""
self.original_attn_processors = None
for _, attn_processor in self.attn_processors.items():
if "Added" in str(attn_processor.__class__.__name__):
raise ValueError(
"`fuse_qkv_projections()` is not supported for models having added KV projections."
)
self.original_attn_processors = self.attn_processors
for module in self.modules():
if isinstance(module, Attention):
module.fuse_projections(fuse=True)
self.set_attn_processor(FusedTripoSGAttnProcessor2_0())
# Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.unfuse_qkv_projections
def unfuse_qkv_projections(self):
"""Disables the fused QKV projection if enabled.
<Tip warning={true}>
This API is 🧪 experimental.
</Tip>
"""
if self.original_attn_processors is not None:
self.set_attn_processor(self.original_attn_processors)
@property
# Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.attn_processors
def attn_processors(self) -> Dict[str, AttentionProcessor]:
r"""
Returns:
`dict` of attention processors: A dictionary containing all attention processors used in the model with
indexed by its weight name.
"""
# set recursively
processors = {}
def fn_recursive_add_processors(
name: str,
module: torch.nn.Module,
processors: Dict[str, AttentionProcessor],
):
if hasattr(module, "get_processor"):
processors[f"{name}.processor"] = module.get_processor()
for sub_name, child in module.named_children():
fn_recursive_add_processors(f"{name}.{sub_name}", child, processors)
return processors
for name, module in self.named_children():
fn_recursive_add_processors(name, module, processors)
return processors
# Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.set_attn_processor
def set_attn_processor(
self, processor: Union[AttentionProcessor, Dict[str, AttentionProcessor]]
):
r"""
Sets the attention processor to use to compute attention.
Parameters:
processor (`dict` of `AttentionProcessor` or only `AttentionProcessor`):
The instantiated processor class or a dictionary of processor classes that will be set as the processor
for **all** `Attention` layers.
If `processor` is a dict, the key needs to define the path to the corresponding cross attention
processor. This is strongly recommended when setting trainable attention processors.
"""
count = len(self.attn_processors.keys())
if isinstance(processor, dict) and len(processor) != count:
raise ValueError(
f"A dict of processors was passed, but the number of processors {len(processor)} does not match the"
f" number of attention layers: {count}. Please make sure to pass {count} processor classes."
)
def fn_recursive_attn_processor(name: str, module: torch.nn.Module, processor):
if hasattr(module, "set_processor"):
if not isinstance(processor, dict):
module.set_processor(processor)
else:
module.set_processor(processor.pop(f"{name}.processor"))
for sub_name, child in module.named_children():
fn_recursive_attn_processor(f"{name}.{sub_name}", child, processor)
for name, module in self.named_children():
fn_recursive_attn_processor(name, module, processor)
def set_default_attn_processor(self):
"""
Disables custom attention processors and sets the default attention implementation.
"""
self.set_attn_processor(TripoSGAttnProcessor2_0())
def forward(
self,
hidden_states: Optional[torch.Tensor],
timestep: Union[int, float, torch.LongTensor],
encoder_hidden_states: Optional[torch.Tensor] = None,
encoder_hidden_states_2: Optional[torch.Tensor] = None,
image_rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
attention_kwargs: Optional[Dict[str, Any]] = None,
return_dict: bool = True,
):
"""
The [`HunyuanDiT2DModel`] forward method.
Args:
hidden_states (`torch.Tensor` of shape `(batch size, dim, height, width)`):
The input tensor.
timestep ( `torch.LongTensor`, *optional*):
Used to indicate denoising step.
encoder_hidden_states ( `torch.Tensor` of shape `(batch size, sequence len, embed dims)`, *optional*):
Conditional embeddings for cross attention layer.
return_dict: bool
Whether to return a dictionary.
"""
if attention_kwargs is not None:
attention_kwargs = attention_kwargs.copy()
lora_scale = attention_kwargs.pop("scale", 1.0)
else:
lora_scale = 1.0
if USE_PEFT_BACKEND:
# weight the lora layers by setting `lora_scale` for each PEFT layer
scale_lora_layers(self, lora_scale)
else:
if (
attention_kwargs is not None
and attention_kwargs.get("scale", None) is not None
):
logger.warning(
"Passing `scale` via `attention_kwargs` when not using the PEFT backend is ineffective."
)
_, N, _ = hidden_states.shape
temb = self.time_embed(timestep).to(hidden_states.dtype)
temb = self.time_proj(temb)
temb = temb.unsqueeze(dim=1) # unsqueeze to concat with hidden_states
hidden_states = self.proj_in(hidden_states)
# N + 1 token
hidden_states = torch.cat([temb, hidden_states], dim=1)
skips = []
for layer, block in enumerate(self.blocks):
skip = None if layer <= self.config.num_layers // 2 else skips.pop()
if self.training and self.gradient_checkpointing:
def create_custom_forward(module):
def custom_forward(*inputs):
return module(*inputs)
return custom_forward
ckpt_kwargs: Dict[str, Any] = (
{"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {}
)
hidden_states = torch.utils.checkpoint.checkpoint(
create_custom_forward(block),
hidden_states,
encoder_hidden_states,
encoder_hidden_states_2,
temb,
image_rotary_emb,
skip,
attention_kwargs,
**ckpt_kwargs,
)
else:
hidden_states = block(
hidden_states,
encoder_hidden_states=encoder_hidden_states,
encoder_hidden_states_2=encoder_hidden_states_2,
temb=temb,
image_rotary_emb=image_rotary_emb,
skip=skip,
attention_kwargs=attention_kwargs,
) # (N, L, D)
if layer < self.config.num_layers // 2:
skips.append(hidden_states)
# final layer
hidden_states = self.norm_out(hidden_states)
hidden_states = hidden_states[:, -N:]
hidden_states = self.proj_out(hidden_states)
if USE_PEFT_BACKEND:
# remove `lora_scale` from each PEFT layer
unscale_lora_layers(self, lora_scale)
if not return_dict:
return (hidden_states,)
return Transformer1DModelOutput(sample=hidden_states)
# Copied from diffusers.models.unets.unet_3d_condition.UNet3DConditionModel.enable_forward_chunking
def enable_forward_chunking(
self, chunk_size: Optional[int] = None, dim: int = 0
) -> None:
"""
Sets the attention processor to use [feed forward
chunking](https://huggingface.co/blog/reformer#2-chunked-feed-forward-layers).
Parameters:
chunk_size (`int`, *optional*):
The chunk size of the feed-forward layers. If not specified, will run feed-forward layer individually
over each tensor of dim=`dim`.
dim (`int`, *optional*, defaults to `0`):
The dimension over which the feed-forward computation should be chunked. Choose between dim=0 (batch)
or dim=1 (sequence length).
"""
if dim not in [0, 1]:
raise ValueError(f"Make sure to set `dim` to either 0 or 1, not {dim}")
# By default chunk size is 1
chunk_size = chunk_size or 1
def fn_recursive_feed_forward(
module: torch.nn.Module, chunk_size: int, dim: int
):
if hasattr(module, "set_chunk_feed_forward"):
module.set_chunk_feed_forward(chunk_size=chunk_size, dim=dim)
for child in module.children():
fn_recursive_feed_forward(child, chunk_size, dim)
for module in self.children():
fn_recursive_feed_forward(module, chunk_size, dim)
# Copied from diffusers.models.unets.unet_3d_condition.UNet3DConditionModel.disable_forward_chunking
def disable_forward_chunking(self):
def fn_recursive_feed_forward(
module: torch.nn.Module, chunk_size: int, dim: int
):
if hasattr(module, "set_chunk_feed_forward"):
module.set_chunk_feed_forward(chunk_size=chunk_size, dim=dim)
for child in module.children():
fn_recursive_feed_forward(child, chunk_size, dim)
for module in self.children():
fn_recursive_feed_forward(module, None, 0)
@@ -0,0 +1,329 @@
import inspect
import math
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import comfy.utils
import numpy as np
import PIL
import PIL.Image
import torch
import trimesh
from diffusers.image_processor import PipelineImageInput
from diffusers.pipelines.pipeline_utils import DiffusionPipeline
from diffusers.schedulers import FlowMatchEulerDiscreteScheduler
from diffusers.utils import logging
from diffusers.utils.torch_utils import randn_tensor
from transformers import (
BitImageProcessor,
Dinov2Model,
)
from ..inference_utils import hierarchical_extract_geometry, flash_extract_geometry
from ..models.autoencoders import TripoSGVAEModel
from ..models.transformers import TripoSGDiTModel
from .pipeline_triposg_output import TripoSGPipelineOutput
from .pipeline_utils import TransformerDiffusionMixin
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
def retrieve_timesteps(
scheduler,
num_inference_steps: Optional[int] = None,
device: Optional[Union[str, torch.device]] = None,
timesteps: Optional[List[int]] = None,
sigmas: Optional[List[float]] = None,
**kwargs,
):
"""
Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles
custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`.
Args:
scheduler (`SchedulerMixin`):
The scheduler to get timesteps from.
num_inference_steps (`int`):
The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`
must be `None`.
device (`str` or `torch.device`, *optional*):
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
timesteps (`List[int]`, *optional*):
Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,
`num_inference_steps` and `sigmas` must be `None`.
sigmas (`List[float]`, *optional*):
Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,
`num_inference_steps` and `timesteps` must be `None`.
Returns:
`Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the
second element is the number of inference steps.
"""
if timesteps is not None and sigmas is not None:
raise ValueError(
"Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values"
)
if timesteps is not None:
accepts_timesteps = "timesteps" in set(
inspect.signature(scheduler.set_timesteps).parameters.keys()
)
if not accepts_timesteps:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" timestep schedules. Please check whether you are using the correct scheduler."
)
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
timesteps = scheduler.timesteps
num_inference_steps = len(timesteps)
elif sigmas is not None:
accept_sigmas = "sigmas" in set(
inspect.signature(scheduler.set_timesteps).parameters.keys()
)
if not accept_sigmas:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" sigmas schedules. Please check whether you are using the correct scheduler."
)
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
timesteps = scheduler.timesteps
num_inference_steps = len(timesteps)
else:
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
timesteps = scheduler.timesteps
return timesteps, num_inference_steps
class TripoSGPipeline(DiffusionPipeline, TransformerDiffusionMixin):
"""
Pipeline for image-to-3D generation.
"""
def __init__(
self,
vae: TripoSGVAEModel,
transformer: TripoSGDiTModel,
scheduler: FlowMatchEulerDiscreteScheduler,
image_encoder_dinov2: Dinov2Model,
feature_extractor_dinov2: BitImageProcessor,
):
super().__init__()
self.register_modules(
vae=vae,
transformer=transformer,
scheduler=scheduler,
image_encoder_dinov2=image_encoder_dinov2,
feature_extractor_dinov2=feature_extractor_dinov2,
)
@property
def guidance_scale(self):
return self._guidance_scale
@property
def do_classifier_free_guidance(self):
return self._guidance_scale > 1
@property
def num_timesteps(self):
return self._num_timesteps
@property
def attention_kwargs(self):
return self._attention_kwargs
@property
def decode_progressive(self):
return self._decode_progressive
def encode_image(self, image, device, num_images_per_prompt):
dtype = next(self.image_encoder_dinov2.parameters()).dtype
if not isinstance(image, torch.Tensor):
image = self.feature_extractor_dinov2(image, return_tensors="pt").pixel_values
image = image.to(device=device, dtype=dtype)
image_embeds = self.image_encoder_dinov2(image).last_hidden_state
image_embeds = image_embeds.repeat_interleave(num_images_per_prompt, dim=0)
uncond_image_embeds = torch.zeros_like(image_embeds)
return image_embeds, uncond_image_embeds
def prepare_latents(
self,
batch_size,
num_tokens,
num_channels_latents,
dtype,
device,
generator,
latents: Optional[torch.Tensor] = None,
):
if latents is not None:
return latents.to(device=device, dtype=dtype)
shape = (batch_size, num_tokens, num_channels_latents)
if isinstance(generator, list) and len(generator) != batch_size:
raise ValueError(
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
return latents
@torch.no_grad()
def __call__(
self,
image: PipelineImageInput,
num_inference_steps: int = 50,
num_tokens: int = 2048,
timesteps: List[int] = None,
guidance_scale: float = 7.0,
num_shapes_per_prompt: int = 1,
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
latents: Optional[torch.FloatTensor] = None,
attention_kwargs: Optional[Dict[str, Any]] = None,
callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
bounds: Union[Tuple[float], List[float], float] = (-1.005, -1.005, -1.005, 1.005, 1.005, 1.005),
dense_octree_depth: int = 8,
hierarchical_octree_depth: int = 9,
flash_octree_depth: int = 9,
use_flash_decoder: bool = True,
return_dict: bool = True,
):
# 1. Define call parameters
self._guidance_scale = guidance_scale
self._attention_kwargs = attention_kwargs
# 2. Define call parameters
if isinstance(image, PIL.Image.Image):
batch_size = 1
elif isinstance(image, list):
batch_size = len(image)
elif isinstance(image, torch.Tensor):
batch_size = image.shape[0]
else:
raise ValueError("Invalid input type for image")
device = self._execution_device
# 3. Encode condition
image_embeds, negative_image_embeds = self.encode_image(
image, device, num_shapes_per_prompt
)
if self.do_classifier_free_guidance:
image_embeds = torch.cat([negative_image_embeds, image_embeds], dim=0)
# 4. Prepare timesteps
timesteps, num_inference_steps = retrieve_timesteps(
self.scheduler, num_inference_steps, device, timesteps
)
num_warmup_steps = max(
len(timesteps) - num_inference_steps * self.scheduler.order, 0
)
self._num_timesteps = len(timesteps)
# 5. Prepare latent variables
num_channels_latents = self.transformer.config.in_channels
latents = self.prepare_latents(
batch_size * num_shapes_per_prompt,
num_tokens,
num_channels_latents,
image_embeds.dtype,
device,
generator,
latents,
)
# 6. Denoising loop
comfy_pbar = comfy.utils.ProgressBar(num_inference_steps)
with self.progress_bar(total=num_inference_steps) as progress_bar:
for i, t in enumerate(timesteps):
# expand the latents if we are doing classifier free guidance
latent_model_input = (
torch.cat([latents] * 2)
if self.do_classifier_free_guidance
else latents
)
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML
timestep = t.expand(latent_model_input.shape[0])
noise_pred = self.transformer(
latent_model_input,
timestep,
encoder_hidden_states=image_embeds,
attention_kwargs=attention_kwargs,
return_dict=False,
)[0]
# perform guidance
if self.do_classifier_free_guidance:
noise_pred_uncond, noise_pred_image = noise_pred.chunk(2)
noise_pred = noise_pred_uncond + self.guidance_scale * (
noise_pred_image - noise_pred_uncond
)
# compute the previous noisy sample x_t -> x_t-1
latents_dtype = latents.dtype
latents = self.scheduler.step(
noise_pred, t, latents, return_dict=False
)[0]
if latents.dtype != latents_dtype:
if torch.backends.mps.is_available():
# some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272
latents = latents.to(latents_dtype)
if callback_on_step_end is not None:
callback_kwargs = {}
for k in callback_on_step_end_tensor_inputs:
callback_kwargs[k] = locals()[k]
callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
latents = callback_outputs.pop("latents", latents)
# call the callback, if provided
if i == len(timesteps) - 1 or (
(i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0
):
progress_bar.update()
comfy_pbar.update_absolute(i + 1)
# 7. decoder mesh
if not use_flash_decoder:
geometric_func = lambda x: self.vae.decode(latents, sampled_points=x).sample
output = hierarchical_extract_geometry(
geometric_func,
device,
bounds=bounds,
dense_octree_depth=dense_octree_depth,
hierarchical_octree_depth=hierarchical_octree_depth,
)
else:
self.vae.set_flash_decoder()
output = flash_extract_geometry(
latents,
self.vae,
bounds=bounds,
octree_depth=flash_octree_depth,
)
meshes = [trimesh.Trimesh(mesh_v_f[0].astype(np.float32), mesh_v_f[1]) for mesh_v_f in output]
# Offload all models
self.maybe_free_model_hooks()
if not return_dict:
return (output, meshes)
return TripoSGPipelineOutput(samples=output, meshes=meshes)
@@ -0,0 +1,17 @@
from dataclasses import dataclass
from typing import List, Optional, Union
import numpy as np
import torch
import trimesh
from diffusers.utils import BaseOutput
@dataclass
class TripoSGPipelineOutput(BaseOutput):
r"""
Output class for ShapeDiff pipelines.
"""
samples: torch.Tensor
meshes: List[trimesh.Trimesh]
@@ -0,0 +1,343 @@
import inspect
import math
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import comfy.utils
import numpy as np
import PIL
import PIL.Image
import torch
import trimesh
from diffusers.image_processor import PipelineImageInput
from diffusers.pipelines.pipeline_utils import DiffusionPipeline
from diffusers.schedulers import FlowMatchEulerDiscreteScheduler # not sure
from diffusers.utils import logging
from diffusers.utils.torch_utils import randn_tensor
from transformers import (
BitImageProcessor,
CLIPTokenizer,
CLIPTextModelWithProjection,
Dinov2Model,
)
from ..inference_utils import hierarchical_extract_geometry, flash_extract_geometry
from ..models.autoencoders import TripoSGVAEModel
from ..models.transformers import TripoSGDiTModel
from .pipeline_triposg_output import TripoSGPipelineOutput
from .pipeline_utils import TransformerDiffusionMixin
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
def retrieve_timesteps(
scheduler,
num_inference_steps: Optional[int] = None,
device: Optional[Union[str, torch.device]] = None,
timesteps: Optional[List[int]] = None,
sigmas: Optional[List[float]] = None,
**kwargs,
):
"""
Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles
custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`.
Args:
scheduler (`SchedulerMixin`):
The scheduler to get timesteps from.
num_inference_steps (`int`):
The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`
must be `None`.
device (`str` or `torch.device`, *optional*):
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
timesteps (`List[int]`, *optional*):
Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,
`num_inference_steps` and `sigmas` must be `None`.
sigmas (`List[float]`, *optional*):
Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,
`num_inference_steps` and `timesteps` must be `None`.
Returns:
`Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the
second element is the number of inference steps.
"""
if timesteps is not None and sigmas is not None:
raise ValueError(
"Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values"
)
if timesteps is not None:
accepts_timesteps = "timesteps" in set(
inspect.signature(scheduler.set_timesteps).parameters.keys()
)
if not accepts_timesteps:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" timestep schedules. Please check whether you are using the correct scheduler."
)
scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
timesteps = scheduler.timesteps
num_inference_steps = len(timesteps)
elif sigmas is not None:
accept_sigmas = "sigmas" in set(
inspect.signature(scheduler.set_timesteps).parameters.keys()
)
if not accept_sigmas:
raise ValueError(
f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
f" sigmas schedules. Please check whether you are using the correct scheduler."
)
scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
timesteps = scheduler.timesteps
num_inference_steps = len(timesteps)
else:
scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
timesteps = scheduler.timesteps
return timesteps, num_inference_steps
class TripoSGScribblePipeline(DiffusionPipeline, TransformerDiffusionMixin):
"""
Pipeline for (scribble and text)-to-3D generation.
"""
def __init__(
self,
vae: TripoSGVAEModel,
transformer: TripoSGDiTModel,
scheduler: FlowMatchEulerDiscreteScheduler,
tokenizer: CLIPTokenizer,
text_encoder: CLIPTextModelWithProjection,
image_encoder_dinov2: Dinov2Model,
feature_extractor_dinov2: BitImageProcessor,
):
super().__init__()
self.register_modules(
vae=vae,
transformer=transformer,
scheduler=scheduler,
tokenizer=tokenizer,
text_encoder=text_encoder,
image_encoder_dinov2=image_encoder_dinov2,
feature_extractor_dinov2=feature_extractor_dinov2
)
@property
def guidance_scale(self):
return self._guidance_scale
@property
def do_classifier_free_guidance(self):
return self._guidance_scale > 1
@property
def num_timesteps(self):
return self._num_timesteps
@property
def attention_kwargs(self):
return self._attention_kwargs
def encode_text(self, prompt, device, num_shapes_per_prompt):
input_ids = self.tokenizer(
[prompt], max_length=self.tokenizer.model_max_length, padding="max_length", truncation=True, return_tensors="pt"
)['input_ids'].to(device)
text_embeds = self.text_encoder(input_ids).last_hidden_state
text_embeds = text_embeds.repeat_interleave(num_shapes_per_prompt, dim=0)
uncond_text_embeds = torch.zeros_like(text_embeds)
return text_embeds, uncond_text_embeds
def encode_image(self, image, device, num_shapes_per_prompt):
dtype = next(self.image_encoder_dinov2.parameters()).dtype
if not isinstance(image, torch.Tensor):
image = self.feature_extractor_dinov2(image, return_tensors="pt").pixel_values
image = image.to(device=device, dtype=dtype)
image_embeds = self.image_encoder_dinov2(image).last_hidden_state
image_embeds = image_embeds.repeat_interleave(num_shapes_per_prompt, dim=0)
uncond_image_embeds = torch.zeros_like(image_embeds)
return image_embeds, uncond_image_embeds
def prepare_latents(
self,
batch_size,
num_tokens,
num_channels_latents,
dtype,
device,
generator,
latents: Optional[torch.Tensor] = None,
):
if latents is not None:
return latents.to(device=device, dtype=dtype)
shape = (batch_size, num_tokens, num_channels_latents)
if isinstance(generator, list) and len(generator) != batch_size:
raise ValueError(
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
)
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
return latents
@torch.no_grad()
def __call__(
self,
image: PipelineImageInput,
prompt: str,
num_tokens: int = 512,
num_inference_steps: int = 50,
timesteps: List[int] = None,
guidance_scale: float = 7.0,
num_shapes_per_prompt: int = 1,
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
latents: Optional[torch.FloatTensor] = None,
attention_kwargs: Optional[Dict[str, Any]] = None,
callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
bounds: Union[Tuple[float], List[float], float] = (-1.005, -1.005, -1.005, 1.005, 1.005, 1.005),
dense_octree_depth: int = 8,
hierarchical_octree_depth: int = 9,
flash_octree_depth: int = 9,
use_flash_decoder: bool = True,
return_dict: bool = True,
):
self._guidance_scale = guidance_scale
self._attention_kwargs = attention_kwargs
# 2. Define call parameters
if isinstance(image, PIL.Image.Image):
batch_size = 1
elif isinstance(image, list):
batch_size = len(image)
elif isinstance(image, torch.Tensor):
batch_size = image.shape[0]
else:
raise ValueError("Invalid input type for image")
device = self._execution_device
# 3. Encode condition
text_embeds, negative_text_embeds = self.encode_text(
prompt, device, num_shapes_per_prompt
)
image_embeds, negative_image_embeds = self.encode_image(
image, device, num_shapes_per_prompt
)
if self.do_classifier_free_guidance:
text_embeds = torch.cat([negative_text_embeds, text_embeds], dim=0)
image_embeds = torch.cat([negative_image_embeds, image_embeds], dim=0)
# 4. Prepare timesteps
timesteps, num_inference_steps = retrieve_timesteps(
self.scheduler, num_inference_steps, device, timesteps
)
num_warmup_steps = max(
len(timesteps) - num_inference_steps * self.scheduler.order, 0
)
self._num_timesteps = len(timesteps)
# 5. Prepare latent variables
num_channels_latents = self.transformer.config.in_channels
latents = self.prepare_latents(
batch_size * num_shapes_per_prompt,
num_tokens,
num_channels_latents,
image_embeds.dtype,
device,
generator,
latents,
)
# 6. Denoising loop
comfy_pbar = comfy.utils.ProgressBar(num_inference_steps)
with self.progress_bar(total=num_inference_steps) as progress_bar:
for i, t in enumerate(timesteps):
# expand the latents if we are doing classifier free guidance
latent_model_input = (
torch.cat([latents] * 2)
if self.do_classifier_free_guidance
else latents
)
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML
timestep = t.expand(latent_model_input.shape[0])
noise_pred = self.transformer(
latent_model_input,
timestep.to(dtype=latents.dtype),
encoder_hidden_states=text_embeds,
encoder_hidden_states_2=image_embeds,
attention_kwargs=attention_kwargs,
return_dict=False,
)[0]
# perform guidance
if self.do_classifier_free_guidance:
noise_pred_uncond, noise_pred_image = noise_pred.chunk(2)
noise_pred = noise_pred_uncond + self.guidance_scale * (
noise_pred_image - noise_pred_uncond
)
# compute the previous noisy sample x_t -> x_t-1
latents_dtype = latents.dtype
latents = self.scheduler.step(
noise_pred, t, latents, return_dict=False
)[0]
if latents.dtype != latents_dtype:
if torch.backends.mps.is_available():
# some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272
latents = latents.to(latents_dtype)
if callback_on_step_end is not None:
callback_kwargs = {}
for k in callback_on_step_end_tensor_inputs:
callback_kwargs[k] = locals()[k]
callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
latents = callback_outputs.pop("latents", latents)
# call the callback, if provided
if i == len(timesteps) - 1 or (
(i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0
):
progress_bar.update()
comfy_pbar.update_absolute(i + 1)
# 7. decoder mesh
if not use_flash_decoder:
geometric_func = lambda x: self.vae.decode(latents, sampled_points=x).sample
output = hierarchical_extract_geometry(
geometric_func,
device,
bounds=bounds,
dense_octree_depth=dense_octree_depth,
hierarchical_octree_depth=hierarchical_octree_depth,
)
else:
self.vae.set_flash_decoder()
output = flash_extract_geometry(
latents,
self.vae,
bounds=bounds,
octree_depth=flash_octree_depth,
)
meshes = [trimesh.Trimesh(mesh_v_f[0].astype(np.float32), mesh_v_f[1]) for mesh_v_f in output]
# Offload all models
self.maybe_free_model_hooks()
if not return_dict:
return (output, meshes)
return TripoSGPipelineOutput(samples=output, meshes=meshes)
@@ -0,0 +1,96 @@
from diffusers.utils import logging
logger = logging.get_logger(__name__)
class TransformerDiffusionMixin:
r"""
Helper for DiffusionPipeline with vae and transformer.(mainly for DIT)
"""
def enable_vae_slicing(self):
r"""
Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to
compute decoding in several steps. This is useful to save some memory and allow larger batch sizes.
"""
self.vae.enable_slicing()
def disable_vae_slicing(self):
r"""
Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to
computing decoding in one step.
"""
self.vae.disable_slicing()
def enable_vae_tiling(self):
r"""
Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to
compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow
processing larger images.
"""
self.vae.enable_tiling()
def disable_vae_tiling(self):
r"""
Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to
computing decoding in one step.
"""
self.vae.disable_tiling()
def fuse_qkv_projections(self, transformer: bool = True, vae: bool = True):
"""
Enables fused QKV projections. For self-attention modules, all projection matrices (i.e., query, key, value)
are fused. For cross-attention modules, key and value projection matrices are fused.
<Tip warning={true}>
This API is 🧪 experimental.
</Tip>
Args:
transformer (`bool`, defaults to `True`): To apply fusion on the Transformer.
vae (`bool`, defaults to `True`): To apply fusion on the VAE.
"""
self.fusing_transformer = False
self.fusing_vae = False
if transformer:
self.fusing_transformer = True
self.transformer.fuse_qkv_projections()
if vae:
self.fusing_vae = True
self.vae.fuse_qkv_projections()
def unfuse_qkv_projections(self, transformer: bool = True, vae: bool = True):
"""Disable QKV projection fusion if enabled.
<Tip warning={true}>
This API is 🧪 experimental.
</Tip>
Args:
transformer (`bool`, defaults to `True`): To apply fusion on the Transformer.
vae (`bool`, defaults to `True`): To apply fusion on the VAE.
"""
if transformer:
if not self.fusing_transformer:
logger.warning(
"The UNet was not initially fused for QKV projections. Doing nothing."
)
else:
self.transformer.unfuse_qkv_projections()
self.fusing_transformer = False
if vae:
if not self.fusing_vae:
logger.warning(
"The VAE was not initially fused for QKV projections. Doing nothing."
)
else:
self.vae.unfuse_qkv_projections()
self.fusing_vae = False
@@ -0,0 +1,5 @@
from .scheduling_rectified_flow import (
RectifiedFlowScheduler,
compute_density_for_timestep_sampling,
compute_loss_weighting,
)
@@ -0,0 +1,327 @@
"""
Adapted from https://github.com/huggingface/diffusers/blob/v0.30.3/src/diffusers/schedulers/scheduling_flow_match_euler_discrete.py.
"""
import math
from dataclasses import dataclass
from typing import List, Optional, Tuple, Union
import numpy as np
import torch
from diffusers.configuration_utils import ConfigMixin, register_to_config
from diffusers.schedulers.scheduling_utils import SchedulerMixin
from diffusers.utils import BaseOutput, logging
from torch.distributions import LogisticNormal
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
# TODO: may move to training_utils.py
def compute_density_for_timestep_sampling(
weighting_scheme: str,
batch_size: int,
logit_mean: float = 0.0,
logit_std: float = 1.0,
mode_scale: float = None,
):
if weighting_scheme == "logit_normal":
# See 3.1 in the SD3 paper ($rf/lognorm(0.00,1.00)$).
u = torch.normal(
mean=logit_mean, std=logit_std, size=(batch_size,), device="cpu"
)
u = torch.nn.functional.sigmoid(u)
elif weighting_scheme == "logit_normal_dist":
u = (
LogisticNormal(loc=logit_mean, scale=logit_std)
.sample((batch_size,))[:, 0]
.to("cpu")
)
elif weighting_scheme == "mode":
u = torch.rand(size=(batch_size,), device="cpu")
u = 1 - u - mode_scale * (torch.cos(math.pi * u / 2) ** 2 - 1 + u)
else:
u = torch.rand(size=(batch_size,), device="cpu")
return u
def compute_loss_weighting(weighting_scheme: str, sigmas=None):
"""
Computes loss weighting scheme for SD3 training.
Courtesy: This was contributed by Rafie Walker in https://github.com/huggingface/diffusers/pull/8528.
SD3 paper reference: https://arxiv.org/abs/2403.03206v1.
"""
if weighting_scheme == "sigma_sqrt":
weighting = (sigmas**-2.0).float()
elif weighting_scheme == "cosmap":
bot = 1 - 2 * sigmas + 2 * sigmas**2
weighting = 2 / (math.pi * bot)
else:
weighting = torch.ones_like(sigmas)
return weighting
@dataclass
class RectifiedFlowSchedulerOutput(BaseOutput):
"""
Output class for the scheduler's `step` function output.
Args:
prev_sample (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)` for images):
Computed sample `(x_{t-1})` of previous timestep. `prev_sample` should be used as next model input in the
denoising loop.
"""
prev_sample: torch.FloatTensor
class RectifiedFlowScheduler(SchedulerMixin, ConfigMixin):
"""
The rectified flow scheduler is a scheduler that is used to propagate the diffusion process in the rectified flow.
This model inherits from [`SchedulerMixin`] and [`ConfigMixin`]. Check the superclass documentation for the generic
methods the library implements for all schedulers such as loading and saving.
Args:
num_train_timesteps (`int`, defaults to 1000):
The number of diffusion steps to train the model.
timestep_spacing (`str`, defaults to `"linspace"`):
The way the timesteps should be scaled. Refer to Table 2 of the [Common Diffusion Noise Schedules and
Sample Steps are Flawed](https://huggingface.co/papers/2305.08891) for more information.
shift (`float`, defaults to 1.0):
The shift value for the timestep schedule.
"""
_compatibles = []
order = 1
@register_to_config
def __init__(
self,
num_train_timesteps: int = 1000,
shift: float = 1.0,
use_dynamic_shifting: bool = False,
):
# pre-compute timesteps and sigmas; no use in fact
# NOTE that shape diffusion sample timesteps randomly or in a distribution,
# instead of sampling from the pre-defined linspace
timesteps = np.array(
[
(1.0 - i / num_train_timesteps) * num_train_timesteps
for i in range(num_train_timesteps)
]
)
timesteps = torch.from_numpy(timesteps).to(dtype=torch.float32)
sigmas = timesteps / num_train_timesteps
if not use_dynamic_shifting:
# when use_dynamic_shifting is True, we apply the timestep shifting on the fly based on the image resolution
sigmas = self.time_shift(sigmas)
self.timesteps = sigmas * num_train_timesteps
self._step_index = None
self._begin_index = None
self.sigmas = sigmas.to("cpu") # to avoid too much CPU/GPU communication
@property
def step_index(self):
"""
The index counter for current timestep. It will increase 1 after each scheduler step.
"""
return self._step_index
@property
def begin_index(self):
"""
The index for the first timestep. It should be set from pipeline with `set_begin_index` method.
"""
return self._begin_index
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.set_begin_index
def set_begin_index(self, begin_index: int = 0):
"""
Sets the begin index for the scheduler. This function should be run from pipeline before the inference.
Args:
begin_index (`int`):
The begin index for the scheduler.
"""
self._begin_index = begin_index
def _sigma_to_t(self, sigma):
return sigma * self.config.num_train_timesteps
def _t_to_sigma(self, timestep):
return timestep / self.config.num_train_timesteps
def time_shift_dynamic(self, mu: float, sigma: float, t: torch.Tensor):
return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)
def time_shift(self, t: torch.Tensor):
return self.config.shift * t / (1 + (self.config.shift - 1) * t)
def set_timesteps(
self,
num_inference_steps: int = None,
device: Union[str, torch.device] = None,
sigmas: Optional[List[float]] = None,
mu: Optional[float] = None,
):
"""
Sets the discrete timesteps used for the diffusion chain (to be run before inference).
Args:
num_inference_steps (`int`):
The number of diffusion steps used when generating samples with a pre-trained model.
device (`str` or `torch.device`, *optional*):
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
"""
if self.config.use_dynamic_shifting and mu is None:
raise ValueError(
" you have to pass a value for `mu` when `use_dynamic_shifting` is set to be `True`"
)
if sigmas is None:
self.num_inference_steps = num_inference_steps
timesteps = np.array(
[
(1.0 - i / num_inference_steps) * self.config.num_train_timesteps
for i in range(num_inference_steps)
]
) # different from the original code in SD3
sigmas = timesteps / self.config.num_train_timesteps
if self.config.use_dynamic_shifting:
sigmas = self.time_shift_dynamic(mu, 1.0, sigmas)
else:
sigmas = self.config.shift * sigmas / (1 + (self.config.shift - 1) * sigmas)
sigmas = torch.from_numpy(sigmas).to(dtype=torch.float32, device=device)
timesteps = sigmas * self.config.num_train_timesteps
self.timesteps = timesteps.to(device=device)
self.sigmas = torch.cat([sigmas, torch.zeros(1, device=sigmas.device)])
self._step_index = None
self._begin_index = None
def index_for_timestep(self, timestep, schedule_timesteps=None):
if schedule_timesteps is None:
schedule_timesteps = self.timesteps
indices = (schedule_timesteps == timestep).nonzero()
# The sigma index that is taken for the **very** first `step`
# is always the second index (or the last index if there is only 1)
# This way we can ensure we don't accidentally skip a sigma in
# case we start in the middle of the denoising schedule (e.g. for image-to-image)
pos = 1 if len(indices) > 1 else 0
return indices[pos].item()
def _init_step_index(self, timestep):
if self.begin_index is None:
if isinstance(timestep, torch.Tensor):
timestep = timestep.to(self.timesteps.device)
self._step_index = self.index_for_timestep(timestep)
else:
self._step_index = self._begin_index
def step(
self,
model_output: torch.FloatTensor,
timestep: Union[float, torch.FloatTensor],
sample: torch.FloatTensor,
s_churn: float = 0.0,
s_tmin: float = 0.0,
s_tmax: float = float("inf"),
s_noise: float = 1.0,
generator: Optional[torch.Generator] = None,
return_dict: bool = True,
) -> Union[RectifiedFlowSchedulerOutput, Tuple]:
"""
Predict the sample from the previous timestep by reversing the SDE. This function propagates the diffusion
process from the learned model outputs (most often the predicted noise).
Args:
model_output (`torch.FloatTensor`):
The direct output from learned diffusion model.
timestep (`float`):
The current discrete timestep in the diffusion chain.
sample (`torch.FloatTensor`):
A current instance of a sample created by the diffusion process.
s_churn (`float`):
s_tmin (`float`):
s_tmax (`float`):
s_noise (`float`, defaults to 1.0):
Scaling factor for noise added to the sample.
generator (`torch.Generator`, *optional*):
A random number generator.
return_dict (`bool`):
Whether or not to return a [`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] or
tuple.
Returns:
[`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] or `tuple`:
If return_dict is `True`, [`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] is
returned, otherwise a tuple is returned where the first element is the sample tensor.
"""
if (
isinstance(timestep, int)
or isinstance(timestep, torch.IntTensor)
or isinstance(timestep, torch.LongTensor)
):
raise ValueError(
(
"Passing integer indices (e.g. from `enumerate(timesteps)`) as timesteps to"
" `EulerDiscreteScheduler.step()` is not supported. Make sure to pass"
" one of the `scheduler.timesteps` as a timestep."
),
)
if self.step_index is None:
self._init_step_index(timestep)
# Upcast to avoid precision issues when computing prev_sample
sample = sample.to(torch.float32)
sigma = self.sigmas[self.step_index]
sigma_next = self.sigmas[self.step_index + 1]
# Here different directions are used for the flow matching
prev_sample = sample + (sigma - sigma_next) * model_output
# Cast sample back to model compatible dtype
prev_sample = prev_sample.to(model_output.dtype)
# upon completion increase step index by one
self._step_index += 1
if not return_dict:
return (prev_sample,)
return RectifiedFlowSchedulerOutput(prev_sample=prev_sample)
def scale_noise(
self,
original_samples: torch.Tensor,
noise: torch.Tensor,
timesteps: torch.IntTensor,
) -> torch.Tensor:
"""
Forward function for the noise scaling in the flow matching.
"""
sigmas = self._t_to_sigma(timesteps.to(dtype=torch.float32))
while len(sigmas.shape) < len(original_samples.shape):
sigmas = sigmas.unsqueeze(-1)
return (1.0 - sigmas) * original_samples + sigmas * noise
def __len__(self):
return self.config.num_train_timesteps
+64
View File
@@ -0,0 +1,64 @@
"""
This module contains type annotations for the project, using
1. Python type hints (https://docs.python.org/3/library/typing.html) for Python objects
2. jaxtyping (https://github.com/google/jaxtyping/blob/main/API.md) for PyTorch tensors
Two types of typing checking can be used:
1. Static type checking with mypy (install with pip and enabled as the default linter in VSCode)
2. Runtime type checking with typeguard (install with pip and triggered at runtime, mainly for tensor dtype and shape checking)
"""
# Basic types
from typing import (
Any,
Callable,
Dict,
Iterable,
List,
Literal,
NamedTuple,
NewType,
Optional,
Sized,
Tuple,
Type,
TypedDict,
TypeVar,
Union,
)
# Tensor dtype
# for jaxtyping usage, see https://github.com/google/jaxtyping/blob/main/API.md
from jaxtyping import Bool, Complex, Float, Inexact, Int, Integer, Num, Shaped, UInt
# Config type
from omegaconf import DictConfig, ListConfig
# PyTorch Tensor type
from torch import Tensor
# Runtime type checking decorator
from typeguard import typechecked as typechecker
# Custom types
class FuncArgs(TypedDict):
"""Type for instantiating a function with keyword arguments"""
name: str
kwargs: Dict[str, Any]
@staticmethod
def validate(variable):
necessary_keys = ["name", "kwargs"]
for key in necessary_keys:
assert key in variable, f"Key {key} is missing in {variable}"
if not isinstance(variable["name"], str):
raise TypeError(
f"Key 'name' should be a string, not {type(variable['name'])}"
)
if not isinstance(variable["kwargs"], dict):
raise TypeError(
f"Key 'kwargs' should be a dictionary, not {type(variable['kwargs'])}"
)
return variable
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -242,6 +242,8 @@ def decimate_mesh(
m = pml.Mesh(verts, faces)
ms = pml.MeshSet()
ms.add_mesh(m, "mesh") # will copy!
ms.meshing_merge_close_vertices()
# filters
# ms.meshing_decimation_clustering(threshold=pml.PercentageValue(1))
+142 -8
View File
@@ -99,6 +99,8 @@ from Hunyuan3D_V2.hy3dgen.shapegen import FaceReducer, FloaterRemover, Degenerat
from Hunyuan3D_V2.hy3dgen.texgen import Hunyuan3DPaintPipeline
from TRELLIS.trellis.pipelines import TrellisImageTo3DPipeline
from TRELLIS.trellis.utils import postprocessing_utils
from TripoSG.pipelines.pipeline_triposg import TripoSGPipeline
from TripoSG.pipelines.pipeline_triposg_scribble import TripoSGScribblePipeline
os.environ['SPCONV_ALGO'] = 'native'
@@ -126,6 +128,8 @@ DIFFUSERS_PIPE_DICT = OrderedDict([
("Hunyuan3DMVDLitePipeline", Hunyuan3D_MVD_Lite_Pipeline),
("Hunyuan3DDiTFlowMatchingPipeline", Hunyuan3DDiTFlowMatchingPipeline),
("Hunyuan3DPaintPipeline", Hunyuan3DPaintPipeline),
("TripoSGPipeline", TripoSGPipeline),
("TripoSGScribblePipeline", TripoSGScribblePipeline),
])
DIFFUSERS_SCHEDULER_DICT = OrderedDict([
@@ -632,11 +636,9 @@ class Decimate_Mesh:
CATEGORY = "Comfy3D/Preprocessor"
def process_mesh(self, mesh, target, remesh, optimalplacement):
vertices, faces = decimate_mesh(mesh.v, mesh.f, target, remesh, optimalplacement)
mesh.v = vertices
mesh.f = faces
vertices, faces = decimate_mesh(mesh.v.detach().cpu().numpy(), mesh.f.detach().cpu().numpy(), target, remesh, optimalplacement)
mesh.v, mesh.f = torch.from_numpy(vertices).to(DEVICE), torch.from_numpy(faces).to(torch.int64).to(DEVICE)
mesh.auto_normal()
return (mesh,)
class Switch_3DGS_Axis:
@@ -1486,6 +1488,7 @@ class Load_Diffusers_Pipeline:
},
"optional": {
"checkpoint_sub_dir": ("STRING", {"default": "", "multiline": False}),
"force_disable_xformers": ("BOOLEAN", {"default": False}),
}
}
@@ -1498,7 +1501,7 @@ class Load_Diffusers_Pipeline:
FUNCTION = "load_diffusers_pipe"
CATEGORY = "Comfy3D/Import|Export"
def load_diffusers_pipe(self, diffusers_pipeline_name, repo_id, custom_pipeline, force_download, checkpoint_sub_dir=""):
def load_diffusers_pipe(self, diffusers_pipeline_name, repo_id, custom_pipeline, force_download, checkpoint_sub_dir="", force_disable_xformers=False):
# resume download pretrained checkpoint
ckpt_download_dir = os.path.join(CKPT_DIFFUSERS_PATH, repo_id)
@@ -1515,9 +1518,9 @@ class Load_Diffusers_Pipeline:
ckpt_path,
torch_dtype=WEIGHT_DTYPE,
custom_pipeline=custom_pipeline,
).to(DEVICE)
).to(DEVICE, WEIGHT_DTYPE)
if hasattr(pipe, 'enable_xformers_memory_efficient_attention'):
if hasattr(pipe, 'enable_xformers_memory_efficient_attention') and not force_disable_xformers:
pipe.enable_xformers_memory_efficient_attention()
return (pipe, )
@@ -4117,6 +4120,137 @@ class Trellis_Structured_3D_Latents_Models:
mesh.auto_normal()
return (mesh,)
class TripoSG_I23D_Model:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"tsg_pipe": ("DIFFUSERS_PIPE",),
"reference_image": ("IMAGE",),
"seed": ("INT", {"default": 42, "min": 0, "max": 0xffffffffffffffff}),
"guidance_scale": ("FLOAT", {"default": 7.0, "min": 0.0, "step": 0.01}),
"num_inference_steps": ("INT", {"default": 50, "min": 1}),
"use_flash_decoder": ("BOOLEAN", {"default": True}),
"flash_octree_depth": ("INT", {"default": 9, "min": 1}),
"hierarchical_octree_depth": ("INT", {"default": 9, "min": 1}),
"dense_octree_depth": ("INT", {"default": 8, "min": 1}),
}
}
RETURN_TYPES = (
"MESH",
)
RETURN_NAMES = (
"mesh",
)
FUNCTION = "run_model"
CATEGORY = "Comfy3D/Algorithm"
@torch.no_grad()
def run_model(
self,
tsg_pipe,
reference_image, # [1, H, W, 3]
seed,
guidance_scale,
num_inference_steps,
use_flash_decoder,
flash_octree_depth,
hierarchical_octree_depth,
dense_octree_depth,
):
single_image = torch_imgs_to_pils(reference_image)[0]
with torch.inference_mode(False):
outputs = tsg_pipe(
image=single_image,
generator=torch.Generator(device=DEVICE).manual_seed(seed),
num_inference_steps=num_inference_steps,
guidance_scale=guidance_scale,
use_flash_decoder=use_flash_decoder,
flash_octree_depth=flash_octree_depth,
hierarchical_octree_depth=hierarchical_octree_depth,
dense_octree_depth=dense_octree_depth,
).samples[0]
vertices, faces = torch.from_numpy(outputs[0].astype(np.float32)).to(DEVICE), torch.from_numpy(np.ascontiguousarray(outputs[1])).to(torch.int64).to(DEVICE)
mesh = Mesh(v=vertices, f=faces, device=DEVICE)
mesh.auto_normal()
return (mesh,)
class TripoSG_Scribble_Model:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"tsg_scribble_pipe": ("DIFFUSERS_PIPE",),
"scribble_image": ("IMAGE",),
"prompt": ("STRING", {
"default": "3D assets",
"multiline": True
}),
"seed": ("INT", {"default": 42, "min": 0, "max": 0xffffffffffffffff}),
"num_inference_steps": ("INT", {"default": 16, "min": 1}),
"scribble_confidence": ("FLOAT", {"default": 0.4, "min": 0.0, "step": 0.01}),
"prompt_confidence": ("FLOAT", {"default": 1.0, "min": 0.0, "step": 0.01}),
"use_flash_decoder": ("BOOLEAN", {"default": False}),
"flash_octree_depth": ("INT", {"default": 8, "min": 1}),
"hierarchical_octree_depth": ("INT", {"default": 8, "min": 1}),
"dense_octree_depth": ("INT", {"default": 8, "min": 1}),
}
}
RETURN_TYPES = (
"MESH",
)
RETURN_NAMES = (
"mesh",
)
FUNCTION = "run_model"
CATEGORY = "Comfy3D/Algorithm"
@torch.no_grad()
def run_model(
self,
tsg_scribble_pipe,
scribble_image, # [1, H, W, 3]
prompt,
seed,
num_inference_steps,
scribble_confidence,
prompt_confidence,
use_flash_decoder,
flash_octree_depth,
hierarchical_octree_depth,
dense_octree_depth,
):
single_image = torch_imgs_to_pils(scribble_image)[0]
with torch.inference_mode(False):
outputs = tsg_scribble_pipe(
image=single_image,
prompt=prompt,
generator=torch.Generator(device=DEVICE).manual_seed(seed),
num_inference_steps=num_inference_steps,
guidance_scale=0, # this is a CFG-distilled model
attention_kwargs={"cross_attention_scale": prompt_confidence, "cross_attention_2_scale": scribble_confidence},
use_flash_decoder=use_flash_decoder,
flash_octree_depth=flash_octree_depth, # there're some boundary problems when using flash decoder with this model
hierarchical_octree_depth=hierarchical_octree_depth,
dense_octree_depth=dense_octree_depth,
).samples[0]
vertices, faces = torch.from_numpy(outputs[0].astype(np.float32)).to(DEVICE), torch.from_numpy(np.ascontiguousarray(outputs[1])).to(torch.int64).to(DEVICE)
mesh = Mesh(v=vertices, f=faces.to(torch.int64), device=DEVICE)
mesh.auto_normal()
return (mesh,)
+1
View File
@@ -48,6 +48,7 @@ libigl
pyvista
pymeshfix
igraph
diso
# configs & extra
torchtyping>=0.1.5