Spaces:
Runtime error
Runtime error
Commit ·
0328207
1
Parent(s): 2b7da74
add code
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +10 -0
- LICENSE +201 -0
- README copy.md +95 -0
- app.py +1464 -0
- b_spline.py +160 -0
- configs/default_config.yaml +31 -0
- configs/self_forcing_dmd.yaml +56 -0
- configs/self_forcing_dmd_vsink.yaml +58 -0
- configs/self_forcing_dmd_vsink_stream.yaml +5 -0
- configs/self_forcing_dmd_vsink_stream_drag.yaml +33 -0
- configs/self_forcing_sid.yaml +57 -0
- demo.py +715 -0
- demo_utils/constant.py +39 -0
- demo_utils/memory.py +174 -0
- demo_utils/taehv.py +477 -0
- demo_utils/utils.py +809 -0
- demo_utils/vae.py +477 -0
- demo_utils/vae_block3.py +364 -0
- demo_utils/vae_torch2trt.py +335 -0
- frequency_utils.py +1020 -0
- images/.gitkeep +0 -0
- inference.py +263 -0
- model/__init__.py +15 -0
- model/base.py +256 -0
- model/causvid.py +417 -0
- model/diffusion.py +131 -0
- model/dmd.py +377 -0
- model/gan.py +352 -0
- model/ode_regression.py +162 -0
- model/sid.py +318 -0
- offline_run.py +97 -0
- optimize_utils.py +275 -0
- palette.py +774 -0
- pipeline/__init__.py +15 -0
- pipeline/bidirectional_diffusion_inference.py +131 -0
- pipeline/bidirectional_inference.py +85 -0
- pipeline/causal_diffusion_inference.py +402 -0
- pipeline/causal_inference.py +1193 -0
- pipeline/self_forcing_training.py +351 -0
- prompts/MovieGenVideoBench.txt +0 -0
- prompts/MovieGenVideoBench_extended.txt +0 -0
- prompts/vbench/all_dimension.txt +946 -0
- prompts/vbench/all_dimension_extended.txt +0 -0
- requirements.txt +43 -0
- scripts/create_lmdb_14b_shards.py +103 -0
- scripts/create_lmdb_iterative.py +58 -0
- scripts/generate_ode_pairs.py +113 -0
- setup.py +7 -0
- stream_drag_inference_wrapper.py +201 -0
- stream_inference.py +111 -0
.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__
|
| 2 |
+
*.egg-info
|
| 3 |
+
|
| 4 |
+
output*
|
| 5 |
+
|
| 6 |
+
*logs/
|
| 7 |
+
|
| 8 |
+
.history
|
| 9 |
+
|
| 10 |
+
saved_labels*/
|
LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [yyyy] [name of copyright owner]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
README copy.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<p align="center">
|
| 2 |
+
<h1 align="center">DragStream</h1>
|
| 3 |
+
<h3 align="center">Streaming Drag-Oriented Interactive Video Manipulation: Drag Anything, Anytime!</h3>
|
| 4 |
+
</p>
|
| 5 |
+
<p align="center">
|
| 6 |
+
<p align="center">
|
| 7 |
+
<a>Junbao Zhou</a><sup>1</sup>
|
| 8 |
+
·
|
| 9 |
+
<a>Yuan Zhou</a><sup>1</sup>
|
| 10 |
+
·
|
| 11 |
+
<a>Kesen Zhao</a><sup>1</sup>
|
| 12 |
+
·
|
| 13 |
+
<a>Qingshan Xu</a><sup>1</sup>
|
| 14 |
+
·
|
| 15 |
+
<a>Beier Zhu</a><sup>1</sup>
|
| 16 |
+
·
|
| 17 |
+
<a>Richang Hong</a><sup>2</sup>
|
| 18 |
+
·
|
| 19 |
+
<a>Hanwang Zhang</a><sup>1</sup><br>
|
| 20 |
+
<sup>1</sup>Nanyang Technological University <sup>2</sup>Hefei University of Technology
|
| 21 |
+
</p>
|
| 22 |
+
<h3 align="center">
|
| 23 |
+
<a href="https://arxiv.org/abs/2510.03550"><img src="https://img.shields.io/badge/Arxiv-2510.03550-B31B1B.svg?logo=arXiv"></a>
|
| 24 |
+
<a href="https://junbao-zhou.github.io/DragStream.github.io/"><img src="https://img.shields.io/badge/Project_Page-Lots_of_Demos-Green"></a>
|
| 25 |
+
<br>
|
| 26 |
+
</h3>
|
| 27 |
+
</p>
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
Achieving streaming, fine-grained control over the outputs of autoregressive video diffusion models remains challenging, making it difficult to ensure that they consistently align with user expectations. To bridge this gap, we propose **stReaming drag-oriEnted interactiVe vidEo manipuLation (REVEL)**, a new task that enables users to modify generated videos *anytime* on *anything* via fine-grained, interactive drag. Beyond DragVideo and SG-I2V, REVEL unifies drag-style video manipulation as editing and animating video frames with both supporting user-specified translation, deformation, and rotation effects, making drag operations versatile. In resolving REVEL, we observe: *i*) drag-induced perturbations accumulate in latent space, causing severe latent distribution drift that halts the drag process; *ii*) streaming drag is easily disturbed by context frames, thereby yielding visually unnatural outcomes. We thus propose a training-free approach, **DragStream**, comprising: *i*) an adaptive distribution self-rectification strategy that leverages neighboring frames' statistics to effectively constrain the drift of latent embeddings; *ii*) a spatial-frequency selective optimization mechanism, allowing the model to fully exploit contextual information while mitigating its interference via selectively propagating visual cues along generation. Our method can be seamlessly integrated into existing autoregressive video diffusion models, and extensive experiments firmly demonstrate the effectiveness of our DragStream
|
| 32 |
+
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+

|
| 36 |
+
|
| 37 |
+
## Requirements
|
| 38 |
+
We tested this repo on the following setup:
|
| 39 |
+
* Nvidia GPU with at least 40 GB memory.
|
| 40 |
+
* Linux operating system.
|
| 41 |
+
* 64 GB RAM.
|
| 42 |
+
|
| 43 |
+
Other hardware setup could also work but hasn't been tested.
|
| 44 |
+
|
| 45 |
+
## Installation
|
| 46 |
+
|
| 47 |
+
### 1. Follow Self-Forcing to Install Dependencies
|
| 48 |
+
|
| 49 |
+
Create a conda environment and install dependencies:
|
| 50 |
+
```
|
| 51 |
+
conda create -n drag-stream python=3.10 -y
|
| 52 |
+
conda activate drag-stream
|
| 53 |
+
pip install -r requirements.txt
|
| 54 |
+
pip install flash-attn --no-build-isolation
|
| 55 |
+
python setup.py develop
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
### 2. Follow Self-Forcing to Download Checkpoints
|
| 59 |
+
```
|
| 60 |
+
huggingface-cli download Wan-AI/Wan2.1-T2V-1.3B --local-dir wan_models/Wan2.1-T2V-1.3B
|
| 61 |
+
huggingface-cli download gdhe17/Self-Forcing checkpoints --local-dir ./checkpoints
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
### 3. Follow Segment-Anything to Install SAM
|
| 65 |
+
|
| 66 |
+
```
|
| 67 |
+
git clone git@github.com:facebookresearch/segment-anything.git
|
| 68 |
+
cd segment-anything; pip install -e .
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
### 4. Follow Segment-Anything to Download SAM Checkpoint
|
| 72 |
+
|
| 73 |
+
- [ViT-H SAM model.](https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth)
|
| 74 |
+
|
| 75 |
+
## Drag/Animate Video with GUI
|
| 76 |
+
|
| 77 |
+
```
|
| 78 |
+
python app.py
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
## CLI Inference with Saved Trajectories
|
| 82 |
+
|
| 83 |
+
```
|
| 84 |
+
python offline_run.py
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
## Reproducibility
|
| 88 |
+
|
| 89 |
+
To ensure every Drag/Animation is performed on the same generated video given the same input conditions, we set the random seed before the initialization of random noise and before the generation process.
|
| 90 |
+
|
| 91 |
+
Please refer to the `set_seed(seed)` in `inference.py`, `stream_inference.py`, `click_gui_video.py`, and `offline_run.py` for details.
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
## Acknowledgements
|
| 95 |
+
This codebase is built on top of the open-source implementation of [Self-Forcing](https://self-forcing.github.io/).
|
app.py
ADDED
|
@@ -0,0 +1,1464 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
|
| 4 |
+
import copy
|
| 5 |
+
import torch
|
| 6 |
+
from torchvision.io import write_video
|
| 7 |
+
from torch.utils.data import Dataset
|
| 8 |
+
import numpy as np
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from hydra import initialize, compose
|
| 11 |
+
from hydra.core.global_hydra import GlobalHydra
|
| 12 |
+
|
| 13 |
+
from b_spline import build_clamped_bspline, equidistant_points_on_spline
|
| 14 |
+
|
| 15 |
+
torch.set_grad_enabled(False)
|
| 16 |
+
|
| 17 |
+
from palette import _palette
|
| 18 |
+
|
| 19 |
+
import gradio as gr
|
| 20 |
+
import numpy as np
|
| 21 |
+
from scipy import ndimage
|
| 22 |
+
from PIL import Image
|
| 23 |
+
import os
|
| 24 |
+
from pathlib import Path
|
| 25 |
+
import cv2
|
| 26 |
+
|
| 27 |
+
# from sam_segment import predict_masks_with_sam
|
| 28 |
+
from segment_anything import SamPredictor, sam_model_registry
|
| 29 |
+
|
| 30 |
+
from tensor_utils import (
|
| 31 |
+
image_to_pil,
|
| 32 |
+
image_to_np,
|
| 33 |
+
bbox_from_mask,
|
| 34 |
+
draw_bbox_on_image,
|
| 35 |
+
draw_mask_on_image,
|
| 36 |
+
draw_points_on_image,
|
| 37 |
+
draw_lines_on_image,
|
| 38 |
+
trajectory_interpolate,
|
| 39 |
+
dilate_mask,
|
| 40 |
+
dilate_masks,
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
from optimize_utils import (
|
| 44 |
+
MultiTrajectory,
|
| 45 |
+
Trajectory,
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
import sys
|
| 49 |
+
|
| 50 |
+
from utils.misc import set_seed
|
| 51 |
+
|
| 52 |
+
from stream_inference_wrapper import StreamInferenceWrapper
|
| 53 |
+
from stream_drag_inference_wrapper import StreamDragInferenceWrapper
|
| 54 |
+
from utils.dataset import TextDataset
|
| 55 |
+
|
| 56 |
+
from video_operations import generate_video, optimize_video
|
| 57 |
+
|
| 58 |
+
# from compute_objmc import visualize_ground_truth_from_trajectory_file
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def extract_layer_as_mask(image_editor, layer_index=0):
|
| 62 |
+
if len(image_editor["layers"]) > layer_index:
|
| 63 |
+
layer = image_editor["layers"][layer_index]
|
| 64 |
+
return image_to_np(layer.convert("L")) > 0
|
| 65 |
+
return None
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def apply_mask_to_image(
|
| 69 |
+
mask: np.ndarray | None,
|
| 70 |
+
image: np.ndarray | Image.Image,
|
| 71 |
+
mask_color: list[int],
|
| 72 |
+
alpha: float,
|
| 73 |
+
) -> None | Image.Image:
|
| 74 |
+
if image is None:
|
| 75 |
+
return None
|
| 76 |
+
if mask is None:
|
| 77 |
+
return image_to_pil(image)
|
| 78 |
+
mask = np.array(mask)
|
| 79 |
+
new_image = draw_mask_on_image(
|
| 80 |
+
image,
|
| 81 |
+
mask,
|
| 82 |
+
mask_color=mask_color,
|
| 83 |
+
alpha=alpha,
|
| 84 |
+
)
|
| 85 |
+
return new_image
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def apply_movable_mask_to_image(
|
| 89 |
+
mask: np.ndarray | None,
|
| 90 |
+
image: np.ndarray | Image.Image,
|
| 91 |
+
):
|
| 92 |
+
return apply_mask_to_image(
|
| 93 |
+
mask=mask,
|
| 94 |
+
image=image,
|
| 95 |
+
mask_color=(255, 255, 255),
|
| 96 |
+
alpha=0.35,
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def apply_target_mask_to_image(
|
| 101 |
+
mask: np.ndarray | None,
|
| 102 |
+
image: np.ndarray | Image.Image,
|
| 103 |
+
):
|
| 104 |
+
return apply_mask_to_image(
|
| 105 |
+
mask=mask,
|
| 106 |
+
image=image,
|
| 107 |
+
mask_color=(255, 64, 64),
|
| 108 |
+
alpha=0.5,
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def get_video_last_frame(
|
| 113 |
+
# video: Optional[torch.Tensor], # None or (t, h, w, c)
|
| 114 |
+
video_path: str,
|
| 115 |
+
):
|
| 116 |
+
"""
|
| 117 |
+
Loads the last frame from a video.
|
| 118 |
+
|
| 119 |
+
Returns:
|
| 120 |
+
Image: The last frame as a PIL Image.
|
| 121 |
+
"""
|
| 122 |
+
print(f"Getting last frame from video: {video_path = }")
|
| 123 |
+
if video_path is None:
|
| 124 |
+
return None
|
| 125 |
+
|
| 126 |
+
cap = cv2.VideoCapture(video_path)
|
| 127 |
+
if not cap.isOpened():
|
| 128 |
+
print(f"Failed to open video: {video_path}")
|
| 129 |
+
return None
|
| 130 |
+
try:
|
| 131 |
+
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 132 |
+
if frame_count <= 0:
|
| 133 |
+
print(f"Video has non-positive frame count: {frame_count}")
|
| 134 |
+
cap.release()
|
| 135 |
+
return None
|
| 136 |
+
|
| 137 |
+
# Try direct seek to last frame
|
| 138 |
+
target_index = frame_count - 1
|
| 139 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, target_index)
|
| 140 |
+
ret, frame = cap.read()
|
| 141 |
+
|
| 142 |
+
# Fallback: iterate to last frame if random access failed
|
| 143 |
+
if (not ret) or frame is None:
|
| 144 |
+
print("Direct seek failed, iterating through frames...")
|
| 145 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
| 146 |
+
last_valid = None
|
| 147 |
+
while True:
|
| 148 |
+
ret_i, frame_i = cap.read()
|
| 149 |
+
if not ret_i:
|
| 150 |
+
break
|
| 151 |
+
last_valid = frame_i
|
| 152 |
+
frame = last_valid
|
| 153 |
+
|
| 154 |
+
if frame is None:
|
| 155 |
+
print("Could not retrieve last frame.")
|
| 156 |
+
return None
|
| 157 |
+
|
| 158 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 159 |
+
last_frame_image = Image.fromarray(frame)
|
| 160 |
+
return last_frame_image
|
| 161 |
+
except Exception as e:
|
| 162 |
+
print(f"Error extracting last frame: {e}")
|
| 163 |
+
return None
|
| 164 |
+
finally:
|
| 165 |
+
cap.release()
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def sam_predict_segmentation(
|
| 169 |
+
sam_predictor: SamPredictor,
|
| 170 |
+
origin_image: Image.Image | np.ndarray,
|
| 171 |
+
restriction_mask: np.ndarray, # (h, w), bool
|
| 172 |
+
click_points: list[tuple[int, int]],
|
| 173 |
+
previous_sam_logits: np.ndarray | None, # (3, 256, 256)
|
| 174 |
+
):
|
| 175 |
+
# print(f"{restriction_mask.shape = }")
|
| 176 |
+
|
| 177 |
+
origin_image_np = image_to_np(origin_image)
|
| 178 |
+
# print(f"{origin_image_np.shape = }")
|
| 179 |
+
sam_predictor.set_image(origin_image_np)
|
| 180 |
+
|
| 181 |
+
if previous_sam_logits is not None:
|
| 182 |
+
print(f"{previous_sam_logits.shape = }")
|
| 183 |
+
else:
|
| 184 |
+
print(f"{previous_sam_logits = }")
|
| 185 |
+
masks, scores, logits = sam_predictor.predict(
|
| 186 |
+
point_coords=np.array(click_points),
|
| 187 |
+
point_labels=np.ones((len(click_points),)),
|
| 188 |
+
mask_input=(previous_sam_logits[0:1] if previous_sam_logits is not None else None),
|
| 189 |
+
multimask_output=True,
|
| 190 |
+
)
|
| 191 |
+
# mask: np.ndarray
|
| 192 |
+
# scores: np.ndarray
|
| 193 |
+
# logits: np.ndarray
|
| 194 |
+
# print(f"{masks.shape = }") # (3, 480, 832)
|
| 195 |
+
# print(f"{logits.shape = }") # (3, 256, 256)
|
| 196 |
+
|
| 197 |
+
mask = masks[0]
|
| 198 |
+
mask *= restriction_mask
|
| 199 |
+
|
| 200 |
+
logits *= cv2.resize(
|
| 201 |
+
restriction_mask.astype(np.uint8),
|
| 202 |
+
dsize=(256, 256),
|
| 203 |
+
interpolation=cv2.INTER_LINEAR,
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
return mask, logits
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def sam_predict_segmentation_wrapper(
|
| 210 |
+
sam_predictor: SamPredictor,
|
| 211 |
+
original_image: Image.Image | np.ndarray,
|
| 212 |
+
restriction_mask: np.ndarray | None,
|
| 213 |
+
previous_click_points: list[tuple[int, int]],
|
| 214 |
+
previous_sam_logits: np.ndarray | None,
|
| 215 |
+
bypass_sam_model: bool,
|
| 216 |
+
evt: gr.SelectData,
|
| 217 |
+
):
|
| 218 |
+
# print(f"{restriction_mask = }")
|
| 219 |
+
original_image = image_to_pil(original_image).convert("RGB")
|
| 220 |
+
|
| 221 |
+
if restriction_mask is None:
|
| 222 |
+
labeled_restriction_mask = np.zeros(
|
| 223 |
+
(original_image.height, original_image.width), dtype=np.int32
|
| 224 |
+
)
|
| 225 |
+
else:
|
| 226 |
+
labeled_restriction_mask, _ = ndimage.label(restriction_mask, structure=np.ones((3, 3)))
|
| 227 |
+
# print(f"{labeled_restriction_mask = }")
|
| 228 |
+
current_click_label = labeled_restriction_mask[evt.index[1], evt.index[0]]
|
| 229 |
+
# print(f"{current_click_label = }")
|
| 230 |
+
|
| 231 |
+
if current_click_label == 0:
|
| 232 |
+
selected_component_mask = np.zeros_like(labeled_restriction_mask, dtype=bool)
|
| 233 |
+
else:
|
| 234 |
+
selected_component_mask = labeled_restriction_mask == current_click_label
|
| 235 |
+
# print(f"{selected_component_mask = }")
|
| 236 |
+
|
| 237 |
+
if bypass_sam_model:
|
| 238 |
+
click_points = [evt.index]
|
| 239 |
+
mask = selected_component_mask
|
| 240 |
+
logits = None
|
| 241 |
+
else:
|
| 242 |
+
click_points = previous_click_points + [evt.index]
|
| 243 |
+
mask, logits = sam_predict_segmentation(
|
| 244 |
+
sam_predictor=sam_predictor,
|
| 245 |
+
origin_image=original_image,
|
| 246 |
+
restriction_mask=selected_component_mask,
|
| 247 |
+
click_points=click_points,
|
| 248 |
+
previous_sam_logits=previous_sam_logits,
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
return mask, click_points, logits
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def draw_all_sam_masks(image: Image.Image | None, mask_list: list[np.ndarray]):
|
| 255 |
+
if image is None:
|
| 256 |
+
return None
|
| 257 |
+
if len(mask_list) == 0:
|
| 258 |
+
pass
|
| 259 |
+
else:
|
| 260 |
+
for mask_idx, mask in enumerate(mask_list):
|
| 261 |
+
image = draw_mask_on_image(
|
| 262 |
+
image,
|
| 263 |
+
mask,
|
| 264 |
+
mask_color=tuple(_palette[mask_idx + 1]),
|
| 265 |
+
alpha=0.65,
|
| 266 |
+
)
|
| 267 |
+
return image
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def draw_sam_mask_wrapper(
|
| 271 |
+
original_image,
|
| 272 |
+
movable_mask,
|
| 273 |
+
current_mask: np.ndarray | None,
|
| 274 |
+
previous_masks: list[np.ndarray],
|
| 275 |
+
click_points: list[tuple[int, int]],
|
| 276 |
+
):
|
| 277 |
+
image = apply_movable_mask_to_image(
|
| 278 |
+
image=original_image,
|
| 279 |
+
mask=movable_mask,
|
| 280 |
+
)
|
| 281 |
+
if image is None:
|
| 282 |
+
return None
|
| 283 |
+
image = draw_all_sam_masks(
|
| 284 |
+
image,
|
| 285 |
+
previous_masks + ([current_mask] if current_mask is not None else []),
|
| 286 |
+
)
|
| 287 |
+
image = draw_points_on_image(
|
| 288 |
+
image,
|
| 289 |
+
click_points,
|
| 290 |
+
color=[(0, 255, 0, 255) for l in click_points],
|
| 291 |
+
radius=5,
|
| 292 |
+
)
|
| 293 |
+
return image
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def save_sam_masks(
|
| 297 |
+
current_mask: np.ndarray | None,
|
| 298 |
+
previous_masks: list[np.ndarray],
|
| 299 |
+
):
|
| 300 |
+
new_masks = previous_masks + ([current_mask] if current_mask is not None else [])
|
| 301 |
+
return None, new_masks, [], None
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def select_target_sam_mask(
|
| 305 |
+
masks_list: list[np.ndarray],
|
| 306 |
+
evt: gr.SelectData,
|
| 307 |
+
):
|
| 308 |
+
is_match_mask = False
|
| 309 |
+
for mask_index, sam_mask in enumerate(masks_list):
|
| 310 |
+
# check if evt point in sam_mask
|
| 311 |
+
if sam_mask[evt.index[1], evt.index[0]]:
|
| 312 |
+
is_match_mask = True
|
| 313 |
+
break
|
| 314 |
+
|
| 315 |
+
if not is_match_mask:
|
| 316 |
+
print(f"Mask not found for {evt.index = }")
|
| 317 |
+
mask_index = -1
|
| 318 |
+
return mask_index
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def draw_rotation_trajectory(
|
| 322 |
+
image,
|
| 323 |
+
points,
|
| 324 |
+
):
|
| 325 |
+
image = draw_points_on_image(
|
| 326 |
+
image,
|
| 327 |
+
[points[0]],
|
| 328 |
+
color="green",
|
| 329 |
+
radius=15,
|
| 330 |
+
)
|
| 331 |
+
if len(points) > 1:
|
| 332 |
+
image = draw_points_on_image(
|
| 333 |
+
image,
|
| 334 |
+
points[1:],
|
| 335 |
+
color=[
|
| 336 |
+
(
|
| 337 |
+
255 - int(float(i) / len(points[1:]) * 255.0),
|
| 338 |
+
64,
|
| 339 |
+
int(float(i) / len(points[1:]) * 255.0),
|
| 340 |
+
255,
|
| 341 |
+
)
|
| 342 |
+
for i in range(len(points[1:]))
|
| 343 |
+
],
|
| 344 |
+
radius=5,
|
| 345 |
+
)
|
| 346 |
+
for point in points[1:]:
|
| 347 |
+
image = draw_lines_on_image(
|
| 348 |
+
image,
|
| 349 |
+
[points[0], point],
|
| 350 |
+
color="green",
|
| 351 |
+
width=3,
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
return image
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def draw_translation_trajectory(
|
| 358 |
+
image,
|
| 359 |
+
points,
|
| 360 |
+
control_points: list[tuple[int, int]] = [],
|
| 361 |
+
is_draw_control_points: bool = True,
|
| 362 |
+
):
|
| 363 |
+
if len(points) == 1:
|
| 364 |
+
image = draw_points_on_image(
|
| 365 |
+
image,
|
| 366 |
+
points,
|
| 367 |
+
color=[(255, 64, 0, 255)],
|
| 368 |
+
radius=6,
|
| 369 |
+
)
|
| 370 |
+
return image
|
| 371 |
+
if is_draw_control_points and (len(control_points) >= 2):
|
| 372 |
+
image = draw_points_on_image(
|
| 373 |
+
image,
|
| 374 |
+
control_points,
|
| 375 |
+
color=[(0, 255, 0, 255) for _ in control_points],
|
| 376 |
+
radius=3,
|
| 377 |
+
)
|
| 378 |
+
image = draw_lines_on_image(
|
| 379 |
+
image,
|
| 380 |
+
control_points,
|
| 381 |
+
color=[(0, 255, 0, 255) for _ in control_points],
|
| 382 |
+
width=2,
|
| 383 |
+
)
|
| 384 |
+
image = draw_lines_on_image(
|
| 385 |
+
image,
|
| 386 |
+
points,
|
| 387 |
+
color=[
|
| 388 |
+
(
|
| 389 |
+
255 - int(float(i) / len(points[1:]) * 255.0),
|
| 390 |
+
64,
|
| 391 |
+
int(float(i) / len(points[1:]) * 255.0),
|
| 392 |
+
255,
|
| 393 |
+
)
|
| 394 |
+
for i in range(len(points))
|
| 395 |
+
],
|
| 396 |
+
width=4,
|
| 397 |
+
)
|
| 398 |
+
image = draw_points_on_image(
|
| 399 |
+
image,
|
| 400 |
+
points,
|
| 401 |
+
color=[
|
| 402 |
+
(
|
| 403 |
+
255 - int(float(i) / len(points[1:]) * 255.0),
|
| 404 |
+
64,
|
| 405 |
+
int(float(i) / len(points[1:]) * 255.0),
|
| 406 |
+
255,
|
| 407 |
+
)
|
| 408 |
+
for i in range(len(points))
|
| 409 |
+
],
|
| 410 |
+
radius=6,
|
| 411 |
+
)
|
| 412 |
+
|
| 413 |
+
return image
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
def draw_all_trajectories(
|
| 417 |
+
image,
|
| 418 |
+
trajectory: MultiTrajectory,
|
| 419 |
+
is_draw_control_points: bool = True,
|
| 420 |
+
):
|
| 421 |
+
print(
|
| 422 |
+
f"""
|
| 423 |
+
draw_all_trajectories:
|
| 424 |
+
"""
|
| 425 |
+
)
|
| 426 |
+
if trajectory.trajectories is None:
|
| 427 |
+
return image
|
| 428 |
+
for traj in trajectory.trajectories:
|
| 429 |
+
if traj.original_trajectory is None:
|
| 430 |
+
continue
|
| 431 |
+
original_traj = traj.original_trajectory
|
| 432 |
+
if original_traj["is_rotation"]:
|
| 433 |
+
image = draw_rotation_trajectory(image, original_traj["points"])
|
| 434 |
+
else:
|
| 435 |
+
image = draw_translation_trajectory(
|
| 436 |
+
image,
|
| 437 |
+
original_traj["points"],
|
| 438 |
+
original_traj.get("control_points", []),
|
| 439 |
+
is_draw_control_points=is_draw_control_points,
|
| 440 |
+
)
|
| 441 |
+
|
| 442 |
+
return image
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def draw_trajectory_image(
|
| 446 |
+
original_image,
|
| 447 |
+
movable_mask,
|
| 448 |
+
mask_index,
|
| 449 |
+
masks_list: list[np.ndarray],
|
| 450 |
+
trajectory: MultiTrajectory,
|
| 451 |
+
is_draw_bbox: bool = True,
|
| 452 |
+
is_draw_control_points: bool = True,
|
| 453 |
+
):
|
| 454 |
+
print(
|
| 455 |
+
f"""
|
| 456 |
+
draw_trajectory_image:
|
| 457 |
+
{mask_index = }
|
| 458 |
+
"""
|
| 459 |
+
)
|
| 460 |
+
image = apply_movable_mask_to_image(
|
| 461 |
+
mask=movable_mask,
|
| 462 |
+
image=original_image,
|
| 463 |
+
)
|
| 464 |
+
image = draw_all_sam_masks(image, masks_list)
|
| 465 |
+
if (
|
| 466 |
+
(mask_index is not None)
|
| 467 |
+
and (mask_index >= 0)
|
| 468 |
+
and (mask_index < len(masks_list))
|
| 469 |
+
and is_draw_bbox
|
| 470 |
+
):
|
| 471 |
+
image = draw_bbox_on_image(image, bbox_from_mask(masks_list[mask_index]))
|
| 472 |
+
image = draw_all_trajectories(
|
| 473 |
+
image,
|
| 474 |
+
trajectory,
|
| 475 |
+
is_draw_control_points=is_draw_control_points,
|
| 476 |
+
)
|
| 477 |
+
return image
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
def update_trajectory(
|
| 481 |
+
trajectory: MultiTrajectory,
|
| 482 |
+
mask_index: int,
|
| 483 |
+
drag_animation_select: str,
|
| 484 |
+
translate_rotate_select: str,
|
| 485 |
+
evt: gr.SelectData,
|
| 486 |
+
):
|
| 487 |
+
print(f"update_trajectory")
|
| 488 |
+
|
| 489 |
+
# Work on a deep copy so Gradio sees a new object
|
| 490 |
+
trajectory = copy.deepcopy(trajectory)
|
| 491 |
+
|
| 492 |
+
if mask_index < 0:
|
| 493 |
+
print(f"Invalid mask_index: {mask_index}")
|
| 494 |
+
return trajectory
|
| 495 |
+
|
| 496 |
+
# print(f"{evt.index = }")
|
| 497 |
+
x_center, y_center = evt.index # evt.value is (x, y)
|
| 498 |
+
|
| 499 |
+
clicked_point = (x_center, y_center)
|
| 500 |
+
print(f"{clicked_point = }")
|
| 501 |
+
|
| 502 |
+
# Ensure trajectories list is large enough
|
| 503 |
+
while len(trajectory.trajectories) <= mask_index:
|
| 504 |
+
trajectory.trajectories.append(Trajectory())
|
| 505 |
+
|
| 506 |
+
existing_traj_obj = trajectory.trajectories[mask_index]
|
| 507 |
+
if existing_traj_obj.original_trajectory is not None:
|
| 508 |
+
current_trajectory = dict(existing_traj_obj.original_trajectory)
|
| 509 |
+
else:
|
| 510 |
+
current_trajectory = {}
|
| 511 |
+
|
| 512 |
+
if translate_rotate_select == "Translation":
|
| 513 |
+
current_trajectory["is_rotation"] = False
|
| 514 |
+
|
| 515 |
+
# Append clicked control point
|
| 516 |
+
control_points = current_trajectory.get("control_points", [])
|
| 517 |
+
control_points = control_points + [clicked_point]
|
| 518 |
+
|
| 519 |
+
# Drag vs Animation behavior
|
| 520 |
+
if drag_animation_select == "Drag":
|
| 521 |
+
# Restrict to last two control points, sample exactly 2 points
|
| 522 |
+
if len(control_points) > 2:
|
| 523 |
+
control_points = [clicked_point]
|
| 524 |
+
num_traj_points = 2
|
| 525 |
+
elif drag_animation_select == "Animation":
|
| 526 |
+
# No restriction on control points, sample N = 1 + 3 * block_number
|
| 527 |
+
num_traj_points = 1 + 3 * int(trajectory.block_number)
|
| 528 |
+
else:
|
| 529 |
+
raise ValueError(f"Invalid drag_animation_select: {drag_animation_select}")
|
| 530 |
+
|
| 531 |
+
current_trajectory["control_points"] = control_points
|
| 532 |
+
|
| 533 |
+
# Compute trajectory points along BSpline (or pad if not enough controls)
|
| 534 |
+
if len(control_points) < 2:
|
| 535 |
+
sampled_pts = [control_points[0]] * num_traj_points
|
| 536 |
+
else:
|
| 537 |
+
spline = build_clamped_bspline(control_points, degree=3)
|
| 538 |
+
pts = equidistant_points_on_spline(spline, num_points=num_traj_points, grid=8000)
|
| 539 |
+
sampled_pts = [(int(round(px)), int(round(py))) for px, py in pts]
|
| 540 |
+
|
| 541 |
+
current_trajectory["points"] = sampled_pts
|
| 542 |
+
|
| 543 |
+
elif translate_rotate_select == "Rotation":
|
| 544 |
+
current_trajectory["is_rotation"] = True
|
| 545 |
+
|
| 546 |
+
# Initialize if missing, else apply 3-point logic
|
| 547 |
+
if "points" not in current_trajectory or current_trajectory["points"] is None:
|
| 548 |
+
current_trajectory["points"] = [clicked_point]
|
| 549 |
+
else:
|
| 550 |
+
pts = current_trajectory["points"] + [clicked_point]
|
| 551 |
+
|
| 552 |
+
# If about to exceed 3, reset to the new point
|
| 553 |
+
if len(pts) > 3:
|
| 554 |
+
current_trajectory["points"] = [clicked_point]
|
| 555 |
+
# If less than 3, just append
|
| 556 |
+
elif len(pts) < 3:
|
| 557 |
+
current_trajectory["points"] = pts
|
| 558 |
+
else:
|
| 559 |
+
# len(pts) == 3: pts[0] is rotation center
|
| 560 |
+
if drag_animation_select == "Animation":
|
| 561 |
+
first = trajectory_interpolate(pts[1:], scale=int(trajectory.block_number))
|
| 562 |
+
second = trajectory_interpolate(first, scale=3)
|
| 563 |
+
current_trajectory["points"] = pts[0:1] + second
|
| 564 |
+
else:
|
| 565 |
+
# Drag: do not interpolate
|
| 566 |
+
current_trajectory["points"] = pts
|
| 567 |
+
else:
|
| 568 |
+
raise ValueError("Invalid translation/rotation selection")
|
| 569 |
+
|
| 570 |
+
# Update the Trajectory object in-place (recomputes block_trajectories)
|
| 571 |
+
existing_traj_obj.set_original_trajectory(current_trajectory)
|
| 572 |
+
# print(f"{trajectory = }")
|
| 573 |
+
|
| 574 |
+
return trajectory
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
def save_trajectory(
|
| 578 |
+
save_dir: Path,
|
| 579 |
+
saved_trajectory: MultiTrajectory,
|
| 580 |
+
original_image: Image.Image,
|
| 581 |
+
current_block_index: int,
|
| 582 |
+
masks: list[np.ndarray],
|
| 583 |
+
):
|
| 584 |
+
print(f"save_trajectory")
|
| 585 |
+
print(f"{save_dir = }")
|
| 586 |
+
print(f"{saved_trajectory = }")
|
| 587 |
+
save_dir = Path(save_dir)
|
| 588 |
+
save_dir.mkdir(parents=True, exist_ok=True)
|
| 589 |
+
|
| 590 |
+
drag_animation_select = saved_trajectory.drag_or_animation_select or "Drag"
|
| 591 |
+
save_prefix = f"block_{current_block_index}_{drag_animation_select}"
|
| 592 |
+
|
| 593 |
+
# Use MultiTrajectory's save method
|
| 594 |
+
saved_trajectory.save(
|
| 595 |
+
save_dir=save_dir,
|
| 596 |
+
prefix=save_prefix,
|
| 597 |
+
)
|
| 598 |
+
|
| 599 |
+
# Save the trajectory image
|
| 600 |
+
trajectory_image = draw_trajectory_image(
|
| 601 |
+
original_image=original_image,
|
| 602 |
+
movable_mask=saved_trajectory.movable_mask,
|
| 603 |
+
mask_index=None,
|
| 604 |
+
masks_list=masks,
|
| 605 |
+
trajectory=saved_trajectory,
|
| 606 |
+
is_draw_bbox=False,
|
| 607 |
+
is_draw_control_points=False,
|
| 608 |
+
)
|
| 609 |
+
trajectory_image.save(save_dir / f"{save_prefix}_trajectory.png")
|
| 610 |
+
|
| 611 |
+
|
| 612 |
+
def clear_current_trajectory(
|
| 613 |
+
idx: int,
|
| 614 |
+
trajectory: MultiTrajectory,
|
| 615 |
+
):
|
| 616 |
+
trajectory = copy.deepcopy(trajectory)
|
| 617 |
+
"""Clear the trajectory at the given mask index."""
|
| 618 |
+
try:
|
| 619 |
+
idx_int = int(idx)
|
| 620 |
+
except Exception:
|
| 621 |
+
return trajectory
|
| 622 |
+
|
| 623 |
+
if not trajectory.trajectories:
|
| 624 |
+
return trajectory
|
| 625 |
+
|
| 626 |
+
if idx_int < 0 or idx_int >= len(trajectory.trajectories):
|
| 627 |
+
return trajectory
|
| 628 |
+
|
| 629 |
+
# Reset this trajectory (keep the mask)
|
| 630 |
+
mask = trajectory.trajectories[idx_int].mask
|
| 631 |
+
trajectory.trajectories[idx_int] = Trajectory(mask=mask)
|
| 632 |
+
|
| 633 |
+
return trajectory
|
| 634 |
+
|
| 635 |
+
|
| 636 |
+
def clear_all_trajectories(
|
| 637 |
+
trajectory: MultiTrajectory,
|
| 638 |
+
):
|
| 639 |
+
trajectory = copy.deepcopy(trajectory)
|
| 640 |
+
"""Clear all trajectories but keep the masks."""
|
| 641 |
+
if trajectory.trajectories is not None:
|
| 642 |
+
for i in range(len(trajectory.trajectories)):
|
| 643 |
+
mask = trajectory.trajectories[i].mask
|
| 644 |
+
trajectory.trajectories[i] = Trajectory(mask=mask)
|
| 645 |
+
return trajectory
|
| 646 |
+
|
| 647 |
+
|
| 648 |
+
def sync_trajectory_masks(saved_trajectory: MultiTrajectory, dilated_masks: list[np.ndarray]):
|
| 649 |
+
"""Resize saved_trajectory.trajectories to match the number of dilated masks,
|
| 650 |
+
and update each Trajectory.mask with the corresponding dilated mask."""
|
| 651 |
+
saved_trajectory = copy.deepcopy(saved_trajectory)
|
| 652 |
+
|
| 653 |
+
current_len = len(saved_trajectory.trajectories)
|
| 654 |
+
target_len = len(dilated_masks) if dilated_masks else 0
|
| 655 |
+
|
| 656 |
+
if target_len > current_len:
|
| 657 |
+
# Expand: append new empty Trajectory objects
|
| 658 |
+
for _ in range(target_len - current_len):
|
| 659 |
+
saved_trajectory.trajectories.append(Trajectory())
|
| 660 |
+
elif target_len < current_len:
|
| 661 |
+
# Shrink: truncate
|
| 662 |
+
saved_trajectory.trajectories = saved_trajectory.trajectories[:target_len]
|
| 663 |
+
|
| 664 |
+
# Update each Trajectory.mask
|
| 665 |
+
for i, mask in enumerate(dilated_masks):
|
| 666 |
+
saved_trajectory.trajectories[i].mask = mask
|
| 667 |
+
|
| 668 |
+
return saved_trajectory
|
| 669 |
+
|
| 670 |
+
|
| 671 |
+
def add_listeners_to_trajectory(
|
| 672 |
+
saved_trajectory: MultiTrajectory,
|
| 673 |
+
prompt_box: gr.Textbox,
|
| 674 |
+
trajectory_block_number_slider: gr.Slider,
|
| 675 |
+
drag_animation_select: gr.Dropdown,
|
| 676 |
+
movable_area_mask: gr.State,
|
| 677 |
+
dilated_saved_sam_predicted_masks: gr.State,
|
| 678 |
+
):
|
| 679 |
+
# Sync prompt into saved_trajectory when prompt_box changes
|
| 680 |
+
|
| 681 |
+
def sync_trajectory_prompt(saved_trajectory: MultiTrajectory, prompt: str):
|
| 682 |
+
saved_trajectory.prompt = prompt
|
| 683 |
+
return saved_trajectory
|
| 684 |
+
|
| 685 |
+
prompt_box.change(
|
| 686 |
+
fn=sync_trajectory_prompt,
|
| 687 |
+
inputs=[saved_trajectory, prompt_box],
|
| 688 |
+
outputs=saved_trajectory,
|
| 689 |
+
trigger_mode="always_last",
|
| 690 |
+
)
|
| 691 |
+
|
| 692 |
+
# Sync block_number into saved_trajectory when trajectory_block_number_slider changes
|
| 693 |
+
def sync_trajectory_block_number(saved_trajectory: MultiTrajectory, block_number: int):
|
| 694 |
+
saved_trajectory.block_number = block_number
|
| 695 |
+
return saved_trajectory
|
| 696 |
+
|
| 697 |
+
trajectory_block_number_slider.change(
|
| 698 |
+
fn=sync_trajectory_block_number,
|
| 699 |
+
inputs=[saved_trajectory, trajectory_block_number_slider],
|
| 700 |
+
outputs=saved_trajectory,
|
| 701 |
+
trigger_mode="always_last",
|
| 702 |
+
)
|
| 703 |
+
|
| 704 |
+
# Sync drag_or_animation_select into saved_trajectory when drag_animation_select changes
|
| 705 |
+
def sync_trajectory_drag_animation(
|
| 706 |
+
saved_trajectory: MultiTrajectory, drag_animation_select: str
|
| 707 |
+
):
|
| 708 |
+
saved_trajectory.drag_or_animation_select = drag_animation_select
|
| 709 |
+
return saved_trajectory
|
| 710 |
+
|
| 711 |
+
drag_animation_select.change(
|
| 712 |
+
fn=sync_trajectory_drag_animation,
|
| 713 |
+
inputs=[saved_trajectory, drag_animation_select],
|
| 714 |
+
outputs=saved_trajectory,
|
| 715 |
+
trigger_mode="always_last",
|
| 716 |
+
)
|
| 717 |
+
|
| 718 |
+
# Sync movable_area_mask into saved_trajectory when it changes
|
| 719 |
+
def sync_trajectory_movable_mask(saved_trajectory: MultiTrajectory, movable_mask):
|
| 720 |
+
saved_trajectory.movable_mask = movable_mask
|
| 721 |
+
return saved_trajectory
|
| 722 |
+
|
| 723 |
+
movable_area_mask.change(
|
| 724 |
+
fn=sync_trajectory_movable_mask,
|
| 725 |
+
inputs=[saved_trajectory, movable_area_mask],
|
| 726 |
+
outputs=saved_trajectory,
|
| 727 |
+
trigger_mode="always_last",
|
| 728 |
+
)
|
| 729 |
+
|
| 730 |
+
# Sync dilated_saved_sam_predicted_masks into saved_trajectory when it changes
|
| 731 |
+
dilated_saved_sam_predicted_masks.change(
|
| 732 |
+
fn=sync_trajectory_masks,
|
| 733 |
+
inputs=[saved_trajectory, dilated_saved_sam_predicted_masks],
|
| 734 |
+
outputs=saved_trajectory,
|
| 735 |
+
trigger_mode="always_last",
|
| 736 |
+
)
|
| 737 |
+
|
| 738 |
+
|
| 739 |
+
def create_generate_video_ui(
|
| 740 |
+
label_root: str | Path,
|
| 741 |
+
text_dataset: Dataset,
|
| 742 |
+
video_path: gr.State,
|
| 743 |
+
stream_drag_inference: StreamDragInferenceWrapper,
|
| 744 |
+
output_dir: str | Path,
|
| 745 |
+
original_image: gr.State,
|
| 746 |
+
):
|
| 747 |
+
with gr.Row():
|
| 748 |
+
prompt_index_number = gr.Number(
|
| 749 |
+
label="Step 1: Select Prompt Index Here",
|
| 750 |
+
interactive=True,
|
| 751 |
+
scale=1,
|
| 752 |
+
)
|
| 753 |
+
prompt_box = gr.Textbox(
|
| 754 |
+
label="Prompt",
|
| 755 |
+
interactive=True,
|
| 756 |
+
scale=3,
|
| 757 |
+
)
|
| 758 |
+
save_dir_text_box = gr.Textbox(
|
| 759 |
+
label="Save Directory",
|
| 760 |
+
interactive=False,
|
| 761 |
+
scale=1,
|
| 762 |
+
)
|
| 763 |
+
prompt_index_number.change(
|
| 764 |
+
fn=lambda prompt_index_number: text_dataset[prompt_index_number]["prompts"],
|
| 765 |
+
inputs=prompt_index_number,
|
| 766 |
+
outputs=[
|
| 767 |
+
prompt_box,
|
| 768 |
+
],
|
| 769 |
+
)
|
| 770 |
+
gr.on(
|
| 771 |
+
triggers=[
|
| 772 |
+
prompt_box.change,
|
| 773 |
+
],
|
| 774 |
+
fn=lambda prompt_index_number, prompt: str(
|
| 775 |
+
label_root / f"{prompt_index_number:04d}-{prompt[:50].replace(' ', '_')}"
|
| 776 |
+
),
|
| 777 |
+
inputs=[prompt_index_number, prompt_box],
|
| 778 |
+
outputs=save_dir_text_box,
|
| 779 |
+
trigger_mode="always_last",
|
| 780 |
+
)
|
| 781 |
+
with gr.Row():
|
| 782 |
+
current_block_index_slider = gr.Slider(
|
| 783 |
+
label="Current Start Block Index",
|
| 784 |
+
minimum=0,
|
| 785 |
+
maximum=50,
|
| 786 |
+
value=0,
|
| 787 |
+
step=1,
|
| 788 |
+
)
|
| 789 |
+
generate_block_number_slider = gr.Slider(
|
| 790 |
+
label="Step 2: Select Number of Blocks to Generate",
|
| 791 |
+
minimum=1,
|
| 792 |
+
maximum=50,
|
| 793 |
+
value=2,
|
| 794 |
+
step=1,
|
| 795 |
+
)
|
| 796 |
+
with gr.Row():
|
| 797 |
+
begin_generate_button = gr.Button(
|
| 798 |
+
value="Step 3: Click Here to Begin Generation",
|
| 799 |
+
)
|
| 800 |
+
refresh_video_display_button = gr.Button(value="Refresh Video Display")
|
| 801 |
+
|
| 802 |
+
with gr.Row():
|
| 803 |
+
video_display = gr.Video()
|
| 804 |
+
|
| 805 |
+
begin_generate_button.click(
|
| 806 |
+
fn=lambda pi, p, sbi, bn: generate_video(
|
| 807 |
+
stream_inference_model=stream_drag_inference,
|
| 808 |
+
prompt_index=pi,
|
| 809 |
+
prompt=p,
|
| 810 |
+
start_block_index=sbi,
|
| 811 |
+
block_number=bn,
|
| 812 |
+
output_dir=output_dir,
|
| 813 |
+
),
|
| 814 |
+
inputs=[
|
| 815 |
+
prompt_index_number,
|
| 816 |
+
prompt_box,
|
| 817 |
+
current_block_index_slider,
|
| 818 |
+
generate_block_number_slider,
|
| 819 |
+
],
|
| 820 |
+
outputs=[video_path, current_block_index_slider],
|
| 821 |
+
)
|
| 822 |
+
gr.on(
|
| 823 |
+
triggers=[
|
| 824 |
+
refresh_video_display_button.click,
|
| 825 |
+
video_path.change,
|
| 826 |
+
],
|
| 827 |
+
fn=lambda video_path: video_path,
|
| 828 |
+
inputs=video_path,
|
| 829 |
+
outputs=video_display,
|
| 830 |
+
trigger_mode="always_last",
|
| 831 |
+
)
|
| 832 |
+
|
| 833 |
+
with gr.Row():
|
| 834 |
+
get_last_frame_button = gr.Button(
|
| 835 |
+
value="Get Last Frame (Normally No Need to Click This, In Case the Last Frame Fails to Update due to Gradio Bug)",
|
| 836 |
+
)
|
| 837 |
+
gr.on(
|
| 838 |
+
triggers=[
|
| 839 |
+
video_path.change,
|
| 840 |
+
get_last_frame_button.click,
|
| 841 |
+
],
|
| 842 |
+
fn=get_video_last_frame,
|
| 843 |
+
inputs=video_path,
|
| 844 |
+
outputs=original_image,
|
| 845 |
+
)
|
| 846 |
+
|
| 847 |
+
return (
|
| 848 |
+
prompt_index_number,
|
| 849 |
+
save_dir_text_box,
|
| 850 |
+
prompt_box,
|
| 851 |
+
current_block_index_slider,
|
| 852 |
+
generate_block_number_slider,
|
| 853 |
+
)
|
| 854 |
+
|
| 855 |
+
|
| 856 |
+
def create_movable_area_ui(
|
| 857 |
+
movable_area_mask: gr.State,
|
| 858 |
+
original_image: gr.State,
|
| 859 |
+
):
|
| 860 |
+
|
| 861 |
+
with gr.Row():
|
| 862 |
+
movable_area_image_editor = gr.ImageEditor(
|
| 863 |
+
label="Step 4: This is Last Frame of Video, Draw Editable Area Here. (Normally This Should Be Large and Cover all Possible Area Where the Object You Want to Move/Animate to)",
|
| 864 |
+
type="pil",
|
| 865 |
+
interactive=True,
|
| 866 |
+
brush=gr.Brush(
|
| 867 |
+
default_size=100,
|
| 868 |
+
colors=[
|
| 869 |
+
"rgba(0, 0, 255, 0.5)",
|
| 870 |
+
],
|
| 871 |
+
default_color="auto",
|
| 872 |
+
color_mode="defaults",
|
| 873 |
+
),
|
| 874 |
+
)
|
| 875 |
+
movable_area_image_editor.change(
|
| 876 |
+
fn=extract_layer_as_mask,
|
| 877 |
+
inputs=movable_area_image_editor,
|
| 878 |
+
outputs=movable_area_mask,
|
| 879 |
+
trigger_mode="always_last",
|
| 880 |
+
)
|
| 881 |
+
original_image.change(
|
| 882 |
+
fn=lambda image: image,
|
| 883 |
+
inputs=original_image,
|
| 884 |
+
outputs=movable_area_image_editor,
|
| 885 |
+
trigger_mode="always_last",
|
| 886 |
+
)
|
| 887 |
+
with gr.Row():
|
| 888 |
+
refresh_movable_area_button = gr.Button(
|
| 889 |
+
value="Refresh Movable Area (Normally No Need to Click This, In Case the Mask Fails to Update due to Gradio Bug)"
|
| 890 |
+
)
|
| 891 |
+
refresh_movable_area_button.click(
|
| 892 |
+
fn=extract_layer_as_mask,
|
| 893 |
+
inputs=movable_area_image_editor,
|
| 894 |
+
outputs=movable_area_mask,
|
| 895 |
+
trigger_mode="always_last",
|
| 896 |
+
)
|
| 897 |
+
|
| 898 |
+
|
| 899 |
+
def create_target_area_ui(
|
| 900 |
+
target_area_mask: gr.State,
|
| 901 |
+
original_image: gr.State,
|
| 902 |
+
movable_area_mask: gr.State,
|
| 903 |
+
):
|
| 904 |
+
with gr.Row():
|
| 905 |
+
target_area_image_editor = gr.ImageEditor(
|
| 906 |
+
label="Step 5: Draw Target Area on the Object You Want to Move/Animate (Normally This Should Be a Subset of Editable Area) (Normally This Mask should be Bigger than the Desired Object)",
|
| 907 |
+
type="pil",
|
| 908 |
+
interactive=True,
|
| 909 |
+
brush=gr.Brush(
|
| 910 |
+
default_size=50,
|
| 911 |
+
colors=[
|
| 912 |
+
"rgba(255, 0, 0, 0.5)",
|
| 913 |
+
],
|
| 914 |
+
default_color="auto",
|
| 915 |
+
color_mode="defaults",
|
| 916 |
+
),
|
| 917 |
+
)
|
| 918 |
+
target_area_image_editor.change(
|
| 919 |
+
fn=extract_layer_as_mask,
|
| 920 |
+
inputs=target_area_image_editor,
|
| 921 |
+
outputs=target_area_mask,
|
| 922 |
+
trigger_mode="always_last",
|
| 923 |
+
)
|
| 924 |
+
gr.on(
|
| 925 |
+
triggers=[
|
| 926 |
+
original_image.change,
|
| 927 |
+
movable_area_mask.change,
|
| 928 |
+
],
|
| 929 |
+
fn=apply_movable_mask_to_image,
|
| 930 |
+
inputs=[
|
| 931 |
+
movable_area_mask,
|
| 932 |
+
original_image,
|
| 933 |
+
],
|
| 934 |
+
outputs=target_area_image_editor,
|
| 935 |
+
trigger_mode="always_last",
|
| 936 |
+
)
|
| 937 |
+
|
| 938 |
+
with gr.Row():
|
| 939 |
+
refresh_target_area_button = gr.Button(
|
| 940 |
+
value="Refresh Target Area (Normally No Need to Click This, In Case the Mask Fails to Update due to Gradio Bug)"
|
| 941 |
+
)
|
| 942 |
+
refresh_target_area_button.click(
|
| 943 |
+
fn=extract_layer_as_mask,
|
| 944 |
+
inputs=target_area_image_editor,
|
| 945 |
+
outputs=target_area_mask,
|
| 946 |
+
trigger_mode="always_last",
|
| 947 |
+
)
|
| 948 |
+
|
| 949 |
+
|
| 950 |
+
def create_sam_segmentation_ui(
|
| 951 |
+
original_image: gr.State,
|
| 952 |
+
movable_area_mask: gr.State,
|
| 953 |
+
target_area_mask: gr.State,
|
| 954 |
+
sam_predictor: SamPredictor,
|
| 955 |
+
sam_click_points: gr.State,
|
| 956 |
+
sam_saved_logits: gr.State,
|
| 957 |
+
current_sam_predicted_mask: gr.State,
|
| 958 |
+
saved_sam_predicted_masks: gr.State,
|
| 959 |
+
dilated_current_sam_predicted_mask: gr.State,
|
| 960 |
+
dilated_saved_sam_predicted_masks: gr.State,
|
| 961 |
+
):
|
| 962 |
+
with gr.Row():
|
| 963 |
+
refresh_sam_segment_click_image_button = gr.Button(
|
| 964 |
+
value="Refresh Target Area Mask Display (Normally No Need to Click This, In Case the Mask Fails to Update due to Gradio Bug)"
|
| 965 |
+
)
|
| 966 |
+
with gr.Row():
|
| 967 |
+
sam_segment_click_image = gr.Image(
|
| 968 |
+
label="Step 6: Click to Perform SAM Segment on Target Area, Segment the Object You Want to Move/Animate. The SAM Mask is Restricted within the Target Area Mask",
|
| 969 |
+
type="pil",
|
| 970 |
+
interactive=True,
|
| 971 |
+
)
|
| 972 |
+
gr.on(
|
| 973 |
+
triggers=[
|
| 974 |
+
original_image.change,
|
| 975 |
+
movable_area_mask.change,
|
| 976 |
+
target_area_mask.change,
|
| 977 |
+
refresh_sam_segment_click_image_button.click,
|
| 978 |
+
],
|
| 979 |
+
fn=lambda movable_mask, target_mask, image: apply_target_mask_to_image(
|
| 980 |
+
target_mask,
|
| 981 |
+
apply_movable_mask_to_image(
|
| 982 |
+
movable_mask,
|
| 983 |
+
image,
|
| 984 |
+
),
|
| 985 |
+
),
|
| 986 |
+
inputs=[
|
| 987 |
+
movable_area_mask,
|
| 988 |
+
target_area_mask,
|
| 989 |
+
original_image,
|
| 990 |
+
],
|
| 991 |
+
outputs=sam_segment_click_image,
|
| 992 |
+
trigger_mode="always_last",
|
| 993 |
+
)
|
| 994 |
+
|
| 995 |
+
with gr.Row():
|
| 996 |
+
dilate_mask_slider = gr.Slider(
|
| 997 |
+
label="Dilate Mask Pixel",
|
| 998 |
+
minimum=0,
|
| 999 |
+
maximum=50,
|
| 1000 |
+
value=15,
|
| 1001 |
+
step=1,
|
| 1002 |
+
)
|
| 1003 |
+
bypass_sam_model_check_box = gr.Checkbox(
|
| 1004 |
+
label="Bypass SAM Model",
|
| 1005 |
+
value=False,
|
| 1006 |
+
)
|
| 1007 |
+
|
| 1008 |
+
def sam_predict_segmentation_wrapper_wrapper(
|
| 1009 |
+
oi,
|
| 1010 |
+
rm,
|
| 1011 |
+
pcp,
|
| 1012 |
+
psl,
|
| 1013 |
+
bs,
|
| 1014 |
+
evt: gr.SelectData,
|
| 1015 |
+
):
|
| 1016 |
+
return sam_predict_segmentation_wrapper(
|
| 1017 |
+
sam_predictor=sam_predictor,
|
| 1018 |
+
original_image=oi,
|
| 1019 |
+
restriction_mask=rm,
|
| 1020 |
+
previous_click_points=pcp,
|
| 1021 |
+
previous_sam_logits=psl,
|
| 1022 |
+
bypass_sam_model=bs,
|
| 1023 |
+
evt=evt,
|
| 1024 |
+
)
|
| 1025 |
+
|
| 1026 |
+
sam_segment_click_image.select(
|
| 1027 |
+
fn=sam_predict_segmentation_wrapper_wrapper,
|
| 1028 |
+
inputs=[
|
| 1029 |
+
original_image,
|
| 1030 |
+
target_area_mask,
|
| 1031 |
+
sam_click_points,
|
| 1032 |
+
sam_saved_logits,
|
| 1033 |
+
bypass_sam_model_check_box,
|
| 1034 |
+
],
|
| 1035 |
+
outputs=[
|
| 1036 |
+
current_sam_predicted_mask,
|
| 1037 |
+
sam_click_points,
|
| 1038 |
+
sam_saved_logits,
|
| 1039 |
+
],
|
| 1040 |
+
trigger_mode="always_last",
|
| 1041 |
+
)
|
| 1042 |
+
gr.on(
|
| 1043 |
+
triggers=[
|
| 1044 |
+
current_sam_predicted_mask.change,
|
| 1045 |
+
dilate_mask_slider.change,
|
| 1046 |
+
],
|
| 1047 |
+
fn=dilate_mask,
|
| 1048 |
+
inputs=[
|
| 1049 |
+
current_sam_predicted_mask,
|
| 1050 |
+
dilate_mask_slider,
|
| 1051 |
+
],
|
| 1052 |
+
outputs=dilated_current_sam_predicted_mask,
|
| 1053 |
+
trigger_mode="always_last",
|
| 1054 |
+
)
|
| 1055 |
+
gr.on(
|
| 1056 |
+
triggers=[
|
| 1057 |
+
saved_sam_predicted_masks.change,
|
| 1058 |
+
dilate_mask_slider.change,
|
| 1059 |
+
],
|
| 1060 |
+
fn=dilate_masks,
|
| 1061 |
+
inputs=[
|
| 1062 |
+
saved_sam_predicted_masks,
|
| 1063 |
+
dilate_mask_slider,
|
| 1064 |
+
],
|
| 1065 |
+
outputs=dilated_saved_sam_predicted_masks,
|
| 1066 |
+
trigger_mode="always_last",
|
| 1067 |
+
)
|
| 1068 |
+
|
| 1069 |
+
|
| 1070 |
+
def create_sam_mask_management_ui(
|
| 1071 |
+
original_image: gr.State,
|
| 1072 |
+
movable_area_mask: gr.State,
|
| 1073 |
+
dilated_current_sam_predicted_mask: gr.State,
|
| 1074 |
+
dilated_saved_sam_predicted_masks: gr.State,
|
| 1075 |
+
sam_click_points: gr.State,
|
| 1076 |
+
current_sam_predicted_mask: gr.State,
|
| 1077 |
+
saved_sam_predicted_masks: gr.State,
|
| 1078 |
+
sam_saved_logits: gr.State,
|
| 1079 |
+
):
|
| 1080 |
+
with gr.Row():
|
| 1081 |
+
save_sam_masks_button = gr.Button(
|
| 1082 |
+
value="Step 7: Save the Current SAM Mask",
|
| 1083 |
+
)
|
| 1084 |
+
cancel_sam_mask_button = gr.Button(value="Cancel Current SAM Mask")
|
| 1085 |
+
delete_sam_mask_button = gr.Button(value="Delete All SAM Masks")
|
| 1086 |
+
save_sam_masks_button.click(
|
| 1087 |
+
fn=save_sam_masks,
|
| 1088 |
+
inputs=[
|
| 1089 |
+
current_sam_predicted_mask,
|
| 1090 |
+
saved_sam_predicted_masks,
|
| 1091 |
+
],
|
| 1092 |
+
outputs=[
|
| 1093 |
+
current_sam_predicted_mask,
|
| 1094 |
+
saved_sam_predicted_masks,
|
| 1095 |
+
sam_click_points,
|
| 1096 |
+
sam_saved_logits,
|
| 1097 |
+
],
|
| 1098 |
+
trigger_mode="always_last",
|
| 1099 |
+
)
|
| 1100 |
+
with gr.Row():
|
| 1101 |
+
sam_segment_display_image = gr.Image(
|
| 1102 |
+
label="Step 8: Display the SAM Segmentation, Click to Select Target Object to Create Trajectory",
|
| 1103 |
+
type="pil",
|
| 1104 |
+
interactive=True,
|
| 1105 |
+
)
|
| 1106 |
+
gr.on(
|
| 1107 |
+
triggers=[
|
| 1108 |
+
original_image.change,
|
| 1109 |
+
movable_area_mask.change,
|
| 1110 |
+
dilated_current_sam_predicted_mask.change,
|
| 1111 |
+
dilated_saved_sam_predicted_masks.change,
|
| 1112 |
+
sam_click_points.change,
|
| 1113 |
+
],
|
| 1114 |
+
fn=draw_sam_mask_wrapper,
|
| 1115 |
+
inputs=[
|
| 1116 |
+
original_image,
|
| 1117 |
+
movable_area_mask,
|
| 1118 |
+
dilated_current_sam_predicted_mask,
|
| 1119 |
+
dilated_saved_sam_predicted_masks,
|
| 1120 |
+
sam_click_points,
|
| 1121 |
+
],
|
| 1122 |
+
outputs=sam_segment_display_image,
|
| 1123 |
+
trigger_mode="always_last",
|
| 1124 |
+
)
|
| 1125 |
+
cancel_sam_mask_button.click(
|
| 1126 |
+
fn=lambda: (None, [], None),
|
| 1127 |
+
outputs=[
|
| 1128 |
+
current_sam_predicted_mask,
|
| 1129 |
+
sam_click_points,
|
| 1130 |
+
sam_saved_logits,
|
| 1131 |
+
],
|
| 1132 |
+
trigger_mode="always_last",
|
| 1133 |
+
)
|
| 1134 |
+
gr.on(
|
| 1135 |
+
triggers=[
|
| 1136 |
+
# target_area_mask.change,
|
| 1137 |
+
delete_sam_mask_button.click,
|
| 1138 |
+
],
|
| 1139 |
+
fn=lambda: (None, [], [], None),
|
| 1140 |
+
outputs=[
|
| 1141 |
+
current_sam_predicted_mask,
|
| 1142 |
+
saved_sam_predicted_masks,
|
| 1143 |
+
sam_click_points,
|
| 1144 |
+
sam_saved_logits,
|
| 1145 |
+
],
|
| 1146 |
+
trigger_mode="always_last",
|
| 1147 |
+
)
|
| 1148 |
+
with gr.Row():
|
| 1149 |
+
current_selected_mask_index_number = gr.Number(
|
| 1150 |
+
label="Current Selected Mask Index",
|
| 1151 |
+
interactive=False,
|
| 1152 |
+
)
|
| 1153 |
+
|
| 1154 |
+
sam_segment_display_image.select(
|
| 1155 |
+
fn=select_target_sam_mask,
|
| 1156 |
+
inputs=[
|
| 1157 |
+
saved_sam_predicted_masks,
|
| 1158 |
+
],
|
| 1159 |
+
outputs=[
|
| 1160 |
+
current_selected_mask_index_number,
|
| 1161 |
+
],
|
| 1162 |
+
trigger_mode="always_last",
|
| 1163 |
+
)
|
| 1164 |
+
|
| 1165 |
+
return current_selected_mask_index_number
|
| 1166 |
+
|
| 1167 |
+
|
| 1168 |
+
def create_trajectory_display_ui(
|
| 1169 |
+
original_image: gr.State,
|
| 1170 |
+
movable_area_mask: gr.State,
|
| 1171 |
+
dilated_saved_sam_predicted_masks: gr.State,
|
| 1172 |
+
saved_trajectory: gr.State,
|
| 1173 |
+
current_selected_mask_index_number: gr.State,
|
| 1174 |
+
):
|
| 1175 |
+
with gr.Row():
|
| 1176 |
+
trajectory_block_number_slider = gr.Slider(
|
| 1177 |
+
label="Step 9: Select Number of Trajectory Blocks (For Animation Only, More Blocks Means Longer Animation, For Drag, This Should be 1)",
|
| 1178 |
+
minimum=1,
|
| 1179 |
+
maximum=10,
|
| 1180 |
+
value=1,
|
| 1181 |
+
step=1,
|
| 1182 |
+
)
|
| 1183 |
+
with gr.Row():
|
| 1184 |
+
drag_animation_select = gr.Dropdown(
|
| 1185 |
+
choices=["Drag", "Animation"],
|
| 1186 |
+
label="Step 10: Select Drag or Animation",
|
| 1187 |
+
)
|
| 1188 |
+
translate_rotate_select = gr.Dropdown(
|
| 1189 |
+
choices=["Translation", "Rotation"],
|
| 1190 |
+
label="Step 11: Select Translation or Rotation",
|
| 1191 |
+
)
|
| 1192 |
+
|
| 1193 |
+
with gr.Row():
|
| 1194 |
+
trajectory_display_image = gr.Image(
|
| 1195 |
+
label="Step 12: Click on the Object in the Image to Create Trajectory. The Translation Trajectory is Controlled by Bspline Interpolation. The Rotation Trajectory is Controlled by 3 Points",
|
| 1196 |
+
type="pil",
|
| 1197 |
+
interactive=False,
|
| 1198 |
+
)
|
| 1199 |
+
gr.on(
|
| 1200 |
+
triggers=[
|
| 1201 |
+
original_image.change,
|
| 1202 |
+
movable_area_mask.change,
|
| 1203 |
+
current_selected_mask_index_number.change,
|
| 1204 |
+
dilated_saved_sam_predicted_masks.change,
|
| 1205 |
+
saved_trajectory.change,
|
| 1206 |
+
],
|
| 1207 |
+
fn=draw_trajectory_image,
|
| 1208 |
+
inputs=[
|
| 1209 |
+
original_image,
|
| 1210 |
+
movable_area_mask,
|
| 1211 |
+
current_selected_mask_index_number,
|
| 1212 |
+
dilated_saved_sam_predicted_masks,
|
| 1213 |
+
saved_trajectory,
|
| 1214 |
+
],
|
| 1215 |
+
outputs=trajectory_display_image,
|
| 1216 |
+
trigger_mode="always_last",
|
| 1217 |
+
)
|
| 1218 |
+
|
| 1219 |
+
trajectory_display_image.select(
|
| 1220 |
+
fn=update_trajectory,
|
| 1221 |
+
inputs=[
|
| 1222 |
+
saved_trajectory,
|
| 1223 |
+
current_selected_mask_index_number,
|
| 1224 |
+
drag_animation_select,
|
| 1225 |
+
translate_rotate_select,
|
| 1226 |
+
],
|
| 1227 |
+
outputs=saved_trajectory,
|
| 1228 |
+
)
|
| 1229 |
+
|
| 1230 |
+
return drag_animation_select, trajectory_block_number_slider
|
| 1231 |
+
|
| 1232 |
+
|
| 1233 |
+
def create_trajectory_management_ui(
|
| 1234 |
+
save_dir_text_box: gr.Textbox,
|
| 1235 |
+
original_image: gr.State,
|
| 1236 |
+
current_block_index_slider: gr.Slider,
|
| 1237 |
+
saved_trajectory: gr.State,
|
| 1238 |
+
dilated_saved_sam_predicted_masks: gr.State,
|
| 1239 |
+
current_selected_mask_index_number: gr.Number,
|
| 1240 |
+
):
|
| 1241 |
+
with gr.Row():
|
| 1242 |
+
save_trajectory_button = gr.Button(
|
| 1243 |
+
value="Step 13: Save Trajectory",
|
| 1244 |
+
)
|
| 1245 |
+
delete_current_trajectory_button = gr.Button(value="Delete Current Trajectory")
|
| 1246 |
+
delete_all_trajectory_button = gr.Button(value="Delete All Trajectories")
|
| 1247 |
+
save_trajectory_button.click(
|
| 1248 |
+
fn=save_trajectory,
|
| 1249 |
+
inputs=[
|
| 1250 |
+
save_dir_text_box,
|
| 1251 |
+
saved_trajectory,
|
| 1252 |
+
original_image,
|
| 1253 |
+
current_block_index_slider,
|
| 1254 |
+
dilated_saved_sam_predicted_masks,
|
| 1255 |
+
],
|
| 1256 |
+
)
|
| 1257 |
+
delete_current_trajectory_button.click(
|
| 1258 |
+
fn=clear_current_trajectory,
|
| 1259 |
+
inputs=[current_selected_mask_index_number, saved_trajectory],
|
| 1260 |
+
outputs=[saved_trajectory],
|
| 1261 |
+
)
|
| 1262 |
+
delete_all_trajectory_button.click(
|
| 1263 |
+
fn=clear_all_trajectories,
|
| 1264 |
+
inputs=[saved_trajectory],
|
| 1265 |
+
outputs=[saved_trajectory],
|
| 1266 |
+
)
|
| 1267 |
+
|
| 1268 |
+
|
| 1269 |
+
def create_ui(
|
| 1270 |
+
text_dataset: Dataset,
|
| 1271 |
+
label_root: str | Path,
|
| 1272 |
+
output_dir: str | Path,
|
| 1273 |
+
sam_predictor: SamPredictor,
|
| 1274 |
+
stream_drag_inference: StreamDragInferenceWrapper,
|
| 1275 |
+
):
|
| 1276 |
+
with gr.Blocks() as demo:
|
| 1277 |
+
video_path = gr.State(value=None)
|
| 1278 |
+
original_image = gr.State(value=None)
|
| 1279 |
+
movable_area_mask = gr.State(value=None)
|
| 1280 |
+
target_area_mask = gr.State(value=None)
|
| 1281 |
+
|
| 1282 |
+
sam_click_points = gr.State(value=[])
|
| 1283 |
+
sam_saved_logits = gr.State(value=None)
|
| 1284 |
+
saved_sam_predicted_masks = gr.State(value=[])
|
| 1285 |
+
current_sam_predicted_mask = gr.State(value=None)
|
| 1286 |
+
|
| 1287 |
+
dilated_current_sam_predicted_mask = gr.State(value=None)
|
| 1288 |
+
dilated_saved_sam_predicted_masks = gr.State(value=[])
|
| 1289 |
+
|
| 1290 |
+
saved_trajectory = gr.State(value=MultiTrajectory())
|
| 1291 |
+
|
| 1292 |
+
(
|
| 1293 |
+
prompt_index_number,
|
| 1294 |
+
save_dir_text_box,
|
| 1295 |
+
prompt_box,
|
| 1296 |
+
current_block_index_slider,
|
| 1297 |
+
generate_block_number_slider,
|
| 1298 |
+
) = create_generate_video_ui(
|
| 1299 |
+
label_root=label_root,
|
| 1300 |
+
text_dataset=text_dataset,
|
| 1301 |
+
video_path=video_path,
|
| 1302 |
+
stream_drag_inference=stream_drag_inference,
|
| 1303 |
+
output_dir=output_dir,
|
| 1304 |
+
original_image=original_image,
|
| 1305 |
+
)
|
| 1306 |
+
|
| 1307 |
+
create_movable_area_ui(movable_area_mask, original_image)
|
| 1308 |
+
create_target_area_ui(target_area_mask, original_image, movable_area_mask)
|
| 1309 |
+
create_sam_segmentation_ui(
|
| 1310 |
+
original_image=original_image,
|
| 1311 |
+
movable_area_mask=movable_area_mask,
|
| 1312 |
+
target_area_mask=target_area_mask,
|
| 1313 |
+
sam_predictor=sam_predictor,
|
| 1314 |
+
sam_click_points=sam_click_points,
|
| 1315 |
+
sam_saved_logits=sam_saved_logits,
|
| 1316 |
+
current_sam_predicted_mask=current_sam_predicted_mask,
|
| 1317 |
+
saved_sam_predicted_masks=saved_sam_predicted_masks,
|
| 1318 |
+
dilated_current_sam_predicted_mask=dilated_current_sam_predicted_mask,
|
| 1319 |
+
dilated_saved_sam_predicted_masks=dilated_saved_sam_predicted_masks,
|
| 1320 |
+
)
|
| 1321 |
+
|
| 1322 |
+
current_selected_mask_index_number = create_sam_mask_management_ui(
|
| 1323 |
+
original_image=original_image,
|
| 1324 |
+
movable_area_mask=movable_area_mask,
|
| 1325 |
+
dilated_current_sam_predicted_mask=dilated_current_sam_predicted_mask,
|
| 1326 |
+
dilated_saved_sam_predicted_masks=dilated_saved_sam_predicted_masks,
|
| 1327 |
+
sam_click_points=sam_click_points,
|
| 1328 |
+
current_sam_predicted_mask=current_sam_predicted_mask,
|
| 1329 |
+
saved_sam_predicted_masks=saved_sam_predicted_masks,
|
| 1330 |
+
sam_saved_logits=sam_saved_logits,
|
| 1331 |
+
)
|
| 1332 |
+
|
| 1333 |
+
drag_animation_select, trajectory_block_number_slider = create_trajectory_display_ui(
|
| 1334 |
+
original_image=original_image,
|
| 1335 |
+
movable_area_mask=movable_area_mask,
|
| 1336 |
+
dilated_saved_sam_predicted_masks=dilated_saved_sam_predicted_masks,
|
| 1337 |
+
saved_trajectory=saved_trajectory,
|
| 1338 |
+
current_selected_mask_index_number=current_selected_mask_index_number,
|
| 1339 |
+
)
|
| 1340 |
+
create_trajectory_management_ui(
|
| 1341 |
+
save_dir_text_box=save_dir_text_box,
|
| 1342 |
+
original_image=original_image,
|
| 1343 |
+
current_block_index_slider=current_block_index_slider,
|
| 1344 |
+
saved_trajectory=saved_trajectory,
|
| 1345 |
+
dilated_saved_sam_predicted_masks=dilated_saved_sam_predicted_masks,
|
| 1346 |
+
current_selected_mask_index_number=current_selected_mask_index_number,
|
| 1347 |
+
)
|
| 1348 |
+
|
| 1349 |
+
add_listeners_to_trajectory(
|
| 1350 |
+
saved_trajectory=saved_trajectory,
|
| 1351 |
+
prompt_box=prompt_box,
|
| 1352 |
+
trajectory_block_number_slider=trajectory_block_number_slider,
|
| 1353 |
+
drag_animation_select=drag_animation_select,
|
| 1354 |
+
movable_area_mask=movable_area_mask,
|
| 1355 |
+
dilated_saved_sam_predicted_masks=dilated_saved_sam_predicted_masks,
|
| 1356 |
+
)
|
| 1357 |
+
|
| 1358 |
+
with gr.Row():
|
| 1359 |
+
begin_optimize_button = gr.Button(
|
| 1360 |
+
value="Step 14: Click Here to Begin Optimize, Wait for a Moment and the Dragged/Animated Video will be Displayed Above",
|
| 1361 |
+
)
|
| 1362 |
+
begin_optimize_button.click(
|
| 1363 |
+
fn=lambda pi, sbi, st: optimize_video(
|
| 1364 |
+
stream_drag_inference_model=stream_drag_inference,
|
| 1365 |
+
output_dir=output_dir,
|
| 1366 |
+
prompt_index=pi,
|
| 1367 |
+
start_block_index=sbi,
|
| 1368 |
+
multi_trajectory=st,
|
| 1369 |
+
),
|
| 1370 |
+
inputs=[
|
| 1371 |
+
prompt_index_number,
|
| 1372 |
+
current_block_index_slider,
|
| 1373 |
+
saved_trajectory,
|
| 1374 |
+
],
|
| 1375 |
+
outputs=[
|
| 1376 |
+
video_path,
|
| 1377 |
+
current_block_index_slider,
|
| 1378 |
+
],
|
| 1379 |
+
)
|
| 1380 |
+
with gr.Row():
|
| 1381 |
+
clear_all_button = gr.Button(
|
| 1382 |
+
value="Step 15: Remember to Click Here to Clear All Before Generation/Editing on Next Video, Otherwise the Previous KV Cache will Affect the Generation/Editing of Next Video",
|
| 1383 |
+
)
|
| 1384 |
+
|
| 1385 |
+
def clear_all():
|
| 1386 |
+
stream_drag_inference.reset()
|
| 1387 |
+
|
| 1388 |
+
return (
|
| 1389 |
+
0,
|
| 1390 |
+
None,
|
| 1391 |
+
None,
|
| 1392 |
+
None,
|
| 1393 |
+
None,
|
| 1394 |
+
[],
|
| 1395 |
+
None,
|
| 1396 |
+
[],
|
| 1397 |
+
None,
|
| 1398 |
+
MultiTrajectory(),
|
| 1399 |
+
)
|
| 1400 |
+
|
| 1401 |
+
clear_all_button.click(
|
| 1402 |
+
fn=clear_all,
|
| 1403 |
+
outputs=[
|
| 1404 |
+
current_block_index_slider,
|
| 1405 |
+
video_path,
|
| 1406 |
+
original_image,
|
| 1407 |
+
movable_area_mask,
|
| 1408 |
+
target_area_mask,
|
| 1409 |
+
sam_click_points,
|
| 1410 |
+
sam_saved_logits,
|
| 1411 |
+
saved_sam_predicted_masks,
|
| 1412 |
+
current_sam_predicted_mask,
|
| 1413 |
+
saved_trajectory,
|
| 1414 |
+
],
|
| 1415 |
+
)
|
| 1416 |
+
|
| 1417 |
+
return demo
|
| 1418 |
+
|
| 1419 |
+
|
| 1420 |
+
def main():
|
| 1421 |
+
sam_model = sam_model_registry["vit_h"](checkpoint="../segment-anything/sam_vit_h_4b8939.pth")
|
| 1422 |
+
sam_model.to(device="cuda")
|
| 1423 |
+
sam_predictor = SamPredictor(sam_model)
|
| 1424 |
+
|
| 1425 |
+
SEED = 42
|
| 1426 |
+
|
| 1427 |
+
text_dataset = TextDataset(prompt_path="prompts/MovieGenVideoBench_extended.txt")
|
| 1428 |
+
|
| 1429 |
+
if GlobalHydra.instance().is_initialized():
|
| 1430 |
+
GlobalHydra.instance().clear()
|
| 1431 |
+
|
| 1432 |
+
config_dir = "configs"
|
| 1433 |
+
stream_config_name = "self_forcing_dmd_vsink_stream_drag"
|
| 1434 |
+
with initialize(version_base=None, config_path=config_dir):
|
| 1435 |
+
stream_config = compose(config_name=stream_config_name)
|
| 1436 |
+
print(f"{stream_config = }")
|
| 1437 |
+
|
| 1438 |
+
stream_drag_inference = StreamDragInferenceWrapper(
|
| 1439 |
+
stream_model_config=stream_config,
|
| 1440 |
+
checkpoint_path="./checkpoints/self_forcing_dmd.pt",
|
| 1441 |
+
total_generate_block_number=36,
|
| 1442 |
+
use_ema=True,
|
| 1443 |
+
seed=SEED,
|
| 1444 |
+
)
|
| 1445 |
+
label_save_dir = Path("./saved_labels")
|
| 1446 |
+
label_save_dir = label_save_dir / f"{stream_config_name}-seed{SEED}"
|
| 1447 |
+
label_save_dir.mkdir(parents=True, exist_ok=True)
|
| 1448 |
+
|
| 1449 |
+
output_save_dir = Path("outputs-editing")
|
| 1450 |
+
output_save_dir = output_save_dir / f"{stream_config_name}-seed{SEED}"
|
| 1451 |
+
output_save_dir.mkdir(parents=True, exist_ok=True)
|
| 1452 |
+
|
| 1453 |
+
demo = create_ui(
|
| 1454 |
+
text_dataset=text_dataset,
|
| 1455 |
+
label_root=label_save_dir,
|
| 1456 |
+
output_dir=output_save_dir,
|
| 1457 |
+
sam_predictor=sam_predictor,
|
| 1458 |
+
stream_drag_inference=stream_drag_inference,
|
| 1459 |
+
)
|
| 1460 |
+
demo.launch(server_name="0.0.0.0")
|
| 1461 |
+
|
| 1462 |
+
|
| 1463 |
+
if __name__ == "__main__":
|
| 1464 |
+
main()
|
b_spline.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Click to place control points, then see a clamped B-spline plus
|
| 2 |
+
# equally spaced points along the curve.
|
| 3 |
+
#
|
| 4 |
+
# Controls:
|
| 5 |
+
# - Left-click: add a control point
|
| 6 |
+
# - Right-click: remove the last control point
|
| 7 |
+
# - '+' / '-': increase/decrease the number of equally spaced points
|
| 8 |
+
# - 'c': clear all points
|
| 9 |
+
#
|
| 10 |
+
# Requirements: numpy, matplotlib, scipy
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
from scipy.interpolate import BSpline
|
| 15 |
+
from scipy.integrate import cumulative_trapezoid
|
| 16 |
+
|
| 17 |
+
# ------------- Spline utilities -------------
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _open_uniform_knots(n, k):
|
| 21 |
+
# n = number of control points, k = degree
|
| 22 |
+
m = n + k + 1
|
| 23 |
+
t = np.zeros(m, dtype=float)
|
| 24 |
+
t[-(k + 1) :] = 1.0
|
| 25 |
+
num_interior = n - k - 1
|
| 26 |
+
if num_interior > 0:
|
| 27 |
+
t[k + 1 : n] = np.linspace(1 / (n - k), (n - k - 1) / (n - k), num_interior)
|
| 28 |
+
return t
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def build_clamped_bspline(control_points, degree=3):
|
| 32 |
+
"""
|
| 33 |
+
Build an open-uniform (clamped) B-spline that passes through the
|
| 34 |
+
first and last control points.
|
| 35 |
+
"""
|
| 36 |
+
P = np.asarray(control_points, dtype=float)
|
| 37 |
+
n = len(P)
|
| 38 |
+
if n < 2:
|
| 39 |
+
raise ValueError("Need at least 2 control points")
|
| 40 |
+
k = min(degree, n - 1) # degree cannot exceed n-1
|
| 41 |
+
t = _open_uniform_knots(n, k)
|
| 42 |
+
return BSpline(t, P, k, axis=0)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def equidistant_points_on_spline(spline, num_points, grid=6000):
|
| 46 |
+
"""
|
| 47 |
+
Return `num_points` points equally spaced in arc length along `spline`.
|
| 48 |
+
"""
|
| 49 |
+
if num_points < 2:
|
| 50 |
+
raise ValueError("num_points must be >= 2")
|
| 51 |
+
|
| 52 |
+
u = np.linspace(0.0, 1.0, grid)
|
| 53 |
+
dCdu = spline.derivative()(u)
|
| 54 |
+
speed = np.linalg.norm(dCdu, axis=1)
|
| 55 |
+
|
| 56 |
+
s = cumulative_trapezoid(speed, u, initial=0.0)
|
| 57 |
+
total_len = s[-1]
|
| 58 |
+
|
| 59 |
+
if total_len <= 1e-12:
|
| 60 |
+
P0 = spline(0.0)
|
| 61 |
+
return np.repeat(P0[None, :], num_points, axis=0)
|
| 62 |
+
|
| 63 |
+
s_targets = np.linspace(0.0, total_len, num_points)
|
| 64 |
+
u_targets = np.interp(s_targets, s, u)
|
| 65 |
+
return spline(u_targets)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
# ------------- Interactive demo -------------
|
| 69 |
+
|
| 70 |
+
if __name__ == "__main__":
|
| 71 |
+
# Optional: draw over an image. Uncomment and set path if needed.
|
| 72 |
+
# img = plt.imread("your_image.png")
|
| 73 |
+
# H, W = img.shape[:2]
|
| 74 |
+
|
| 75 |
+
points = []
|
| 76 |
+
sample_count = [25] # use a list so we can modify inside callbacks
|
| 77 |
+
sample_count = [2] # use a list so we can modify inside callbacks
|
| 78 |
+
|
| 79 |
+
fig, ax = plt.subplots()
|
| 80 |
+
ax.set_aspect("equal", adjustable="box")
|
| 81 |
+
|
| 82 |
+
# If drawing over an image, uncomment:
|
| 83 |
+
# ax.imshow(img, extent=[0, W, H, 0], origin='upper')
|
| 84 |
+
# ax.set_xlim(0, W)
|
| 85 |
+
# ax.set_ylim(H, 0)
|
| 86 |
+
# Otherwise, use a unit square canvas:
|
| 87 |
+
ax.set_xlim(0, 1)
|
| 88 |
+
ax.set_ylim(0, 1)
|
| 89 |
+
|
| 90 |
+
title_template = "Left-click: add, Right-click: undo | +/-: change N | c: clear | N = {}"
|
| 91 |
+
ax.set_title(title_template.format(sample_count[0]))
|
| 92 |
+
|
| 93 |
+
# Artists
|
| 94 |
+
scatter_ctrl = ax.scatter([], [], c="r", s=25, zorder=3, label="control points")
|
| 95 |
+
(ctrl_line,) = ax.plot([], [], "r--", lw=1, alpha=0.6, zorder=2, label="control polygon")
|
| 96 |
+
(curve_line,) = ax.plot([], [], "g-", lw=2, zorder=1, label="B-spline")
|
| 97 |
+
eq_scatter = ax.scatter([], [], c="b", s=20, zorder=4, label="equally spaced points")
|
| 98 |
+
|
| 99 |
+
ax.legend(loc="upper right")
|
| 100 |
+
|
| 101 |
+
def update_plot():
|
| 102 |
+
if points:
|
| 103 |
+
P = np.array(points, dtype=float)
|
| 104 |
+
scatter_ctrl.set_offsets(P)
|
| 105 |
+
ctrl_line.set_data(P[:, 0], P[:, 1])
|
| 106 |
+
else:
|
| 107 |
+
scatter_ctrl.set_offsets(np.empty((0, 2)))
|
| 108 |
+
ctrl_line.set_data([], [])
|
| 109 |
+
|
| 110 |
+
if len(points) >= 2:
|
| 111 |
+
try:
|
| 112 |
+
spl = build_clamped_bspline(points, degree=3)
|
| 113 |
+
# For a smooth preview of the curve
|
| 114 |
+
C = spl(np.linspace(0, 1, 1000))
|
| 115 |
+
curve_line.set_data(C[:, 0], C[:, 1])
|
| 116 |
+
|
| 117 |
+
# Equally spaced points along the curve
|
| 118 |
+
N = max(2, sample_count[0])
|
| 119 |
+
eq_pts = equidistant_points_on_spline(spl, num_points=N, grid=8000)
|
| 120 |
+
eq_scatter.set_offsets(eq_pts)
|
| 121 |
+
except Exception as e:
|
| 122 |
+
print("Error building spline:", e)
|
| 123 |
+
curve_line.set_data([], [])
|
| 124 |
+
eq_scatter.set_offsets(np.empty((0, 2)))
|
| 125 |
+
else:
|
| 126 |
+
curve_line.set_data([], [])
|
| 127 |
+
eq_scatter.set_offsets(np.empty((0, 2)))
|
| 128 |
+
|
| 129 |
+
ax.set_title(title_template.format(sample_count[0]))
|
| 130 |
+
fig.canvas.draw_idle()
|
| 131 |
+
|
| 132 |
+
def onclick(event):
|
| 133 |
+
if event.inaxes != ax:
|
| 134 |
+
return
|
| 135 |
+
if event.button == 1: # left: add
|
| 136 |
+
if event.xdata is None or event.ydata is None:
|
| 137 |
+
return
|
| 138 |
+
points.append([event.xdata, event.ydata])
|
| 139 |
+
update_plot()
|
| 140 |
+
elif event.button == 3: # right: undo last
|
| 141 |
+
if points:
|
| 142 |
+
points.pop()
|
| 143 |
+
update_plot()
|
| 144 |
+
|
| 145 |
+
def onkey(event):
|
| 146 |
+
if event.key in ["+", "="]:
|
| 147 |
+
sample_count[0] += 1
|
| 148 |
+
update_plot()
|
| 149 |
+
elif event.key == "-":
|
| 150 |
+
if sample_count[0] > 2:
|
| 151 |
+
sample_count[0] -= 1
|
| 152 |
+
update_plot()
|
| 153 |
+
elif event.key in ["c", "C"]:
|
| 154 |
+
points.clear()
|
| 155 |
+
update_plot()
|
| 156 |
+
|
| 157 |
+
fig.canvas.mpl_connect("button_press_event", onclick)
|
| 158 |
+
fig.canvas.mpl_connect("key_press_event", onkey)
|
| 159 |
+
|
| 160 |
+
plt.show()
|
configs/default_config.yaml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
independent_first_frame: false
|
| 2 |
+
warp_denoising_step: false
|
| 3 |
+
weight_decay: 0.01
|
| 4 |
+
same_step_across_blocks: true
|
| 5 |
+
discriminator_lr_multiplier: 1.0
|
| 6 |
+
last_step_only: false
|
| 7 |
+
i2v: false
|
| 8 |
+
num_training_frames: 21
|
| 9 |
+
gc_interval: 100
|
| 10 |
+
context_noise: 0
|
| 11 |
+
causal: true
|
| 12 |
+
|
| 13 |
+
ckpt_step: 0
|
| 14 |
+
prompt_name: MovieGenVideoBench
|
| 15 |
+
prompt_path: prompts/MovieGenVideoBench.txt
|
| 16 |
+
eval_first_n: 64
|
| 17 |
+
num_samples: 1
|
| 18 |
+
height: 480
|
| 19 |
+
width: 832
|
| 20 |
+
num_frames: 81
|
| 21 |
+
|
| 22 |
+
no_save: false
|
| 23 |
+
no_visualize: false
|
| 24 |
+
logdir: logs
|
| 25 |
+
wandb_save_dir: ""
|
| 26 |
+
disable_wandb: false
|
| 27 |
+
|
| 28 |
+
# Recommended: prevent Hydra from chdir into run dir (optional but common)
|
| 29 |
+
hydra:
|
| 30 |
+
job:
|
| 31 |
+
chdir: false
|
configs/self_forcing_dmd.yaml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- default_config
|
| 3 |
+
- _self_
|
| 4 |
+
|
| 5 |
+
generator_ckpt: checkpoints/ode_init.pt
|
| 6 |
+
generator_fsdp_wrap_strategy: size
|
| 7 |
+
real_score_fsdp_wrap_strategy: size
|
| 8 |
+
fake_score_fsdp_wrap_strategy: size
|
| 9 |
+
real_name: Wan2.1-T2V-14B
|
| 10 |
+
# real_name: Wan2.1-T2V-1.3B
|
| 11 |
+
text_encoder_fsdp_wrap_strategy: size
|
| 12 |
+
denoising_step_list:
|
| 13 |
+
- 1000
|
| 14 |
+
- 750
|
| 15 |
+
- 500
|
| 16 |
+
- 250
|
| 17 |
+
warp_denoising_step: true # need to remove - 0 in denoising_step_list if warp_denoising_step is true
|
| 18 |
+
ts_schedule: false
|
| 19 |
+
num_train_timestep: 1000
|
| 20 |
+
timestep_shift: 5.0
|
| 21 |
+
guidance_scale: 3.0
|
| 22 |
+
denoising_loss_type: flow
|
| 23 |
+
mixed_precision: true
|
| 24 |
+
seed: 0
|
| 25 |
+
wandb_host: WANDB_HOST
|
| 26 |
+
wandb_key: WANDB_KEY
|
| 27 |
+
wandb_entity: WANDB_ENTITY
|
| 28 |
+
wandb_project: WANDB_PROJECT
|
| 29 |
+
sharding_strategy: hybrid_full
|
| 30 |
+
lr: 2.0e-06
|
| 31 |
+
lr_critic: 4.0e-07
|
| 32 |
+
beta1: 0.0
|
| 33 |
+
beta2: 0.999
|
| 34 |
+
beta1_critic: 0.0
|
| 35 |
+
beta2_critic: 0.999
|
| 36 |
+
data_path: prompts/vidprom_filtered_extended.txt
|
| 37 |
+
batch_size: 1
|
| 38 |
+
ema_weight: 0.99
|
| 39 |
+
ema_start_step: 200
|
| 40 |
+
total_batch_size: 64
|
| 41 |
+
log_iters: 500
|
| 42 |
+
negative_prompt: '色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走'
|
| 43 |
+
dfake_gen_update_ratio: 5
|
| 44 |
+
image_or_video_shape:
|
| 45 |
+
- 1
|
| 46 |
+
- 21
|
| 47 |
+
- 16
|
| 48 |
+
- 60
|
| 49 |
+
- 104
|
| 50 |
+
distribution_loss: dmd
|
| 51 |
+
trainer: score_distillation
|
| 52 |
+
gradient_checkpointing: true
|
| 53 |
+
num_frame_per_block: 3
|
| 54 |
+
load_raw_video: false
|
| 55 |
+
model_kwargs:
|
| 56 |
+
timestep_shift: 5.0
|
configs/self_forcing_dmd_vsink.yaml
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- default_config
|
| 3 |
+
- _self_
|
| 4 |
+
|
| 5 |
+
generator_ckpt: checkpoints/ode_init.pt
|
| 6 |
+
generator_fsdp_wrap_strategy: size
|
| 7 |
+
real_score_fsdp_wrap_strategy: size
|
| 8 |
+
fake_score_fsdp_wrap_strategy: size
|
| 9 |
+
# real_name: Wan2.1-T2V-1.3B
|
| 10 |
+
real_name: Wan2.1-T2V-1.3B
|
| 11 |
+
text_encoder_fsdp_wrap_strategy: size
|
| 12 |
+
denoising_step_list:
|
| 13 |
+
- 1000
|
| 14 |
+
- 750
|
| 15 |
+
- 500
|
| 16 |
+
- 250
|
| 17 |
+
warp_denoising_step: true # need to remove - 0 in denoising_step_list if warp_denoising_step is true
|
| 18 |
+
ts_schedule: false
|
| 19 |
+
num_train_timestep: 1000
|
| 20 |
+
timestep_shift: 5.0
|
| 21 |
+
guidance_scale: 3.0
|
| 22 |
+
denoising_loss_type: flow
|
| 23 |
+
mixed_precision: true
|
| 24 |
+
seed: 0
|
| 25 |
+
wandb_host: WANDB_HOST
|
| 26 |
+
wandb_key: WANDB_KEY
|
| 27 |
+
wandb_entity: WANDB_ENTITY
|
| 28 |
+
wandb_project: WANDB_PROJECT
|
| 29 |
+
sharding_strategy: hybrid_full
|
| 30 |
+
lr: 2.0e-06
|
| 31 |
+
lr_critic: 4.0e-07
|
| 32 |
+
beta1: 0.0
|
| 33 |
+
beta2: 0.999
|
| 34 |
+
beta1_critic: 0.0
|
| 35 |
+
beta2_critic: 0.999
|
| 36 |
+
data_path: prompts/vidprom_filtered_extended.txt
|
| 37 |
+
batch_size: 1
|
| 38 |
+
ema_weight: 0.99
|
| 39 |
+
ema_start_step: 200
|
| 40 |
+
total_batch_size: 64
|
| 41 |
+
log_iters: 400
|
| 42 |
+
negative_prompt: '色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走'
|
| 43 |
+
dfake_gen_update_ratio: 5
|
| 44 |
+
image_or_video_shape:
|
| 45 |
+
- 1
|
| 46 |
+
- 21
|
| 47 |
+
- 16
|
| 48 |
+
- 60
|
| 49 |
+
- 104
|
| 50 |
+
distribution_loss: dmd
|
| 51 |
+
trainer: score_distillation
|
| 52 |
+
gradient_checkpointing: true
|
| 53 |
+
num_frame_per_block: 3
|
| 54 |
+
load_raw_video: false
|
| 55 |
+
model_kwargs:
|
| 56 |
+
timestep_shift: 5.0
|
| 57 |
+
local_attn_size: 21
|
| 58 |
+
sink_size: 3
|
configs/self_forcing_dmd_vsink_stream.yaml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- self_forcing_dmd_vsink
|
| 3 |
+
- _self_
|
| 4 |
+
|
| 5 |
+
vae_offload_cpu: false
|
configs/self_forcing_dmd_vsink_stream_drag.yaml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- self_forcing_dmd_vsink_stream
|
| 3 |
+
- _self_
|
| 4 |
+
|
| 5 |
+
drag_optim_config:
|
| 6 |
+
record_feature_block_indexes:
|
| 7 |
+
- 12
|
| 8 |
+
- 13
|
| 9 |
+
- 14
|
| 10 |
+
- 15
|
| 11 |
+
|
| 12 |
+
optimize_denoising_steps_indexes:
|
| 13 |
+
- 1
|
| 14 |
+
|
| 15 |
+
optimize_iter: 5
|
| 16 |
+
optimize_lr: 0.03
|
| 17 |
+
|
| 18 |
+
translation_step: 16.0
|
| 19 |
+
rotation_step: 8.0
|
| 20 |
+
|
| 21 |
+
normalize_latent_after_drag_optimize: True
|
| 22 |
+
normalize_latent_after_post_merge: True
|
| 23 |
+
dynamic_chunk_normalization_block_number: 2
|
| 24 |
+
|
| 25 |
+
feature_scaling_factor: 2.0
|
| 26 |
+
|
| 27 |
+
gradient_gaussian_padding: 1.5
|
| 28 |
+
gradient_gaussian_sigma: 1.5
|
| 29 |
+
|
| 30 |
+
feature_fft_cutoff:
|
| 31 |
+
- 0.2
|
| 32 |
+
- 0.4
|
| 33 |
+
- 0.6
|
configs/self_forcing_sid.yaml
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- default_config
|
| 3 |
+
- _self_
|
| 4 |
+
|
| 5 |
+
generator_ckpt: checkpoints/ode_init.pt
|
| 6 |
+
generator_fsdp_wrap_strategy: size
|
| 7 |
+
real_score_fsdp_wrap_strategy: size
|
| 8 |
+
fake_score_fsdp_wrap_strategy: size
|
| 9 |
+
real_name: Wan2.1-T2V-1.3B
|
| 10 |
+
text_encoder_fsdp_wrap_strategy: size
|
| 11 |
+
denoising_step_list:
|
| 12 |
+
- 1000
|
| 13 |
+
- 750
|
| 14 |
+
- 500
|
| 15 |
+
- 250
|
| 16 |
+
warp_denoising_step: true # need to remove - 0 in denoising_step_list if warp_denoising_step is true
|
| 17 |
+
ts_schedule: false
|
| 18 |
+
num_train_timestep: 1000
|
| 19 |
+
timestep_shift: 5.0
|
| 20 |
+
guidance_scale: 3.0
|
| 21 |
+
denoising_loss_type: flow
|
| 22 |
+
mixed_precision: true
|
| 23 |
+
seed: 0
|
| 24 |
+
wandb_host: WANDB_HOST
|
| 25 |
+
wandb_key: WANDB_KEY
|
| 26 |
+
wandb_entity: WANDB_ENTITY
|
| 27 |
+
wandb_project: WANDB_PROJECT
|
| 28 |
+
sharding_strategy: hybrid_full
|
| 29 |
+
lr: 2.0e-06
|
| 30 |
+
lr_critic: 2.0e-06
|
| 31 |
+
beta1: 0.0
|
| 32 |
+
beta2: 0.999
|
| 33 |
+
beta1_critic: 0.0
|
| 34 |
+
beta2_critic: 0.999
|
| 35 |
+
weight_decay: 0.0
|
| 36 |
+
data_path: prompts/vidprom_filtered_extended.txt
|
| 37 |
+
batch_size: 1
|
| 38 |
+
sid_alpha: 1.0
|
| 39 |
+
ema_weight: 0.99
|
| 40 |
+
ema_start_step: 200
|
| 41 |
+
total_batch_size: 64
|
| 42 |
+
log_iters: 50
|
| 43 |
+
negative_prompt: '色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走'
|
| 44 |
+
dfake_gen_update_ratio: 5
|
| 45 |
+
image_or_video_shape:
|
| 46 |
+
- 1
|
| 47 |
+
- 21
|
| 48 |
+
- 16
|
| 49 |
+
- 60
|
| 50 |
+
- 104
|
| 51 |
+
distribution_loss: dmd
|
| 52 |
+
trainer: score_distillation
|
| 53 |
+
gradient_checkpointing: true
|
| 54 |
+
num_frame_per_block: 3
|
| 55 |
+
load_raw_video: false
|
| 56 |
+
model_kwargs:
|
| 57 |
+
timestep_shift: 5.0
|
demo.py
ADDED
|
@@ -0,0 +1,715 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Demo for Self-Forcing.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
+
import random
|
| 8 |
+
import time
|
| 9 |
+
import base64
|
| 10 |
+
import argparse
|
| 11 |
+
import hashlib
|
| 12 |
+
import subprocess
|
| 13 |
+
import urllib.request
|
| 14 |
+
from io import BytesIO
|
| 15 |
+
from PIL import Image
|
| 16 |
+
import numpy as np
|
| 17 |
+
import torch
|
| 18 |
+
from omegaconf import OmegaConf
|
| 19 |
+
from flask import Flask, render_template, jsonify
|
| 20 |
+
from flask_socketio import SocketIO, emit
|
| 21 |
+
import queue
|
| 22 |
+
from threading import Thread, Event
|
| 23 |
+
|
| 24 |
+
from pipeline import CausalInferencePipeline
|
| 25 |
+
from demo_utils.constant import ZERO_VAE_CACHE
|
| 26 |
+
from demo_utils.vae_block3 import VAEDecoderWrapper
|
| 27 |
+
from utils.wan_wrapper import WanDiffusionWrapper, WanTextEncoder
|
| 28 |
+
from demo_utils.utils import generate_timestamp
|
| 29 |
+
from demo_utils.memory import (
|
| 30 |
+
gpu,
|
| 31 |
+
get_cuda_free_memory_gb,
|
| 32 |
+
DynamicSwapInstaller,
|
| 33 |
+
move_model_to_device_with_memory_preservation,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
# Parse arguments
|
| 37 |
+
parser = argparse.ArgumentParser()
|
| 38 |
+
parser.add_argument("--port", type=int, default=5001)
|
| 39 |
+
parser.add_argument("--host", type=str, default="0.0.0.0")
|
| 40 |
+
parser.add_argument("--checkpoint_path", type=str, default="./checkpoints/self_forcing_dmd.pt")
|
| 41 |
+
parser.add_argument("--config_path", type=str, default="./configs/self_forcing_dmd.yaml")
|
| 42 |
+
parser.add_argument("--trt", action="store_true")
|
| 43 |
+
args = parser.parse_args()
|
| 44 |
+
|
| 45 |
+
print(f"Free VRAM {get_cuda_free_memory_gb(gpu)} GB")
|
| 46 |
+
low_memory = get_cuda_free_memory_gb(gpu) < 40
|
| 47 |
+
|
| 48 |
+
# Load models
|
| 49 |
+
config = OmegaConf.load(args.config_path)
|
| 50 |
+
default_config = OmegaConf.load("configs/default_config.yaml")
|
| 51 |
+
config = OmegaConf.merge(default_config, config)
|
| 52 |
+
|
| 53 |
+
text_encoder = WanTextEncoder()
|
| 54 |
+
|
| 55 |
+
# Global variables for dynamic model switching
|
| 56 |
+
current_vae_decoder = None
|
| 57 |
+
current_use_taehv = False
|
| 58 |
+
fp8_applied = False
|
| 59 |
+
torch_compile_applied = False
|
| 60 |
+
global frame_number
|
| 61 |
+
frame_number = 0
|
| 62 |
+
anim_name = ""
|
| 63 |
+
frame_rate = 6
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def initialize_vae_decoder(use_taehv=False, use_trt=False):
|
| 67 |
+
"""Initialize VAE decoder based on the selected option"""
|
| 68 |
+
global current_vae_decoder, current_use_taehv
|
| 69 |
+
|
| 70 |
+
if use_trt:
|
| 71 |
+
from demo_utils.vae import VAETRTWrapper
|
| 72 |
+
|
| 73 |
+
current_vae_decoder = VAETRTWrapper()
|
| 74 |
+
return current_vae_decoder
|
| 75 |
+
|
| 76 |
+
if use_taehv:
|
| 77 |
+
from demo_utils.taehv import TAEHV
|
| 78 |
+
|
| 79 |
+
# Check if taew2_1.pth exists in checkpoints folder, download if missing
|
| 80 |
+
taehv_checkpoint_path = "checkpoints/taew2_1.pth"
|
| 81 |
+
if not os.path.exists(taehv_checkpoint_path):
|
| 82 |
+
print(
|
| 83 |
+
f"taew2_1.pth not found in checkpoints folder {taehv_checkpoint_path}. Downloading..."
|
| 84 |
+
)
|
| 85 |
+
os.makedirs("checkpoints", exist_ok=True)
|
| 86 |
+
download_url = "https://github.com/madebyollin/taehv/raw/main/taew2_1.pth"
|
| 87 |
+
try:
|
| 88 |
+
urllib.request.urlretrieve(download_url, taehv_checkpoint_path)
|
| 89 |
+
print(f"Successfully downloaded taew2_1.pth to {taehv_checkpoint_path}")
|
| 90 |
+
except Exception as e:
|
| 91 |
+
print(f"Failed to download taew2_1.pth: {e}")
|
| 92 |
+
raise
|
| 93 |
+
|
| 94 |
+
class DotDict(dict):
|
| 95 |
+
__getattr__ = dict.__getitem__
|
| 96 |
+
__setattr__ = dict.__setitem__
|
| 97 |
+
|
| 98 |
+
class TAEHVDiffusersWrapper(torch.nn.Module):
|
| 99 |
+
def __init__(self):
|
| 100 |
+
super().__init__()
|
| 101 |
+
self.dtype = torch.float16
|
| 102 |
+
self.taehv = TAEHV(checkpoint_path=taehv_checkpoint_path).to(self.dtype)
|
| 103 |
+
self.config = DotDict(scaling_factor=1.0)
|
| 104 |
+
|
| 105 |
+
def decode(self, latents, return_dict=None):
|
| 106 |
+
# n, c, t, h, w = latents.shape
|
| 107 |
+
# low-memory, set parallel=True for faster + higher memory
|
| 108 |
+
return self.taehv.decode_video(latents, parallel=False).mul_(2).sub_(1)
|
| 109 |
+
|
| 110 |
+
current_vae_decoder = TAEHVDiffusersWrapper()
|
| 111 |
+
else:
|
| 112 |
+
current_vae_decoder = VAEDecoderWrapper()
|
| 113 |
+
vae_state_dict = torch.load("wan_models/Wan2.1-T2V-1.3B/Wan2.1_VAE.pth", map_location="cpu")
|
| 114 |
+
decoder_state_dict = {}
|
| 115 |
+
for key, value in vae_state_dict.items():
|
| 116 |
+
if "decoder." in key or "conv2" in key:
|
| 117 |
+
decoder_state_dict[key] = value
|
| 118 |
+
current_vae_decoder.load_state_dict(decoder_state_dict)
|
| 119 |
+
|
| 120 |
+
current_vae_decoder.eval()
|
| 121 |
+
current_vae_decoder.to(dtype=torch.float16)
|
| 122 |
+
current_vae_decoder.requires_grad_(False)
|
| 123 |
+
current_vae_decoder.to(gpu)
|
| 124 |
+
current_use_taehv = use_taehv
|
| 125 |
+
|
| 126 |
+
print(f"✅ VAE decoder initialized with {'TAEHV' if use_taehv else 'default VAE'}")
|
| 127 |
+
return current_vae_decoder
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
# Initialize with default VAE
|
| 131 |
+
vae_decoder = initialize_vae_decoder(use_taehv=False, use_trt=args.trt)
|
| 132 |
+
|
| 133 |
+
transformer = WanDiffusionWrapper(is_causal=True)
|
| 134 |
+
state_dict = torch.load(args.checkpoint_path, map_location="cpu")
|
| 135 |
+
transformer.load_state_dict(state_dict["generator_ema"])
|
| 136 |
+
|
| 137 |
+
text_encoder.eval()
|
| 138 |
+
transformer.eval()
|
| 139 |
+
|
| 140 |
+
transformer.to(dtype=torch.float16)
|
| 141 |
+
text_encoder.to(dtype=torch.bfloat16)
|
| 142 |
+
|
| 143 |
+
text_encoder.requires_grad_(False)
|
| 144 |
+
transformer.requires_grad_(False)
|
| 145 |
+
|
| 146 |
+
pipeline = CausalInferencePipeline(
|
| 147 |
+
config,
|
| 148 |
+
device=gpu,
|
| 149 |
+
generator=transformer,
|
| 150 |
+
text_encoder=text_encoder,
|
| 151 |
+
vae=vae_decoder,
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
if low_memory:
|
| 155 |
+
DynamicSwapInstaller.install_model(text_encoder, device=gpu)
|
| 156 |
+
else:
|
| 157 |
+
text_encoder.to(gpu)
|
| 158 |
+
transformer.to(gpu)
|
| 159 |
+
|
| 160 |
+
# Flask and SocketIO setup
|
| 161 |
+
app = Flask(__name__)
|
| 162 |
+
app.config["SECRET_KEY"] = "frontend_buffered_demo"
|
| 163 |
+
socketio = SocketIO(app, cors_allowed_origins="*")
|
| 164 |
+
|
| 165 |
+
generation_active = False
|
| 166 |
+
stop_event = Event()
|
| 167 |
+
frame_send_queue = queue.Queue()
|
| 168 |
+
sender_thread = None
|
| 169 |
+
models_compiled = False
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def tensor_to_base64_frame(frame_tensor):
|
| 173 |
+
"""Convert a single frame tensor to base64 image string."""
|
| 174 |
+
global frame_number, anim_name
|
| 175 |
+
# Clamp and normalize to 0-255
|
| 176 |
+
frame = torch.clamp(frame_tensor.float(), -1.0, 1.0) * 127.5 + 127.5
|
| 177 |
+
frame = frame.to(torch.uint8).cpu().numpy()
|
| 178 |
+
|
| 179 |
+
# CHW -> HWC
|
| 180 |
+
if len(frame.shape) == 3:
|
| 181 |
+
frame = np.transpose(frame, (1, 2, 0))
|
| 182 |
+
|
| 183 |
+
# Convert to PIL Image
|
| 184 |
+
if frame.shape[2] == 3: # RGB
|
| 185 |
+
image = Image.fromarray(frame, "RGB")
|
| 186 |
+
else: # Handle other formats
|
| 187 |
+
image = Image.fromarray(frame)
|
| 188 |
+
|
| 189 |
+
# Convert to base64
|
| 190 |
+
buffer = BytesIO()
|
| 191 |
+
image.save(buffer, format="JPEG", quality=100)
|
| 192 |
+
if not os.path.exists("./images/%s" % anim_name):
|
| 193 |
+
os.makedirs("./images/%s" % anim_name)
|
| 194 |
+
frame_number += 1
|
| 195 |
+
image.save("./images/%s/%s_%03d.jpg" % (anim_name, anim_name, frame_number))
|
| 196 |
+
img_str = base64.b64encode(buffer.getvalue()).decode()
|
| 197 |
+
return f"data:image/jpeg;base64,{img_str}"
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def frame_sender_worker():
|
| 201 |
+
"""Background thread that processes frame send queue non-blocking."""
|
| 202 |
+
global frame_send_queue, generation_active, stop_event
|
| 203 |
+
|
| 204 |
+
print("📡 Frame sender thread started")
|
| 205 |
+
|
| 206 |
+
while True:
|
| 207 |
+
frame_data = None
|
| 208 |
+
try:
|
| 209 |
+
# Get frame data from queue
|
| 210 |
+
frame_data = frame_send_queue.get(timeout=1.0)
|
| 211 |
+
|
| 212 |
+
if frame_data is None: # Shutdown signal
|
| 213 |
+
frame_send_queue.task_done() # Mark shutdown signal as done
|
| 214 |
+
break
|
| 215 |
+
|
| 216 |
+
frame_tensor, frame_index, block_index, job_id = frame_data
|
| 217 |
+
|
| 218 |
+
# Convert tensor to base64
|
| 219 |
+
base64_frame = tensor_to_base64_frame(frame_tensor)
|
| 220 |
+
|
| 221 |
+
# Send via SocketIO
|
| 222 |
+
try:
|
| 223 |
+
socketio.emit(
|
| 224 |
+
"frame_ready",
|
| 225 |
+
{
|
| 226 |
+
"data": base64_frame,
|
| 227 |
+
"frame_index": frame_index,
|
| 228 |
+
"block_index": block_index,
|
| 229 |
+
"job_id": job_id,
|
| 230 |
+
},
|
| 231 |
+
)
|
| 232 |
+
except Exception as e:
|
| 233 |
+
print(f"⚠️ Failed to send frame {frame_index}: {e}")
|
| 234 |
+
|
| 235 |
+
frame_send_queue.task_done()
|
| 236 |
+
|
| 237 |
+
except queue.Empty:
|
| 238 |
+
# Check if we should continue running
|
| 239 |
+
if not generation_active and frame_send_queue.empty():
|
| 240 |
+
break
|
| 241 |
+
except Exception as e:
|
| 242 |
+
print(f"❌ Frame sender error: {e}")
|
| 243 |
+
# Make sure to mark task as done even if there's an error
|
| 244 |
+
if frame_data is not None:
|
| 245 |
+
try:
|
| 246 |
+
frame_send_queue.task_done()
|
| 247 |
+
except Exception as e:
|
| 248 |
+
print(f"❌ Failed to mark frame task as done: {e}")
|
| 249 |
+
break
|
| 250 |
+
|
| 251 |
+
print("📡 Frame sender thread stopped")
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
@torch.no_grad()
|
| 255 |
+
def generate_video_stream(
|
| 256 |
+
prompt, seed, enable_torch_compile=False, enable_fp8=False, use_taehv=False
|
| 257 |
+
):
|
| 258 |
+
"""Generate video and push frames immediately to frontend."""
|
| 259 |
+
global generation_active, stop_event, frame_send_queue, sender_thread, models_compiled, torch_compile_applied, fp8_applied, current_vae_decoder, current_use_taehv, frame_rate, anim_name
|
| 260 |
+
|
| 261 |
+
try:
|
| 262 |
+
generation_active = True
|
| 263 |
+
stop_event.clear()
|
| 264 |
+
job_id = generate_timestamp()
|
| 265 |
+
|
| 266 |
+
# Start frame sender thread if not already running
|
| 267 |
+
if sender_thread is None or not sender_thread.is_alive():
|
| 268 |
+
sender_thread = Thread(target=frame_sender_worker, daemon=True)
|
| 269 |
+
sender_thread.start()
|
| 270 |
+
|
| 271 |
+
# Emit progress updates
|
| 272 |
+
def emit_progress(message, progress):
|
| 273 |
+
try:
|
| 274 |
+
socketio.emit(
|
| 275 |
+
"progress",
|
| 276 |
+
{
|
| 277 |
+
"message": message,
|
| 278 |
+
"progress": progress,
|
| 279 |
+
"job_id": job_id,
|
| 280 |
+
},
|
| 281 |
+
)
|
| 282 |
+
except Exception as e:
|
| 283 |
+
print(f"❌ Failed to emit progress: {e}")
|
| 284 |
+
|
| 285 |
+
emit_progress("Starting generation...", 0)
|
| 286 |
+
|
| 287 |
+
# Handle VAE decoder switching
|
| 288 |
+
if use_taehv != current_use_taehv:
|
| 289 |
+
emit_progress("Switching VAE decoder...", 2)
|
| 290 |
+
print(f"🔄 Switching VAE decoder to {'TAEHV' if use_taehv else 'default VAE'}")
|
| 291 |
+
current_vae_decoder = initialize_vae_decoder(use_taehv=use_taehv)
|
| 292 |
+
# Update pipeline with new VAE decoder
|
| 293 |
+
pipeline.vae = current_vae_decoder
|
| 294 |
+
|
| 295 |
+
# Handle FP8 quantization
|
| 296 |
+
if enable_fp8 and not fp8_applied:
|
| 297 |
+
emit_progress("Applying FP8 quantization...", 3)
|
| 298 |
+
print("🔧 Applying FP8 quantization to transformer")
|
| 299 |
+
from torchao.quantization.quant_api import (
|
| 300 |
+
quantize_,
|
| 301 |
+
Float8DynamicActivationFloat8WeightConfig,
|
| 302 |
+
PerTensor,
|
| 303 |
+
)
|
| 304 |
+
|
| 305 |
+
quantize_(
|
| 306 |
+
transformer,
|
| 307 |
+
Float8DynamicActivationFloat8WeightConfig(granularity=PerTensor()),
|
| 308 |
+
)
|
| 309 |
+
fp8_applied = True
|
| 310 |
+
|
| 311 |
+
# Text encoding
|
| 312 |
+
emit_progress("Encoding text prompt...", 8)
|
| 313 |
+
conditional_dict = text_encoder(text_prompts=[prompt])
|
| 314 |
+
for key, value in conditional_dict.items():
|
| 315 |
+
conditional_dict[key] = value.to(dtype=torch.float16)
|
| 316 |
+
if low_memory:
|
| 317 |
+
gpu_memory_preservation = get_cuda_free_memory_gb(gpu) + 5
|
| 318 |
+
move_model_to_device_with_memory_preservation(
|
| 319 |
+
text_encoder,
|
| 320 |
+
target_device=gpu,
|
| 321 |
+
preserved_memory_gb=gpu_memory_preservation,
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
# Handle torch.compile if enabled
|
| 325 |
+
torch_compile_applied = enable_torch_compile
|
| 326 |
+
if enable_torch_compile and not models_compiled:
|
| 327 |
+
# Compile transformer and decoder
|
| 328 |
+
transformer.compile(mode="max-autotune-no-cudagraphs")
|
| 329 |
+
if not current_use_taehv and not low_memory and not args.trt:
|
| 330 |
+
current_vae_decoder.compile(mode="max-autotune-no-cudagraphs")
|
| 331 |
+
|
| 332 |
+
# Initialize generation
|
| 333 |
+
emit_progress("Initializing generation...", 12)
|
| 334 |
+
|
| 335 |
+
rnd = torch.Generator(gpu).manual_seed(seed)
|
| 336 |
+
# all_latents = torch.zeros([1, 21, 16, 60, 104], device=gpu, dtype=torch.bfloat16)
|
| 337 |
+
|
| 338 |
+
pipeline._initialize_kv_cache(batch_size=1, dtype=torch.float16, device=gpu)
|
| 339 |
+
pipeline._initialize_crossattn_cache(batch_size=1, dtype=torch.float16, device=gpu)
|
| 340 |
+
|
| 341 |
+
noise = torch.randn([1, 21, 16, 60, 104], device=gpu, dtype=torch.float16, generator=rnd)
|
| 342 |
+
|
| 343 |
+
# Generation parameters
|
| 344 |
+
num_blocks = 7
|
| 345 |
+
current_start_frame = 0
|
| 346 |
+
num_input_frames = 0
|
| 347 |
+
all_num_frames = [pipeline.num_frame_per_block] * num_blocks
|
| 348 |
+
if current_use_taehv:
|
| 349 |
+
vae_cache = None
|
| 350 |
+
else:
|
| 351 |
+
vae_cache = ZERO_VAE_CACHE
|
| 352 |
+
for i in range(len(vae_cache)):
|
| 353 |
+
vae_cache[i] = vae_cache[i].to(device=gpu, dtype=torch.float16)
|
| 354 |
+
|
| 355 |
+
total_frames_sent = 0
|
| 356 |
+
generation_start_time = time.time()
|
| 357 |
+
|
| 358 |
+
emit_progress("Generating frames... (frontend handles timing)", 15)
|
| 359 |
+
|
| 360 |
+
for idx, current_num_frames in enumerate(all_num_frames):
|
| 361 |
+
if not generation_active or stop_event.is_set():
|
| 362 |
+
break
|
| 363 |
+
|
| 364 |
+
progress = int(((idx + 1) / len(all_num_frames)) * 80) + 15
|
| 365 |
+
|
| 366 |
+
# Special message for first block with torch.compile
|
| 367 |
+
if idx == 0 and torch_compile_applied and not models_compiled:
|
| 368 |
+
emit_progress(
|
| 369 |
+
f"Processing block 1/{len(all_num_frames)} - Compiling models (may take 5-10 minutes)...",
|
| 370 |
+
progress,
|
| 371 |
+
)
|
| 372 |
+
print(f"🔥 Processing block {idx+1}/{len(all_num_frames)}")
|
| 373 |
+
models_compiled = True
|
| 374 |
+
else:
|
| 375 |
+
emit_progress(
|
| 376 |
+
f"Processing block {idx+1}/{len(all_num_frames)}...",
|
| 377 |
+
progress,
|
| 378 |
+
)
|
| 379 |
+
print(f"🔄 Processing block {idx+1}/{len(all_num_frames)}")
|
| 380 |
+
|
| 381 |
+
block_start_time = time.time()
|
| 382 |
+
|
| 383 |
+
noisy_input = noise[
|
| 384 |
+
:,
|
| 385 |
+
current_start_frame
|
| 386 |
+
- num_input_frames : current_start_frame
|
| 387 |
+
+ current_num_frames
|
| 388 |
+
- num_input_frames,
|
| 389 |
+
]
|
| 390 |
+
|
| 391 |
+
# Denoising loop
|
| 392 |
+
denoising_start = time.time()
|
| 393 |
+
for index, current_timestep in enumerate(pipeline.denoising_step_list):
|
| 394 |
+
if not generation_active or stop_event.is_set():
|
| 395 |
+
break
|
| 396 |
+
|
| 397 |
+
timestep = (
|
| 398 |
+
torch.ones(
|
| 399 |
+
[1, current_num_frames],
|
| 400 |
+
device=noise.device,
|
| 401 |
+
dtype=torch.int64,
|
| 402 |
+
)
|
| 403 |
+
* current_timestep
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
if index < len(pipeline.denoising_step_list) - 1:
|
| 407 |
+
_, denoised_pred = transformer(
|
| 408 |
+
noisy_image_or_video=noisy_input,
|
| 409 |
+
conditional_dict=conditional_dict,
|
| 410 |
+
timestep=timestep,
|
| 411 |
+
kv_cache=pipeline.kv_cache1,
|
| 412 |
+
crossattn_cache=pipeline.crossattn_cache,
|
| 413 |
+
current_start=current_start_frame * pipeline.frame_seq_length,
|
| 414 |
+
)
|
| 415 |
+
next_timestep = pipeline.denoising_step_list[index + 1]
|
| 416 |
+
noisy_input = pipeline.scheduler.add_noise(
|
| 417 |
+
denoised_pred.flatten(0, 1),
|
| 418 |
+
torch.randn_like(denoised_pred.flatten(0, 1)),
|
| 419 |
+
next_timestep
|
| 420 |
+
* torch.ones(
|
| 421 |
+
[1 * current_num_frames],
|
| 422 |
+
device=noise.device,
|
| 423 |
+
dtype=torch.long,
|
| 424 |
+
),
|
| 425 |
+
).unflatten(0, denoised_pred.shape[:2])
|
| 426 |
+
else:
|
| 427 |
+
_, denoised_pred = transformer(
|
| 428 |
+
noisy_image_or_video=noisy_input,
|
| 429 |
+
conditional_dict=conditional_dict,
|
| 430 |
+
timestep=timestep,
|
| 431 |
+
kv_cache=pipeline.kv_cache1,
|
| 432 |
+
crossattn_cache=pipeline.crossattn_cache,
|
| 433 |
+
current_start=current_start_frame * pipeline.frame_seq_length,
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
if not generation_active or stop_event.is_set():
|
| 437 |
+
break
|
| 438 |
+
|
| 439 |
+
denoising_time = time.time() - denoising_start
|
| 440 |
+
print(f"⚡ Block {idx+1} denoising completed in {denoising_time:.2f}s")
|
| 441 |
+
|
| 442 |
+
# Record output
|
| 443 |
+
# all_latents[:, current_start_frame:current_start_frame + current_num_frames] = denoised_pred
|
| 444 |
+
|
| 445 |
+
# Update KV cache for next block
|
| 446 |
+
if idx != len(all_num_frames) - 1:
|
| 447 |
+
transformer(
|
| 448 |
+
noisy_image_or_video=denoised_pred,
|
| 449 |
+
conditional_dict=conditional_dict,
|
| 450 |
+
timestep=torch.zeros_like(timestep),
|
| 451 |
+
kv_cache=pipeline.kv_cache1,
|
| 452 |
+
crossattn_cache=pipeline.crossattn_cache,
|
| 453 |
+
current_start=current_start_frame * pipeline.frame_seq_length,
|
| 454 |
+
)
|
| 455 |
+
|
| 456 |
+
# Decode to pixels and send frames immediately
|
| 457 |
+
print(f"🎨 Decoding block {idx+1} to pixels...")
|
| 458 |
+
decode_start = time.time()
|
| 459 |
+
if args.trt:
|
| 460 |
+
all_current_pixels = []
|
| 461 |
+
for i in range(denoised_pred.shape[1]):
|
| 462 |
+
is_first_frame = (
|
| 463 |
+
torch.tensor(1.0).cuda().half()
|
| 464 |
+
if idx == 0 and i == 0
|
| 465 |
+
else torch.tensor(0.0).cuda().half()
|
| 466 |
+
)
|
| 467 |
+
outputs = vae_decoder.forward(
|
| 468 |
+
denoised_pred[:, i : i + 1, :, :, :].half(),
|
| 469 |
+
is_first_frame,
|
| 470 |
+
*vae_cache,
|
| 471 |
+
)
|
| 472 |
+
# outputs = vae_decoder.forward(denoised_pred.float(), *vae_cache)
|
| 473 |
+
current_pixels, vae_cache = outputs[0], outputs[1:]
|
| 474 |
+
print(current_pixels.max(), current_pixels.min())
|
| 475 |
+
all_current_pixels.append(current_pixels.clone())
|
| 476 |
+
pixels = torch.cat(all_current_pixels, dim=1)
|
| 477 |
+
if idx == 0:
|
| 478 |
+
pixels = pixels[:, 3:, :, :, :] # Skip first 3 frames of first block
|
| 479 |
+
else:
|
| 480 |
+
if current_use_taehv:
|
| 481 |
+
if vae_cache is None:
|
| 482 |
+
vae_cache = denoised_pred
|
| 483 |
+
else:
|
| 484 |
+
denoised_pred = torch.cat([vae_cache, denoised_pred], dim=1)
|
| 485 |
+
vae_cache = denoised_pred[:, -3:, :, :, :]
|
| 486 |
+
pixels = current_vae_decoder.decode(denoised_pred)
|
| 487 |
+
print(f"denoised_pred shape: {denoised_pred.shape}")
|
| 488 |
+
print(f"pixels shape: {pixels.shape}")
|
| 489 |
+
if idx == 0:
|
| 490 |
+
pixels = pixels[:, 3:, :, :, :] # Skip first 3 frames of first block
|
| 491 |
+
else:
|
| 492 |
+
pixels = pixels[:, 12:, :, :, :]
|
| 493 |
+
|
| 494 |
+
else:
|
| 495 |
+
pixels, vae_cache = current_vae_decoder(denoised_pred.half(), *vae_cache)
|
| 496 |
+
if idx == 0:
|
| 497 |
+
pixels = pixels[:, 3:, :, :, :] # Skip first 3 frames of first block
|
| 498 |
+
|
| 499 |
+
decode_time = time.time() - decode_start
|
| 500 |
+
print(f"🎨 Block {idx+1} VAE decoding completed in {decode_time:.2f}s")
|
| 501 |
+
|
| 502 |
+
# Queue frames for non-blocking sending
|
| 503 |
+
block_frames = pixels.shape[1]
|
| 504 |
+
print(f"📡 Queueing {block_frames} frames from block {idx+1} for sending...")
|
| 505 |
+
queue_start = time.time()
|
| 506 |
+
|
| 507 |
+
for frame_idx in range(block_frames):
|
| 508 |
+
if not generation_active or stop_event.is_set():
|
| 509 |
+
break
|
| 510 |
+
|
| 511 |
+
frame_tensor = pixels[0, frame_idx].cpu()
|
| 512 |
+
|
| 513 |
+
# Queue frame data in non-blocking way
|
| 514 |
+
frame_send_queue.put((frame_tensor, total_frames_sent, idx, job_id))
|
| 515 |
+
total_frames_sent += 1
|
| 516 |
+
|
| 517 |
+
queue_time = time.time() - queue_start
|
| 518 |
+
block_time = time.time() - block_start_time
|
| 519 |
+
print(
|
| 520 |
+
f"✅ Block {idx+1} completed in {block_time:.2f}s ({block_frames} frames queued in {queue_time:.3f}s)"
|
| 521 |
+
)
|
| 522 |
+
|
| 523 |
+
current_start_frame += current_num_frames
|
| 524 |
+
|
| 525 |
+
generation_time = time.time() - generation_start_time
|
| 526 |
+
print(
|
| 527 |
+
f"🎉 Generation completed in {generation_time:.2f}s! {total_frames_sent} frames queued for sending"
|
| 528 |
+
)
|
| 529 |
+
|
| 530 |
+
# Wait for all frames to be sent before completing
|
| 531 |
+
emit_progress("Waiting for all frames to be sent...", 97)
|
| 532 |
+
print("⏳ Waiting for all frames to be sent...")
|
| 533 |
+
frame_send_queue.join() # Wait for all queued frames to be processed
|
| 534 |
+
print("✅ All frames sent successfully!")
|
| 535 |
+
|
| 536 |
+
generate_mp4_from_images("./images", "./videos/" + anim_name + ".mp4", frame_rate)
|
| 537 |
+
# Final progress update
|
| 538 |
+
emit_progress("Generation complete!", 100)
|
| 539 |
+
|
| 540 |
+
try:
|
| 541 |
+
socketio.emit(
|
| 542 |
+
"generation_complete",
|
| 543 |
+
{
|
| 544 |
+
"message": "Video generation completed!",
|
| 545 |
+
"total_frames": total_frames_sent,
|
| 546 |
+
"generation_time": f"{generation_time:.2f}s",
|
| 547 |
+
"job_id": job_id,
|
| 548 |
+
},
|
| 549 |
+
)
|
| 550 |
+
except Exception as e:
|
| 551 |
+
print(f"❌ Failed to emit generation complete: {e}")
|
| 552 |
+
|
| 553 |
+
except Exception as e:
|
| 554 |
+
print(f"❌ Generation failed: {e}")
|
| 555 |
+
try:
|
| 556 |
+
socketio.emit(
|
| 557 |
+
"error",
|
| 558 |
+
{"message": f"Generation failed: {str(e)}", "job_id": job_id},
|
| 559 |
+
)
|
| 560 |
+
except Exception as e:
|
| 561 |
+
print(f"❌ Failed to emit error: {e}")
|
| 562 |
+
finally:
|
| 563 |
+
generation_active = False
|
| 564 |
+
stop_event.set()
|
| 565 |
+
|
| 566 |
+
# Clean up sender thread
|
| 567 |
+
try:
|
| 568 |
+
frame_send_queue.put(None)
|
| 569 |
+
except Exception as e:
|
| 570 |
+
print(f"❌ Failed to put None in frame_send_queue: {e}")
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
def generate_mp4_from_images(image_directory, output_video_path, fps=24):
|
| 574 |
+
"""
|
| 575 |
+
Generate an MP4 video from a directory of images ordered alphabetically.
|
| 576 |
+
|
| 577 |
+
:param image_directory: Path to the directory containing images.
|
| 578 |
+
:param output_video_path: Path where the output MP4 will be saved.
|
| 579 |
+
:param fps: Frames per second for the output video.
|
| 580 |
+
"""
|
| 581 |
+
global anim_name
|
| 582 |
+
# Construct the ffmpeg command
|
| 583 |
+
cmd = [
|
| 584 |
+
"ffmpeg",
|
| 585 |
+
"-framerate",
|
| 586 |
+
str(fps),
|
| 587 |
+
"-i",
|
| 588 |
+
os.path.join(
|
| 589 |
+
image_directory, anim_name + "/" + anim_name + "_%03d.jpg"
|
| 590 |
+
), # Adjust the pattern if necessary
|
| 591 |
+
"-c:v",
|
| 592 |
+
"libx264",
|
| 593 |
+
"-pix_fmt",
|
| 594 |
+
"yuv420p",
|
| 595 |
+
output_video_path,
|
| 596 |
+
]
|
| 597 |
+
try:
|
| 598 |
+
subprocess.run(cmd, check=True)
|
| 599 |
+
print(f"Video saved to {output_video_path}")
|
| 600 |
+
except subprocess.CalledProcessError as e:
|
| 601 |
+
print(f"An error occurred: {e}")
|
| 602 |
+
|
| 603 |
+
|
| 604 |
+
def calculate_sha256(data):
|
| 605 |
+
# Convert data to bytes if it's not already
|
| 606 |
+
if isinstance(data, str):
|
| 607 |
+
data = data.encode()
|
| 608 |
+
# Calculate SHA-256 hash
|
| 609 |
+
sha256_hash = hashlib.sha256(data).hexdigest()
|
| 610 |
+
return sha256_hash
|
| 611 |
+
|
| 612 |
+
|
| 613 |
+
# Socket.IO event handlers
|
| 614 |
+
@socketio.on("connect")
|
| 615 |
+
def handle_connect():
|
| 616 |
+
print("Client connected")
|
| 617 |
+
emit("status", {"message": "Connected to frontend-buffered demo server"})
|
| 618 |
+
|
| 619 |
+
|
| 620 |
+
@socketio.on("disconnect")
|
| 621 |
+
def handle_disconnect():
|
| 622 |
+
print("Client disconnected")
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
@socketio.on("start_generation")
|
| 626 |
+
def handle_start_generation(data):
|
| 627 |
+
global generation_active, frame_number, anim_name, frame_rate
|
| 628 |
+
|
| 629 |
+
frame_number = 0
|
| 630 |
+
if generation_active:
|
| 631 |
+
emit("error", {"message": "Generation already in progress"})
|
| 632 |
+
return
|
| 633 |
+
|
| 634 |
+
prompt = data.get("prompt", "")
|
| 635 |
+
|
| 636 |
+
seed = data.get("seed", -1)
|
| 637 |
+
if seed == -1:
|
| 638 |
+
seed = random.randint(0, 2**32)
|
| 639 |
+
|
| 640 |
+
# Extract words up to the first punctuation or newline
|
| 641 |
+
words_up_to_punctuation = re.split(r"[^\w\s]", prompt)[0].strip() if prompt else ""
|
| 642 |
+
if not words_up_to_punctuation:
|
| 643 |
+
words_up_to_punctuation = re.split(r"[\n\r]", prompt)[0].strip()
|
| 644 |
+
|
| 645 |
+
# Calculate SHA-256 hash of the entire prompt
|
| 646 |
+
sha256_hash = calculate_sha256(prompt)
|
| 647 |
+
|
| 648 |
+
# Create anim_name with the extracted words and first 10 characters of the hash
|
| 649 |
+
anim_name = f"{words_up_to_punctuation[:20]}_{str(seed)}_{sha256_hash[:10]}"
|
| 650 |
+
|
| 651 |
+
generation_active = True
|
| 652 |
+
generation_start_time = time.time()
|
| 653 |
+
enable_torch_compile = data.get("enable_torch_compile", False)
|
| 654 |
+
enable_fp8 = data.get("enable_fp8", False)
|
| 655 |
+
use_taehv = data.get("use_taehv", False)
|
| 656 |
+
frame_rate = data.get("fps", 6)
|
| 657 |
+
|
| 658 |
+
if not prompt:
|
| 659 |
+
emit("error", {"message": "Prompt is required"})
|
| 660 |
+
return
|
| 661 |
+
|
| 662 |
+
# Start generation in background thread
|
| 663 |
+
socketio.start_background_task(
|
| 664 |
+
generate_video_stream,
|
| 665 |
+
prompt,
|
| 666 |
+
seed,
|
| 667 |
+
enable_torch_compile,
|
| 668 |
+
enable_fp8,
|
| 669 |
+
use_taehv,
|
| 670 |
+
)
|
| 671 |
+
emit(
|
| 672 |
+
"status",
|
| 673 |
+
{"message": "Generation started - frames will be sent immediately"},
|
| 674 |
+
)
|
| 675 |
+
|
| 676 |
+
|
| 677 |
+
@socketio.on("stop_generation")
|
| 678 |
+
def handle_stop_generation():
|
| 679 |
+
global generation_active, stop_event, frame_send_queue
|
| 680 |
+
generation_active = False
|
| 681 |
+
stop_event.set()
|
| 682 |
+
|
| 683 |
+
# Signal sender thread to stop (will be processed after current frames)
|
| 684 |
+
try:
|
| 685 |
+
frame_send_queue.put(None)
|
| 686 |
+
except Exception as e:
|
| 687 |
+
print(f"❌ Failed to put None in frame_send_queue: {e}")
|
| 688 |
+
|
| 689 |
+
emit("status", {"message": "Generation stopped"})
|
| 690 |
+
|
| 691 |
+
|
| 692 |
+
# Web routes
|
| 693 |
+
|
| 694 |
+
|
| 695 |
+
@app.route("/")
|
| 696 |
+
def index():
|
| 697 |
+
return render_template("demo.html")
|
| 698 |
+
|
| 699 |
+
|
| 700 |
+
@app.route("/api/status")
|
| 701 |
+
def api_status():
|
| 702 |
+
return jsonify(
|
| 703 |
+
{
|
| 704 |
+
"generation_active": generation_active,
|
| 705 |
+
"free_vram_gb": get_cuda_free_memory_gb(gpu),
|
| 706 |
+
"fp8_applied": fp8_applied,
|
| 707 |
+
"torch_compile_applied": torch_compile_applied,
|
| 708 |
+
"current_use_taehv": current_use_taehv,
|
| 709 |
+
}
|
| 710 |
+
)
|
| 711 |
+
|
| 712 |
+
|
| 713 |
+
if __name__ == "__main__":
|
| 714 |
+
print(f"🚀 Starting demo on http://{args.host}:{args.port}")
|
| 715 |
+
socketio.run(app, host=args.host, port=args.port, debug=False)
|
demo_utils/constant.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
ZERO_VAE_CACHE = [
|
| 4 |
+
torch.zeros(1, 16, 2, 60, 104),
|
| 5 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 6 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 7 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 8 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 9 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 10 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 11 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 12 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 13 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 14 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 15 |
+
torch.zeros(1, 384, 2, 60, 104),
|
| 16 |
+
torch.zeros(1, 192, 2, 120, 208),
|
| 17 |
+
torch.zeros(1, 384, 2, 120, 208),
|
| 18 |
+
torch.zeros(1, 384, 2, 120, 208),
|
| 19 |
+
torch.zeros(1, 384, 2, 120, 208),
|
| 20 |
+
torch.zeros(1, 384, 2, 120, 208),
|
| 21 |
+
torch.zeros(1, 384, 2, 120, 208),
|
| 22 |
+
torch.zeros(1, 384, 2, 120, 208),
|
| 23 |
+
torch.zeros(1, 192, 2, 240, 416),
|
| 24 |
+
torch.zeros(1, 192, 2, 240, 416),
|
| 25 |
+
torch.zeros(1, 192, 2, 240, 416),
|
| 26 |
+
torch.zeros(1, 192, 2, 240, 416),
|
| 27 |
+
torch.zeros(1, 192, 2, 240, 416),
|
| 28 |
+
torch.zeros(1, 192, 2, 240, 416),
|
| 29 |
+
torch.zeros(1, 96, 2, 480, 832),
|
| 30 |
+
torch.zeros(1, 96, 2, 480, 832),
|
| 31 |
+
torch.zeros(1, 96, 2, 480, 832),
|
| 32 |
+
torch.zeros(1, 96, 2, 480, 832),
|
| 33 |
+
torch.zeros(1, 96, 2, 480, 832),
|
| 34 |
+
torch.zeros(1, 96, 2, 480, 832),
|
| 35 |
+
torch.zeros(1, 96, 2, 480, 832),
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
feat_names = [f"vae_cache_{i}" for i in range(len(ZERO_VAE_CACHE))]
|
| 39 |
+
ALL_INPUTS_NAMES = ["z", "use_cache"] + feat_names
|
demo_utils/memory.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copied from https://github.com/lllyasviel/FramePack/tree/main/demo_utils
|
| 2 |
+
# Apache-2.0 License
|
| 3 |
+
# By lllyasviel
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
cpu = torch.device("cpu")
|
| 8 |
+
gpu = torch.device(f"cuda:{torch.cuda.current_device()}")
|
| 9 |
+
gpu_complete_modules = []
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class DynamicSwapInstaller:
|
| 13 |
+
@staticmethod
|
| 14 |
+
def _install_module(
|
| 15 |
+
module: torch.nn.Module,
|
| 16 |
+
**kwargs,
|
| 17 |
+
):
|
| 18 |
+
original_class = module.__class__
|
| 19 |
+
module.__dict__["forge_backup_original_class"] = original_class
|
| 20 |
+
|
| 21 |
+
def hacked_get_attr(
|
| 22 |
+
self,
|
| 23 |
+
name: str,
|
| 24 |
+
):
|
| 25 |
+
if "_parameters" in self.__dict__:
|
| 26 |
+
_parameters = self.__dict__["_parameters"]
|
| 27 |
+
if name in _parameters:
|
| 28 |
+
p = _parameters[name]
|
| 29 |
+
if p is None:
|
| 30 |
+
return None
|
| 31 |
+
if p.__class__ == torch.nn.Parameter:
|
| 32 |
+
return torch.nn.Parameter(p.to(**kwargs), requires_grad=p.requires_grad)
|
| 33 |
+
else:
|
| 34 |
+
return p.to(**kwargs)
|
| 35 |
+
if "_buffers" in self.__dict__:
|
| 36 |
+
_buffers = self.__dict__["_buffers"]
|
| 37 |
+
if name in _buffers:
|
| 38 |
+
return _buffers[name].to(**kwargs)
|
| 39 |
+
return super(original_class, self).__getattr__(name)
|
| 40 |
+
|
| 41 |
+
module.__class__ = type(
|
| 42 |
+
"DynamicSwap_" + original_class.__name__,
|
| 43 |
+
(original_class,),
|
| 44 |
+
{
|
| 45 |
+
"__getattr__": hacked_get_attr,
|
| 46 |
+
},
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
return
|
| 50 |
+
|
| 51 |
+
@staticmethod
|
| 52 |
+
def _uninstall_module(
|
| 53 |
+
module: torch.nn.Module,
|
| 54 |
+
):
|
| 55 |
+
if "forge_backup_original_class" in module.__dict__:
|
| 56 |
+
module.__class__ = module.__dict__.pop("forge_backup_original_class")
|
| 57 |
+
return
|
| 58 |
+
|
| 59 |
+
@staticmethod
|
| 60 |
+
def install_model(
|
| 61 |
+
model: torch.nn.Module,
|
| 62 |
+
**kwargs,
|
| 63 |
+
):
|
| 64 |
+
for m in model.modules():
|
| 65 |
+
DynamicSwapInstaller._install_module(m, **kwargs)
|
| 66 |
+
return
|
| 67 |
+
|
| 68 |
+
@staticmethod
|
| 69 |
+
def uninstall_model(
|
| 70 |
+
model: torch.nn.Module,
|
| 71 |
+
):
|
| 72 |
+
for m in model.modules():
|
| 73 |
+
DynamicSwapInstaller._uninstall_module(m)
|
| 74 |
+
return
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def fake_diffusers_current_device(
|
| 78 |
+
model: torch.nn.Module,
|
| 79 |
+
target_device: torch.device,
|
| 80 |
+
):
|
| 81 |
+
if hasattr(model, "scale_shift_table"):
|
| 82 |
+
model.scale_shift_table.data = model.scale_shift_table.data.to(target_device)
|
| 83 |
+
return
|
| 84 |
+
|
| 85 |
+
for k, p in model.named_modules():
|
| 86 |
+
if hasattr(p, "weight"):
|
| 87 |
+
p.to(target_device)
|
| 88 |
+
return
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def get_cuda_free_memory_gb(
|
| 92 |
+
device=None,
|
| 93 |
+
):
|
| 94 |
+
if device is None:
|
| 95 |
+
device = gpu
|
| 96 |
+
|
| 97 |
+
memory_stats = torch.cuda.memory_stats(device)
|
| 98 |
+
bytes_active = memory_stats["active_bytes.all.current"]
|
| 99 |
+
bytes_reserved = memory_stats["reserved_bytes.all.current"]
|
| 100 |
+
bytes_free_cuda, _ = torch.cuda.mem_get_info(device)
|
| 101 |
+
bytes_inactive_reserved = bytes_reserved - bytes_active
|
| 102 |
+
bytes_total_available = bytes_free_cuda + bytes_inactive_reserved
|
| 103 |
+
return bytes_total_available / (1024**3)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def move_model_to_device_with_memory_preservation(
|
| 107 |
+
model,
|
| 108 |
+
target_device,
|
| 109 |
+
preserved_memory_gb=0,
|
| 110 |
+
):
|
| 111 |
+
print(
|
| 112 |
+
f"Moving {model.__class__.__name__} to {target_device} with preserved memory: {preserved_memory_gb} GB"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
for m in model.modules():
|
| 116 |
+
if get_cuda_free_memory_gb(target_device) <= preserved_memory_gb:
|
| 117 |
+
torch.cuda.empty_cache()
|
| 118 |
+
return
|
| 119 |
+
|
| 120 |
+
if hasattr(m, "weight"):
|
| 121 |
+
m.to(device=target_device)
|
| 122 |
+
|
| 123 |
+
model.to(device=target_device)
|
| 124 |
+
torch.cuda.empty_cache()
|
| 125 |
+
return
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def offload_model_from_device_for_memory_preservation(
|
| 129 |
+
model,
|
| 130 |
+
target_device,
|
| 131 |
+
preserved_memory_gb=0,
|
| 132 |
+
):
|
| 133 |
+
print(
|
| 134 |
+
f"Offloading {model.__class__.__name__} from {target_device} to preserve memory: {preserved_memory_gb} GB"
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
for m in model.modules():
|
| 138 |
+
if get_cuda_free_memory_gb(target_device) >= preserved_memory_gb:
|
| 139 |
+
torch.cuda.empty_cache()
|
| 140 |
+
return
|
| 141 |
+
|
| 142 |
+
if hasattr(m, "weight"):
|
| 143 |
+
m.to(device=cpu)
|
| 144 |
+
|
| 145 |
+
model.to(device=cpu)
|
| 146 |
+
torch.cuda.empty_cache()
|
| 147 |
+
return
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def unload_complete_models(
|
| 151 |
+
*args,
|
| 152 |
+
):
|
| 153 |
+
for m in gpu_complete_modules + list(args):
|
| 154 |
+
m.to(device=cpu)
|
| 155 |
+
print(f"Unloaded {m.__class__.__name__} as complete.")
|
| 156 |
+
|
| 157 |
+
gpu_complete_modules.clear()
|
| 158 |
+
torch.cuda.empty_cache()
|
| 159 |
+
return
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def load_model_as_complete(
|
| 163 |
+
model,
|
| 164 |
+
target_device,
|
| 165 |
+
unload=True,
|
| 166 |
+
):
|
| 167 |
+
if unload:
|
| 168 |
+
unload_complete_models()
|
| 169 |
+
|
| 170 |
+
model.to(device=target_device)
|
| 171 |
+
print(f"Loaded {model.__class__.__name__} to {target_device} as complete.")
|
| 172 |
+
|
| 173 |
+
gpu_complete_modules.append(model)
|
| 174 |
+
return
|
demo_utils/taehv.py
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Tiny AutoEncoder for Hunyuan Video
|
| 4 |
+
(DNN for encoding / decoding videos to Hunyuan Video's latent space)
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
from tqdm.auto import tqdm
|
| 11 |
+
from collections import namedtuple
|
| 12 |
+
|
| 13 |
+
DecoderResult = namedtuple("DecoderResult", ("frame", "memory"))
|
| 14 |
+
TWorkItem = namedtuple("TWorkItem", ("input_tensor", "block_index"))
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def conv(
|
| 18 |
+
n_in,
|
| 19 |
+
n_out,
|
| 20 |
+
**kwargs,
|
| 21 |
+
):
|
| 22 |
+
return nn.Conv2d(
|
| 23 |
+
n_in,
|
| 24 |
+
n_out,
|
| 25 |
+
3,
|
| 26 |
+
padding=1,
|
| 27 |
+
**kwargs,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class Clamp(nn.Module):
|
| 32 |
+
def forward(
|
| 33 |
+
self,
|
| 34 |
+
x,
|
| 35 |
+
):
|
| 36 |
+
return torch.tanh(x / 3) * 3
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class MemBlock(nn.Module):
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
n_in,
|
| 43 |
+
n_out,
|
| 44 |
+
):
|
| 45 |
+
super().__init__()
|
| 46 |
+
self.conv = nn.Sequential(
|
| 47 |
+
conv(n_in * 2, n_out),
|
| 48 |
+
nn.ReLU(inplace=True),
|
| 49 |
+
conv(n_out, n_out),
|
| 50 |
+
nn.ReLU(inplace=True),
|
| 51 |
+
conv(n_out, n_out),
|
| 52 |
+
)
|
| 53 |
+
self.skip = nn.Conv2d(n_in, n_out, 1, bias=False) if n_in != n_out else nn.Identity()
|
| 54 |
+
self.act = nn.ReLU(inplace=True)
|
| 55 |
+
|
| 56 |
+
def forward(
|
| 57 |
+
self,
|
| 58 |
+
x,
|
| 59 |
+
past,
|
| 60 |
+
):
|
| 61 |
+
return self.act(
|
| 62 |
+
self.conv(
|
| 63 |
+
torch.cat([x, past], 1),
|
| 64 |
+
)
|
| 65 |
+
+ self.skip(x),
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class TPool(nn.Module):
|
| 70 |
+
def __init__(
|
| 71 |
+
self,
|
| 72 |
+
n_f,
|
| 73 |
+
stride,
|
| 74 |
+
):
|
| 75 |
+
super().__init__()
|
| 76 |
+
self.stride = stride
|
| 77 |
+
self.conv = nn.Conv2d(
|
| 78 |
+
n_f * stride,
|
| 79 |
+
n_f,
|
| 80 |
+
1,
|
| 81 |
+
bias=False,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
def forward(
|
| 85 |
+
self,
|
| 86 |
+
x,
|
| 87 |
+
):
|
| 88 |
+
_NT, C, H, W = x.shape
|
| 89 |
+
return self.conv(
|
| 90 |
+
x.reshape(-1, self.stride * C, H, W),
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class TGrow(nn.Module):
|
| 95 |
+
def __init__(
|
| 96 |
+
self,
|
| 97 |
+
n_f,
|
| 98 |
+
stride,
|
| 99 |
+
):
|
| 100 |
+
super().__init__()
|
| 101 |
+
self.stride = stride
|
| 102 |
+
self.conv = nn.Conv2d(
|
| 103 |
+
n_f,
|
| 104 |
+
n_f * stride,
|
| 105 |
+
1,
|
| 106 |
+
bias=False,
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
def forward(
|
| 110 |
+
self,
|
| 111 |
+
x,
|
| 112 |
+
):
|
| 113 |
+
_NT, C, H, W = x.shape
|
| 114 |
+
x = self.conv(x)
|
| 115 |
+
return x.reshape(-1, C, H, W)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def apply_model_with_memblocks(
|
| 119 |
+
model,
|
| 120 |
+
x,
|
| 121 |
+
parallel,
|
| 122 |
+
show_progress_bar,
|
| 123 |
+
):
|
| 124 |
+
"""
|
| 125 |
+
Apply a sequential model with memblocks to the given input.
|
| 126 |
+
Args:
|
| 127 |
+
- model: nn.Sequential of blocks to apply
|
| 128 |
+
- x: input data, of dimensions NTCHW
|
| 129 |
+
- parallel: if True, parallelize over timesteps (fast but uses O(T) memory)
|
| 130 |
+
if False, each timestep will be processed sequentially (slow but uses O(1) memory)
|
| 131 |
+
- show_progress_bar: if True, enables tqdm progressbar display
|
| 132 |
+
|
| 133 |
+
Returns NTCHW tensor of output data.
|
| 134 |
+
"""
|
| 135 |
+
assert x.ndim == 5, f"TAEHV operates on NTCHW tensors, but got {x.ndim}-dim tensor"
|
| 136 |
+
N, T, C, H, W = x.shape
|
| 137 |
+
if parallel:
|
| 138 |
+
x = x.reshape(N * T, C, H, W)
|
| 139 |
+
# parallel over input timesteps, iterate over blocks
|
| 140 |
+
for b in tqdm(model, disable=not show_progress_bar):
|
| 141 |
+
if isinstance(b, MemBlock):
|
| 142 |
+
NT, C, H, W = x.shape
|
| 143 |
+
T = NT // N
|
| 144 |
+
_x = x.reshape(N, T, C, H, W)
|
| 145 |
+
mem = F.pad(_x, (0, 0, 0, 0, 0, 0, 1, 0), value=0)[:, :T].reshape(x.shape)
|
| 146 |
+
x = b(x, mem)
|
| 147 |
+
else:
|
| 148 |
+
x = b(x)
|
| 149 |
+
NT, C, H, W = x.shape
|
| 150 |
+
T = NT // N
|
| 151 |
+
x = x.view(N, T, C, H, W)
|
| 152 |
+
else:
|
| 153 |
+
# TODO(oboerbohan): at least on macos this still gradually uses more memory during decode...
|
| 154 |
+
# need to fix :(
|
| 155 |
+
out = []
|
| 156 |
+
# iterate over input timesteps and also iterate over blocks.
|
| 157 |
+
# because of the cursed TPool/TGrow blocks, this is not a nested loop,
|
| 158 |
+
# it's actually a ***graph traversal*** problem! so let's make a queue
|
| 159 |
+
work_queue = [
|
| 160 |
+
TWorkItem(xt, 0) for t, xt in enumerate(x.reshape(N, T * C, H, W).chunk(T, dim=1))
|
| 161 |
+
]
|
| 162 |
+
# in addition to manually managing our queue, we also need to manually manage our progressbar.
|
| 163 |
+
# we'll update it for every source node that we consume.
|
| 164 |
+
progress_bar = tqdm(range(T), disable=not show_progress_bar)
|
| 165 |
+
# we'll also need a separate addressable memory per node as well
|
| 166 |
+
mem = [None] * len(model)
|
| 167 |
+
while work_queue:
|
| 168 |
+
xt, i = work_queue.pop(0)
|
| 169 |
+
if i == 0:
|
| 170 |
+
# new source node consumed
|
| 171 |
+
progress_bar.update(1)
|
| 172 |
+
if i == len(model):
|
| 173 |
+
# reached end of the graph, append result to output list
|
| 174 |
+
out.append(xt)
|
| 175 |
+
else:
|
| 176 |
+
# fetch the block to process
|
| 177 |
+
b = model[i]
|
| 178 |
+
if isinstance(b, MemBlock):
|
| 179 |
+
# mem blocks are simple since we're visiting the graph in causal order
|
| 180 |
+
if mem[i] is None:
|
| 181 |
+
xt_new = b(xt, xt * 0)
|
| 182 |
+
mem[i] = xt
|
| 183 |
+
else:
|
| 184 |
+
xt_new = b(xt, mem[i])
|
| 185 |
+
mem[i].copy_(
|
| 186 |
+
xt
|
| 187 |
+
) # inplace might reduce mysterious pytorch memory allocations? doesn't help though
|
| 188 |
+
# add successor to work queue
|
| 189 |
+
work_queue.insert(0, TWorkItem(xt_new, i + 1))
|
| 190 |
+
elif isinstance(b, TPool):
|
| 191 |
+
# pool blocks are miserable
|
| 192 |
+
if mem[i] is None:
|
| 193 |
+
mem[i] = [] # pool memory is itself a queue of inputs to pool
|
| 194 |
+
mem[i].append(xt)
|
| 195 |
+
if len(mem[i]) > b.stride:
|
| 196 |
+
# pool mem is in invalid state, we should have pooled before this
|
| 197 |
+
raise ValueError("???")
|
| 198 |
+
elif len(mem[i]) < b.stride:
|
| 199 |
+
# pool mem is not yet full, go back to processing the work queue
|
| 200 |
+
pass
|
| 201 |
+
else:
|
| 202 |
+
# pool mem is ready, run the pool block
|
| 203 |
+
N, C, H, W = xt.shape
|
| 204 |
+
xt = b(torch.cat(mem[i], 1).view(N * b.stride, C, H, W))
|
| 205 |
+
# reset the pool mem
|
| 206 |
+
mem[i] = []
|
| 207 |
+
# add successor to work queue
|
| 208 |
+
work_queue.insert(0, TWorkItem(xt, i + 1))
|
| 209 |
+
elif isinstance(b, TGrow):
|
| 210 |
+
xt = b(xt)
|
| 211 |
+
NT, C, H, W = xt.shape
|
| 212 |
+
# each tgrow has multiple successor nodes
|
| 213 |
+
for xt_next in reversed(xt.view(N, b.stride * C, H, W).chunk(b.stride, 1)):
|
| 214 |
+
# add successor to work queue
|
| 215 |
+
work_queue.insert(0, TWorkItem(xt_next, i + 1))
|
| 216 |
+
else:
|
| 217 |
+
# normal block with no funny business
|
| 218 |
+
xt = b(xt)
|
| 219 |
+
# add successor to work queue
|
| 220 |
+
work_queue.insert(0, TWorkItem(xt, i + 1))
|
| 221 |
+
progress_bar.close()
|
| 222 |
+
x = torch.stack(out, 1)
|
| 223 |
+
return x
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
class TAEHV(nn.Module):
|
| 227 |
+
latent_channels = 16
|
| 228 |
+
image_channels = 3
|
| 229 |
+
|
| 230 |
+
def __init__(
|
| 231 |
+
self,
|
| 232 |
+
checkpoint_path="taehv.pth",
|
| 233 |
+
decoder_time_upscale=(True, True),
|
| 234 |
+
decoder_space_upscale=(True, True, True),
|
| 235 |
+
):
|
| 236 |
+
"""Initialize pretrained TAEHV from the given checkpoint.
|
| 237 |
+
|
| 238 |
+
Arg:
|
| 239 |
+
checkpoint_path: path to weight file to load. taehv.pth for Hunyuan, taew2_1.pth for Wan 2.1.
|
| 240 |
+
decoder_time_upscale: whether temporal upsampling is enabled for each block. upsampling can be disabled for a cheaper preview.
|
| 241 |
+
decoder_space_upscale: whether spatial upsampling is enabled for each block. upsampling can be disabled for a cheaper preview.
|
| 242 |
+
"""
|
| 243 |
+
super().__init__()
|
| 244 |
+
self.encoder = nn.Sequential(
|
| 245 |
+
conv(TAEHV.image_channels, 64),
|
| 246 |
+
nn.ReLU(inplace=True),
|
| 247 |
+
TPool(64, 2),
|
| 248 |
+
conv(64, 64, stride=2, bias=False),
|
| 249 |
+
MemBlock(64, 64),
|
| 250 |
+
MemBlock(64, 64),
|
| 251 |
+
MemBlock(64, 64),
|
| 252 |
+
TPool(64, 2),
|
| 253 |
+
conv(64, 64, stride=2, bias=False),
|
| 254 |
+
MemBlock(64, 64),
|
| 255 |
+
MemBlock(64, 64),
|
| 256 |
+
MemBlock(64, 64),
|
| 257 |
+
TPool(64, 1),
|
| 258 |
+
conv(64, 64, stride=2, bias=False),
|
| 259 |
+
MemBlock(64, 64),
|
| 260 |
+
MemBlock(64, 64),
|
| 261 |
+
MemBlock(64, 64),
|
| 262 |
+
conv(64, TAEHV.latent_channels),
|
| 263 |
+
)
|
| 264 |
+
n_f = [256, 128, 64, 64]
|
| 265 |
+
self.frames_to_trim = 2 ** sum(decoder_time_upscale) - 1
|
| 266 |
+
self.decoder = nn.Sequential(
|
| 267 |
+
Clamp(),
|
| 268 |
+
conv(TAEHV.latent_channels, n_f[0]),
|
| 269 |
+
nn.ReLU(inplace=True),
|
| 270 |
+
MemBlock(n_f[0], n_f[0]),
|
| 271 |
+
MemBlock(n_f[0], n_f[0]),
|
| 272 |
+
MemBlock(n_f[0], n_f[0]),
|
| 273 |
+
nn.Upsample(scale_factor=2 if decoder_space_upscale[0] else 1),
|
| 274 |
+
TGrow(n_f[0], 1),
|
| 275 |
+
conv(n_f[0], n_f[1], bias=False),
|
| 276 |
+
MemBlock(n_f[1], n_f[1]),
|
| 277 |
+
MemBlock(n_f[1], n_f[1]),
|
| 278 |
+
MemBlock(n_f[1], n_f[1]),
|
| 279 |
+
nn.Upsample(scale_factor=2 if decoder_space_upscale[1] else 1),
|
| 280 |
+
TGrow(n_f[1], 2 if decoder_time_upscale[0] else 1),
|
| 281 |
+
conv(n_f[1], n_f[2], bias=False),
|
| 282 |
+
MemBlock(n_f[2], n_f[2]),
|
| 283 |
+
MemBlock(n_f[2], n_f[2]),
|
| 284 |
+
MemBlock(n_f[2], n_f[2]),
|
| 285 |
+
nn.Upsample(scale_factor=2 if decoder_space_upscale[2] else 1),
|
| 286 |
+
TGrow(n_f[2], 2 if decoder_time_upscale[1] else 1),
|
| 287 |
+
conv(n_f[2], n_f[3], bias=False),
|
| 288 |
+
nn.ReLU(inplace=True),
|
| 289 |
+
conv(n_f[3], TAEHV.image_channels),
|
| 290 |
+
)
|
| 291 |
+
if checkpoint_path is not None:
|
| 292 |
+
self.load_state_dict(
|
| 293 |
+
self.patch_tgrow_layers(
|
| 294 |
+
torch.load(checkpoint_path, map_location="cpu", weights_only=True)
|
| 295 |
+
)
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
def patch_tgrow_layers(
|
| 299 |
+
self,
|
| 300 |
+
sd,
|
| 301 |
+
):
|
| 302 |
+
"""Patch TGrow layers to use a smaller kernel if needed.
|
| 303 |
+
|
| 304 |
+
Args:
|
| 305 |
+
sd: state dict to patch
|
| 306 |
+
"""
|
| 307 |
+
new_sd = self.state_dict()
|
| 308 |
+
for i, layer in enumerate(self.decoder):
|
| 309 |
+
if isinstance(layer, TGrow):
|
| 310 |
+
key = f"decoder.{i}.conv.weight"
|
| 311 |
+
if sd[key].shape[0] > new_sd[key].shape[0]:
|
| 312 |
+
# take the last-timestep output channels
|
| 313 |
+
sd[key] = sd[key][-new_sd[key].shape[0] :]
|
| 314 |
+
return sd
|
| 315 |
+
|
| 316 |
+
def encode_video(
|
| 317 |
+
self,
|
| 318 |
+
x,
|
| 319 |
+
parallel=True,
|
| 320 |
+
show_progress_bar=True,
|
| 321 |
+
):
|
| 322 |
+
"""Encode a sequence of frames.
|
| 323 |
+
|
| 324 |
+
Args:
|
| 325 |
+
x: input NTCHW RGB (C=3) tensor with values in [0, 1].
|
| 326 |
+
parallel: if True, all frames will be processed at once.
|
| 327 |
+
(this is faster but may require more memory).
|
| 328 |
+
if False, frames will be processed sequentially.
|
| 329 |
+
Returns NTCHW latent tensor with ~Gaussian values.
|
| 330 |
+
"""
|
| 331 |
+
return apply_model_with_memblocks(
|
| 332 |
+
self.encoder,
|
| 333 |
+
x,
|
| 334 |
+
parallel,
|
| 335 |
+
show_progress_bar,
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
def decode_video(
|
| 339 |
+
self,
|
| 340 |
+
x,
|
| 341 |
+
parallel=True,
|
| 342 |
+
show_progress_bar=False,
|
| 343 |
+
):
|
| 344 |
+
"""Decode a sequence of frames.
|
| 345 |
+
|
| 346 |
+
Args:
|
| 347 |
+
x: input NTCHW latent (C=12) tensor with ~Gaussian values.
|
| 348 |
+
parallel: if True, all frames will be processed at once.
|
| 349 |
+
(this is faster but may require more memory).
|
| 350 |
+
if False, frames will be processed sequentially.
|
| 351 |
+
Returns NTCHW RGB tensor with ~[0, 1] values.
|
| 352 |
+
"""
|
| 353 |
+
x = apply_model_with_memblocks(
|
| 354 |
+
self.decoder,
|
| 355 |
+
x,
|
| 356 |
+
parallel,
|
| 357 |
+
show_progress_bar,
|
| 358 |
+
)
|
| 359 |
+
# return x[:, self.frames_to_trim:]
|
| 360 |
+
return x
|
| 361 |
+
|
| 362 |
+
def forward(
|
| 363 |
+
self,
|
| 364 |
+
x,
|
| 365 |
+
):
|
| 366 |
+
return self.c(x)
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
@torch.no_grad()
|
| 370 |
+
def main():
|
| 371 |
+
"""Run TAEHV roundtrip reconstruction on the given video paths."""
|
| 372 |
+
import os
|
| 373 |
+
import sys
|
| 374 |
+
import cv2 # no highly esteemed deed is commemorated here
|
| 375 |
+
|
| 376 |
+
class VideoTensorReader:
|
| 377 |
+
def __init__(
|
| 378 |
+
self,
|
| 379 |
+
video_file_path,
|
| 380 |
+
):
|
| 381 |
+
self.cap = cv2.VideoCapture(video_file_path)
|
| 382 |
+
assert self.cap.isOpened(), f"Could not load {video_file_path}"
|
| 383 |
+
self.fps = self.cap.get(cv2.CAP_PROP_FPS)
|
| 384 |
+
|
| 385 |
+
def __iter__(
|
| 386 |
+
self,
|
| 387 |
+
):
|
| 388 |
+
return self
|
| 389 |
+
|
| 390 |
+
def __next__(
|
| 391 |
+
self,
|
| 392 |
+
):
|
| 393 |
+
ret, frame = self.cap.read()
|
| 394 |
+
if not ret:
|
| 395 |
+
self.cap.release()
|
| 396 |
+
raise StopIteration # End of video or error
|
| 397 |
+
return torch.from_numpy(
|
| 398 |
+
cv2.cvtColor(frame, cv2.COLOR_BGR2RGB),
|
| 399 |
+
).permute(
|
| 400 |
+
2, 0, 1
|
| 401 |
+
) # BGR HWC -> RGB CHW
|
| 402 |
+
|
| 403 |
+
class VideoTensorWriter:
|
| 404 |
+
def __init__(
|
| 405 |
+
self,
|
| 406 |
+
video_file_path,
|
| 407 |
+
width_height,
|
| 408 |
+
fps=30,
|
| 409 |
+
):
|
| 410 |
+
self.writer = cv2.VideoWriter(
|
| 411 |
+
video_file_path,
|
| 412 |
+
cv2.VideoWriter_fourcc(*"mp4v"),
|
| 413 |
+
fps,
|
| 414 |
+
width_height,
|
| 415 |
+
)
|
| 416 |
+
assert self.writer.isOpened(), f"Could not create writer for {video_file_path}"
|
| 417 |
+
|
| 418 |
+
def write(
|
| 419 |
+
self,
|
| 420 |
+
frame_tensor,
|
| 421 |
+
):
|
| 422 |
+
assert frame_tensor.ndim == 3 and frame_tensor.shape[0] == 3, f"{frame_tensor.shape}??"
|
| 423 |
+
self.writer.write(
|
| 424 |
+
cv2.cvtColor(frame_tensor.permute(1, 2, 0).numpy(), cv2.COLOR_RGB2BGR)
|
| 425 |
+
) # RGB CHW -> BGR HWC
|
| 426 |
+
|
| 427 |
+
def __del__(
|
| 428 |
+
self,
|
| 429 |
+
):
|
| 430 |
+
if hasattr(self, "writer"):
|
| 431 |
+
self.writer.release()
|
| 432 |
+
|
| 433 |
+
dev = torch.device(
|
| 434 |
+
"cuda"
|
| 435 |
+
if torch.cuda.is_available()
|
| 436 |
+
else "mps" if torch.backends.mps.is_available() else "cpu"
|
| 437 |
+
)
|
| 438 |
+
dtype = torch.float16
|
| 439 |
+
checkpoint_path = os.getenv("TAEHV_CHECKPOINT_PATH", "taehv.pth")
|
| 440 |
+
checkpoint_name = os.path.splitext(os.path.basename(checkpoint_path))[0]
|
| 441 |
+
print(
|
| 442 |
+
f"Using device \033[31m{dev}\033[0m, dtype \033[32m{dtype}\033[0m, checkpoint \033[34m{checkpoint_name}\033[0m ({checkpoint_path})"
|
| 443 |
+
)
|
| 444 |
+
taehv = TAEHV(checkpoint_path=checkpoint_path).to(dev, dtype)
|
| 445 |
+
for video_path in sys.argv[1:]:
|
| 446 |
+
print(f"Processing {video_path}...")
|
| 447 |
+
video_in = VideoTensorReader(video_path)
|
| 448 |
+
video = torch.stack(list(video_in), 0)[None]
|
| 449 |
+
vid_dev = video.to(dev, dtype).div_(255.0)
|
| 450 |
+
# convert to device tensor
|
| 451 |
+
if video.numel() < 100_000_000:
|
| 452 |
+
print(f" {video_path} seems small enough, will process all frames in parallel")
|
| 453 |
+
# convert to device tensor
|
| 454 |
+
vid_enc = taehv.encode_video(vid_dev)
|
| 455 |
+
print(f" Encoded {video_path} -> {vid_enc.shape}. Decoding...")
|
| 456 |
+
vid_dec = taehv.decode_video(vid_enc)
|
| 457 |
+
print(f" Decoded {video_path} -> {vid_dec.shape}")
|
| 458 |
+
else:
|
| 459 |
+
print(f" {video_path} seems large, will process each frame sequentially")
|
| 460 |
+
# convert to device tensor
|
| 461 |
+
vid_enc = taehv.encode_video(vid_dev, parallel=False)
|
| 462 |
+
print(f" Encoded {video_path} -> {vid_enc.shape}. Decoding...")
|
| 463 |
+
vid_dec = taehv.decode_video(vid_enc, parallel=False)
|
| 464 |
+
print(f" Decoded {video_path} -> {vid_dec.shape}")
|
| 465 |
+
video_out_path = video_path + f".reconstructed_by_{checkpoint_name}.mp4"
|
| 466 |
+
video_out = VideoTensorWriter(
|
| 467 |
+
video_out_path,
|
| 468 |
+
(vid_dec.shape[-1], vid_dec.shape[-2]),
|
| 469 |
+
fps=int(round(video_in.fps)),
|
| 470 |
+
)
|
| 471 |
+
for frame in vid_dec.clamp_(0, 1).mul_(255).round_().byte().cpu()[0]:
|
| 472 |
+
video_out.write(frame)
|
| 473 |
+
print(f" Saved to {video_out_path}")
|
| 474 |
+
|
| 475 |
+
|
| 476 |
+
if __name__ == "__main__":
|
| 477 |
+
main()
|
demo_utils/utils.py
ADDED
|
@@ -0,0 +1,809 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copied from https://github.com/lllyasviel/FramePack/tree/main/demo_utils
|
| 2 |
+
# Apache-2.0 License
|
| 3 |
+
# By lllyasviel
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import cv2
|
| 7 |
+
import json
|
| 8 |
+
import random
|
| 9 |
+
import glob
|
| 10 |
+
import torch
|
| 11 |
+
import einops
|
| 12 |
+
import numpy as np
|
| 13 |
+
import datetime
|
| 14 |
+
import torchvision
|
| 15 |
+
|
| 16 |
+
from PIL import Image
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def min_resize(
|
| 20 |
+
x,
|
| 21 |
+
m,
|
| 22 |
+
):
|
| 23 |
+
if x.shape[0] < x.shape[1]:
|
| 24 |
+
s0 = m
|
| 25 |
+
s1 = int(float(m) / float(x.shape[0]) * float(x.shape[1]))
|
| 26 |
+
else:
|
| 27 |
+
s0 = int(float(m) / float(x.shape[1]) * float(x.shape[0]))
|
| 28 |
+
s1 = m
|
| 29 |
+
new_max = max(s1, s0)
|
| 30 |
+
raw_max = max(x.shape[0], x.shape[1])
|
| 31 |
+
if new_max < raw_max:
|
| 32 |
+
interpolation = cv2.INTER_AREA
|
| 33 |
+
else:
|
| 34 |
+
interpolation = cv2.INTER_LANCZOS4
|
| 35 |
+
y = cv2.resize(x, (s1, s0), interpolation=interpolation)
|
| 36 |
+
return y
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def d_resize(
|
| 40 |
+
x,
|
| 41 |
+
y,
|
| 42 |
+
):
|
| 43 |
+
H, W, C = y.shape
|
| 44 |
+
new_min = min(H, W)
|
| 45 |
+
raw_min = min(x.shape[0], x.shape[1])
|
| 46 |
+
if new_min < raw_min:
|
| 47 |
+
interpolation = cv2.INTER_AREA
|
| 48 |
+
else:
|
| 49 |
+
interpolation = cv2.INTER_LANCZOS4
|
| 50 |
+
y = cv2.resize(x, (W, H), interpolation=interpolation)
|
| 51 |
+
return y
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def resize_and_center_crop(
|
| 55 |
+
image,
|
| 56 |
+
target_width,
|
| 57 |
+
target_height,
|
| 58 |
+
):
|
| 59 |
+
if target_height == image.shape[0] and target_width == image.shape[1]:
|
| 60 |
+
return image
|
| 61 |
+
|
| 62 |
+
pil_image = Image.fromarray(image)
|
| 63 |
+
original_width, original_height = pil_image.size
|
| 64 |
+
scale_factor = max(target_width / original_width, target_height / original_height)
|
| 65 |
+
resized_width = int(round(original_width * scale_factor))
|
| 66 |
+
resized_height = int(round(original_height * scale_factor))
|
| 67 |
+
resized_image = pil_image.resize((resized_width, resized_height), Image.LANCZOS)
|
| 68 |
+
left = (resized_width - target_width) / 2
|
| 69 |
+
top = (resized_height - target_height) / 2
|
| 70 |
+
right = (resized_width + target_width) / 2
|
| 71 |
+
bottom = (resized_height + target_height) / 2
|
| 72 |
+
cropped_image = resized_image.crop((left, top, right, bottom))
|
| 73 |
+
return np.array(cropped_image)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def resize_and_center_crop_pytorch(
|
| 77 |
+
image,
|
| 78 |
+
target_width,
|
| 79 |
+
target_height,
|
| 80 |
+
):
|
| 81 |
+
B, C, H, W = image.shape
|
| 82 |
+
|
| 83 |
+
if H == target_height and W == target_width:
|
| 84 |
+
return image
|
| 85 |
+
|
| 86 |
+
scale_factor = max(target_width / W, target_height / H)
|
| 87 |
+
resized_width = int(round(W * scale_factor))
|
| 88 |
+
resized_height = int(round(H * scale_factor))
|
| 89 |
+
|
| 90 |
+
resized = torch.nn.functional.interpolate(
|
| 91 |
+
image,
|
| 92 |
+
size=(resized_height, resized_width),
|
| 93 |
+
mode="bilinear",
|
| 94 |
+
align_corners=False,
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
top = (resized_height - target_height) // 2
|
| 98 |
+
left = (resized_width - target_width) // 2
|
| 99 |
+
cropped = resized[:, :, top : top + target_height, left : left + target_width]
|
| 100 |
+
|
| 101 |
+
return cropped
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def resize_without_crop(
|
| 105 |
+
image,
|
| 106 |
+
target_width,
|
| 107 |
+
target_height,
|
| 108 |
+
):
|
| 109 |
+
if target_height == image.shape[0] and target_width == image.shape[1]:
|
| 110 |
+
return image
|
| 111 |
+
|
| 112 |
+
pil_image = Image.fromarray(image)
|
| 113 |
+
resized_image = pil_image.resize((target_width, target_height), Image.LANCZOS)
|
| 114 |
+
return np.array(resized_image)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def just_crop(
|
| 118 |
+
image,
|
| 119 |
+
w,
|
| 120 |
+
h,
|
| 121 |
+
):
|
| 122 |
+
if h == image.shape[0] and w == image.shape[1]:
|
| 123 |
+
return image
|
| 124 |
+
|
| 125 |
+
original_height, original_width = image.shape[:2]
|
| 126 |
+
k = min(original_height / h, original_width / w)
|
| 127 |
+
new_width = int(round(w * k))
|
| 128 |
+
new_height = int(round(h * k))
|
| 129 |
+
x_start = (original_width - new_width) // 2
|
| 130 |
+
y_start = (original_height - new_height) // 2
|
| 131 |
+
cropped_image = image[y_start : y_start + new_height, x_start : x_start + new_width]
|
| 132 |
+
return cropped_image
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def write_to_json(
|
| 136 |
+
data,
|
| 137 |
+
file_path,
|
| 138 |
+
):
|
| 139 |
+
temp_file_path = file_path + ".tmp"
|
| 140 |
+
with open(temp_file_path, "wt", encoding="utf-8") as temp_file:
|
| 141 |
+
json.dump(data, temp_file, indent=4)
|
| 142 |
+
os.replace(temp_file_path, file_path)
|
| 143 |
+
return
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def read_from_json(
|
| 147 |
+
file_path,
|
| 148 |
+
):
|
| 149 |
+
with open(file_path, "rt", encoding="utf-8") as file:
|
| 150 |
+
data = json.load(file)
|
| 151 |
+
return data
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def get_active_parameters(
|
| 155 |
+
m,
|
| 156 |
+
):
|
| 157 |
+
return {k: v for k, v in m.named_parameters() if v.requires_grad}
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def cast_training_params(
|
| 161 |
+
m,
|
| 162 |
+
dtype=torch.float32,
|
| 163 |
+
):
|
| 164 |
+
result = {}
|
| 165 |
+
for n, param in m.named_parameters():
|
| 166 |
+
if param.requires_grad:
|
| 167 |
+
param.data = param.to(dtype)
|
| 168 |
+
result[n] = param
|
| 169 |
+
return result
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def separate_lora_AB(
|
| 173 |
+
parameters,
|
| 174 |
+
B_patterns=None,
|
| 175 |
+
):
|
| 176 |
+
parameters_normal = {}
|
| 177 |
+
parameters_B = {}
|
| 178 |
+
|
| 179 |
+
if B_patterns is None:
|
| 180 |
+
B_patterns = [".lora_B.", "__zero__"]
|
| 181 |
+
|
| 182 |
+
for k, v in parameters.items():
|
| 183 |
+
if any(B_pattern in k for B_pattern in B_patterns):
|
| 184 |
+
parameters_B[k] = v
|
| 185 |
+
else:
|
| 186 |
+
parameters_normal[k] = v
|
| 187 |
+
|
| 188 |
+
return parameters_normal, parameters_B
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def set_attr_recursive(
|
| 192 |
+
obj,
|
| 193 |
+
attr,
|
| 194 |
+
value,
|
| 195 |
+
):
|
| 196 |
+
attrs = attr.split(".")
|
| 197 |
+
for name in attrs[:-1]:
|
| 198 |
+
obj = getattr(obj, name)
|
| 199 |
+
setattr(obj, attrs[-1], value)
|
| 200 |
+
return
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def print_tensor_list_size(
|
| 204 |
+
tensors,
|
| 205 |
+
):
|
| 206 |
+
total_size = 0
|
| 207 |
+
total_elements = 0
|
| 208 |
+
|
| 209 |
+
if isinstance(tensors, dict):
|
| 210 |
+
tensors = tensors.values()
|
| 211 |
+
|
| 212 |
+
for tensor in tensors:
|
| 213 |
+
total_size += tensor.nelement() * tensor.element_size()
|
| 214 |
+
total_elements += tensor.nelement()
|
| 215 |
+
|
| 216 |
+
total_size_MB = total_size / (1024**2)
|
| 217 |
+
total_elements_B = total_elements / 1e9
|
| 218 |
+
|
| 219 |
+
print(f"Total number of tensors: {len(tensors)}")
|
| 220 |
+
print(f"Total size of tensors: {total_size_MB:.2f} MB")
|
| 221 |
+
print(f"Total number of parameters: {total_elements_B:.3f} billion")
|
| 222 |
+
return
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
@torch.no_grad()
|
| 226 |
+
def batch_mixture(
|
| 227 |
+
a,
|
| 228 |
+
b=None,
|
| 229 |
+
probability_a=0.5,
|
| 230 |
+
mask_a=None,
|
| 231 |
+
):
|
| 232 |
+
batch_size = a.size(0)
|
| 233 |
+
|
| 234 |
+
if b is None:
|
| 235 |
+
b = torch.zeros_like(a)
|
| 236 |
+
|
| 237 |
+
if mask_a is None:
|
| 238 |
+
mask_a = torch.rand(batch_size) < probability_a
|
| 239 |
+
|
| 240 |
+
mask_a = mask_a.to(a.device)
|
| 241 |
+
mask_a = mask_a.reshape((batch_size,) + (1,) * (a.dim() - 1))
|
| 242 |
+
result = torch.where(mask_a, a, b)
|
| 243 |
+
return result
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
@torch.no_grad()
|
| 247 |
+
def zero_module(
|
| 248 |
+
module,
|
| 249 |
+
):
|
| 250 |
+
for p in module.parameters():
|
| 251 |
+
p.detach().zero_()
|
| 252 |
+
return module
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
@torch.no_grad()
|
| 256 |
+
def supress_lower_channels(
|
| 257 |
+
m,
|
| 258 |
+
k,
|
| 259 |
+
alpha=0.01,
|
| 260 |
+
):
|
| 261 |
+
data = m.weight.data.clone()
|
| 262 |
+
|
| 263 |
+
assert int(data.shape[1]) >= k
|
| 264 |
+
|
| 265 |
+
data[:, :k] = data[:, :k] * alpha
|
| 266 |
+
m.weight.data = data.contiguous().clone()
|
| 267 |
+
return m
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def freeze_module(
|
| 271 |
+
m,
|
| 272 |
+
):
|
| 273 |
+
if not hasattr(m, "_forward_inside_frozen_module"):
|
| 274 |
+
m._forward_inside_frozen_module = m.forward
|
| 275 |
+
m.requires_grad_(False)
|
| 276 |
+
m.forward = torch.no_grad()(m.forward)
|
| 277 |
+
return m
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def get_latest_safetensors(
|
| 281 |
+
folder_path,
|
| 282 |
+
):
|
| 283 |
+
safetensors_files = glob.glob(os.path.join(folder_path, "*.safetensors"))
|
| 284 |
+
|
| 285 |
+
if not safetensors_files:
|
| 286 |
+
raise ValueError("No file to resume!")
|
| 287 |
+
|
| 288 |
+
latest_file = max(safetensors_files, key=os.path.getmtime)
|
| 289 |
+
latest_file = os.path.abspath(os.path.realpath(latest_file))
|
| 290 |
+
return latest_file
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def generate_random_prompt_from_tags(
|
| 294 |
+
tags_str,
|
| 295 |
+
min_length=3,
|
| 296 |
+
max_length=32,
|
| 297 |
+
):
|
| 298 |
+
tags = tags_str.split(", ")
|
| 299 |
+
tags = random.sample(tags, k=min(random.randint(min_length, max_length), len(tags)))
|
| 300 |
+
prompt = ", ".join(tags)
|
| 301 |
+
return prompt
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def interpolate_numbers(
|
| 305 |
+
a,
|
| 306 |
+
b,
|
| 307 |
+
n,
|
| 308 |
+
round_to_int=False,
|
| 309 |
+
gamma=1.0,
|
| 310 |
+
):
|
| 311 |
+
numbers = a + (b - a) * (np.linspace(0, 1, n) ** gamma)
|
| 312 |
+
if round_to_int:
|
| 313 |
+
numbers = np.round(numbers).astype(int)
|
| 314 |
+
return numbers.tolist()
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def uniform_random_by_intervals(
|
| 318 |
+
inclusive,
|
| 319 |
+
exclusive,
|
| 320 |
+
n,
|
| 321 |
+
round_to_int=False,
|
| 322 |
+
):
|
| 323 |
+
edges = np.linspace(0, 1, n + 1)
|
| 324 |
+
points = np.random.uniform(edges[:-1], edges[1:])
|
| 325 |
+
numbers = inclusive + (exclusive - inclusive) * points
|
| 326 |
+
if round_to_int:
|
| 327 |
+
numbers = np.round(numbers).astype(int)
|
| 328 |
+
return numbers.tolist()
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
def soft_append_bcthw(
|
| 332 |
+
history,
|
| 333 |
+
current,
|
| 334 |
+
overlap=0,
|
| 335 |
+
):
|
| 336 |
+
if overlap <= 0:
|
| 337 |
+
return torch.cat([history, current], dim=2)
|
| 338 |
+
|
| 339 |
+
assert (
|
| 340 |
+
history.shape[2] >= overlap
|
| 341 |
+
), f"History length ({history.shape[2]}) must be >= overlap ({overlap})"
|
| 342 |
+
assert (
|
| 343 |
+
current.shape[2] >= overlap
|
| 344 |
+
), f"Current length ({current.shape[2]}) must be >= overlap ({overlap})"
|
| 345 |
+
|
| 346 |
+
weights = torch.linspace(1, 0, overlap, dtype=history.dtype, device=history.device).view(
|
| 347 |
+
1, 1, -1, 1, 1
|
| 348 |
+
)
|
| 349 |
+
blended = weights * history[:, :, -overlap:] + (1 - weights) * current[:, :, :overlap]
|
| 350 |
+
output = torch.cat([history[:, :, :-overlap], blended, current[:, :, overlap:]], dim=2)
|
| 351 |
+
|
| 352 |
+
return output.to(history)
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
def save_bcthw_as_mp4(
|
| 356 |
+
x,
|
| 357 |
+
output_filename,
|
| 358 |
+
fps=10,
|
| 359 |
+
crf=0,
|
| 360 |
+
):
|
| 361 |
+
b, c, t, h, w = x.shape
|
| 362 |
+
|
| 363 |
+
per_row = b
|
| 364 |
+
for p in [6, 5, 4, 3, 2]:
|
| 365 |
+
if b % p == 0:
|
| 366 |
+
per_row = p
|
| 367 |
+
break
|
| 368 |
+
|
| 369 |
+
os.makedirs(
|
| 370 |
+
os.path.dirname(os.path.abspath(os.path.realpath(output_filename))),
|
| 371 |
+
exist_ok=True,
|
| 372 |
+
)
|
| 373 |
+
x = torch.clamp(x.float(), -1.0, 1.0) * 127.5 + 127.5
|
| 374 |
+
x = x.detach().cpu().to(torch.uint8)
|
| 375 |
+
x = einops.rearrange(x, "(m n) c t h w -> t (m h) (n w) c", n=per_row)
|
| 376 |
+
torchvision.io.write_video(
|
| 377 |
+
output_filename,
|
| 378 |
+
x,
|
| 379 |
+
fps=fps,
|
| 380 |
+
video_codec="libx264",
|
| 381 |
+
options={"crf": str(int(crf))},
|
| 382 |
+
)
|
| 383 |
+
return x
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def save_bcthw_as_png(
|
| 387 |
+
x,
|
| 388 |
+
output_filename,
|
| 389 |
+
):
|
| 390 |
+
os.makedirs(
|
| 391 |
+
os.path.dirname(os.path.abspath(os.path.realpath(output_filename))),
|
| 392 |
+
exist_ok=True,
|
| 393 |
+
)
|
| 394 |
+
x = torch.clamp(x.float(), -1.0, 1.0) * 127.5 + 127.5
|
| 395 |
+
x = x.detach().cpu().to(torch.uint8)
|
| 396 |
+
x = einops.rearrange(x, "b c t h w -> c (b h) (t w)")
|
| 397 |
+
torchvision.io.write_png(x, output_filename)
|
| 398 |
+
return output_filename
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
def save_bchw_as_png(
|
| 402 |
+
x,
|
| 403 |
+
output_filename,
|
| 404 |
+
):
|
| 405 |
+
os.makedirs(
|
| 406 |
+
os.path.dirname(os.path.abspath(os.path.realpath(output_filename))),
|
| 407 |
+
exist_ok=True,
|
| 408 |
+
)
|
| 409 |
+
x = torch.clamp(x.float(), -1.0, 1.0) * 127.5 + 127.5
|
| 410 |
+
x = x.detach().cpu().to(torch.uint8)
|
| 411 |
+
x = einops.rearrange(x, "b c h w -> c h (b w)")
|
| 412 |
+
torchvision.io.write_png(x, output_filename)
|
| 413 |
+
return output_filename
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
def add_tensors_with_padding(
|
| 417 |
+
tensor1,
|
| 418 |
+
tensor2,
|
| 419 |
+
):
|
| 420 |
+
if tensor1.shape == tensor2.shape:
|
| 421 |
+
return tensor1 + tensor2
|
| 422 |
+
|
| 423 |
+
shape1 = tensor1.shape
|
| 424 |
+
shape2 = tensor2.shape
|
| 425 |
+
|
| 426 |
+
new_shape = tuple(max(s1, s2) for s1, s2 in zip(shape1, shape2))
|
| 427 |
+
|
| 428 |
+
padded_tensor1 = torch.zeros(new_shape)
|
| 429 |
+
padded_tensor2 = torch.zeros(new_shape)
|
| 430 |
+
|
| 431 |
+
padded_tensor1[tuple(slice(0, s) for s in shape1)] = tensor1
|
| 432 |
+
padded_tensor2[tuple(slice(0, s) for s in shape2)] = tensor2
|
| 433 |
+
|
| 434 |
+
result = padded_tensor1 + padded_tensor2
|
| 435 |
+
return result
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
def print_free_mem():
|
| 439 |
+
torch.cuda.empty_cache()
|
| 440 |
+
free_mem, total_mem = torch.cuda.mem_get_info(0)
|
| 441 |
+
free_mem_mb = free_mem / (1024**2)
|
| 442 |
+
total_mem_mb = total_mem / (1024**2)
|
| 443 |
+
print(f"Free memory: {free_mem_mb:.2f} MB")
|
| 444 |
+
print(f"Total memory: {total_mem_mb:.2f} MB")
|
| 445 |
+
return
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
def print_gpu_parameters(
|
| 449 |
+
device,
|
| 450 |
+
state_dict,
|
| 451 |
+
log_count=1,
|
| 452 |
+
):
|
| 453 |
+
summary = {"device": device, "keys_count": len(state_dict)}
|
| 454 |
+
|
| 455 |
+
logged_params = {}
|
| 456 |
+
for i, (key, tensor) in enumerate(state_dict.items()):
|
| 457 |
+
if i >= log_count:
|
| 458 |
+
break
|
| 459 |
+
logged_params[key] = tensor.flatten()[:3].tolist()
|
| 460 |
+
|
| 461 |
+
summary["params"] = logged_params
|
| 462 |
+
|
| 463 |
+
print(str(summary))
|
| 464 |
+
return
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
def visualize_txt_as_img(
|
| 468 |
+
width,
|
| 469 |
+
height,
|
| 470 |
+
text,
|
| 471 |
+
font_path="font/DejaVuSans.ttf",
|
| 472 |
+
size=18,
|
| 473 |
+
):
|
| 474 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 475 |
+
|
| 476 |
+
txt = Image.new("RGB", (width, height), color="white")
|
| 477 |
+
draw = ImageDraw.Draw(txt)
|
| 478 |
+
font = ImageFont.truetype(font_path, size=size)
|
| 479 |
+
|
| 480 |
+
if text == "":
|
| 481 |
+
return np.array(txt)
|
| 482 |
+
|
| 483 |
+
# Split text into lines that fit within the image width
|
| 484 |
+
lines = []
|
| 485 |
+
words = text.split()
|
| 486 |
+
current_line = words[0]
|
| 487 |
+
|
| 488 |
+
for word in words[1:]:
|
| 489 |
+
line_with_word = f"{current_line} {word}"
|
| 490 |
+
if draw.textbbox((0, 0), line_with_word, font=font)[2] <= width:
|
| 491 |
+
current_line = line_with_word
|
| 492 |
+
else:
|
| 493 |
+
lines.append(current_line)
|
| 494 |
+
current_line = word
|
| 495 |
+
|
| 496 |
+
lines.append(current_line)
|
| 497 |
+
|
| 498 |
+
# Draw the text line by line
|
| 499 |
+
y = 0
|
| 500 |
+
line_height = draw.textbbox((0, 0), "A", font=font)[3]
|
| 501 |
+
|
| 502 |
+
for line in lines:
|
| 503 |
+
if y + line_height > height:
|
| 504 |
+
break # stop drawing if the next line will be outside the image
|
| 505 |
+
draw.text((0, y), line, fill="black", font=font)
|
| 506 |
+
y += line_height
|
| 507 |
+
|
| 508 |
+
return np.array(txt)
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
def blue_mark(
|
| 512 |
+
x,
|
| 513 |
+
):
|
| 514 |
+
x = x.copy()
|
| 515 |
+
c = x[:, :, 2]
|
| 516 |
+
b = cv2.blur(c, (9, 9))
|
| 517 |
+
x[:, :, 2] = ((c - b) * 16.0 + b).clip(-1, 1)
|
| 518 |
+
return x
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def green_mark(
|
| 522 |
+
x,
|
| 523 |
+
):
|
| 524 |
+
x = x.copy()
|
| 525 |
+
x[:, :, 2] = -1
|
| 526 |
+
x[:, :, 0] = -1
|
| 527 |
+
return x
|
| 528 |
+
|
| 529 |
+
|
| 530 |
+
def frame_mark(
|
| 531 |
+
x,
|
| 532 |
+
):
|
| 533 |
+
x = x.copy()
|
| 534 |
+
x[:64] = -1
|
| 535 |
+
x[-64:] = -1
|
| 536 |
+
x[:, :8] = 1
|
| 537 |
+
x[:, -8:] = 1
|
| 538 |
+
return x
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
@torch.inference_mode()
|
| 542 |
+
def pytorch2numpy(
|
| 543 |
+
imgs,
|
| 544 |
+
):
|
| 545 |
+
results = []
|
| 546 |
+
for x in imgs:
|
| 547 |
+
y = x.movedim(0, -1)
|
| 548 |
+
y = y * 127.5 + 127.5
|
| 549 |
+
y = y.detach().float().cpu().numpy().clip(0, 255).astype(np.uint8)
|
| 550 |
+
results.append(y)
|
| 551 |
+
return results
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
@torch.inference_mode()
|
| 555 |
+
def numpy2pytorch(
|
| 556 |
+
imgs,
|
| 557 |
+
):
|
| 558 |
+
h = torch.from_numpy(np.stack(imgs, axis=0)).float() / 127.5 - 1.0
|
| 559 |
+
h = h.movedim(-1, 1)
|
| 560 |
+
return h
|
| 561 |
+
|
| 562 |
+
|
| 563 |
+
@torch.no_grad()
|
| 564 |
+
def duplicate_prefix_to_suffix(
|
| 565 |
+
x,
|
| 566 |
+
count,
|
| 567 |
+
zero_out=False,
|
| 568 |
+
):
|
| 569 |
+
if zero_out:
|
| 570 |
+
return torch.cat([x, torch.zeros_like(x[:count])], dim=0)
|
| 571 |
+
else:
|
| 572 |
+
return torch.cat([x, x[:count]], dim=0)
|
| 573 |
+
|
| 574 |
+
|
| 575 |
+
def weighted_mse(a, b, weight):
|
| 576 |
+
return torch.mean(weight.float() * (a.float() - b.float()) ** 2)
|
| 577 |
+
|
| 578 |
+
|
| 579 |
+
def clamped_linear_interpolation(
|
| 580 |
+
x,
|
| 581 |
+
x_min,
|
| 582 |
+
y_min,
|
| 583 |
+
x_max,
|
| 584 |
+
y_max,
|
| 585 |
+
sigma=1.0,
|
| 586 |
+
):
|
| 587 |
+
x = (x - x_min) / (x_max - x_min)
|
| 588 |
+
x = max(0.0, min(x, 1.0))
|
| 589 |
+
x = x**sigma
|
| 590 |
+
return y_min + x * (y_max - y_min)
|
| 591 |
+
|
| 592 |
+
|
| 593 |
+
def expand_to_dims(x, target_dims):
|
| 594 |
+
return x.view(*x.shape, *([1] * max(0, target_dims - x.dim())))
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
def repeat_to_batch_size(
|
| 598 |
+
tensor: torch.Tensor,
|
| 599 |
+
batch_size: int,
|
| 600 |
+
):
|
| 601 |
+
if tensor is None:
|
| 602 |
+
return None
|
| 603 |
+
|
| 604 |
+
first_dim = tensor.shape[0]
|
| 605 |
+
|
| 606 |
+
if first_dim == batch_size:
|
| 607 |
+
return tensor
|
| 608 |
+
|
| 609 |
+
if batch_size % first_dim != 0:
|
| 610 |
+
raise ValueError(
|
| 611 |
+
f"Cannot evenly repeat first dim {first_dim} to match batch_size {batch_size}."
|
| 612 |
+
)
|
| 613 |
+
|
| 614 |
+
repeat_times = batch_size // first_dim
|
| 615 |
+
|
| 616 |
+
return tensor.repeat(repeat_times, *[1] * (tensor.dim() - 1))
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
def dim5(
|
| 620 |
+
x,
|
| 621 |
+
):
|
| 622 |
+
return expand_to_dims(x, 5)
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
def dim4(
|
| 626 |
+
x,
|
| 627 |
+
):
|
| 628 |
+
return expand_to_dims(x, 4)
|
| 629 |
+
|
| 630 |
+
|
| 631 |
+
def dim3(
|
| 632 |
+
x,
|
| 633 |
+
):
|
| 634 |
+
return expand_to_dims(x, 3)
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
def crop_or_pad_yield_mask(
|
| 638 |
+
x,
|
| 639 |
+
length,
|
| 640 |
+
):
|
| 641 |
+
B, F, C = x.shape
|
| 642 |
+
device = x.device
|
| 643 |
+
dtype = x.dtype
|
| 644 |
+
|
| 645 |
+
if F < length:
|
| 646 |
+
y = torch.zeros((B, length, C), dtype=dtype, device=device)
|
| 647 |
+
mask = torch.zeros((B, length), dtype=torch.bool, device=device)
|
| 648 |
+
y[:, :F, :] = x
|
| 649 |
+
mask[:, :F] = True
|
| 650 |
+
return y, mask
|
| 651 |
+
|
| 652 |
+
return x[:, :length, :], torch.ones((B, length), dtype=torch.bool, device=device)
|
| 653 |
+
|
| 654 |
+
|
| 655 |
+
def extend_dim(
|
| 656 |
+
x,
|
| 657 |
+
dim,
|
| 658 |
+
minimal_length,
|
| 659 |
+
zero_pad=False,
|
| 660 |
+
):
|
| 661 |
+
original_length = int(x.shape[dim])
|
| 662 |
+
|
| 663 |
+
if original_length >= minimal_length:
|
| 664 |
+
return x
|
| 665 |
+
|
| 666 |
+
if zero_pad:
|
| 667 |
+
padding_shape = list(x.shape)
|
| 668 |
+
padding_shape[dim] = minimal_length - original_length
|
| 669 |
+
padding = torch.zeros(padding_shape, dtype=x.dtype, device=x.device)
|
| 670 |
+
else:
|
| 671 |
+
idx = (slice(None),) * dim + (slice(-1, None),) + (slice(None),) * (len(x.shape) - dim - 1)
|
| 672 |
+
last_element = x[idx]
|
| 673 |
+
padding = last_element.repeat_interleave(minimal_length - original_length, dim=dim)
|
| 674 |
+
|
| 675 |
+
return torch.cat([x, padding], dim=dim)
|
| 676 |
+
|
| 677 |
+
|
| 678 |
+
def lazy_positional_encoding(
|
| 679 |
+
t,
|
| 680 |
+
repeats=None,
|
| 681 |
+
):
|
| 682 |
+
if not isinstance(t, list):
|
| 683 |
+
t = [t]
|
| 684 |
+
|
| 685 |
+
from diffusers.models.embeddings import get_timestep_embedding
|
| 686 |
+
|
| 687 |
+
te = torch.tensor(t)
|
| 688 |
+
te = get_timestep_embedding(
|
| 689 |
+
timesteps=te,
|
| 690 |
+
embedding_dim=256,
|
| 691 |
+
flip_sin_to_cos=True,
|
| 692 |
+
downscale_freq_shift=0.0,
|
| 693 |
+
scale=1.0,
|
| 694 |
+
)
|
| 695 |
+
|
| 696 |
+
if repeats is None:
|
| 697 |
+
return te
|
| 698 |
+
|
| 699 |
+
te = te[:, None, :].expand(-1, repeats, -1)
|
| 700 |
+
|
| 701 |
+
return te
|
| 702 |
+
|
| 703 |
+
|
| 704 |
+
def state_dict_offset_merge(
|
| 705 |
+
A,
|
| 706 |
+
B,
|
| 707 |
+
C=None,
|
| 708 |
+
):
|
| 709 |
+
result = {}
|
| 710 |
+
keys = A.keys()
|
| 711 |
+
|
| 712 |
+
for key in keys:
|
| 713 |
+
A_value = A[key]
|
| 714 |
+
B_value = B[key].to(A_value)
|
| 715 |
+
|
| 716 |
+
if C is None:
|
| 717 |
+
result[key] = A_value + B_value
|
| 718 |
+
else:
|
| 719 |
+
C_value = C[key].to(A_value)
|
| 720 |
+
result[key] = A_value + B_value - C_value
|
| 721 |
+
|
| 722 |
+
return result
|
| 723 |
+
|
| 724 |
+
|
| 725 |
+
def state_dict_weighted_merge(
|
| 726 |
+
state_dicts,
|
| 727 |
+
weights,
|
| 728 |
+
):
|
| 729 |
+
if len(state_dicts) != len(weights):
|
| 730 |
+
raise ValueError("Number of state dictionaries must match number of weights")
|
| 731 |
+
|
| 732 |
+
if not state_dicts:
|
| 733 |
+
return {}
|
| 734 |
+
|
| 735 |
+
total_weight = sum(weights)
|
| 736 |
+
|
| 737 |
+
if total_weight == 0:
|
| 738 |
+
raise ValueError("Sum of weights cannot be zero")
|
| 739 |
+
|
| 740 |
+
normalized_weights = [w / total_weight for w in weights]
|
| 741 |
+
|
| 742 |
+
keys = state_dicts[0].keys()
|
| 743 |
+
result = {}
|
| 744 |
+
|
| 745 |
+
for key in keys:
|
| 746 |
+
result[key] = state_dicts[0][key] * normalized_weights[0]
|
| 747 |
+
|
| 748 |
+
for i in range(1, len(state_dicts)):
|
| 749 |
+
state_dict_value = state_dicts[i][key].to(result[key])
|
| 750 |
+
result[key] += state_dict_value * normalized_weights[i]
|
| 751 |
+
|
| 752 |
+
return result
|
| 753 |
+
|
| 754 |
+
|
| 755 |
+
def group_files_by_folder(
|
| 756 |
+
all_files,
|
| 757 |
+
):
|
| 758 |
+
grouped_files = {}
|
| 759 |
+
|
| 760 |
+
for file in all_files:
|
| 761 |
+
folder_name = os.path.basename(os.path.dirname(file))
|
| 762 |
+
if folder_name not in grouped_files:
|
| 763 |
+
grouped_files[folder_name] = []
|
| 764 |
+
grouped_files[folder_name].append(file)
|
| 765 |
+
|
| 766 |
+
list_of_lists = list(grouped_files.values())
|
| 767 |
+
return list_of_lists
|
| 768 |
+
|
| 769 |
+
|
| 770 |
+
def generate_timestamp():
|
| 771 |
+
now = datetime.datetime.now()
|
| 772 |
+
timestamp = now.strftime("%y%m%d_%H%M%S")
|
| 773 |
+
milliseconds = f"{int(now.microsecond / 1000):03d}"
|
| 774 |
+
random_number = random.randint(0, 9999)
|
| 775 |
+
return f"{timestamp}_{milliseconds}_{random_number}"
|
| 776 |
+
|
| 777 |
+
|
| 778 |
+
def write_PIL_image_with_png_info(
|
| 779 |
+
image,
|
| 780 |
+
metadata,
|
| 781 |
+
path,
|
| 782 |
+
):
|
| 783 |
+
from PIL.PngImagePlugin import PngInfo
|
| 784 |
+
|
| 785 |
+
png_info = PngInfo()
|
| 786 |
+
for key, value in metadata.items():
|
| 787 |
+
png_info.add_text(key, value)
|
| 788 |
+
|
| 789 |
+
image.save(path, "PNG", pnginfo=png_info)
|
| 790 |
+
return image
|
| 791 |
+
|
| 792 |
+
|
| 793 |
+
def torch_safe_save(
|
| 794 |
+
content,
|
| 795 |
+
path,
|
| 796 |
+
):
|
| 797 |
+
torch.save(content, path + "_tmp")
|
| 798 |
+
os.replace(path + "_tmp", path)
|
| 799 |
+
return path
|
| 800 |
+
|
| 801 |
+
|
| 802 |
+
def move_optimizer_to_device(
|
| 803 |
+
optimizer,
|
| 804 |
+
device,
|
| 805 |
+
):
|
| 806 |
+
for state in optimizer.state.values():
|
| 807 |
+
for k, v in state.items():
|
| 808 |
+
if isinstance(v, torch.Tensor):
|
| 809 |
+
state[k] = v.to(device)
|
demo_utils/vae.py
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List
|
| 2 |
+
from einops import rearrange
|
| 3 |
+
import tensorrt as trt
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
|
| 7 |
+
from demo_utils.constant import ALL_INPUTS_NAMES, ZERO_VAE_CACHE
|
| 8 |
+
from wan.modules.vae import AttentionBlock, CausalConv3d, RMS_norm, Upsample
|
| 9 |
+
|
| 10 |
+
CACHE_T = 2
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ResidualBlock(nn.Module):
|
| 14 |
+
|
| 15 |
+
def __init__(
|
| 16 |
+
self,
|
| 17 |
+
in_dim,
|
| 18 |
+
out_dim,
|
| 19 |
+
dropout=0.0,
|
| 20 |
+
):
|
| 21 |
+
super().__init__()
|
| 22 |
+
self.in_dim = in_dim
|
| 23 |
+
self.out_dim = out_dim
|
| 24 |
+
|
| 25 |
+
# layers
|
| 26 |
+
self.residual = nn.Sequential(
|
| 27 |
+
RMS_norm(in_dim, images=False),
|
| 28 |
+
nn.SiLU(),
|
| 29 |
+
CausalConv3d(in_dim, out_dim, 3, padding=1),
|
| 30 |
+
RMS_norm(out_dim, images=False),
|
| 31 |
+
nn.SiLU(),
|
| 32 |
+
nn.Dropout(dropout),
|
| 33 |
+
CausalConv3d(out_dim, out_dim, 3, padding=1),
|
| 34 |
+
)
|
| 35 |
+
self.shortcut = CausalConv3d(in_dim, out_dim, 1) if in_dim != out_dim else nn.Identity()
|
| 36 |
+
|
| 37 |
+
def forward(
|
| 38 |
+
self,
|
| 39 |
+
x,
|
| 40 |
+
feat_cache_1,
|
| 41 |
+
feat_cache_2,
|
| 42 |
+
):
|
| 43 |
+
h = self.shortcut(x)
|
| 44 |
+
feat_cache = feat_cache_1
|
| 45 |
+
out_feat_cache = []
|
| 46 |
+
for layer in self.residual:
|
| 47 |
+
if isinstance(layer, CausalConv3d):
|
| 48 |
+
cache_x = x[:, :, -CACHE_T:, :, :].clone()
|
| 49 |
+
if cache_x.shape[2] < 2 and feat_cache is not None:
|
| 50 |
+
# cache last frame of last two chunk
|
| 51 |
+
cache_x = torch.cat(
|
| 52 |
+
[
|
| 53 |
+
feat_cache[:, :, -1, :, :].unsqueeze(2).to(cache_x.device),
|
| 54 |
+
cache_x,
|
| 55 |
+
],
|
| 56 |
+
dim=2,
|
| 57 |
+
)
|
| 58 |
+
x = layer(x, feat_cache)
|
| 59 |
+
out_feat_cache.append(cache_x)
|
| 60 |
+
feat_cache = feat_cache_2
|
| 61 |
+
else:
|
| 62 |
+
x = layer(x)
|
| 63 |
+
return x + h, *out_feat_cache
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class Resample(nn.Module):
|
| 67 |
+
|
| 68 |
+
def __init__(
|
| 69 |
+
self,
|
| 70 |
+
dim,
|
| 71 |
+
mode,
|
| 72 |
+
):
|
| 73 |
+
assert mode in ("none", "upsample2d", "upsample3d")
|
| 74 |
+
super().__init__()
|
| 75 |
+
self.dim = dim
|
| 76 |
+
self.mode = mode
|
| 77 |
+
|
| 78 |
+
# layers
|
| 79 |
+
if mode == "upsample2d":
|
| 80 |
+
self.resample = nn.Sequential(
|
| 81 |
+
Upsample(scale_factor=(2.0, 2.0), mode="nearest"),
|
| 82 |
+
nn.Conv2d(dim, dim // 2, 3, padding=1),
|
| 83 |
+
)
|
| 84 |
+
elif mode == "upsample3d":
|
| 85 |
+
self.resample = nn.Sequential(
|
| 86 |
+
Upsample(scale_factor=(2.0, 2.0), mode="nearest"),
|
| 87 |
+
nn.Conv2d(dim, dim // 2, 3, padding=1),
|
| 88 |
+
)
|
| 89 |
+
self.time_conv = CausalConv3d(dim, dim * 2, (3, 1, 1), padding=(1, 0, 0))
|
| 90 |
+
else:
|
| 91 |
+
self.resample = nn.Identity()
|
| 92 |
+
|
| 93 |
+
def forward(
|
| 94 |
+
self,
|
| 95 |
+
x,
|
| 96 |
+
is_first_frame,
|
| 97 |
+
feat_cache,
|
| 98 |
+
):
|
| 99 |
+
if self.mode == "upsample3d":
|
| 100 |
+
b, c, t, h, w = x.size()
|
| 101 |
+
# x, out_feat_cache = torch.cond(
|
| 102 |
+
# is_first_frame,
|
| 103 |
+
# lambda: (torch.cat([torch.zeros_like(x), x], dim=2), feat_cache.clone()),
|
| 104 |
+
# lambda: self.temporal_conv(x, feat_cache),
|
| 105 |
+
# )
|
| 106 |
+
# x, out_feat_cache = torch.cond(
|
| 107 |
+
# is_first_frame,
|
| 108 |
+
# lambda: (torch.cat([torch.zeros_like(x), x], dim=2), feat_cache.clone()),
|
| 109 |
+
# lambda: self.temporal_conv(x, feat_cache),
|
| 110 |
+
# )
|
| 111 |
+
x, out_feat_cache = self.temporal_conv(x, is_first_frame, feat_cache)
|
| 112 |
+
out_feat_cache = torch.cond(
|
| 113 |
+
is_first_frame,
|
| 114 |
+
lambda: feat_cache.clone().contiguous(),
|
| 115 |
+
lambda: out_feat_cache.clone().contiguous(),
|
| 116 |
+
)
|
| 117 |
+
# if is_first_frame:
|
| 118 |
+
# x = torch.cat([torch.zeros_like(x), x], dim=2)
|
| 119 |
+
# out_feat_cache = feat_cache.clone()
|
| 120 |
+
# else:
|
| 121 |
+
# x, out_feat_cache = self.temporal_conv(x, feat_cache)
|
| 122 |
+
else:
|
| 123 |
+
out_feat_cache = None
|
| 124 |
+
t = x.shape[2]
|
| 125 |
+
x = rearrange(x, "b c t h w -> (b t) c h w")
|
| 126 |
+
x = self.resample(x)
|
| 127 |
+
x = rearrange(x, "(b t) c h w -> b c t h w", t=t)
|
| 128 |
+
return x, out_feat_cache
|
| 129 |
+
|
| 130 |
+
def temporal_conv(
|
| 131 |
+
self,
|
| 132 |
+
x,
|
| 133 |
+
is_first_frame,
|
| 134 |
+
feat_cache,
|
| 135 |
+
):
|
| 136 |
+
b, c, t, h, w = x.size()
|
| 137 |
+
cache_x = x[:, :, -CACHE_T:, :, :].clone()
|
| 138 |
+
if cache_x.shape[2] < 2 and feat_cache is not None:
|
| 139 |
+
cache_x = torch.cat([torch.zeros_like(cache_x), cache_x], dim=2)
|
| 140 |
+
x = torch.cond(
|
| 141 |
+
is_first_frame,
|
| 142 |
+
lambda: torch.cat([torch.zeros_like(x), x], dim=1).contiguous(),
|
| 143 |
+
lambda: self.time_conv(x, feat_cache).contiguous(),
|
| 144 |
+
)
|
| 145 |
+
# x = self.time_conv(x, feat_cache)
|
| 146 |
+
out_feat_cache = cache_x
|
| 147 |
+
|
| 148 |
+
x = x.reshape(b, 2, c, t, h, w)
|
| 149 |
+
x = torch.stack((x[:, 0, :, :, :, :], x[:, 1, :, :, :, :]), 3)
|
| 150 |
+
x = x.reshape(b, c, t * 2, h, w)
|
| 151 |
+
return x.contiguous(), out_feat_cache.contiguous()
|
| 152 |
+
|
| 153 |
+
def init_weight(
|
| 154 |
+
self,
|
| 155 |
+
conv,
|
| 156 |
+
):
|
| 157 |
+
conv_weight = conv.weight
|
| 158 |
+
nn.init.zeros_(conv_weight)
|
| 159 |
+
c1, c2, t, h, w = conv_weight.size()
|
| 160 |
+
one_matrix = torch.eye(c1, c2)
|
| 161 |
+
init_matrix = one_matrix
|
| 162 |
+
nn.init.zeros_(conv_weight)
|
| 163 |
+
# conv_weight.data[:,:,-1,1,1] = init_matrix * 0.5
|
| 164 |
+
conv_weight.data[:, :, 1, 0, 0] = init_matrix # * 0.5
|
| 165 |
+
conv.weight.data.copy_(conv_weight)
|
| 166 |
+
nn.init.zeros_(conv.bias.data)
|
| 167 |
+
|
| 168 |
+
def init_weight2(
|
| 169 |
+
self,
|
| 170 |
+
conv,
|
| 171 |
+
):
|
| 172 |
+
conv_weight = conv.weight.data
|
| 173 |
+
nn.init.zeros_(conv_weight)
|
| 174 |
+
c1, c2, t, h, w = conv_weight.size()
|
| 175 |
+
init_matrix = torch.eye(c1 // 2, c2)
|
| 176 |
+
# init_matrix = repeat(init_matrix, 'o ... -> (o 2) ...').permute(1,0,2).contiguous().reshape(c1,c2)
|
| 177 |
+
conv_weight[: c1 // 2, :, -1, 0, 0] = init_matrix
|
| 178 |
+
conv_weight[c1 // 2 :, :, -1, 0, 0] = init_matrix
|
| 179 |
+
conv.weight.data.copy_(conv_weight)
|
| 180 |
+
nn.init.zeros_(conv.bias.data)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
class VAEDecoderWrapperSingle(nn.Module):
|
| 184 |
+
def __init__(
|
| 185 |
+
self,
|
| 186 |
+
):
|
| 187 |
+
super().__init__()
|
| 188 |
+
self.decoder = VAEDecoder3d()
|
| 189 |
+
mean = [
|
| 190 |
+
-0.7571,
|
| 191 |
+
-0.7089,
|
| 192 |
+
-0.9113,
|
| 193 |
+
0.1075,
|
| 194 |
+
-0.1745,
|
| 195 |
+
0.9653,
|
| 196 |
+
-0.1517,
|
| 197 |
+
1.5508,
|
| 198 |
+
0.4134,
|
| 199 |
+
-0.0715,
|
| 200 |
+
0.5517,
|
| 201 |
+
-0.3632,
|
| 202 |
+
-0.1922,
|
| 203 |
+
-0.9497,
|
| 204 |
+
0.2503,
|
| 205 |
+
-0.2921,
|
| 206 |
+
]
|
| 207 |
+
std = [
|
| 208 |
+
2.8184,
|
| 209 |
+
1.4541,
|
| 210 |
+
2.3275,
|
| 211 |
+
2.6558,
|
| 212 |
+
1.2196,
|
| 213 |
+
1.7708,
|
| 214 |
+
2.6052,
|
| 215 |
+
2.0743,
|
| 216 |
+
3.2687,
|
| 217 |
+
2.1526,
|
| 218 |
+
2.8652,
|
| 219 |
+
1.5579,
|
| 220 |
+
1.6382,
|
| 221 |
+
1.1253,
|
| 222 |
+
2.8251,
|
| 223 |
+
1.9160,
|
| 224 |
+
]
|
| 225 |
+
self.mean = torch.tensor(mean, dtype=torch.float32)
|
| 226 |
+
self.std = torch.tensor(std, dtype=torch.float32)
|
| 227 |
+
self.z_dim = 16
|
| 228 |
+
self.conv2 = CausalConv3d(self.z_dim, self.z_dim, 1)
|
| 229 |
+
|
| 230 |
+
def forward(
|
| 231 |
+
self,
|
| 232 |
+
z: torch.Tensor,
|
| 233 |
+
is_first_frame: torch.Tensor,
|
| 234 |
+
*feat_cache: List[torch.Tensor],
|
| 235 |
+
):
|
| 236 |
+
# from [batch_size, num_frames, num_channels, height, width]
|
| 237 |
+
# to [batch_size, num_channels, num_frames, height, width]
|
| 238 |
+
z = z.permute(0, 2, 1, 3, 4)
|
| 239 |
+
assert z.shape[2] == 1
|
| 240 |
+
feat_cache = list(feat_cache)
|
| 241 |
+
is_first_frame = is_first_frame.bool()
|
| 242 |
+
|
| 243 |
+
device, dtype = z.device, z.dtype
|
| 244 |
+
scale = [
|
| 245 |
+
self.mean.to(device=device, dtype=dtype),
|
| 246 |
+
1.0 / self.std.to(device=device, dtype=dtype),
|
| 247 |
+
]
|
| 248 |
+
|
| 249 |
+
if isinstance(scale[0], torch.Tensor):
|
| 250 |
+
z = z / scale[1].view(1, self.z_dim, 1, 1, 1) + scale[0].view(1, self.z_dim, 1, 1, 1)
|
| 251 |
+
else:
|
| 252 |
+
z = z / scale[1] + scale[0]
|
| 253 |
+
x = self.conv2(z)
|
| 254 |
+
out, feat_cache = self.decoder(x, is_first_frame, feat_cache=feat_cache)
|
| 255 |
+
out = out.clamp_(-1, 1)
|
| 256 |
+
# from [batch_size, num_channels, num_frames, height, width]
|
| 257 |
+
# to [batch_size, num_frames, num_channels, height, width]
|
| 258 |
+
out = out.permute(0, 2, 1, 3, 4)
|
| 259 |
+
return out, feat_cache
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
class VAEDecoder3d(nn.Module):
|
| 263 |
+
def __init__(
|
| 264 |
+
self,
|
| 265 |
+
dim=96,
|
| 266 |
+
z_dim=16,
|
| 267 |
+
dim_mult=[1, 2, 4, 4],
|
| 268 |
+
num_res_blocks=2,
|
| 269 |
+
attn_scales=[],
|
| 270 |
+
temperal_upsample=[True, True, False],
|
| 271 |
+
dropout=0.0,
|
| 272 |
+
):
|
| 273 |
+
super().__init__()
|
| 274 |
+
self.dim = dim
|
| 275 |
+
self.z_dim = z_dim
|
| 276 |
+
self.dim_mult = dim_mult
|
| 277 |
+
self.num_res_blocks = num_res_blocks
|
| 278 |
+
self.attn_scales = attn_scales
|
| 279 |
+
self.temperal_upsample = temperal_upsample
|
| 280 |
+
self.cache_t = 2
|
| 281 |
+
self.decoder_conv_num = 32
|
| 282 |
+
|
| 283 |
+
# dimensions
|
| 284 |
+
dims = [dim * u for u in [dim_mult[-1]] + dim_mult[::-1]]
|
| 285 |
+
scale = 1.0 / 2 ** (len(dim_mult) - 2)
|
| 286 |
+
|
| 287 |
+
# init block
|
| 288 |
+
self.conv1 = CausalConv3d(z_dim, dims[0], 3, padding=1)
|
| 289 |
+
|
| 290 |
+
# middle blocks
|
| 291 |
+
self.middle = nn.Sequential(
|
| 292 |
+
ResidualBlock(dims[0], dims[0], dropout),
|
| 293 |
+
AttentionBlock(dims[0]),
|
| 294 |
+
ResidualBlock(dims[0], dims[0], dropout),
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
# upsample blocks
|
| 298 |
+
upsamples = []
|
| 299 |
+
for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])):
|
| 300 |
+
# residual (+attention) blocks
|
| 301 |
+
if i == 1 or i == 2 or i == 3:
|
| 302 |
+
in_dim = in_dim // 2
|
| 303 |
+
for _ in range(num_res_blocks + 1):
|
| 304 |
+
upsamples.append(ResidualBlock(in_dim, out_dim, dropout))
|
| 305 |
+
if scale in attn_scales:
|
| 306 |
+
upsamples.append(AttentionBlock(out_dim))
|
| 307 |
+
in_dim = out_dim
|
| 308 |
+
|
| 309 |
+
# upsample block
|
| 310 |
+
if i != len(dim_mult) - 1:
|
| 311 |
+
mode = "upsample3d" if temperal_upsample[i] else "upsample2d"
|
| 312 |
+
upsamples.append(Resample(out_dim, mode=mode))
|
| 313 |
+
scale *= 2.0
|
| 314 |
+
self.upsamples = nn.Sequential(*upsamples)
|
| 315 |
+
|
| 316 |
+
# output blocks
|
| 317 |
+
self.head = nn.Sequential(
|
| 318 |
+
RMS_norm(out_dim, images=False),
|
| 319 |
+
nn.SiLU(),
|
| 320 |
+
CausalConv3d(out_dim, 3, 3, padding=1),
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
def forward(
|
| 324 |
+
self,
|
| 325 |
+
x: torch.Tensor,
|
| 326 |
+
is_first_frame: torch.Tensor,
|
| 327 |
+
feat_cache: List[torch.Tensor],
|
| 328 |
+
):
|
| 329 |
+
idx = 0
|
| 330 |
+
out_feat_cache = []
|
| 331 |
+
|
| 332 |
+
# conv1
|
| 333 |
+
cache_x = x[:, :, -self.cache_t :, :, :].clone()
|
| 334 |
+
if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
|
| 335 |
+
# cache last frame of last two chunk
|
| 336 |
+
cache_x = torch.cat(
|
| 337 |
+
[
|
| 338 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device),
|
| 339 |
+
cache_x,
|
| 340 |
+
],
|
| 341 |
+
dim=2,
|
| 342 |
+
)
|
| 343 |
+
x = self.conv1(x, feat_cache[idx])
|
| 344 |
+
out_feat_cache.append(cache_x)
|
| 345 |
+
idx += 1
|
| 346 |
+
|
| 347 |
+
# middle
|
| 348 |
+
for layer in self.middle:
|
| 349 |
+
if isinstance(layer, ResidualBlock) and feat_cache is not None:
|
| 350 |
+
x, out_feat_cache_1, out_feat_cache_2 = layer(
|
| 351 |
+
x, feat_cache[idx], feat_cache[idx + 1]
|
| 352 |
+
)
|
| 353 |
+
idx += 2
|
| 354 |
+
out_feat_cache.append(out_feat_cache_1)
|
| 355 |
+
out_feat_cache.append(out_feat_cache_2)
|
| 356 |
+
else:
|
| 357 |
+
x = layer(x)
|
| 358 |
+
|
| 359 |
+
# upsamples
|
| 360 |
+
for layer in self.upsamples:
|
| 361 |
+
if isinstance(layer, Resample):
|
| 362 |
+
x, cache_x = layer(x, is_first_frame, feat_cache[idx])
|
| 363 |
+
if cache_x is not None:
|
| 364 |
+
out_feat_cache.append(cache_x)
|
| 365 |
+
idx += 1
|
| 366 |
+
else:
|
| 367 |
+
x, out_feat_cache_1, out_feat_cache_2 = layer(
|
| 368 |
+
x, feat_cache[idx], feat_cache[idx + 1]
|
| 369 |
+
)
|
| 370 |
+
idx += 2
|
| 371 |
+
out_feat_cache.append(out_feat_cache_1)
|
| 372 |
+
out_feat_cache.append(out_feat_cache_2)
|
| 373 |
+
|
| 374 |
+
# head
|
| 375 |
+
for layer in self.head:
|
| 376 |
+
if isinstance(layer, CausalConv3d) and feat_cache is not None:
|
| 377 |
+
cache_x = x[:, :, -self.cache_t :, :, :].clone()
|
| 378 |
+
if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
|
| 379 |
+
# cache last frame of last two chunk
|
| 380 |
+
cache_x = torch.cat(
|
| 381 |
+
[
|
| 382 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device),
|
| 383 |
+
cache_x,
|
| 384 |
+
],
|
| 385 |
+
dim=2,
|
| 386 |
+
)
|
| 387 |
+
x = layer(x, feat_cache[idx])
|
| 388 |
+
out_feat_cache.append(cache_x)
|
| 389 |
+
idx += 1
|
| 390 |
+
else:
|
| 391 |
+
x = layer(x)
|
| 392 |
+
return x, out_feat_cache
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
class VAETRTWrapper:
|
| 396 |
+
def __init__(
|
| 397 |
+
self,
|
| 398 |
+
):
|
| 399 |
+
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
|
| 400 |
+
with open("checkpoints/vae_decoder_int8.trt", "rb") as f, trt.Runtime(TRT_LOGGER) as rt:
|
| 401 |
+
self.engine: trt.ICudaEngine = rt.deserialize_cuda_engine(f.read())
|
| 402 |
+
|
| 403 |
+
self.context: trt.IExecutionContext = self.engine.create_execution_context()
|
| 404 |
+
self.stream = torch.cuda.current_stream().cuda_stream
|
| 405 |
+
|
| 406 |
+
# ──────────────────────────────
|
| 407 |
+
# 2️⃣ Feed the engine with tensors
|
| 408 |
+
# (name-based API in TRT ≥10)
|
| 409 |
+
# ──────────────────────────────
|
| 410 |
+
self.dtype_map = {
|
| 411 |
+
trt.float32: torch.float32,
|
| 412 |
+
trt.float16: torch.float16,
|
| 413 |
+
trt.int8: torch.int8,
|
| 414 |
+
trt.int32: torch.int32,
|
| 415 |
+
}
|
| 416 |
+
test_input = torch.zeros(1, 16, 1, 60, 104).cuda().half()
|
| 417 |
+
is_first_frame = torch.tensor(1.0).cuda().half()
|
| 418 |
+
test_cache_inputs = [c.cuda().half() for c in ZERO_VAE_CACHE]
|
| 419 |
+
test_inputs = [test_input, is_first_frame] + test_cache_inputs
|
| 420 |
+
|
| 421 |
+
# keep references so buffers stay alive
|
| 422 |
+
self.device_buffers, self.outputs = {}, []
|
| 423 |
+
|
| 424 |
+
# ---- inputs ----
|
| 425 |
+
for i, name in enumerate(ALL_INPUTS_NAMES):
|
| 426 |
+
tensor, scale = test_inputs[i], 1 / 127
|
| 427 |
+
tensor = self.quantize_if_needed(tensor, self.engine.get_tensor_dtype(name), scale)
|
| 428 |
+
|
| 429 |
+
# dynamic shapes
|
| 430 |
+
if -1 in self.engine.get_tensor_shape(name):
|
| 431 |
+
# new API :contentReference[oaicite:0]{index=0}
|
| 432 |
+
self.context.set_input_shape(name, tuple(tensor.shape))
|
| 433 |
+
|
| 434 |
+
# replaces bindings[] :contentReference[oaicite:1]{index=1}
|
| 435 |
+
self.context.set_tensor_address(name, int(tensor.data_ptr()))
|
| 436 |
+
self.device_buffers[name] = tensor # keep pointer alive
|
| 437 |
+
|
| 438 |
+
# ---- (after all input shapes are known) infer output shapes ----
|
| 439 |
+
# propagates shapes :contentReference[oaicite:2]{index=2}
|
| 440 |
+
self.context.infer_shapes()
|
| 441 |
+
|
| 442 |
+
for i in range(self.engine.num_io_tensors):
|
| 443 |
+
name = self.engine.get_tensor_name(i)
|
| 444 |
+
# replaces binding_is_input :contentReference[oaicite:3]{index=3}
|
| 445 |
+
if self.engine.get_tensor_mode(name) == trt.TensorIOMode.OUTPUT:
|
| 446 |
+
shape = tuple(self.context.get_tensor_shape(name))
|
| 447 |
+
dtype = self.dtype_map[self.engine.get_tensor_dtype(name)]
|
| 448 |
+
out = torch.empty(shape, dtype=dtype, device="cuda").contiguous()
|
| 449 |
+
|
| 450 |
+
self.context.set_tensor_address(name, int(out.data_ptr()))
|
| 451 |
+
self.outputs.append(out)
|
| 452 |
+
self.device_buffers[name] = out
|
| 453 |
+
|
| 454 |
+
# helper to quant-convert on the fly
|
| 455 |
+
def quantize_if_needed(
|
| 456 |
+
self,
|
| 457 |
+
t,
|
| 458 |
+
expected_dtype,
|
| 459 |
+
scale,
|
| 460 |
+
):
|
| 461 |
+
if expected_dtype == trt.int8 and t.dtype != torch.int8:
|
| 462 |
+
t = torch.clamp((t / scale).round(), -128, 127).to(torch.int8).contiguous()
|
| 463 |
+
return t # keep pointer alive
|
| 464 |
+
|
| 465 |
+
def forward(
|
| 466 |
+
self,
|
| 467 |
+
*test_inputs,
|
| 468 |
+
):
|
| 469 |
+
for i, name in enumerate(ALL_INPUTS_NAMES):
|
| 470 |
+
tensor, scale = test_inputs[i], 1 / 127
|
| 471 |
+
tensor = self.quantize_if_needed(tensor, self.engine.get_tensor_dtype(name), scale)
|
| 472 |
+
self.context.set_tensor_address(name, int(tensor.data_ptr()))
|
| 473 |
+
self.device_buffers[name] = tensor
|
| 474 |
+
|
| 475 |
+
self.context.execute_async_v3(stream_handle=self.stream)
|
| 476 |
+
torch.cuda.current_stream().synchronize()
|
| 477 |
+
return self.outputs
|
demo_utils/vae_block3.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List
|
| 2 |
+
from einops import rearrange
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
|
| 6 |
+
from wan.modules.vae import (
|
| 7 |
+
AttentionBlock,
|
| 8 |
+
CausalConv3d,
|
| 9 |
+
RMS_norm,
|
| 10 |
+
ResidualBlock,
|
| 11 |
+
Upsample,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class Resample(nn.Module):
|
| 16 |
+
|
| 17 |
+
def __init__(
|
| 18 |
+
self,
|
| 19 |
+
dim,
|
| 20 |
+
mode,
|
| 21 |
+
):
|
| 22 |
+
assert mode in (
|
| 23 |
+
"none",
|
| 24 |
+
"upsample2d",
|
| 25 |
+
"upsample3d",
|
| 26 |
+
"downsample2d",
|
| 27 |
+
"downsample3d",
|
| 28 |
+
)
|
| 29 |
+
super().__init__()
|
| 30 |
+
self.dim = dim
|
| 31 |
+
self.mode = mode
|
| 32 |
+
self.cache_t = 2
|
| 33 |
+
|
| 34 |
+
# layers
|
| 35 |
+
if mode == "upsample2d":
|
| 36 |
+
self.resample = nn.Sequential(
|
| 37 |
+
Upsample(scale_factor=(2.0, 2.0), mode="nearest"),
|
| 38 |
+
nn.Conv2d(dim, dim // 2, 3, padding=1),
|
| 39 |
+
)
|
| 40 |
+
elif mode == "upsample3d":
|
| 41 |
+
self.resample = nn.Sequential(
|
| 42 |
+
Upsample(scale_factor=(2.0, 2.0), mode="nearest"),
|
| 43 |
+
nn.Conv2d(dim, dim // 2, 3, padding=1),
|
| 44 |
+
)
|
| 45 |
+
self.time_conv = CausalConv3d(dim, dim * 2, (3, 1, 1), padding=(1, 0, 0))
|
| 46 |
+
|
| 47 |
+
elif mode == "downsample2d":
|
| 48 |
+
self.resample = nn.Sequential(
|
| 49 |
+
nn.ZeroPad2d((0, 1, 0, 1)),
|
| 50 |
+
nn.Conv2d(dim, dim, 3, stride=(2, 2)),
|
| 51 |
+
)
|
| 52 |
+
elif mode == "downsample3d":
|
| 53 |
+
self.resample = nn.Sequential(
|
| 54 |
+
nn.ZeroPad2d((0, 1, 0, 1)),
|
| 55 |
+
nn.Conv2d(dim, dim, 3, stride=(2, 2)),
|
| 56 |
+
)
|
| 57 |
+
self.time_conv = CausalConv3d(dim, dim, (3, 1, 1), stride=(2, 1, 1), padding=(0, 0, 0))
|
| 58 |
+
|
| 59 |
+
else:
|
| 60 |
+
self.resample = nn.Identity()
|
| 61 |
+
|
| 62 |
+
def forward(
|
| 63 |
+
self,
|
| 64 |
+
x,
|
| 65 |
+
feat_cache=None,
|
| 66 |
+
feat_idx=[0],
|
| 67 |
+
):
|
| 68 |
+
b, c, t, h, w = x.size()
|
| 69 |
+
if self.mode == "upsample3d":
|
| 70 |
+
if feat_cache is not None:
|
| 71 |
+
idx = feat_idx[0]
|
| 72 |
+
if feat_cache[idx] is None:
|
| 73 |
+
feat_cache[idx] = "Rep"
|
| 74 |
+
feat_idx[0] += 1
|
| 75 |
+
else:
|
| 76 |
+
|
| 77 |
+
cache_x = x[:, :, -self.cache_t :, :, :].clone()
|
| 78 |
+
if (
|
| 79 |
+
cache_x.shape[2] < 2
|
| 80 |
+
and feat_cache[idx] is not None
|
| 81 |
+
and feat_cache[idx] != "Rep"
|
| 82 |
+
):
|
| 83 |
+
# cache last frame of last two chunk
|
| 84 |
+
cache_x = torch.cat(
|
| 85 |
+
[
|
| 86 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device),
|
| 87 |
+
cache_x,
|
| 88 |
+
],
|
| 89 |
+
dim=2,
|
| 90 |
+
)
|
| 91 |
+
if (
|
| 92 |
+
cache_x.shape[2] < 2
|
| 93 |
+
and feat_cache[idx] is not None
|
| 94 |
+
and feat_cache[idx] == "Rep"
|
| 95 |
+
):
|
| 96 |
+
cache_x = torch.cat(
|
| 97 |
+
[
|
| 98 |
+
torch.zeros_like(cache_x).to(cache_x.device),
|
| 99 |
+
cache_x,
|
| 100 |
+
],
|
| 101 |
+
dim=2,
|
| 102 |
+
)
|
| 103 |
+
if feat_cache[idx] == "Rep":
|
| 104 |
+
x = self.time_conv(x)
|
| 105 |
+
else:
|
| 106 |
+
x = self.time_conv(x, feat_cache[idx])
|
| 107 |
+
feat_cache[idx] = cache_x
|
| 108 |
+
feat_idx[0] += 1
|
| 109 |
+
|
| 110 |
+
x = x.reshape(b, 2, c, t, h, w)
|
| 111 |
+
x = torch.stack((x[:, 0, :, :, :, :], x[:, 1, :, :, :, :]), 3)
|
| 112 |
+
x = x.reshape(b, c, t * 2, h, w)
|
| 113 |
+
t = x.shape[2]
|
| 114 |
+
x = rearrange(x, "b c t h w -> (b t) c h w")
|
| 115 |
+
x = self.resample(x)
|
| 116 |
+
x = rearrange(x, "(b t) c h w -> b c t h w", t=t)
|
| 117 |
+
|
| 118 |
+
if self.mode == "downsample3d":
|
| 119 |
+
if feat_cache is not None:
|
| 120 |
+
idx = feat_idx[0]
|
| 121 |
+
if feat_cache[idx] is None:
|
| 122 |
+
feat_cache[idx] = x.clone()
|
| 123 |
+
feat_idx[0] += 1
|
| 124 |
+
else:
|
| 125 |
+
|
| 126 |
+
cache_x = x[:, :, -1:, :, :].clone()
|
| 127 |
+
# if cache_x.shape[2] < 2 and feat_cache[idx] is not None and feat_cache[idx]!='Rep':
|
| 128 |
+
# # cache last frame of last two chunk
|
| 129 |
+
# cache_x = torch.cat([feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device), cache_x], dim=2)
|
| 130 |
+
|
| 131 |
+
x = self.time_conv(torch.cat([feat_cache[idx][:, :, -1:, :, :], x], 2))
|
| 132 |
+
feat_cache[idx] = cache_x
|
| 133 |
+
feat_idx[0] += 1
|
| 134 |
+
return x
|
| 135 |
+
|
| 136 |
+
def init_weight(
|
| 137 |
+
self,
|
| 138 |
+
conv,
|
| 139 |
+
):
|
| 140 |
+
conv_weight = conv.weight
|
| 141 |
+
nn.init.zeros_(conv_weight)
|
| 142 |
+
c1, c2, t, h, w = conv_weight.size()
|
| 143 |
+
one_matrix = torch.eye(c1, c2)
|
| 144 |
+
init_matrix = one_matrix
|
| 145 |
+
nn.init.zeros_(conv_weight)
|
| 146 |
+
# conv_weight.data[:,:,-1,1,1] = init_matrix * 0.5
|
| 147 |
+
conv_weight.data[:, :, 1, 0, 0] = init_matrix # * 0.5
|
| 148 |
+
conv.weight.data.copy_(conv_weight)
|
| 149 |
+
nn.init.zeros_(conv.bias.data)
|
| 150 |
+
|
| 151 |
+
def init_weight2(
|
| 152 |
+
self,
|
| 153 |
+
conv,
|
| 154 |
+
):
|
| 155 |
+
conv_weight = conv.weight.data
|
| 156 |
+
nn.init.zeros_(conv_weight)
|
| 157 |
+
c1, c2, t, h, w = conv_weight.size()
|
| 158 |
+
init_matrix = torch.eye(c1 // 2, c2)
|
| 159 |
+
# init_matrix = repeat(init_matrix, 'o ... -> (o 2) ...').permute(1,0,2).contiguous().reshape(c1,c2)
|
| 160 |
+
conv_weight[: c1 // 2, :, -1, 0, 0] = init_matrix
|
| 161 |
+
conv_weight[c1 // 2 :, :, -1, 0, 0] = init_matrix
|
| 162 |
+
conv.weight.data.copy_(conv_weight)
|
| 163 |
+
nn.init.zeros_(conv.bias.data)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
class VAEDecoderWrapper(nn.Module):
|
| 167 |
+
def __init__(
|
| 168 |
+
self,
|
| 169 |
+
):
|
| 170 |
+
super().__init__()
|
| 171 |
+
self.decoder = VAEDecoder3d()
|
| 172 |
+
mean = [
|
| 173 |
+
-0.7571,
|
| 174 |
+
-0.7089,
|
| 175 |
+
-0.9113,
|
| 176 |
+
0.1075,
|
| 177 |
+
-0.1745,
|
| 178 |
+
0.9653,
|
| 179 |
+
-0.1517,
|
| 180 |
+
1.5508,
|
| 181 |
+
0.4134,
|
| 182 |
+
-0.0715,
|
| 183 |
+
0.5517,
|
| 184 |
+
-0.3632,
|
| 185 |
+
-0.1922,
|
| 186 |
+
-0.9497,
|
| 187 |
+
0.2503,
|
| 188 |
+
-0.2921,
|
| 189 |
+
]
|
| 190 |
+
std = [
|
| 191 |
+
2.8184,
|
| 192 |
+
1.4541,
|
| 193 |
+
2.3275,
|
| 194 |
+
2.6558,
|
| 195 |
+
1.2196,
|
| 196 |
+
1.7708,
|
| 197 |
+
2.6052,
|
| 198 |
+
2.0743,
|
| 199 |
+
3.2687,
|
| 200 |
+
2.1526,
|
| 201 |
+
2.8652,
|
| 202 |
+
1.5579,
|
| 203 |
+
1.6382,
|
| 204 |
+
1.1253,
|
| 205 |
+
2.8251,
|
| 206 |
+
1.9160,
|
| 207 |
+
]
|
| 208 |
+
self.mean = torch.tensor(mean, dtype=torch.float32)
|
| 209 |
+
self.std = torch.tensor(std, dtype=torch.float32)
|
| 210 |
+
self.z_dim = 16
|
| 211 |
+
self.conv2 = CausalConv3d(self.z_dim, self.z_dim, 1)
|
| 212 |
+
|
| 213 |
+
def forward(
|
| 214 |
+
self,
|
| 215 |
+
z: torch.Tensor,
|
| 216 |
+
*feat_cache: List[torch.Tensor],
|
| 217 |
+
):
|
| 218 |
+
# from [batch_size, num_frames, num_channels, height, width]
|
| 219 |
+
# to [batch_size, num_channels, num_frames, height, width]
|
| 220 |
+
z = z.permute(0, 2, 1, 3, 4)
|
| 221 |
+
feat_cache = list(feat_cache)
|
| 222 |
+
print("Length of feat_cache: ", len(feat_cache))
|
| 223 |
+
|
| 224 |
+
device, dtype = z.device, z.dtype
|
| 225 |
+
scale = [
|
| 226 |
+
self.mean.to(device=device, dtype=dtype),
|
| 227 |
+
1.0 / self.std.to(device=device, dtype=dtype),
|
| 228 |
+
]
|
| 229 |
+
|
| 230 |
+
if isinstance(scale[0], torch.Tensor):
|
| 231 |
+
z = z / scale[1].view(1, self.z_dim, 1, 1, 1) + scale[0].view(1, self.z_dim, 1, 1, 1)
|
| 232 |
+
else:
|
| 233 |
+
z = z / scale[1] + scale[0]
|
| 234 |
+
iter_ = z.shape[2]
|
| 235 |
+
x = self.conv2(z)
|
| 236 |
+
for i in range(iter_):
|
| 237 |
+
if i == 0:
|
| 238 |
+
out, feat_cache = self.decoder(x[:, :, i : i + 1, :, :], feat_cache=feat_cache)
|
| 239 |
+
else:
|
| 240 |
+
out_, feat_cache = self.decoder(x[:, :, i : i + 1, :, :], feat_cache=feat_cache)
|
| 241 |
+
out = torch.cat([out, out_], 2)
|
| 242 |
+
|
| 243 |
+
out = out.float().clamp_(-1, 1)
|
| 244 |
+
# from [batch_size, num_channels, num_frames, height, width]
|
| 245 |
+
# to [batch_size, num_frames, num_channels, height, width]
|
| 246 |
+
out = out.permute(0, 2, 1, 3, 4)
|
| 247 |
+
return out, feat_cache
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
class VAEDecoder3d(nn.Module):
|
| 251 |
+
def __init__(
|
| 252 |
+
self,
|
| 253 |
+
dim=96,
|
| 254 |
+
z_dim=16,
|
| 255 |
+
dim_mult=[1, 2, 4, 4],
|
| 256 |
+
num_res_blocks=2,
|
| 257 |
+
attn_scales=[],
|
| 258 |
+
temperal_upsample=[True, True, False],
|
| 259 |
+
dropout=0.0,
|
| 260 |
+
):
|
| 261 |
+
super().__init__()
|
| 262 |
+
self.dim = dim
|
| 263 |
+
self.z_dim = z_dim
|
| 264 |
+
self.dim_mult = dim_mult
|
| 265 |
+
self.num_res_blocks = num_res_blocks
|
| 266 |
+
self.attn_scales = attn_scales
|
| 267 |
+
self.temperal_upsample = temperal_upsample
|
| 268 |
+
self.cache_t = 2
|
| 269 |
+
self.decoder_conv_num = 32
|
| 270 |
+
|
| 271 |
+
# dimensions
|
| 272 |
+
dims = [dim * u for u in [dim_mult[-1]] + dim_mult[::-1]]
|
| 273 |
+
scale = 1.0 / 2 ** (len(dim_mult) - 2)
|
| 274 |
+
|
| 275 |
+
# init block
|
| 276 |
+
self.conv1 = CausalConv3d(z_dim, dims[0], 3, padding=1)
|
| 277 |
+
|
| 278 |
+
# middle blocks
|
| 279 |
+
self.middle = nn.Sequential(
|
| 280 |
+
ResidualBlock(dims[0], dims[0], dropout),
|
| 281 |
+
AttentionBlock(dims[0]),
|
| 282 |
+
ResidualBlock(dims[0], dims[0], dropout),
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
# upsample blocks
|
| 286 |
+
upsamples = []
|
| 287 |
+
for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])):
|
| 288 |
+
# residual (+attention) blocks
|
| 289 |
+
if i == 1 or i == 2 or i == 3:
|
| 290 |
+
in_dim = in_dim // 2
|
| 291 |
+
for _ in range(num_res_blocks + 1):
|
| 292 |
+
upsamples.append(ResidualBlock(in_dim, out_dim, dropout))
|
| 293 |
+
if scale in attn_scales:
|
| 294 |
+
upsamples.append(AttentionBlock(out_dim))
|
| 295 |
+
in_dim = out_dim
|
| 296 |
+
|
| 297 |
+
# upsample block
|
| 298 |
+
if i != len(dim_mult) - 1:
|
| 299 |
+
mode = "upsample3d" if temperal_upsample[i] else "upsample2d"
|
| 300 |
+
upsamples.append(Resample(out_dim, mode=mode))
|
| 301 |
+
scale *= 2.0
|
| 302 |
+
self.upsamples = nn.Sequential(*upsamples)
|
| 303 |
+
|
| 304 |
+
# output blocks
|
| 305 |
+
self.head = nn.Sequential(
|
| 306 |
+
RMS_norm(out_dim, images=False),
|
| 307 |
+
nn.SiLU(),
|
| 308 |
+
CausalConv3d(out_dim, 3, 3, padding=1),
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
def forward(
|
| 312 |
+
self,
|
| 313 |
+
x: torch.Tensor,
|
| 314 |
+
feat_cache: List[torch.Tensor],
|
| 315 |
+
):
|
| 316 |
+
feat_idx = [0]
|
| 317 |
+
|
| 318 |
+
# conv1
|
| 319 |
+
idx = feat_idx[0]
|
| 320 |
+
cache_x = x[:, :, -self.cache_t :, :, :].clone()
|
| 321 |
+
if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
|
| 322 |
+
# cache last frame of last two chunk
|
| 323 |
+
cache_x = torch.cat(
|
| 324 |
+
[
|
| 325 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device),
|
| 326 |
+
cache_x,
|
| 327 |
+
],
|
| 328 |
+
dim=2,
|
| 329 |
+
)
|
| 330 |
+
x = self.conv1(x, feat_cache[idx])
|
| 331 |
+
feat_cache[idx] = cache_x
|
| 332 |
+
feat_idx[0] += 1
|
| 333 |
+
|
| 334 |
+
# middle
|
| 335 |
+
for layer in self.middle:
|
| 336 |
+
if isinstance(layer, ResidualBlock) and feat_cache is not None:
|
| 337 |
+
x = layer(x, feat_cache, feat_idx)
|
| 338 |
+
else:
|
| 339 |
+
x = layer(x)
|
| 340 |
+
|
| 341 |
+
# upsamples
|
| 342 |
+
for layer in self.upsamples:
|
| 343 |
+
x = layer(x, feat_cache, feat_idx)
|
| 344 |
+
|
| 345 |
+
# head
|
| 346 |
+
for layer in self.head:
|
| 347 |
+
if isinstance(layer, CausalConv3d) and feat_cache is not None:
|
| 348 |
+
idx = feat_idx[0]
|
| 349 |
+
cache_x = x[:, :, -self.cache_t :, :, :].clone()
|
| 350 |
+
if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
|
| 351 |
+
# cache last frame of last two chunk
|
| 352 |
+
cache_x = torch.cat(
|
| 353 |
+
[
|
| 354 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device),
|
| 355 |
+
cache_x,
|
| 356 |
+
],
|
| 357 |
+
dim=2,
|
| 358 |
+
)
|
| 359 |
+
x = layer(x, feat_cache[idx])
|
| 360 |
+
feat_cache[idx] = cache_x
|
| 361 |
+
feat_idx[0] += 1
|
| 362 |
+
else:
|
| 363 |
+
x = layer(x)
|
| 364 |
+
return x, feat_cache
|
demo_utils/vae_torch2trt.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ---- INT8 (optional) ----
|
| 2 |
+
from demo_utils.vae import (
|
| 3 |
+
VAEDecoderWrapperSingle, # main nn.Module
|
| 4 |
+
ZERO_VAE_CACHE, # helper constants shipped with your code base
|
| 5 |
+
)
|
| 6 |
+
import pycuda.driver as cuda # ← add
|
| 7 |
+
import pycuda.autoinit # noqa
|
| 8 |
+
|
| 9 |
+
import sys
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
import torch
|
| 13 |
+
import tensorrt as trt
|
| 14 |
+
|
| 15 |
+
from utils.dataset import ShardingLMDBDataset
|
| 16 |
+
|
| 17 |
+
data_path = "/mnt/localssd/wanx_14B_shift-3.0_cfg-5.0_lmdb_oneshard"
|
| 18 |
+
dataset = ShardingLMDBDataset(data_path, max_pair=int(1e8))
|
| 19 |
+
dataloader = torch.utils.data.DataLoader(dataset, batch_size=1, num_workers=0)
|
| 20 |
+
|
| 21 |
+
# ─────────────────────────────────────────────────────────
|
| 22 |
+
# 1️⃣ Bring the PyTorch model into scope
|
| 23 |
+
# (all code you pasted lives in `vae_decoder.py`)
|
| 24 |
+
# ─────────────────────────────────────────────────────────
|
| 25 |
+
|
| 26 |
+
# --- dummy tensors (exact shapes you posted) ---
|
| 27 |
+
dummy_input = torch.randn(1, 1, 16, 60, 104).half().cuda()
|
| 28 |
+
is_first_frame = torch.tensor([1.0], device="cuda", dtype=torch.float16)
|
| 29 |
+
dummy_cache_input = [
|
| 30 |
+
torch.randn(*s.shape).half().cuda() if isinstance(s, torch.Tensor) else s
|
| 31 |
+
for s in ZERO_VAE_CACHE # keep exactly the same ordering
|
| 32 |
+
]
|
| 33 |
+
inputs = [dummy_input, is_first_frame, *dummy_cache_input]
|
| 34 |
+
|
| 35 |
+
# ─────────────────────────────────────────────────────────
|
| 36 |
+
# 2️⃣ Export → ONNX
|
| 37 |
+
# ─────────────────────────────────────────────────────────
|
| 38 |
+
model = VAEDecoderWrapperSingle().half().cuda().eval()
|
| 39 |
+
|
| 40 |
+
vae_state_dict = torch.load("wan_models/Wan2.1-T2V-1.3B/Wan2.1_VAE.pth", map_location="cpu")
|
| 41 |
+
decoder_state_dict = {}
|
| 42 |
+
for key, value in vae_state_dict.items():
|
| 43 |
+
if "decoder." in key or "conv2" in key:
|
| 44 |
+
decoder_state_dict[key] = value
|
| 45 |
+
model.load_state_dict(decoder_state_dict)
|
| 46 |
+
model = model.half().cuda().eval() # only batch dim dynamic
|
| 47 |
+
|
| 48 |
+
onnx_path = Path("vae_decoder.onnx")
|
| 49 |
+
feat_names = [f"vae_cache_{i}" for i in range(len(dummy_cache_input))]
|
| 50 |
+
all_inputs_names = ["z", "use_cache"] + feat_names
|
| 51 |
+
|
| 52 |
+
with torch.inference_mode():
|
| 53 |
+
torch.onnx.export(
|
| 54 |
+
model,
|
| 55 |
+
tuple(inputs), # must be a tuple
|
| 56 |
+
onnx_path.as_posix(),
|
| 57 |
+
input_names=all_inputs_names,
|
| 58 |
+
output_names=["rgb_out", "cache_out"],
|
| 59 |
+
opset_version=17,
|
| 60 |
+
do_constant_folding=True,
|
| 61 |
+
dynamo=True,
|
| 62 |
+
)
|
| 63 |
+
print(f"✅ ONNX graph saved to {onnx_path.resolve()}")
|
| 64 |
+
|
| 65 |
+
# (Optional) quick sanity-check with ONNX-Runtime
|
| 66 |
+
try:
|
| 67 |
+
import onnxruntime as ort
|
| 68 |
+
|
| 69 |
+
sess = ort.InferenceSession(onnx_path.as_posix(), providers=["CUDAExecutionProvider"])
|
| 70 |
+
ort_inputs = {n: t.cpu().numpy() for n, t in zip(all_inputs_names, inputs)}
|
| 71 |
+
_ = sess.run(None, ort_inputs)
|
| 72 |
+
print("✅ ONNX graph is executable")
|
| 73 |
+
except Exception as e:
|
| 74 |
+
print("⚠️ ONNX check failed:", e)
|
| 75 |
+
|
| 76 |
+
# ─────────────────────────────────────────────────────────
|
| 77 |
+
# 3️⃣ Build the TensorRT engine
|
| 78 |
+
# ─────────────────────────────────────────────────────────
|
| 79 |
+
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
|
| 80 |
+
builder = trt.Builder(TRT_LOGGER)
|
| 81 |
+
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
|
| 82 |
+
parser = trt.OnnxParser(network, TRT_LOGGER)
|
| 83 |
+
|
| 84 |
+
with open(onnx_path, "rb") as f:
|
| 85 |
+
if not parser.parse(f.read()):
|
| 86 |
+
for i in range(parser.num_errors):
|
| 87 |
+
print(parser.get_error(i))
|
| 88 |
+
sys.exit("❌ ONNX → TRT parsing failed")
|
| 89 |
+
|
| 90 |
+
config = builder.create_builder_config()
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def set_workspace(
|
| 94 |
+
config,
|
| 95 |
+
bytes_,
|
| 96 |
+
):
|
| 97 |
+
"""Version-agnostic workspace limit."""
|
| 98 |
+
if hasattr(config, "max_workspace_size"): # TRT 8 / 9
|
| 99 |
+
config.max_workspace_size = bytes_
|
| 100 |
+
else: # TRT 10+
|
| 101 |
+
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, bytes_)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
# …
|
| 105 |
+
config = builder.create_builder_config()
|
| 106 |
+
set_workspace(config, 4 << 30) # 4 GB
|
| 107 |
+
# 4 GB
|
| 108 |
+
|
| 109 |
+
if builder.platform_has_fast_fp16:
|
| 110 |
+
config.set_flag(trt.BuilderFlag.FP16)
|
| 111 |
+
|
| 112 |
+
# ---- INT8 (optional) ----
|
| 113 |
+
# provide a calibrator if you need an INT8 engine; comment this
|
| 114 |
+
# block if you only care about FP16.
|
| 115 |
+
# ─────────────────────────────────────────────────────────
|
| 116 |
+
# helper: version-agnostic workspace limit
|
| 117 |
+
# ─────────────────────────────────────────────────────────
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def set_workspace(
|
| 121 |
+
config: trt.IBuilderConfig,
|
| 122 |
+
bytes_: int = 4 << 30,
|
| 123 |
+
):
|
| 124 |
+
"""
|
| 125 |
+
TRT < 10.x → config.max_workspace_size
|
| 126 |
+
TRT ≥ 10.x → config.set_memory_pool_limit(...)
|
| 127 |
+
"""
|
| 128 |
+
if hasattr(config, "max_workspace_size"): # TRT 8 / 9
|
| 129 |
+
config.max_workspace_size = bytes_
|
| 130 |
+
else: # TRT 10+
|
| 131 |
+
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, bytes_)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
# ─────────────────────────────────────────────────────────
|
| 135 |
+
# (optional) INT-8 calibrator
|
| 136 |
+
# ─────────────────────────────────────────────────────────
|
| 137 |
+
# ‼ Only keep this block if you really need INT-8 ‼ # gracefully skip if PyCUDA not present
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
class VAECalibrator(trt.IInt8EntropyCalibrator2):
|
| 141 |
+
def __init__(
|
| 142 |
+
self,
|
| 143 |
+
loader,
|
| 144 |
+
cache="calibration.cache",
|
| 145 |
+
max_batches=10,
|
| 146 |
+
):
|
| 147 |
+
super().__init__()
|
| 148 |
+
self.loader = iter(loader)
|
| 149 |
+
self.batch_size = loader.batch_size or 1
|
| 150 |
+
self.max_batches = max_batches
|
| 151 |
+
self.count = 0
|
| 152 |
+
self.cache_file = cache
|
| 153 |
+
self.stream = cuda.Stream()
|
| 154 |
+
self.dev_ptrs = {}
|
| 155 |
+
|
| 156 |
+
# --- TRT 10 needs BOTH spellings ---
|
| 157 |
+
def get_batch_size(
|
| 158 |
+
self,
|
| 159 |
+
):
|
| 160 |
+
return self.batch_size
|
| 161 |
+
|
| 162 |
+
def getBatchSize(
|
| 163 |
+
self,
|
| 164 |
+
):
|
| 165 |
+
return self.batch_size
|
| 166 |
+
|
| 167 |
+
def get_batch(
|
| 168 |
+
self,
|
| 169 |
+
names,
|
| 170 |
+
):
|
| 171 |
+
if self.count >= self.max_batches:
|
| 172 |
+
return None
|
| 173 |
+
|
| 174 |
+
# Randomly sample a number from 1 to 10
|
| 175 |
+
import random
|
| 176 |
+
|
| 177 |
+
vae_idx = random.randint(0, 10)
|
| 178 |
+
data = next(self.loader)
|
| 179 |
+
|
| 180 |
+
latent = data["ode_latent"][0][:, :1]
|
| 181 |
+
is_first_frame = torch.tensor([1.0], device="cuda", dtype=torch.float16)
|
| 182 |
+
feat_cache = ZERO_VAE_CACHE
|
| 183 |
+
for i in range(vae_idx):
|
| 184 |
+
inputs = [latent, is_first_frame, *feat_cache]
|
| 185 |
+
with torch.inference_mode():
|
| 186 |
+
outputs = model(*inputs)
|
| 187 |
+
latent = data["ode_latent"][0][:, i + 1 : i + 2]
|
| 188 |
+
is_first_frame = torch.tensor([0.0], device="cuda", dtype=torch.float16)
|
| 189 |
+
feat_cache = outputs[1:]
|
| 190 |
+
|
| 191 |
+
# -------- ensure context is current --------
|
| 192 |
+
z_np = latent.cpu().numpy().astype("float32")
|
| 193 |
+
|
| 194 |
+
ptrs = [] # list[int] – one entry per name
|
| 195 |
+
for name in names: # <-- match TRT's binding order
|
| 196 |
+
if name == "z":
|
| 197 |
+
arr = z_np
|
| 198 |
+
elif name == "use_cache":
|
| 199 |
+
arr = is_first_frame.cpu().numpy().astype("float32")
|
| 200 |
+
else:
|
| 201 |
+
idx = int(name.split("_")[-1]) # "vae_cache_17" -> 17
|
| 202 |
+
arr = feat_cache[idx].cpu().numpy().astype("float32")
|
| 203 |
+
|
| 204 |
+
if name not in self.dev_ptrs:
|
| 205 |
+
self.dev_ptrs[name] = cuda.mem_alloc(arr.nbytes)
|
| 206 |
+
|
| 207 |
+
cuda.memcpy_htod_async(self.dev_ptrs[name], arr, self.stream)
|
| 208 |
+
ptrs.append(int(self.dev_ptrs[name])) # ***int() is required***
|
| 209 |
+
|
| 210 |
+
self.stream.synchronize()
|
| 211 |
+
self.count += 1
|
| 212 |
+
print(f"Calibration batch {self.count}/{self.max_batches}")
|
| 213 |
+
return ptrs
|
| 214 |
+
|
| 215 |
+
# --- calibration-cache helpers (both spellings) ---
|
| 216 |
+
def read_calibration_cache(
|
| 217 |
+
self,
|
| 218 |
+
):
|
| 219 |
+
try:
|
| 220 |
+
with open(self.cache_file, "rb") as f:
|
| 221 |
+
return f.read()
|
| 222 |
+
except FileNotFoundError:
|
| 223 |
+
return None
|
| 224 |
+
|
| 225 |
+
def readCalibrationCache(
|
| 226 |
+
self,
|
| 227 |
+
):
|
| 228 |
+
return self.read_calibration_cache()
|
| 229 |
+
|
| 230 |
+
def write_calibration_cache(
|
| 231 |
+
self,
|
| 232 |
+
cache,
|
| 233 |
+
):
|
| 234 |
+
with open(self.cache_file, "wb") as f:
|
| 235 |
+
f.write(cache)
|
| 236 |
+
|
| 237 |
+
def writeCalibrationCache(
|
| 238 |
+
self,
|
| 239 |
+
cache,
|
| 240 |
+
):
|
| 241 |
+
self.write_calibration_cache(cache)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
# ─────────────────────────────────────────────────────────
|
| 245 |
+
# Builder-config + optimisation profile
|
| 246 |
+
# ─────────────────────────────────────────────────────────
|
| 247 |
+
config = builder.create_builder_config()
|
| 248 |
+
set_workspace(config, 4 << 30) # 4 GB
|
| 249 |
+
|
| 250 |
+
# ► enable FP16 if possible
|
| 251 |
+
if builder.platform_has_fast_fp16:
|
| 252 |
+
config.set_flag(trt.BuilderFlag.FP16)
|
| 253 |
+
|
| 254 |
+
# ► enable INT-8 (delete this block if you don’t need it)
|
| 255 |
+
if cuda is not None:
|
| 256 |
+
config.set_flag(trt.BuilderFlag.INT8)
|
| 257 |
+
# supply any representative batch you like – here we reuse the latent z
|
| 258 |
+
calib = VAECalibrator(dataloader)
|
| 259 |
+
# TRT-10 renamed the setter:
|
| 260 |
+
if hasattr(config, "set_int8_calibrator"): # TRT 10+
|
| 261 |
+
config.set_int8_calibrator(calib)
|
| 262 |
+
else: # TRT ≤ 9
|
| 263 |
+
config.int8_calibrator = calib
|
| 264 |
+
|
| 265 |
+
# ---- optimisation profile ----
|
| 266 |
+
profile = builder.create_optimization_profile()
|
| 267 |
+
profile.set_shape(
|
| 268 |
+
all_inputs_names[0], # latent z
|
| 269 |
+
min=(1, 1, 16, 60, 104),
|
| 270 |
+
opt=(1, 1, 16, 60, 104),
|
| 271 |
+
max=(1, 1, 16, 60, 104),
|
| 272 |
+
)
|
| 273 |
+
profile.set_shape("use_cache", min=(1,), opt=(1,), max=(1,)) # scalar flag
|
| 274 |
+
for name, tensor in zip(all_inputs_names[2:], dummy_cache_input):
|
| 275 |
+
profile.set_shape(name, tensor.shape, tensor.shape, tensor.shape)
|
| 276 |
+
|
| 277 |
+
config.add_optimization_profile(profile)
|
| 278 |
+
|
| 279 |
+
# ─────────────────────────────────────────────────────────
|
| 280 |
+
# Build the engine (API changed in TRT-10)
|
| 281 |
+
# ─────────────────────────────────────────────────────────
|
| 282 |
+
print("⚙️ Building engine … (can take a minute)")
|
| 283 |
+
|
| 284 |
+
if hasattr(builder, "build_serialized_network"): # TRT 10+
|
| 285 |
+
serialized_engine = builder.build_serialized_network(network, config)
|
| 286 |
+
assert serialized_engine is not None, "build_serialized_network() failed"
|
| 287 |
+
plan_path = Path("checkpoints/vae_decoder_int8.trt")
|
| 288 |
+
plan_path.write_bytes(serialized_engine)
|
| 289 |
+
engine_bytes = serialized_engine # keep for smoke-test
|
| 290 |
+
else: # TRT ≤ 9
|
| 291 |
+
engine = builder.build_engine(network, config)
|
| 292 |
+
assert engine is not None, "build_engine() returned None"
|
| 293 |
+
plan_path = Path("checkpoints/vae_decoder_int8.trt")
|
| 294 |
+
plan_path.write_bytes(engine.serialize())
|
| 295 |
+
engine_bytes = engine.serialize()
|
| 296 |
+
|
| 297 |
+
print(f"✅ TensorRT engine written to {plan_path.resolve()}")
|
| 298 |
+
|
| 299 |
+
# ─────────────────────────────────────────────────────────
|
| 300 |
+
# 4️⃣ Quick smoke-test with the brand-new engine
|
| 301 |
+
# ─────────────────────────────────────────────────────────
|
| 302 |
+
with trt.Runtime(TRT_LOGGER) as rt:
|
| 303 |
+
engine = rt.deserialize_cuda_engine(engine_bytes)
|
| 304 |
+
context = engine.create_execution_context()
|
| 305 |
+
stream = torch.cuda.current_stream().cuda_stream
|
| 306 |
+
|
| 307 |
+
# pre-allocate device buffers once
|
| 308 |
+
device_buffers, outputs = {}, []
|
| 309 |
+
dtype_map = {
|
| 310 |
+
trt.float32: torch.float32,
|
| 311 |
+
trt.float16: torch.float16,
|
| 312 |
+
trt.int8: torch.int8,
|
| 313 |
+
trt.int32: torch.int32,
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
for name, tensor in zip(all_inputs_names, inputs):
|
| 317 |
+
if -1 in engine.get_tensor_shape(name): # dynamic input
|
| 318 |
+
context.set_input_shape(name, tensor.shape)
|
| 319 |
+
context.set_tensor_address(name, int(tensor.data_ptr()))
|
| 320 |
+
device_buffers[name] = tensor
|
| 321 |
+
|
| 322 |
+
context.infer_shapes() # propagate ⇢ outputs
|
| 323 |
+
for i in range(engine.num_io_tensors):
|
| 324 |
+
name = engine.get_tensor_name(i)
|
| 325 |
+
if engine.get_tensor_mode(name) == trt.TensorIOMode.OUTPUT:
|
| 326 |
+
shape = tuple(context.get_tensor_shape(name))
|
| 327 |
+
dtype = dtype_map[engine.get_tensor_dtype(name)]
|
| 328 |
+
out = torch.empty(shape, dtype=dtype, device="cuda")
|
| 329 |
+
context.set_tensor_address(name, int(out.data_ptr()))
|
| 330 |
+
outputs.append(out)
|
| 331 |
+
print(f"output {name} shape: {shape}")
|
| 332 |
+
|
| 333 |
+
context.execute_async_v3(stream_handle=stream)
|
| 334 |
+
torch.cuda.current_stream().synchronize()
|
| 335 |
+
print("✅ TRT execution OK – first output shape:", outputs[0].shape)
|
frequency_utils.py
ADDED
|
@@ -0,0 +1,1020 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from typing import List, Sequence, Tuple, Union
|
| 3 |
+
import torch
|
| 4 |
+
from torch import Tensor
|
| 5 |
+
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import numpy as np
|
| 8 |
+
import os
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
torch.set_printoptions(
|
| 12 |
+
linewidth=10000,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _get_center_distance(size: Tuple[int], device: str = "cpu") -> Tensor:
|
| 17 |
+
"""Compute the distance of each matrix element to the center.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
size (Tuple[int]): [m, n].
|
| 21 |
+
device (str, optional): cpu/cuda. Defaults to 'cpu'.
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
Tensor: [m, n].
|
| 25 |
+
"""
|
| 26 |
+
m, n = size
|
| 27 |
+
i_ind = torch.tile(
|
| 28 |
+
torch.tensor([[[i]] for i in range(m)], device=device), dims=[1, n, 1]
|
| 29 |
+
).float() # [m, n, 1]
|
| 30 |
+
j_ind = torch.tile(
|
| 31 |
+
torch.tensor([[[i] for i in range(n)]], device=device), dims=[m, 1, 1]
|
| 32 |
+
).float() # [m, n, 1]
|
| 33 |
+
ij_ind = torch.cat([i_ind, j_ind], dim=-1) # [m, n, 2]
|
| 34 |
+
ij_ind = ij_ind.reshape([m * n, 1, 2]) # [m * n, 1, 2]
|
| 35 |
+
center_ij = torch.tensor(((m - 1) / 2, (n - 1) / 2), device=device).reshape(1, 2)
|
| 36 |
+
center_ij = torch.tile(center_ij, dims=[m * n, 1, 1])
|
| 37 |
+
dist = torch.cdist(ij_ind, center_ij, p=2).reshape([m, n])
|
| 38 |
+
return dist
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _get_ideal_weights(
|
| 42 |
+
size: Tuple[int], D0: int, lowpass: bool = True, device: str = "cpu"
|
| 43 |
+
) -> Tensor:
|
| 44 |
+
"""Get H(u, v) of ideal bandpass filter.
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
size (Tuple[int]): [H, W].
|
| 48 |
+
D0 (int): The cutoff frequency.
|
| 49 |
+
lowpass (bool): True for low-pass filter, otherwise for high-pass filter. Defaults to True.
|
| 50 |
+
device (str, optional): cpu/cuda. Defaults to 'cpu'.
|
| 51 |
+
|
| 52 |
+
Returns:
|
| 53 |
+
Tensor: [H, W].
|
| 54 |
+
"""
|
| 55 |
+
center_distance = _get_center_distance(size, device)
|
| 56 |
+
center_distance[center_distance > D0] = -1
|
| 57 |
+
center_distance[center_distance != -1] = 1
|
| 58 |
+
if lowpass is True:
|
| 59 |
+
center_distance[center_distance == -1] = 0
|
| 60 |
+
else:
|
| 61 |
+
center_distance[center_distance == 1] = 0
|
| 62 |
+
center_distance[center_distance == -1] = 1
|
| 63 |
+
return center_distance
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _to_freq(image: Tensor) -> Tensor:
|
| 67 |
+
"""Convert from spatial domain to frequency domain.
|
| 68 |
+
|
| 69 |
+
Args:
|
| 70 |
+
image (Tensor): [B, C, H, W].
|
| 71 |
+
|
| 72 |
+
Returns:
|
| 73 |
+
Tensor: [B, C, H, W]
|
| 74 |
+
"""
|
| 75 |
+
img_fft = torch.fft.fft2(image)
|
| 76 |
+
img_fft_shift = torch.fft.fftshift(img_fft)
|
| 77 |
+
return img_fft_shift
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def _to_space(image_fft: Tensor) -> Tensor:
|
| 81 |
+
"""Convert from frequency domain to spatial domain.
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
image_fft (Tensor): [B, C, H, W].
|
| 85 |
+
|
| 86 |
+
Returns:
|
| 87 |
+
Tensor: [B, C, H, W].
|
| 88 |
+
"""
|
| 89 |
+
img_ifft_shift = torch.fft.ifftshift(image_fft)
|
| 90 |
+
img_ifft = torch.fft.ifft2(img_ifft_shift)
|
| 91 |
+
img = img_ifft.real.clamp(0, 1)
|
| 92 |
+
return img
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def ideal_bandpass(image: Tensor, D0: int, lowpass: bool = True) -> Tensor:
|
| 96 |
+
"""Low-pass filter for images.
|
| 97 |
+
|
| 98 |
+
Args:
|
| 99 |
+
image (Tensor): [B, C, H, W].
|
| 100 |
+
D0 (int): Cutoff frequency.
|
| 101 |
+
lowpass (bool): True for low-pass filter, otherwise for high-pass filter. Defaults to True.
|
| 102 |
+
|
| 103 |
+
Returns:
|
| 104 |
+
Tensor: [B, C, H, W].
|
| 105 |
+
"""
|
| 106 |
+
img_fft = _to_freq(image)
|
| 107 |
+
weights = _get_ideal_weights(img_fft.shape[-2:], D0=D0, lowpass=lowpass, device=image.device)
|
| 108 |
+
img_fft = img_fft * weights
|
| 109 |
+
img = _to_space(img_fft)
|
| 110 |
+
return img
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
# Butterworth
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def _get_butterworth_weights(size: Tuple[int], D0: int, n: int, device: str = "cpu") -> Tensor:
|
| 117 |
+
"""Get H(u, v) of Butterworth filter.
|
| 118 |
+
|
| 119 |
+
Args:
|
| 120 |
+
size (Tuple[int]): [H, W].
|
| 121 |
+
D0 (int): The cutoff frequency.
|
| 122 |
+
n (int): Order of Butterworth filters.
|
| 123 |
+
device (str, optional): cpu/cuda. Defaults to 'cpu'.
|
| 124 |
+
|
| 125 |
+
Returns:
|
| 126 |
+
Tensor: [H, W].
|
| 127 |
+
"""
|
| 128 |
+
center_distance = _get_center_distance(size=size, device=device)
|
| 129 |
+
weights = 1 / (1 + torch.pow(center_distance / D0, 2 * n))
|
| 130 |
+
return weights
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def butterworth(image: Tensor, D0: int, n: int) -> Tensor:
|
| 134 |
+
"""Butterworth low-pass filter for images.
|
| 135 |
+
|
| 136 |
+
Args:
|
| 137 |
+
image (Tensor): [B, C, H, W].
|
| 138 |
+
D0 (int): Cutoff frequency.
|
| 139 |
+
n (int): Order of the Butterworth low-pass filter.
|
| 140 |
+
|
| 141 |
+
Returns:
|
| 142 |
+
Tensor: [B, C, H, W].
|
| 143 |
+
"""
|
| 144 |
+
img_fft = _to_freq(image)
|
| 145 |
+
weights = _get_butterworth_weights(image.shape[-2:], D0, n, device=image.device)
|
| 146 |
+
img_fft = weights * img_fft
|
| 147 |
+
img = _to_space(img_fft)
|
| 148 |
+
return img
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
# def my_butterworth_low_pass_filter(
|
| 152 |
+
# shape,
|
| 153 |
+
# stop_freqs: List[float],
|
| 154 |
+
# n=4,
|
| 155 |
+
# ):
|
| 156 |
+
# assert len(shape) == len(stop_freqs)
|
| 157 |
+
|
| 158 |
+
# grid = torch.meshgrid(
|
| 159 |
+
# *[torch.arange(s, dtype=torch.float32) for s in shape],
|
| 160 |
+
# indexing='ij',
|
| 161 |
+
# )
|
| 162 |
+
# # ( [shape[0], shape[1], ..., shape[N]] ) * len(shape)
|
| 163 |
+
# indices = torch.stack(grid, dim=-1).float()
|
| 164 |
+
# # print(f"{indices.shape = }")
|
| 165 |
+
# # [shape[0], shape[1], ..., shape[N], len(shape)]
|
| 166 |
+
|
| 167 |
+
# max_len = torch.tensor(shape).float()
|
| 168 |
+
# max_len -= 1.0
|
| 169 |
+
# max_len /= 2.0
|
| 170 |
+
# # print(f"{max_len = }")
|
| 171 |
+
# # print(f"{max_len.shape = }")
|
| 172 |
+
# # [len(shape)]
|
| 173 |
+
# max_len = max_len.view(*([1]*len(shape)), -1)
|
| 174 |
+
# # print(f"{max_len.shape = }")
|
| 175 |
+
# # [1, 1, ..., 1, len(shape)]
|
| 176 |
+
|
| 177 |
+
# normalized_indices = indices / max_len
|
| 178 |
+
# # [shape[0], shape[1], ..., shape[N], len(shape)]
|
| 179 |
+
|
| 180 |
+
# normalized_indices_offset = normalized_indices - 1
|
| 181 |
+
# # print(f"{normalized_indices_offset.shape = }")
|
| 182 |
+
# # [shape[0], shape[1], ..., shape[N], len(shape)]
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
# stop_freqs_torch = torch.tensor(stop_freqs).float().view(*([1]*len(shape)), -1)
|
| 186 |
+
# # print(f"{stop_freqs_torch.shape = }")
|
| 187 |
+
# # [1, 1, ..., 1, len(shape)]
|
| 188 |
+
|
| 189 |
+
# scaled_normalized_indices_offset = normalized_indices_offset / stop_freqs_torch
|
| 190 |
+
# # print(f"{scaled_normalized_indices_offset.shape = }")
|
| 191 |
+
# # [shape[0], shape[1], ..., shape[N], len(shape)]
|
| 192 |
+
|
| 193 |
+
# filter_ = 1.0 / (1.0 + torch.pow(scaled_normalized_indices_offset.norm(p=2, dim=-1), 2 * n))
|
| 194 |
+
# return filter_
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
# def my_butterworth_low_pass_filter_non_center(
|
| 198 |
+
# shape,
|
| 199 |
+
# stop_freqs: List[float],
|
| 200 |
+
# n=4,
|
| 201 |
+
# ):
|
| 202 |
+
# new_shape = [
|
| 203 |
+
# 2*i-1
|
| 204 |
+
# for i in shape
|
| 205 |
+
# ]
|
| 206 |
+
# filter_ = my_butterworth_low_pass_filter(
|
| 207 |
+
# new_shape,
|
| 208 |
+
# n=n,
|
| 209 |
+
# stop_freqs=stop_freqs,
|
| 210 |
+
# )
|
| 211 |
+
|
| 212 |
+
# if len(shape) == 1:
|
| 213 |
+
# crop_filter = filter_[-shape[0]:]
|
| 214 |
+
# elif len(shape) == 2:
|
| 215 |
+
# crop_filter = filter_[-shape[0]:, -shape[1]:]
|
| 216 |
+
# elif len(shape) == 3:
|
| 217 |
+
# crop_filter = filter_[-shape[0]:, -shape[1]:, -shape[2]:]
|
| 218 |
+
# else:
|
| 219 |
+
# raise ValueError("Shape must be 1D, 2D, or 3D.")
|
| 220 |
+
# return crop_filter
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
# def my_butterworth_high_pass_filter(
|
| 224 |
+
# shape,
|
| 225 |
+
# stop_freqs: List[float],
|
| 226 |
+
# n=4,
|
| 227 |
+
# ):
|
| 228 |
+
# assert len(shape) == len(stop_freqs)
|
| 229 |
+
|
| 230 |
+
# grid = torch.meshgrid(
|
| 231 |
+
# *[torch.arange(s, dtype=torch.float32) for s in shape],
|
| 232 |
+
# indexing='ij',
|
| 233 |
+
# )
|
| 234 |
+
# # ( [shape[0], shape[1], ..., shape[N]] ) * len(shape)
|
| 235 |
+
# indices = torch.stack(grid, dim=-1).float()
|
| 236 |
+
# # print(f"{indices.shape = }")
|
| 237 |
+
# # [shape[0], shape[1], ..., shape[N], len(shape)]
|
| 238 |
+
|
| 239 |
+
# max_len = torch.tensor(shape).float()
|
| 240 |
+
# max_len -= 1.0
|
| 241 |
+
# max_len /= 2.0
|
| 242 |
+
# # print(f"{max_len = }")
|
| 243 |
+
# # print(f"{max_len.shape = }")
|
| 244 |
+
# # [len(shape)]
|
| 245 |
+
# max_len = max_len.view(*([1]*len(shape)), -1)
|
| 246 |
+
# # print(f"{max_len.shape = }")
|
| 247 |
+
# # [1, 1, ..., 1, len(shape)]
|
| 248 |
+
|
| 249 |
+
# normalized_indices = indices / max_len
|
| 250 |
+
# # [shape[0], shape[1], ..., shape[N], len(shape)]
|
| 251 |
+
|
| 252 |
+
# normalized_indices_offset = normalized_indices - 1
|
| 253 |
+
# # print(f"{normalized_indices_offset.shape = }")
|
| 254 |
+
# # [shape[0], shape[1], ..., shape[N], len(shape)]
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
# stop_freqs_torch = torch.tensor(stop_freqs).float().view(*([1]*len(shape)), -1)
|
| 258 |
+
# # print(f"{stop_freqs_torch.shape = }")
|
| 259 |
+
# # [1, 1, ..., 1, len(shape)]
|
| 260 |
+
|
| 261 |
+
# scaled_normalized_indices_offset = stop_freqs_torch / normalized_indices_offset
|
| 262 |
+
# # print(f"{scaled_normalized_indices_offset.shape = }")
|
| 263 |
+
# # [shape[0], shape[1], ..., shape[N], len(shape)]
|
| 264 |
+
|
| 265 |
+
# filter_ = 1.0 / (1.0 + torch.pow(scaled_normalized_indices_offset.norm(p=2, dim=-1), 2 * n))
|
| 266 |
+
# return filter_
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
# def my_butterworth_high_pass_filter_non_center(
|
| 270 |
+
# shape,
|
| 271 |
+
# stop_freqs: List[float],
|
| 272 |
+
# n=4,
|
| 273 |
+
# ):
|
| 274 |
+
# new_shape = [
|
| 275 |
+
# 2*i-1
|
| 276 |
+
# for i in shape
|
| 277 |
+
# ]
|
| 278 |
+
# filter_ = my_butterworth_high_pass_filter(
|
| 279 |
+
# new_shape,
|
| 280 |
+
# n=n,
|
| 281 |
+
# stop_freqs=stop_freqs,
|
| 282 |
+
# )
|
| 283 |
+
|
| 284 |
+
# if len(shape) == 1:
|
| 285 |
+
# crop_filter = filter_[-shape[0]:]
|
| 286 |
+
# elif len(shape) == 2:
|
| 287 |
+
# crop_filter = filter_[-shape[0]:, -shape[1]:]
|
| 288 |
+
# elif len(shape) == 3:
|
| 289 |
+
# crop_filter = filter_[-shape[0]:, -shape[1]:, -shape[2]:]
|
| 290 |
+
# else:
|
| 291 |
+
# raise ValueError("Shape must be 1D, 2D, or 3D.")
|
| 292 |
+
# return crop_filter
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
# ------------------------ Image loading ------------------------
|
| 296 |
+
def load_grayscale_image():
|
| 297 |
+
# Try common sample images; fall back to skimage if available; else ask user to put an image in cwd
|
| 298 |
+
candidates = ["onion.png", "cameraman.tif", "peppers.png", "lena.png", "camera.png"]
|
| 299 |
+
for name in candidates:
|
| 300 |
+
if os.path.exists(name):
|
| 301 |
+
# img = Image.open(name).convert('L')
|
| 302 |
+
img = Image.open(name).convert("RGB")
|
| 303 |
+
image_np = np.asarray(img, dtype=np.float64)
|
| 304 |
+
# print(f"{image_np = }")
|
| 305 |
+
image_np = image_np / 255.0
|
| 306 |
+
# print(f"{image_np = }")
|
| 307 |
+
return image_np
|
| 308 |
+
|
| 309 |
+
raise FileNotFoundError(
|
| 310 |
+
"Could not find a local image. Place an image (e.g., cameraman.tif/peppers.png) in the working directory."
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
# ------------------------ DCT implementations (orthonormal) ------------------------
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def dct2_matrix_ortho(N, device="cpu", dtype=torch.float32):
|
| 318 |
+
# T2[k, n] = sqrt(2/N) * beta(k) * cos(pi/N * (n + 0.5) * k), beta(0)=1/sqrt(2)
|
| 319 |
+
n = torch.arange(N, device=device, dtype=dtype)
|
| 320 |
+
k = torch.arange(N, device=device, dtype=dtype).unsqueeze(1)
|
| 321 |
+
W = torch.cos(math.pi / N * (n + 0.5) * k) # [N, N]
|
| 322 |
+
beta = torch.ones(N, device=device, dtype=dtype)
|
| 323 |
+
beta[0] = 1 / math.sqrt(2.0)
|
| 324 |
+
T = (math.sqrt(2.0 / N) * beta).unsqueeze(1) * W
|
| 325 |
+
return T # orthonormal; inverse is T.T
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def dct1_matrix_ortho(N, device="cpu", dtype=torch.float32):
|
| 329 |
+
# T1[k, n] = sqrt(2/(N-1)) * alpha(k) * alpha(n) * cos(pi/(N-1) * n*k)
|
| 330 |
+
# alpha(0)=alpha(N-1)=1/sqrt(2), else 1. Self-inverse (orthonormal and symmetric).
|
| 331 |
+
if N < 2:
|
| 332 |
+
# N=1 trivial case
|
| 333 |
+
return torch.ones((1, 1), device=device, dtype=dtype)
|
| 334 |
+
n = torch.arange(N, device=device, dtype=dtype)
|
| 335 |
+
k = torch.arange(N, device=device, dtype=dtype).unsqueeze(1)
|
| 336 |
+
C = torch.cos(math.pi / (N - 1) * (n * k)) # [N, N]
|
| 337 |
+
alpha = torch.ones(N, device=device, dtype=dtype)
|
| 338 |
+
alpha[0] = 1 / math.sqrt(2.0)
|
| 339 |
+
alpha[-1] = 1 / math.sqrt(2.0)
|
| 340 |
+
T = math.sqrt(2.0 / (N - 1)) * (alpha.unsqueeze(1) * C * alpha.unsqueeze(0))
|
| 341 |
+
return T # orthonormal, symmetric, self-inverse
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
def dct2_ortho(x, T2=None):
|
| 345 |
+
# x: [N] float tensor. Returns DCT-II (orthonormal) [N].
|
| 346 |
+
x = x.reshape(-1)
|
| 347 |
+
N = x.numel()
|
| 348 |
+
if T2 is None:
|
| 349 |
+
T2 = dct2_matrix_ortho(N, device=x.device, dtype=x.dtype)
|
| 350 |
+
return T2 @ x
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
def idct2_ortho(X, T2=None):
|
| 354 |
+
# Inverse of DCT-II (orthonormal) is transpose
|
| 355 |
+
X = X.reshape(-1)
|
| 356 |
+
N = X.numel()
|
| 357 |
+
if T2 is None:
|
| 358 |
+
T2 = dct2_matrix_ortho(N, device=X.device, dtype=X.dtype)
|
| 359 |
+
return T2.t() @ X
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
def dct1_ortho(x, T1=None):
|
| 363 |
+
# x: [N] float tensor. Returns DCT-I (orthonormal) [N].
|
| 364 |
+
x = x.reshape(-1)
|
| 365 |
+
N = x.numel()
|
| 366 |
+
if T1 is None:
|
| 367 |
+
T1 = dct1_matrix_ortho(N, device=x.device, dtype=x.dtype)
|
| 368 |
+
return T1 @ x
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
def idct1_ortho(X, T1=None):
|
| 372 |
+
# DCT-I orthonormal is self-inverse
|
| 373 |
+
X = X.reshape(-1)
|
| 374 |
+
N = X.numel()
|
| 375 |
+
if T1 is None:
|
| 376 |
+
T1 = dct1_matrix_ortho(N, device=X.device, dtype=X.dtype)
|
| 377 |
+
return T1 @ X
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def _complex_dtype_from_real(real_dtype):
|
| 381 |
+
if real_dtype == torch.float32:
|
| 382 |
+
return torch.complex64
|
| 383 |
+
if real_dtype == torch.float64:
|
| 384 |
+
return torch.complex128
|
| 385 |
+
raise TypeError("Only float32/float64 supported.")
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def dct2_fft(x, dim=-1, norm="ortho"):
|
| 389 |
+
"""
|
| 390 |
+
DCT-II via even-symmetric 2N extension and torch.fft.rfft.
|
| 391 |
+
x: real tensor (..., N)
|
| 392 |
+
Returns: real tensor (..., N)
|
| 393 |
+
norm: 'ortho' (orthonormal, like scipy.fft.dct(..., type=2, norm='ortho')) or None (unnormalized).
|
| 394 |
+
"""
|
| 395 |
+
if not torch.is_floating_point(x):
|
| 396 |
+
raise TypeError("x must be float tensor")
|
| 397 |
+
N = x.shape[dim]
|
| 398 |
+
if N < 1:
|
| 399 |
+
return x.clone()
|
| 400 |
+
|
| 401 |
+
# Even extension [x, flip(x)]
|
| 402 |
+
x_flip = torch.flip(x, dims=(dim,))
|
| 403 |
+
s = torch.cat([x, x_flip], dim=dim) # (..., 2N)
|
| 404 |
+
|
| 405 |
+
# RFFT over length 2N
|
| 406 |
+
S = torch.fft.rfft(s, n=2 * N, dim=dim) # (..., N+1)
|
| 407 |
+
|
| 408 |
+
# k = 0..N-1
|
| 409 |
+
k = torch.arange(N, device=x.device, dtype=x.dtype)
|
| 410 |
+
# exp(-j*pi*k/(2N))
|
| 411 |
+
ctype = _complex_dtype_from_real(x.dtype)
|
| 412 |
+
twiddle = torch.exp(-1j * math.pi * k / (2.0 * N)).to(dtype=ctype, device=x.device)
|
| 413 |
+
for _ in range(dim, S.dim() - 1):
|
| 414 |
+
twiddle = twiddle.unsqueeze(-1)
|
| 415 |
+
|
| 416 |
+
# Take real part; factor 1/2 (see derivation)
|
| 417 |
+
C = (S.narrow(dim, 0, N) * twiddle).real * 0.5 # (..., N)
|
| 418 |
+
|
| 419 |
+
if norm == "ortho":
|
| 420 |
+
# Orthonormal scaling: sqrt(2/N) * beta(k), beta(0)=1/sqrt(2)
|
| 421 |
+
C = C * math.sqrt(2.0 / N)
|
| 422 |
+
index0 = [slice(None)] * C.dim()
|
| 423 |
+
index0[dim] = 0
|
| 424 |
+
C[tuple(index0)] /= math.sqrt(2.0)
|
| 425 |
+
elif norm is None:
|
| 426 |
+
pass
|
| 427 |
+
else:
|
| 428 |
+
raise ValueError("norm must be 'ortho' or None")
|
| 429 |
+
return C
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
def idct2_fft(C, dim=-1, norm="ortho"):
|
| 433 |
+
"""
|
| 434 |
+
Inverse of dct2_fft (i.e., DCT-III) using torch.fft.irfft.
|
| 435 |
+
C: real tensor (..., N) with same norm used in dct2_fft.
|
| 436 |
+
Returns real tensor (..., N).
|
| 437 |
+
"""
|
| 438 |
+
if not torch.is_floating_point(C):
|
| 439 |
+
raise TypeError("C must be float tensor")
|
| 440 |
+
N = C.shape[dim]
|
| 441 |
+
if N < 1:
|
| 442 |
+
return C.clone()
|
| 443 |
+
|
| 444 |
+
# Undo orthonormal scaling to get "unnormalized" DCT-II coefficients
|
| 445 |
+
Cun = C
|
| 446 |
+
if norm == "ortho":
|
| 447 |
+
Cun = C / math.sqrt(2.0 / N)
|
| 448 |
+
index0 = [slice(None)] * Cun.dim()
|
| 449 |
+
index0[dim] = 0
|
| 450 |
+
Cun = Cun.clone()
|
| 451 |
+
Cun[tuple(index0)] *= math.sqrt(2.0)
|
| 452 |
+
elif norm is None:
|
| 453 |
+
Cun = C
|
| 454 |
+
else:
|
| 455 |
+
raise ValueError("norm must be 'ortho' or None")
|
| 456 |
+
|
| 457 |
+
# Build unique half-spectrum (length N+1) for the 2N-length irfft
|
| 458 |
+
# S[k] = 2*Cun[k] * exp(+j*pi*k/(2N)), for k=0..N-1
|
| 459 |
+
k = torch.arange(N, device=C.device, dtype=C.dtype)
|
| 460 |
+
ctype = _complex_dtype_from_real(C.dtype)
|
| 461 |
+
twiddle = torch.exp(+1j * math.pi * k / (2.0 * N)).to(dtype=ctype, device=C.device)
|
| 462 |
+
for _ in range(dim, C.dim() - 1):
|
| 463 |
+
twiddle = twiddle.unsqueeze(-1)
|
| 464 |
+
|
| 465 |
+
# Allocate (..., N+1)
|
| 466 |
+
new_shape = list(Cun.shape)
|
| 467 |
+
new_shape[dim] = N + 1
|
| 468 |
+
S_half = torch.zeros(*new_shape, dtype=ctype, device=C.device)
|
| 469 |
+
|
| 470 |
+
# Fill 0..N-1
|
| 471 |
+
# real times complex -> cast below
|
| 472 |
+
S_part = (2.0 * Cun) * twiddle.real - 0j
|
| 473 |
+
S_part = (2.0 * Cun).to(ctype) * twiddle
|
| 474 |
+
S_half.narrow(dim, 0, N).copy_(S_part)
|
| 475 |
+
|
| 476 |
+
# Nyquist (k=N) is zero for the chosen even-symmetric extension
|
| 477 |
+
indexN = [slice(None)] * S_half.dim()
|
| 478 |
+
indexN[dim] = N
|
| 479 |
+
S_half[tuple(indexN)] = 0
|
| 480 |
+
|
| 481 |
+
# irfft to length 2N, take first N samples
|
| 482 |
+
s = torch.fft.irfft(S_half, n=2 * N, dim=dim) # (..., 2N)
|
| 483 |
+
|
| 484 |
+
# Slice first N along dim
|
| 485 |
+
x = s.narrow(dim, 0, N)
|
| 486 |
+
return x
|
| 487 |
+
|
| 488 |
+
|
| 489 |
+
# --------- N-D (multi-axis) DCT-II / IDCT-II built from the 1D versions ---------
|
| 490 |
+
def _normalize_dims(dims, ndim):
|
| 491 |
+
if isinstance(dims, int):
|
| 492 |
+
dims = (dims,)
|
| 493 |
+
dims = tuple(d if d >= 0 else d + ndim for d in dims)
|
| 494 |
+
if any(d < 0 or d >= ndim for d in dims):
|
| 495 |
+
raise ValueError("dims out of range for input tensor.")
|
| 496 |
+
# You can enforce uniqueness if desired:
|
| 497 |
+
if len(set(dims)) != len(dims):
|
| 498 |
+
raise ValueError("dims must be unique.")
|
| 499 |
+
return dims
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
def dct2_nd_fft(x, dims, norm="ortho"):
|
| 503 |
+
"""
|
| 504 |
+
N-D DCT-II applied along the specified dimensions.
|
| 505 |
+
x: real tensor
|
| 506 |
+
dims: tuple of axes (e.g., (-2,-1) for 2D, (-3,-2,-1) for 3D)
|
| 507 |
+
norm: 'ortho' or None
|
| 508 |
+
"""
|
| 509 |
+
dims = _normalize_dims(dims, x.ndim)
|
| 510 |
+
y = x
|
| 511 |
+
for d in dims:
|
| 512 |
+
y = dct2_fft(y, dim=d, norm=norm)
|
| 513 |
+
return y
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
def idct2_nd_fft(X, dims, norm="ortho"):
|
| 517 |
+
"""
|
| 518 |
+
N-D inverse of DCT-II (DCT-III) along the specified dimensions.
|
| 519 |
+
"""
|
| 520 |
+
dims = _normalize_dims(dims, X.ndim)
|
| 521 |
+
y = X
|
| 522 |
+
for d in dims:
|
| 523 |
+
y = idct2_fft(y, dim=d, norm=norm)
|
| 524 |
+
return y
|
| 525 |
+
|
| 526 |
+
|
| 527 |
+
def _to_device_dtype(x, device, dtype):
|
| 528 |
+
if device is None:
|
| 529 |
+
device = x.device if isinstance(x, torch.Tensor) else "cpu"
|
| 530 |
+
if dtype is None:
|
| 531 |
+
dtype = torch.float64 # match MATLAB double
|
| 532 |
+
return device, dtype
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
def _omega_grid_1d(N, shifted, device, dtype):
|
| 536 |
+
# Digital radian frequency samples on FFT bins.
|
| 537 |
+
# unshifted: ω_k = 2π k / N, k=0..N-1 (DC at index 0)
|
| 538 |
+
# shifted: fftshift layout (DC at center), monotonically increasing from negative to positive
|
| 539 |
+
k = torch.arange(N, device=device, dtype=dtype)
|
| 540 |
+
w = 2.0 * math.pi * k / N
|
| 541 |
+
# [0, 2π)
|
| 542 |
+
if shifted:
|
| 543 |
+
w = torch.fft.fftshift(w) # center DC
|
| 544 |
+
return w
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
def _tan_half_abs(w, eps=1e-12):
|
| 548 |
+
# Safe |tan(w/2)| to avoid overflow at w=π.
|
| 549 |
+
half = 0.5 * w
|
| 550 |
+
c = torch.cos(half)
|
| 551 |
+
s = torch.sin(half)
|
| 552 |
+
# Where cos is near zero, use a very large value (approach infinity)
|
| 553 |
+
# large but not inf to avoid NaNs downstream
|
| 554 |
+
large = torch.finfo(w.dtype).max ** 0.5
|
| 555 |
+
t = torch.where(c.abs() < eps, torch.sign(s) * large, s / c)
|
| 556 |
+
return t.abs()
|
| 557 |
+
|
| 558 |
+
|
| 559 |
+
def butterworth_mask_1d(
|
| 560 |
+
N,
|
| 561 |
+
fc,
|
| 562 |
+
order,
|
| 563 |
+
btype="low",
|
| 564 |
+
shifted=False,
|
| 565 |
+
device=None,
|
| 566 |
+
dtype=None,
|
| 567 |
+
):
|
| 568 |
+
"""
|
| 569 |
+
1D Butterworth frequency mask equivalent to MATLAB butter+freqz magnitude.
|
| 570 |
+
- N: number of FFT bins
|
| 571 |
+
- fc: normalized cutoff(s) in cycles/sample (relative to 1 sample) with 0 < fc < 0.5
|
| 572 |
+
low/high: scalar; bandpass/stop: [f1, f2] with 0 < f1 < f2 < 0.5
|
| 573 |
+
* fc is equivalent to Wn / 2 in MATLAB's butter function. e.g. butter(4, 0.25) is equivalent to fc=0.125 here.
|
| 574 |
+
- order: integer >= 1
|
| 575 |
+
- btype: 'low', 'high', 'bandpass', 'stop'
|
| 576 |
+
- shifted: if True, return mask in fftshift layout (DC at center)
|
| 577 |
+
"""
|
| 578 |
+
assert isinstance(N, int) and N >= 2
|
| 579 |
+
assert isinstance(order, int) and order >= 1
|
| 580 |
+
btype = btype.lower()
|
| 581 |
+
if btype in ("low", "high"):
|
| 582 |
+
fc = float(fc)
|
| 583 |
+
assert 0.0 < fc < 0.5
|
| 584 |
+
else:
|
| 585 |
+
assert len(fc) == 2
|
| 586 |
+
f1, f2 = float(fc[0]), float(fc[1])
|
| 587 |
+
assert 0.0 < f1 < f2 < 0.5
|
| 588 |
+
fc = (f1, f2)
|
| 589 |
+
|
| 590 |
+
device, dtype = _to_device_dtype(torch.empty(0), device, dtype)
|
| 591 |
+
w = _omega_grid_1d(N, shifted=shifted, device=device, dtype=dtype) # 0..2π (or centered)
|
| 592 |
+
# Bilinear mapping (prewarped): Ω = 2 * tan(ω/2)
|
| 593 |
+
Om = 2.0 * _tan_half_abs(w) # analog rad/sec (normalized T=1)
|
| 594 |
+
|
| 595 |
+
if btype == "low":
|
| 596 |
+
# Prewarp analog cutoff: Ωc = 2*tan(π*fc)
|
| 597 |
+
Oc = 2.0 * math.tan(math.pi * fc)
|
| 598 |
+
ratio = (Om / Oc).clamp_min(0)
|
| 599 |
+
mag = 1.0 / torch.sqrt(1.0 + ratio.pow(2 * order))
|
| 600 |
+
elif btype == "high":
|
| 601 |
+
Oc = 2.0 * math.tan(math.pi * fc)
|
| 602 |
+
# Handle Om=0 => magnitude=0
|
| 603 |
+
ratio = torch.where(Om > 0, (Oc / Om), torch.full_like(Om, float("inf")))
|
| 604 |
+
mag = 1.0 / torch.sqrt(1.0 + ratio.pow(2 * order))
|
| 605 |
+
elif btype == "bandpass":
|
| 606 |
+
f1, f2 = fc
|
| 607 |
+
O1 = 2.0 * math.tan(math.pi * f1)
|
| 608 |
+
O2 = 2.0 * math.tan(math.pi * f2)
|
| 609 |
+
B = O2 - O1
|
| 610 |
+
O0 = math.sqrt(O1 * O2)
|
| 611 |
+
# D(Ω) = (Ω^2 - Ω0^2)/(B*Ω)
|
| 612 |
+
denom = B * Om
|
| 613 |
+
# denom=0 at Om=0 -> D=inf, magnitude=0
|
| 614 |
+
D = torch.where(denom != 0, (Om.pow(2) - O0**2) / denom, torch.full_like(Om, float("inf")))
|
| 615 |
+
mag = 1.0 / torch.sqrt(1.0 + D.abs().pow(2 * order))
|
| 616 |
+
elif btype in ("stop", "bandstop", "bandreject"):
|
| 617 |
+
f1, f2 = fc
|
| 618 |
+
O1 = 2.0 * math.tan(math.pi * f1)
|
| 619 |
+
O2 = 2.0 * math.tan(math.pi * f2)
|
| 620 |
+
B = O2 - O1
|
| 621 |
+
O0 = math.sqrt(O1 * O2)
|
| 622 |
+
# D(Ω) = (B*Ω)/(Ω^2 - Ω0^2)
|
| 623 |
+
denom = Om.pow(2) - O0**2
|
| 624 |
+
# denom=0 at Om=O0 -> D=inf, magnitude=0
|
| 625 |
+
D = torch.where(denom != 0, (B * Om) / denom, torch.full_like(Om, float("inf")))
|
| 626 |
+
mag = 1.0 / torch.sqrt(1.0 + D.abs().pow(2 * order))
|
| 627 |
+
else:
|
| 628 |
+
raise ValueError("btype must be 'low', 'high', 'bandpass', or 'stop'.")
|
| 629 |
+
|
| 630 |
+
return mag.to(dtype=dtype, device=device)
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
def butterworth_mask_2d_separable(
|
| 634 |
+
shape,
|
| 635 |
+
fc,
|
| 636 |
+
order,
|
| 637 |
+
btype="low",
|
| 638 |
+
shifted=False,
|
| 639 |
+
device=None,
|
| 640 |
+
dtype=None,
|
| 641 |
+
):
|
| 642 |
+
"""
|
| 643 |
+
2D separable Butterworth mask (rows × cols), equivalent to applying 1D Butterworth along rows and columns (zero-phase). Not an isotropic circular Butterworth.
|
| 644 |
+
- shape: (M, N)
|
| 645 |
+
- fc: scalar or 2-tuple for low/high; for band types, pass 2-tuples for each axis: ([f1y,f2y], [f1x,f2x]) You can also pass scalar or 2-tuple to apply same cutoffs on both axes.
|
| 646 |
+
- order: integer or 2-tuple for (order_y, order_x)
|
| 647 |
+
- btype: 'low', 'high', 'bandpass', 'stop'
|
| 648 |
+
- shifted: if True, both axes are centered (fftshift layout)
|
| 649 |
+
"""
|
| 650 |
+
M, N = int(shape[0]), int(shape[1])
|
| 651 |
+
assert M >= 2 and N >= 2
|
| 652 |
+
device, dtype = _to_device_dtype(torch.empty(0), device, dtype)
|
| 653 |
+
|
| 654 |
+
# Normalize fc/order to per-axis tuples
|
| 655 |
+
if btype in ("low", "high"):
|
| 656 |
+
if not isinstance(fc, (list, tuple)):
|
| 657 |
+
fcy = fcx = fc
|
| 658 |
+
else:
|
| 659 |
+
assert len(fc) == 2
|
| 660 |
+
fcy, fcx = fc
|
| 661 |
+
else:
|
| 662 |
+
# band types
|
| 663 |
+
if isinstance(fc[0], (list, tuple)) and isinstance(fc[1], (list, tuple)):
|
| 664 |
+
fcy, fcx = fc
|
| 665 |
+
else:
|
| 666 |
+
# same band on both axes
|
| 667 |
+
fcy = fcx = fc
|
| 668 |
+
|
| 669 |
+
if isinstance(order, (list, tuple)):
|
| 670 |
+
oy, ox = int(order[0]), int(order[1])
|
| 671 |
+
else:
|
| 672 |
+
oy = ox = int(order)
|
| 673 |
+
|
| 674 |
+
Hy = butterworth_mask_1d(M, fcy, oy, btype=btype, shifted=shifted, device=device, dtype=dtype)
|
| 675 |
+
Hx = butterworth_mask_1d(N, fcx, ox, btype=btype, shifted=shifted, device=device, dtype=dtype)
|
| 676 |
+
|
| 677 |
+
# Outer product to build separable 2D mask
|
| 678 |
+
H2 = Hy.reshape(M, 1) * Hx.reshape(1, N)
|
| 679 |
+
return H2
|
| 680 |
+
|
| 681 |
+
|
| 682 |
+
def _freqvec_norm(
|
| 683 |
+
N: int,
|
| 684 |
+
shifted: bool,
|
| 685 |
+
device=None,
|
| 686 |
+
dtype=None,
|
| 687 |
+
):
|
| 688 |
+
"""
|
| 689 |
+
Normalized frequency vector in [-0.5, 0.5), length N.
|
| 690 |
+
- shifted=False: DC at index 0 (unshifted FFT layout)
|
| 691 |
+
- shifted=True: DC at center (fftshift layout)
|
| 692 |
+
"""
|
| 693 |
+
if device is None:
|
| 694 |
+
device = "cpu"
|
| 695 |
+
if dtype is None:
|
| 696 |
+
dtype = torch.float64
|
| 697 |
+
k = torch.arange(N, device=device, dtype=dtype)
|
| 698 |
+
if shifted:
|
| 699 |
+
f = (k - torch.floor(torch.tensor(N / 2, dtype=dtype, device=device))) / N
|
| 700 |
+
else:
|
| 701 |
+
f = k / N
|
| 702 |
+
f = torch.where(f >= 0.5, f - 1.0, f) # wrap into [-0.5, 0.5)
|
| 703 |
+
return f # [N]
|
| 704 |
+
|
| 705 |
+
|
| 706 |
+
def _radial_frequency_nd(
|
| 707 |
+
shape: Sequence[int],
|
| 708 |
+
shifted: bool,
|
| 709 |
+
device=None,
|
| 710 |
+
dtype=None,
|
| 711 |
+
):
|
| 712 |
+
"""
|
| 713 |
+
Radial normalized frequency R in [-0.5,0.5) computed over all axes.
|
| 714 |
+
Returns R with shape 'shape'.
|
| 715 |
+
"""
|
| 716 |
+
if device is None:
|
| 717 |
+
device = "cpu"
|
| 718 |
+
if dtype is None:
|
| 719 |
+
dtype = torch.float64
|
| 720 |
+
grids = [_freqvec_norm(N, shifted=shifted, device=device, dtype=dtype) for N in shape]
|
| 721 |
+
# list of tensors, each shape = shape
|
| 722 |
+
meshes = torch.meshgrid(*grids, indexing="ij")
|
| 723 |
+
R2 = torch.zeros(shape, dtype=dtype, device=device)
|
| 724 |
+
for g in meshes:
|
| 725 |
+
R2 = R2 + g**2
|
| 726 |
+
R = torch.sqrt(R2)
|
| 727 |
+
return R
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
def butterworth_nd(
|
| 731 |
+
shape: Sequence[int],
|
| 732 |
+
cutoff: Union[float, Tuple[float, float]],
|
| 733 |
+
order: int,
|
| 734 |
+
btype: str = "low",
|
| 735 |
+
shifted: bool = False,
|
| 736 |
+
device=None,
|
| 737 |
+
dtype=None,
|
| 738 |
+
):
|
| 739 |
+
"""Isotropic N-D Butterworth mask (low/high/bandpass/bandstop).
|
| 740 |
+
Args:
|
| 741 |
+
shape: iterable of ints, e.g., (H, W) or (D, H, W) ...
|
| 742 |
+
cutoff:
|
| 743 |
+
- 'low'/'high': scalar D0 in (0, 0.5]
|
| 744 |
+
- 'bandpass'/'bandstop': tuple (D1, D2) with 0 < D1 < D2 <= 0.5
|
| 745 |
+
order: integer >= 1
|
| 746 |
+
btype: 'low' | 'high' | 'bandpass' | 'bandstop' (alias 'stop')
|
| 747 |
+
shifted: if True, mask is centered (fftshift layout); else unshifted
|
| 748 |
+
device, dtype: optional torch device/dtype (defaults: CPU, float64)
|
| 749 |
+
|
| 750 |
+
Returns:
|
| 751 |
+
H: tensor with shape 'shape', values in [0, 1].
|
| 752 |
+
"""
|
| 753 |
+
assert len(shape) >= 1 and all(int(s) >= 1 for s in shape), "Invalid shape."
|
| 754 |
+
order = int(order)
|
| 755 |
+
assert order >= 1, "order must be >= 1"
|
| 756 |
+
btype = btype.lower()
|
| 757 |
+
if btype in ("low", "high"):
|
| 758 |
+
D0 = float(cutoff)
|
| 759 |
+
# assert 0.0 < D0 <= 0.5, "cutoff must be in (0, 0.5]"
|
| 760 |
+
else:
|
| 761 |
+
D1, D2 = float(cutoff[0]), float(cutoff[1])
|
| 762 |
+
# assert 0.0 < D1 < D2 <= 0.5, "for band types: 0 < D1 < D2 <= 0.5"
|
| 763 |
+
B = D2 - D1
|
| 764 |
+
D0 = math.sqrt(D1 * D2)
|
| 765 |
+
|
| 766 |
+
if device is None:
|
| 767 |
+
device = "cpu"
|
| 768 |
+
if dtype is None:
|
| 769 |
+
dtype = torch.float64
|
| 770 |
+
|
| 771 |
+
R = _radial_frequency_nd(
|
| 772 |
+
tuple(int(s) for s in shape), shifted=shifted, device=device, dtype=dtype
|
| 773 |
+
)
|
| 774 |
+
eps = torch.finfo(dtype).eps
|
| 775 |
+
# print(f"{R = }")
|
| 776 |
+
|
| 777 |
+
if btype == "low":
|
| 778 |
+
# H = 1 / (1 + (R/D0)^(2n))
|
| 779 |
+
ratio = (R / D0).clamp_min(0)
|
| 780 |
+
H = 1.0 / (1.0 + ratio.pow(2 * order))
|
| 781 |
+
|
| 782 |
+
elif btype == "high":
|
| 783 |
+
# H = 1 / (1 + (D0/R)^(2n)), H(DC)=0
|
| 784 |
+
# avoid divide-by-zero at R=0
|
| 785 |
+
safe_R = torch.where(R > 0, R, torch.tensor(1.0, device=device, dtype=dtype)) # dummy
|
| 786 |
+
ratio = D0 / safe_R
|
| 787 |
+
H = 1.0 / (1.0 + ratio.pow(2 * order))
|
| 788 |
+
# enforce DC = 0
|
| 789 |
+
H = torch.where(R > 0, H, torch.zeros_like(H))
|
| 790 |
+
|
| 791 |
+
elif btype == "bandpass":
|
| 792 |
+
# D = (R^2 - D0^2) / (B*R); H = 1 / (1 + |D|^(2n))
|
| 793 |
+
# Handle R=0 -> D=inf -> H=0
|
| 794 |
+
denom = B * R
|
| 795 |
+
D = torch.where(denom != 0, (R.pow(2) - D0**2) / denom, torch.full_like(R, float("inf")))
|
| 796 |
+
H = 1.0 / (1.0 + D.abs().pow(2 * order))
|
| 797 |
+
|
| 798 |
+
elif btype in ("bandstop", "stop", "bandreject"):
|
| 799 |
+
# D = (B*R) / (R^2 - D0^2); H = 1 / (1 + |D|^(2n))
|
| 800 |
+
# Handle R^2 - D0^2 = 0 -> D=inf -> H=0 (deep notch at R=D0)
|
| 801 |
+
denom = R.pow(2) - D0**2
|
| 802 |
+
D = torch.where(denom != 0, (B * R) / denom, torch.full_like(R, float("inf")))
|
| 803 |
+
H = 1.0 / (1.0 + D.abs().pow(2 * order))
|
| 804 |
+
|
| 805 |
+
else:
|
| 806 |
+
raise ValueError("btype must be 'low', 'high', 'bandpass', or 'bandstop'.")
|
| 807 |
+
|
| 808 |
+
return H
|
| 809 |
+
|
| 810 |
+
|
| 811 |
+
def butterworth_low_pass_filter(
|
| 812 |
+
tensor: torch.Tensor,
|
| 813 |
+
dims: Sequence[int],
|
| 814 |
+
cutoff: float,
|
| 815 |
+
order: int,
|
| 816 |
+
shifted: bool = False,
|
| 817 |
+
device=None,
|
| 818 |
+
dtype=None,
|
| 819 |
+
):
|
| 820 |
+
"""
|
| 821 |
+
Applies a Butterworth low-pass filter to the input tensor.
|
| 822 |
+
|
| 823 |
+
the dims specify which dim should be perform filtering
|
| 824 |
+
|
| 825 |
+
return filtered tensor
|
| 826 |
+
"""
|
| 827 |
+
if not isinstance(dims, (list, tuple)):
|
| 828 |
+
dims = (dims,)
|
| 829 |
+
ndims_total = tensor.ndim
|
| 830 |
+
# Normalize dims (handle negatives)
|
| 831 |
+
norm_dims = _normalize_dims(dims, ndim=ndims_total)
|
| 832 |
+
|
| 833 |
+
original_dtype = tensor.dtype
|
| 834 |
+
work_dtype = dtype or (tensor.dtype if torch.is_floating_point(tensor) else torch.float32)
|
| 835 |
+
if work_dtype == torch.bfloat16 or work_dtype == torch.float16:
|
| 836 |
+
work_dtype = torch.float32
|
| 837 |
+
device = device or tensor.device
|
| 838 |
+
|
| 839 |
+
# Prepare frequency-domain representation
|
| 840 |
+
x = tensor.to(device=device, dtype=work_dtype)
|
| 841 |
+
X = torch.fft.fftn(x, dim=norm_dims)
|
| 842 |
+
if shifted:
|
| 843 |
+
X = torch.fft.fftshift(X, dim=norm_dims)
|
| 844 |
+
|
| 845 |
+
# Build isotropic Butterworth mask over the selected dims
|
| 846 |
+
shape_subset = [x.shape[d] for d in norm_dims]
|
| 847 |
+
H_small = butterworth_nd(
|
| 848 |
+
shape=shape_subset,
|
| 849 |
+
cutoff=cutoff,
|
| 850 |
+
order=order,
|
| 851 |
+
btype="low",
|
| 852 |
+
shifted=shifted,
|
| 853 |
+
device=device,
|
| 854 |
+
dtype=work_dtype,
|
| 855 |
+
)
|
| 856 |
+
|
| 857 |
+
# Broadcast mask into full tensor shape
|
| 858 |
+
mask_shape = [1] * ndims_total
|
| 859 |
+
for i, d in enumerate(norm_dims):
|
| 860 |
+
mask_shape[d] = shape_subset[i]
|
| 861 |
+
H = H_small.view(*mask_shape)
|
| 862 |
+
|
| 863 |
+
# Apply mask
|
| 864 |
+
X_filtered = X * H
|
| 865 |
+
|
| 866 |
+
# Inverse FFT
|
| 867 |
+
if shifted:
|
| 868 |
+
X_filtered = torch.fft.ifftshift(X_filtered, dim=norm_dims)
|
| 869 |
+
x_filtered = torch.fft.ifftn(X_filtered, dim=norm_dims).real
|
| 870 |
+
|
| 871 |
+
return x_filtered.to(dtype=original_dtype)
|
| 872 |
+
|
| 873 |
+
|
| 874 |
+
# def fft_denoise(tensor, dim, fft_ratio):
|
| 875 |
+
# assert len(dim) == 2
|
| 876 |
+
# original_dtype = tensor.dtype
|
| 877 |
+
# tensor = tensor.to(torch.float32)
|
| 878 |
+
# # Create low pass filter
|
| 879 |
+
# LPF = butterworth_low_pass_filter(
|
| 880 |
+
# (tensor.shape[dim[0]], tensor.shape[dim[1]]),
|
| 881 |
+
# n=4,
|
| 882 |
+
# d_s=fft_ratio,
|
| 883 |
+
# )
|
| 884 |
+
# LPF = LPF.to(dtype=tensor.dtype, device=tensor.device)
|
| 885 |
+
# # print(f"{LPF = }")
|
| 886 |
+
# # print(f"{LPF.shape = }")
|
| 887 |
+
# for _ in range(dim[0]):
|
| 888 |
+
# LPF = LPF.unsqueeze(0)
|
| 889 |
+
# for _ in range(dim[1] + 1, len(tensor.shape)):
|
| 890 |
+
# LPF = LPF.unsqueeze(-1)
|
| 891 |
+
# # print(f"{LPF.shape = }")
|
| 892 |
+
# # FFT
|
| 893 |
+
# latents_freq_k = torch.fft.fftn(tensor, dim=dim)
|
| 894 |
+
# # print(f"{latents_freq_k.shape = }")
|
| 895 |
+
# latents_freq_k = torch.fft.fftshift(latents_freq_k, dim=dim)
|
| 896 |
+
# # print(f"{latents_freq_k.shape = }")
|
| 897 |
+
|
| 898 |
+
# new_freq_k = latents_freq_k * LPF
|
| 899 |
+
|
| 900 |
+
# # IFFT
|
| 901 |
+
# new_freq_k = torch.fft.ifftshift(new_freq_k, dim=dim)
|
| 902 |
+
# denoised_k = torch.fft.ifftn(new_freq_k, dim=dim).real
|
| 903 |
+
# denoised_k = denoised_k.to(original_dtype)
|
| 904 |
+
# return denoised_k
|
| 905 |
+
|
| 906 |
+
|
| 907 |
+
if __name__ == "__main__":
|
| 908 |
+
# x = torch.linspace(0, 2 * np.pi, 8)
|
| 909 |
+
# y = torch.linspace(0, 2 * np.pi, 8)
|
| 910 |
+
# X, Y = torch.meshgrid(x, y, indexing='ij')
|
| 911 |
+
# latents = (
|
| 912 |
+
# torch.sin(2 * X + Y) +
|
| 913 |
+
# torch.sin(X + 3 * Y) +
|
| 914 |
+
# torch.sin(3 * X - 2 * Y)
|
| 915 |
+
# ) + 1
|
| 916 |
+
# latents += 0.01 * torch.randn_like(latents) # Add Gaussian noise
|
| 917 |
+
# # latents = torch.randn([8, 8])
|
| 918 |
+
# print(f"latents = \n{latents}")
|
| 919 |
+
|
| 920 |
+
# latents_freq = torch.fft.fftn(latents, dim=(-2, -1))
|
| 921 |
+
# print(f"latents_freq = \n{torch.abs(latents_freq)}")
|
| 922 |
+
|
| 923 |
+
# latents_freq_shift = torch.fft.fftshift(latents_freq, dim=(-2, -1))
|
| 924 |
+
# print(f"latents_freq_shift = \n{torch.abs(latents_freq_shift)}")
|
| 925 |
+
|
| 926 |
+
# latents_freq_dct = dct_2d(latents)
|
| 927 |
+
# print(f"latents_freq_dct = \n{latents_freq_dct}")
|
| 928 |
+
|
| 929 |
+
# LPF_1 = butterworth_low_pass_filter(latents=latents, d_s=-1.0)
|
| 930 |
+
|
| 931 |
+
# print(f"LPF_1 = \n{LPF_1}")
|
| 932 |
+
|
| 933 |
+
# LPF_2 = my_butterworth_low_pass_filter_non_center(
|
| 934 |
+
# shape=latents.shape,
|
| 935 |
+
# stop_freqs=[0.25, 0.25],
|
| 936 |
+
# n=4,
|
| 937 |
+
# )
|
| 938 |
+
# print(f"LPF_2 = \n{LPF_2}")
|
| 939 |
+
|
| 940 |
+
# LPF_3 = my_butterworth_low_pass_filter(
|
| 941 |
+
# shape=latents.shape,
|
| 942 |
+
# stop_freqs=[0.25, 0.25],
|
| 943 |
+
# n=4,
|
| 944 |
+
# )
|
| 945 |
+
# print(f"LPF_3 = \n{LPF_3}")
|
| 946 |
+
|
| 947 |
+
# img = load_grayscale_image()
|
| 948 |
+
# # Extract middle column as 1-D signal
|
| 949 |
+
# col = img.shape[1] // 2 - 1
|
| 950 |
+
# print(f"{col = }")
|
| 951 |
+
# x_np = img[:, col].astype(np.float32) # [H]
|
| 952 |
+
# # print(f"{x_np = }")
|
| 953 |
+
# N = x_np.shape[0]
|
| 954 |
+
# print(f"{N = }")
|
| 955 |
+
|
| 956 |
+
# device = 'cpu'
|
| 957 |
+
# dtype = torch.float64
|
| 958 |
+
|
| 959 |
+
# x = torch.from_numpy(img).to(device=device, dtype=dtype)
|
| 960 |
+
# print(f"{x = }")
|
| 961 |
+
|
| 962 |
+
# # Transforms
|
| 963 |
+
# Xf = torch.fft.fftn(x, dim=(-3, -2, -1), norm=None) # complex64
|
| 964 |
+
# print(f"{Xf = }")
|
| 965 |
+
# x_reconstructed = torch.fft.ifftn(Xf, dim=(-3, -2, -1), norm=None)
|
| 966 |
+
# print(f"{x_reconstructed = }")
|
| 967 |
+
# print(f"{(x - x_reconstructed).abs().max() = }")
|
| 968 |
+
|
| 969 |
+
# Xd2 = dct2_nd_fft(x, dims=(-3, -2, -1), norm="ortho") # float
|
| 970 |
+
# print(f"{Xd2 = }")
|
| 971 |
+
# x_reconstructed = idct2_nd_fft(Xd2, dims=(-1, -2, -3), norm="ortho")
|
| 972 |
+
# print(f"{x_reconstructed = }")
|
| 973 |
+
# print(f"{(x - x_reconstructed).abs().max() = }")
|
| 974 |
+
|
| 975 |
+
# H1 = butterworth_mask_1d(16, 0.125, 4, btype='low', shifted=True)
|
| 976 |
+
# print(f"{H1 = }")
|
| 977 |
+
|
| 978 |
+
H2 = butterworth_nd([30, 52], 1.0, 4, btype="low", shifted=True)
|
| 979 |
+
print(f"{H2 = }")
|
| 980 |
+
|
| 981 |
+
# ---- Planar wave demo with Butterworth low-pass filtering ----
|
| 982 |
+
def demo_planar_wave():
|
| 983 |
+
# Generate 2D planar wave: low-frequency + added high-frequency component
|
| 984 |
+
H, W = 128, 128
|
| 985 |
+
device = "cpu"
|
| 986 |
+
y = torch.arange(H, device=device).view(H, 1)
|
| 987 |
+
x = torch.arange(W, device=device).view(1, W)
|
| 988 |
+
|
| 989 |
+
# Low-frequency component
|
| 990 |
+
kx_low, ky_low = 2, 3
|
| 991 |
+
low = torch.sin(2 * math.pi * (kx_low * x / W + ky_low * y / H))
|
| 992 |
+
|
| 993 |
+
# High-frequency component
|
| 994 |
+
kx_high, ky_high = 20, 24
|
| 995 |
+
high = 0.5 * torch.sin(2 * math.pi * (kx_high * x / W + ky_high * y / H))
|
| 996 |
+
|
| 997 |
+
signal = low + high
|
| 998 |
+
|
| 999 |
+
# Apply Butterworth low-pass (cutoff chosen to keep low freq, attenuate high freq)
|
| 1000 |
+
cutoff = 0.12 # normalized radial cutoff (<=0.5)
|
| 1001 |
+
order = 4
|
| 1002 |
+
filtered = butterworth_low_pass_filter(
|
| 1003 |
+
signal, dims=(-2, -1), cutoff=cutoff, order=order, shifted=True
|
| 1004 |
+
)
|
| 1005 |
+
|
| 1006 |
+
# Metrics
|
| 1007 |
+
mse_before = (signal - low).pow(2).mean()
|
| 1008 |
+
mse_after = (filtered - low).pow(2).mean()
|
| 1009 |
+
residual_energy_ratio = (filtered - low).pow(2).sum() / (signal - low).pow(2).sum()
|
| 1010 |
+
|
| 1011 |
+
print("Planar wave demo:")
|
| 1012 |
+
print(f"mse_before={mse_before.item():.6e}")
|
| 1013 |
+
print(f"mse_after ={mse_after.item():.6e}")
|
| 1014 |
+
print(f"residual_energy_ratio={residual_energy_ratio.item():.4%}")
|
| 1015 |
+
# Quick sanity: high frequency suppression (should be << 1)
|
| 1016 |
+
assert (
|
| 1017 |
+
mse_after < mse_before
|
| 1018 |
+
), "Filtering did not reduce error to low-frequency ground truth."
|
| 1019 |
+
|
| 1020 |
+
demo_planar_wave()
|
images/.gitkeep
ADDED
|
File without changes
|
inference.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
|
| 5 |
+
import torch
|
| 6 |
+
from omegaconf import OmegaConf
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
from torchvision import transforms
|
| 9 |
+
from torchvision.io import write_video
|
| 10 |
+
from einops import rearrange
|
| 11 |
+
import torch.distributed as dist
|
| 12 |
+
from torch.utils.data import DataLoader, SequentialSampler
|
| 13 |
+
from torch.utils.data.distributed import DistributedSampler
|
| 14 |
+
|
| 15 |
+
from pipeline import (
|
| 16 |
+
CausalDiffusionInferencePipeline,
|
| 17 |
+
CausalInferencePipeline,
|
| 18 |
+
)
|
| 19 |
+
from utils.dataset import TextDataset, TextImagePairDataset
|
| 20 |
+
from utils.misc import set_seed
|
| 21 |
+
from hydra import initialize, compose
|
| 22 |
+
from hydra.core.global_hydra import GlobalHydra
|
| 23 |
+
|
| 24 |
+
from demo_utils.memory import gpu, get_cuda_free_memory_gb, DynamicSwapInstaller
|
| 25 |
+
|
| 26 |
+
from pathlib import Path
|
| 27 |
+
|
| 28 |
+
config_name = "self_forcing_dmd_vsink"
|
| 29 |
+
output_chunk_number = 21
|
| 30 |
+
output_latent_frame_number = 21
|
| 31 |
+
# output_latent_frame_number = 81
|
| 32 |
+
seed = 42
|
| 33 |
+
import sys
|
| 34 |
+
|
| 35 |
+
sys.argv.extend(
|
| 36 |
+
[
|
| 37 |
+
"--output_folder",
|
| 38 |
+
f"outputs/{output_latent_frame_number}-{config_name}-seed{seed}",
|
| 39 |
+
# f"outputs-test/{output_latent_frame_number}-{config_name}-seed{seed}",
|
| 40 |
+
"--config_dir",
|
| 41 |
+
"configs",
|
| 42 |
+
"--config_name",
|
| 43 |
+
config_name,
|
| 44 |
+
"--num_output_frames",
|
| 45 |
+
f"{output_latent_frame_number}",
|
| 46 |
+
"--data_path",
|
| 47 |
+
"prompts/MovieGenVideoBench_extended.txt",
|
| 48 |
+
"--checkpoint_path",
|
| 49 |
+
"./checkpoints/self_forcing_dmd.pt",
|
| 50 |
+
"--use_ema",
|
| 51 |
+
"--seed",
|
| 52 |
+
f"{seed}",
|
| 53 |
+
]
|
| 54 |
+
)
|
| 55 |
+
print(f"{sys.argv = }")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
parser = argparse.ArgumentParser()
|
| 59 |
+
parser.add_argument("--config_dir", type=str, help="Directory to the config file")
|
| 60 |
+
parser.add_argument("--config_name", type=str, help="Name to the config file")
|
| 61 |
+
parser.add_argument("--checkpoint_path", type=str, help="Path to the checkpoint folder")
|
| 62 |
+
parser.add_argument("--data_path", type=str, help="Path to the dataset")
|
| 63 |
+
parser.add_argument("--extended_prompt_path", type=str, help="Path to the extended prompt")
|
| 64 |
+
parser.add_argument("--output_folder", type=str, help="Output folder")
|
| 65 |
+
parser.add_argument(
|
| 66 |
+
"--num_output_frames",
|
| 67 |
+
type=int,
|
| 68 |
+
default=21,
|
| 69 |
+
help="Number of overlap frames between sliding windows",
|
| 70 |
+
)
|
| 71 |
+
parser.add_argument(
|
| 72 |
+
"--i2v",
|
| 73 |
+
action="store_true",
|
| 74 |
+
help="Whether to perform I2V (or T2V by default)",
|
| 75 |
+
)
|
| 76 |
+
parser.add_argument("--use_ema", action="store_true", help="Whether to use EMA parameters")
|
| 77 |
+
parser.add_argument("--seed", type=int, default=0, help="Random seed")
|
| 78 |
+
parser.add_argument(
|
| 79 |
+
"--num_samples",
|
| 80 |
+
type=int,
|
| 81 |
+
default=1,
|
| 82 |
+
help="Number of samples to generate per prompt",
|
| 83 |
+
)
|
| 84 |
+
args = parser.parse_args()
|
| 85 |
+
|
| 86 |
+
# Initialize distributed inference
|
| 87 |
+
if "LOCAL_RANK" in os.environ:
|
| 88 |
+
dist.init_process_group(backend="nccl")
|
| 89 |
+
local_rank = int(os.environ["LOCAL_RANK"])
|
| 90 |
+
torch.cuda.set_device(local_rank)
|
| 91 |
+
device = torch.device(f"cuda:{local_rank}")
|
| 92 |
+
world_size = dist.get_world_size()
|
| 93 |
+
set_seed(args.seed + local_rank)
|
| 94 |
+
else:
|
| 95 |
+
device = torch.device("cuda")
|
| 96 |
+
local_rank = 0
|
| 97 |
+
world_size = 1
|
| 98 |
+
set_seed(args.seed)
|
| 99 |
+
|
| 100 |
+
print(f"Free VRAM {get_cuda_free_memory_gb(gpu)} GB")
|
| 101 |
+
low_memory = get_cuda_free_memory_gb(gpu) < 40
|
| 102 |
+
|
| 103 |
+
torch.set_grad_enabled(False)
|
| 104 |
+
|
| 105 |
+
if GlobalHydra.instance().is_initialized():
|
| 106 |
+
GlobalHydra.instance().clear()
|
| 107 |
+
|
| 108 |
+
with initialize(version_base=None, config_path=args.config_dir):
|
| 109 |
+
config = compose(config_name=args.config_name)
|
| 110 |
+
print(f"{config = }")
|
| 111 |
+
|
| 112 |
+
# Initialize pipeline
|
| 113 |
+
if hasattr(config, "denoising_step_list"):
|
| 114 |
+
# Few-step inference
|
| 115 |
+
pipeline = CausalInferencePipeline(config, device=device)
|
| 116 |
+
else:
|
| 117 |
+
# Multi-step diffusion inference
|
| 118 |
+
pipeline = CausalDiffusionInferencePipeline(config, device=device)
|
| 119 |
+
|
| 120 |
+
if args.checkpoint_path:
|
| 121 |
+
state_dict = torch.load(args.checkpoint_path, map_location="cpu")
|
| 122 |
+
pipeline.generator.load_state_dict(
|
| 123 |
+
state_dict["generator" if not args.use_ema else "generator_ema"]
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
pipeline = pipeline.to(dtype=torch.bfloat16)
|
| 127 |
+
if low_memory:
|
| 128 |
+
DynamicSwapInstaller.install_model(pipeline.text_encoder, device=gpu)
|
| 129 |
+
else:
|
| 130 |
+
pipeline.text_encoder.to(device=gpu)
|
| 131 |
+
pipeline.generator.to(device=gpu)
|
| 132 |
+
pipeline.vae.to(device=gpu)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# Create dataset
|
| 136 |
+
if args.i2v:
|
| 137 |
+
assert not dist.is_initialized(), "I2V does not support distributed inference yet"
|
| 138 |
+
transform = transforms.Compose(
|
| 139 |
+
[
|
| 140 |
+
transforms.Resize((480, 832)),
|
| 141 |
+
transforms.ToTensor(),
|
| 142 |
+
transforms.Normalize([0.5], [0.5]),
|
| 143 |
+
]
|
| 144 |
+
)
|
| 145 |
+
dataset = TextImagePairDataset(args.data_path, transform=transform)
|
| 146 |
+
else:
|
| 147 |
+
dataset = TextDataset(
|
| 148 |
+
prompt_path=args.data_path,
|
| 149 |
+
extended_prompt_path=args.extended_prompt_path,
|
| 150 |
+
)
|
| 151 |
+
num_prompts = len(dataset)
|
| 152 |
+
print(f"Number of prompts: {num_prompts}")
|
| 153 |
+
|
| 154 |
+
if dist.is_initialized():
|
| 155 |
+
sampler = DistributedSampler(dataset, shuffle=False, drop_last=True)
|
| 156 |
+
else:
|
| 157 |
+
sampler = SequentialSampler(dataset)
|
| 158 |
+
dataloader = DataLoader(dataset, batch_size=1, sampler=sampler, num_workers=0, drop_last=False)
|
| 159 |
+
|
| 160 |
+
# Create output directory (only on main process to avoid race conditions)
|
| 161 |
+
if local_rank == 0:
|
| 162 |
+
os.makedirs(args.output_folder, exist_ok=True)
|
| 163 |
+
|
| 164 |
+
if dist.is_initialized():
|
| 165 |
+
dist.barrier()
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def encode(self, videos: torch.Tensor) -> torch.Tensor:
|
| 169 |
+
device, dtype = videos[0].device, videos[0].dtype
|
| 170 |
+
scale = [
|
| 171 |
+
self.mean.to(device=device, dtype=dtype),
|
| 172 |
+
1.0 / self.std.to(device=device, dtype=dtype),
|
| 173 |
+
]
|
| 174 |
+
output = [self.model.encode(u.unsqueeze(0), scale).float().squeeze(0) for u in videos]
|
| 175 |
+
|
| 176 |
+
output = torch.stack(output, dim=0)
|
| 177 |
+
return output
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
for i, batch_data in tqdm(enumerate(dataloader), disable=(local_rank != 0)):
|
| 181 |
+
idx = batch_data["idx"].item()
|
| 182 |
+
|
| 183 |
+
# For DataLoader batch_size=1, the batch_data is already a single item, but in a batch container
|
| 184 |
+
# Unpack the batch data for convenience
|
| 185 |
+
if isinstance(batch_data, dict):
|
| 186 |
+
batch = batch_data
|
| 187 |
+
elif isinstance(batch_data, list):
|
| 188 |
+
batch = batch_data[0] # First (and only) item in the batch
|
| 189 |
+
|
| 190 |
+
all_video = []
|
| 191 |
+
num_generated_frames = 0 # Number of generated (latent) frames
|
| 192 |
+
|
| 193 |
+
set_seed(args.seed)
|
| 194 |
+
if args.i2v:
|
| 195 |
+
# For image-to-video, batch contains image and caption
|
| 196 |
+
prompt = batch["prompts"][0] # Get caption from batch
|
| 197 |
+
prompts = [prompt] * args.num_samples
|
| 198 |
+
|
| 199 |
+
# Process the image
|
| 200 |
+
image = (
|
| 201 |
+
batch["image"]
|
| 202 |
+
.squeeze(0)
|
| 203 |
+
.unsqueeze(0)
|
| 204 |
+
.unsqueeze(2)
|
| 205 |
+
.to(device=device, dtype=torch.bfloat16)
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
# Encode the input image as the first latent
|
| 209 |
+
initial_latent = pipeline.vae.encode_to_latent(image).to(
|
| 210 |
+
device=device, dtype=torch.bfloat16
|
| 211 |
+
)
|
| 212 |
+
initial_latent = initial_latent.repeat(args.num_samples, 1, 1, 1, 1)
|
| 213 |
+
|
| 214 |
+
sampled_noise = torch.randn(
|
| 215 |
+
[args.num_samples, args.num_output_frames - 1, 16, 60, 104],
|
| 216 |
+
device=device,
|
| 217 |
+
dtype=torch.bfloat16,
|
| 218 |
+
)
|
| 219 |
+
else:
|
| 220 |
+
# For text-to-video, batch is just the text prompt
|
| 221 |
+
prompt = batch["prompts"][0]
|
| 222 |
+
extended_prompt = batch["extended_prompts"][0] if "extended_prompts" in batch else None
|
| 223 |
+
if extended_prompt is not None:
|
| 224 |
+
prompts = [extended_prompt] * args.num_samples
|
| 225 |
+
else:
|
| 226 |
+
prompts = [prompt] * args.num_samples
|
| 227 |
+
initial_latent = None
|
| 228 |
+
|
| 229 |
+
sampled_noise = torch.randn(
|
| 230 |
+
[args.num_samples, args.num_output_frames, 16, 60, 104],
|
| 231 |
+
device=device,
|
| 232 |
+
dtype=torch.bfloat16,
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
set_seed(args.seed)
|
| 236 |
+
# Generate 81 frames
|
| 237 |
+
video, latents = pipeline.inference(
|
| 238 |
+
noise=sampled_noise,
|
| 239 |
+
text_prompts=prompts,
|
| 240 |
+
return_latents=True,
|
| 241 |
+
initial_latent=initial_latent,
|
| 242 |
+
low_memory=low_memory,
|
| 243 |
+
)
|
| 244 |
+
current_video = rearrange(video, "b t c h w -> b t h w c").cpu()
|
| 245 |
+
all_video.append(current_video)
|
| 246 |
+
num_generated_frames += latents.shape[1]
|
| 247 |
+
|
| 248 |
+
# Final output video
|
| 249 |
+
video = 255.0 * torch.cat(all_video, dim=1)
|
| 250 |
+
|
| 251 |
+
# Clear VAE cache
|
| 252 |
+
pipeline.vae.model.clear_cache()
|
| 253 |
+
|
| 254 |
+
# Save the video if the current prompt is not a dummy prompt
|
| 255 |
+
if idx < num_prompts:
|
| 256 |
+
model = "regular" if not args.use_ema else "ema"
|
| 257 |
+
for seed_idx in range(args.num_samples):
|
| 258 |
+
# All processes save their videos
|
| 259 |
+
output_path = os.path.join(
|
| 260 |
+
args.output_folder,
|
| 261 |
+
f"{idx}-{prompt[:50].replace(' ', '_')}-{seed_idx}_{model}.mp4",
|
| 262 |
+
)
|
| 263 |
+
write_video(output_path, video[seed_idx], fps=16)
|
model/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .diffusion import CausalDiffusion
|
| 2 |
+
from .causvid import CausVid
|
| 3 |
+
from .dmd import DMD
|
| 4 |
+
from .gan import GAN
|
| 5 |
+
from .sid import SiD
|
| 6 |
+
from .ode_regression import ODERegression
|
| 7 |
+
|
| 8 |
+
__all__ = [
|
| 9 |
+
"CausalDiffusion",
|
| 10 |
+
"CausVid",
|
| 11 |
+
"DMD",
|
| 12 |
+
"GAN",
|
| 13 |
+
"SiD",
|
| 14 |
+
"ODERegression",
|
| 15 |
+
]
|
model/base.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Tuple
|
| 2 |
+
from einops import rearrange
|
| 3 |
+
from torch import nn
|
| 4 |
+
import torch.distributed as dist
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from pipeline import SelfForcingTrainingPipeline
|
| 8 |
+
from utils.loss import get_denoising_loss
|
| 9 |
+
from utils.wan_wrapper import WanDiffusionWrapper, WanTextEncoder, WanVAEWrapper
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class BaseModel(nn.Module):
|
| 13 |
+
def __init__(
|
| 14 |
+
self,
|
| 15 |
+
args,
|
| 16 |
+
device,
|
| 17 |
+
):
|
| 18 |
+
super().__init__()
|
| 19 |
+
self._initialize_models(args, device)
|
| 20 |
+
|
| 21 |
+
self.device = device
|
| 22 |
+
self.args = args
|
| 23 |
+
self.dtype = torch.bfloat16 if args.mixed_precision else torch.float32
|
| 24 |
+
if hasattr(args, "denoising_step_list"):
|
| 25 |
+
self.denoising_step_list = torch.tensor(args.denoising_step_list, dtype=torch.long)
|
| 26 |
+
if args.warp_denoising_step:
|
| 27 |
+
timesteps = torch.cat(
|
| 28 |
+
(
|
| 29 |
+
self.scheduler.timesteps.cpu(),
|
| 30 |
+
torch.tensor([0], dtype=torch.float32),
|
| 31 |
+
)
|
| 32 |
+
)
|
| 33 |
+
self.denoising_step_list = timesteps[1000 - self.denoising_step_list]
|
| 34 |
+
|
| 35 |
+
def _initialize_models(self, args, device):
|
| 36 |
+
self.real_model_name = getattr(args, "real_name", "Wan2.1-T2V-1.3B")
|
| 37 |
+
self.fake_model_name = getattr(args, "fake_name", "Wan2.1-T2V-1.3B")
|
| 38 |
+
|
| 39 |
+
self.generator = WanDiffusionWrapper(**getattr(args, "model_kwargs", {}), is_causal=True)
|
| 40 |
+
self.generator.model.requires_grad_(True)
|
| 41 |
+
|
| 42 |
+
self.real_score = WanDiffusionWrapper(model_name=self.real_model_name, is_causal=False)
|
| 43 |
+
self.real_score.model.requires_grad_(False)
|
| 44 |
+
|
| 45 |
+
self.fake_score = WanDiffusionWrapper(model_name=self.fake_model_name, is_causal=False)
|
| 46 |
+
self.fake_score.model.requires_grad_(True)
|
| 47 |
+
|
| 48 |
+
self.text_encoder = WanTextEncoder()
|
| 49 |
+
self.text_encoder.requires_grad_(False)
|
| 50 |
+
|
| 51 |
+
self.vae = WanVAEWrapper()
|
| 52 |
+
self.vae.requires_grad_(False)
|
| 53 |
+
|
| 54 |
+
self.scheduler = self.generator.get_scheduler()
|
| 55 |
+
self.scheduler.timesteps = self.scheduler.timesteps.to(device)
|
| 56 |
+
|
| 57 |
+
def _get_timestep(
|
| 58 |
+
self,
|
| 59 |
+
min_timestep: int,
|
| 60 |
+
max_timestep: int,
|
| 61 |
+
batch_size: int,
|
| 62 |
+
num_frame: int,
|
| 63 |
+
num_frame_per_block: int,
|
| 64 |
+
uniform_timestep: bool = False,
|
| 65 |
+
) -> torch.Tensor:
|
| 66 |
+
"""
|
| 67 |
+
Randomly generate a timestep tensor based on the generator's task type. It uniformly samples a timestep
|
| 68 |
+
from the range [min_timestep, max_timestep], and returns a tensor of shape [batch_size, num_frame].
|
| 69 |
+
- If uniform_timestep, it will use the same timestep for all frames.
|
| 70 |
+
- If not uniform_timestep, it will use a different timestep for each block.
|
| 71 |
+
"""
|
| 72 |
+
if uniform_timestep:
|
| 73 |
+
timestep = torch.randint(
|
| 74 |
+
min_timestep,
|
| 75 |
+
max_timestep,
|
| 76 |
+
[batch_size, 1],
|
| 77 |
+
device=self.device,
|
| 78 |
+
dtype=torch.long,
|
| 79 |
+
).repeat(1, num_frame)
|
| 80 |
+
return timestep
|
| 81 |
+
else:
|
| 82 |
+
timestep = torch.randint(
|
| 83 |
+
min_timestep,
|
| 84 |
+
max_timestep,
|
| 85 |
+
[batch_size, num_frame],
|
| 86 |
+
device=self.device,
|
| 87 |
+
dtype=torch.long,
|
| 88 |
+
)
|
| 89 |
+
# make the noise level the same within every block
|
| 90 |
+
if self.independent_first_frame:
|
| 91 |
+
# the first frame is always kept the same
|
| 92 |
+
timestep_from_second = timestep[:, 1:]
|
| 93 |
+
timestep_from_second = timestep_from_second.reshape(
|
| 94 |
+
timestep_from_second.shape[0], -1, num_frame_per_block
|
| 95 |
+
)
|
| 96 |
+
timestep_from_second[:, :, 1:] = timestep_from_second[:, :, 0:1]
|
| 97 |
+
timestep_from_second = timestep_from_second.reshape(
|
| 98 |
+
timestep_from_second.shape[0], -1
|
| 99 |
+
)
|
| 100 |
+
timestep = torch.cat([timestep[:, 0:1], timestep_from_second], dim=1)
|
| 101 |
+
else:
|
| 102 |
+
timestep = timestep.reshape(timestep.shape[0], -1, num_frame_per_block)
|
| 103 |
+
timestep[:, :, 1:] = timestep[:, :, 0:1]
|
| 104 |
+
timestep = timestep.reshape(timestep.shape[0], -1)
|
| 105 |
+
return timestep
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class SelfForcingModel(BaseModel):
|
| 109 |
+
def __init__(
|
| 110 |
+
self,
|
| 111 |
+
args,
|
| 112 |
+
device,
|
| 113 |
+
):
|
| 114 |
+
super().__init__(args, device)
|
| 115 |
+
self.denoising_loss_func = get_denoising_loss(args.denoising_loss_type)()
|
| 116 |
+
|
| 117 |
+
def _run_generator(
|
| 118 |
+
self,
|
| 119 |
+
image_or_video_shape,
|
| 120 |
+
conditional_dict: dict,
|
| 121 |
+
initial_latent: torch.tensor = None,
|
| 122 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 123 |
+
"""
|
| 124 |
+
Optionally simulate the generator's input from noise using backward simulation
|
| 125 |
+
and then run the generator for one-step.
|
| 126 |
+
Input:
|
| 127 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 128 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 129 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 130 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 131 |
+
- initial_latent: a tensor containing the initial latents [B, F, C, H, W].
|
| 132 |
+
Output:
|
| 133 |
+
- pred_image: a tensor with shape [B, F, C, H, W].
|
| 134 |
+
- denoised_timestep: an integer
|
| 135 |
+
"""
|
| 136 |
+
# Step 1: Sample noise and backward simulate the generator's input
|
| 137 |
+
assert getattr(
|
| 138 |
+
self.args, "backward_simulation", True
|
| 139 |
+
), "Backward simulation needs to be enabled"
|
| 140 |
+
if initial_latent is not None:
|
| 141 |
+
conditional_dict["initial_latent"] = initial_latent
|
| 142 |
+
if self.args.i2v:
|
| 143 |
+
noise_shape = [
|
| 144 |
+
image_or_video_shape[0],
|
| 145 |
+
image_or_video_shape[1] - 1,
|
| 146 |
+
*image_or_video_shape[2:],
|
| 147 |
+
]
|
| 148 |
+
else:
|
| 149 |
+
noise_shape = image_or_video_shape.copy()
|
| 150 |
+
|
| 151 |
+
# During training, the number of generated frames should be uniformly sampled from
|
| 152 |
+
# [21, self.num_training_frames], but still being a multiple of self.num_frame_per_block
|
| 153 |
+
min_num_frames = 20 if self.args.independent_first_frame else 21
|
| 154 |
+
max_num_frames = (
|
| 155 |
+
self.num_training_frames - 1
|
| 156 |
+
if self.args.independent_first_frame
|
| 157 |
+
else self.num_training_frames
|
| 158 |
+
)
|
| 159 |
+
assert max_num_frames % self.num_frame_per_block == 0
|
| 160 |
+
assert min_num_frames % self.num_frame_per_block == 0
|
| 161 |
+
max_num_blocks = max_num_frames // self.num_frame_per_block
|
| 162 |
+
min_num_blocks = min_num_frames // self.num_frame_per_block
|
| 163 |
+
num_generated_blocks = torch.randint(
|
| 164 |
+
min_num_blocks, max_num_blocks + 1, (1,), device=self.device
|
| 165 |
+
)
|
| 166 |
+
dist.broadcast(num_generated_blocks, src=0)
|
| 167 |
+
num_generated_blocks = num_generated_blocks.item()
|
| 168 |
+
num_generated_frames = num_generated_blocks * self.num_frame_per_block
|
| 169 |
+
if self.args.independent_first_frame and initial_latent is None:
|
| 170 |
+
num_generated_frames += 1
|
| 171 |
+
min_num_frames += 1
|
| 172 |
+
# Sync num_generated_frames across all processes
|
| 173 |
+
noise_shape[1] = num_generated_frames
|
| 174 |
+
|
| 175 |
+
pred_image_or_video, denoised_timestep_from, denoised_timestep_to = (
|
| 176 |
+
self._consistency_backward_simulation(
|
| 177 |
+
noise=torch.randn(noise_shape, device=self.device, dtype=self.dtype),
|
| 178 |
+
**conditional_dict,
|
| 179 |
+
)
|
| 180 |
+
)
|
| 181 |
+
# Slice last 21 frames
|
| 182 |
+
if pred_image_or_video.shape[1] > 21:
|
| 183 |
+
with torch.no_grad():
|
| 184 |
+
# Reencode to get image latent
|
| 185 |
+
latent_to_decode = pred_image_or_video[:, :-20, ...]
|
| 186 |
+
# Deccode to video
|
| 187 |
+
pixels = self.vae.decode_to_pixel(latent_to_decode)
|
| 188 |
+
frame = pixels[:, -1:, ...].to(self.dtype)
|
| 189 |
+
frame = rearrange(frame, "b t c h w -> b c t h w")
|
| 190 |
+
# Encode frame to get image latent
|
| 191 |
+
image_latent = self.vae.encode_to_latent(frame).to(self.dtype)
|
| 192 |
+
pred_image_or_video_last_21 = torch.cat(
|
| 193 |
+
[image_latent, pred_image_or_video[:, -20:, ...]], dim=1
|
| 194 |
+
)
|
| 195 |
+
else:
|
| 196 |
+
pred_image_or_video_last_21 = pred_image_or_video
|
| 197 |
+
|
| 198 |
+
if num_generated_frames != min_num_frames:
|
| 199 |
+
# Currently, we do not use gradient for the first chunk, since it contains image latents
|
| 200 |
+
gradient_mask = torch.ones_like(pred_image_or_video_last_21, dtype=torch.bool)
|
| 201 |
+
if self.args.independent_first_frame:
|
| 202 |
+
gradient_mask[:, :1] = False
|
| 203 |
+
else:
|
| 204 |
+
gradient_mask[:, : self.num_frame_per_block] = False
|
| 205 |
+
else:
|
| 206 |
+
gradient_mask = None
|
| 207 |
+
|
| 208 |
+
pred_image_or_video_last_21 = pred_image_or_video_last_21.to(self.dtype)
|
| 209 |
+
return (
|
| 210 |
+
pred_image_or_video_last_21,
|
| 211 |
+
gradient_mask,
|
| 212 |
+
denoised_timestep_from,
|
| 213 |
+
denoised_timestep_to,
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
def _consistency_backward_simulation(
|
| 217 |
+
self,
|
| 218 |
+
noise: torch.Tensor,
|
| 219 |
+
**conditional_dict: dict,
|
| 220 |
+
) -> torch.Tensor:
|
| 221 |
+
"""
|
| 222 |
+
Simulate the generator's input from noise to avoid training/inference mismatch.
|
| 223 |
+
See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.
|
| 224 |
+
Here we use the consistency sampler (https://arxiv.org/abs/2303.01469)
|
| 225 |
+
Input:
|
| 226 |
+
- noise: a tensor sampled from N(0, 1) with shape [B, F, C, H, W] where the number of frame is 1 for images.
|
| 227 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 228 |
+
Output:
|
| 229 |
+
- output: a tensor with shape [B, T, F, C, H, W].
|
| 230 |
+
T is the total number of timesteps. output[0] is a pure noise and output[i] and i>0
|
| 231 |
+
represents the x0 prediction at each timestep.
|
| 232 |
+
"""
|
| 233 |
+
if self.inference_pipeline is None:
|
| 234 |
+
self._initialize_inference_pipeline()
|
| 235 |
+
|
| 236 |
+
return self.inference_pipeline.inference_with_trajectory(noise=noise, **conditional_dict)
|
| 237 |
+
|
| 238 |
+
def _initialize_inference_pipeline(
|
| 239 |
+
self,
|
| 240 |
+
):
|
| 241 |
+
"""
|
| 242 |
+
Lazy initialize the inference pipeline during the first backward simulation run.
|
| 243 |
+
Here we encapsulate the inference code with a model-dependent outside function.
|
| 244 |
+
We pass our FSDP-wrapped modules into the pipeline to save memory.
|
| 245 |
+
"""
|
| 246 |
+
self.inference_pipeline = SelfForcingTrainingPipeline(
|
| 247 |
+
denoising_step_list=self.denoising_step_list,
|
| 248 |
+
scheduler=self.scheduler,
|
| 249 |
+
generator=self.generator,
|
| 250 |
+
num_frame_per_block=self.num_frame_per_block,
|
| 251 |
+
independent_first_frame=self.args.independent_first_frame,
|
| 252 |
+
same_step_across_blocks=self.args.same_step_across_blocks,
|
| 253 |
+
last_step_only=self.args.last_step_only,
|
| 254 |
+
num_max_frames=self.num_training_frames,
|
| 255 |
+
context_noise=self.args.context_noise,
|
| 256 |
+
)
|
model/causvid.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn.functional as F
|
| 2 |
+
from typing import Tuple
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from model.base import BaseModel
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class CausVid(BaseModel):
|
| 9 |
+
def __init__(
|
| 10 |
+
self,
|
| 11 |
+
args,
|
| 12 |
+
device,
|
| 13 |
+
):
|
| 14 |
+
"""
|
| 15 |
+
Initialize the DMD (Distribution Matching Distillation) module.
|
| 16 |
+
This class is self-contained and compute generator and fake score losses
|
| 17 |
+
in the forward pass.
|
| 18 |
+
"""
|
| 19 |
+
super().__init__(args, device)
|
| 20 |
+
self.num_frame_per_block = getattr(args, "num_frame_per_block", 1)
|
| 21 |
+
self.num_training_frames = getattr(args, "num_training_frames", 21)
|
| 22 |
+
|
| 23 |
+
if self.num_frame_per_block > 1:
|
| 24 |
+
self.generator.model.num_frame_per_block = self.num_frame_per_block
|
| 25 |
+
|
| 26 |
+
self.independent_first_frame = getattr(args, "independent_first_frame", False)
|
| 27 |
+
if self.independent_first_frame:
|
| 28 |
+
self.generator.model.independent_first_frame = True
|
| 29 |
+
if args.gradient_checkpointing:
|
| 30 |
+
self.generator.enable_gradient_checkpointing()
|
| 31 |
+
self.fake_score.enable_gradient_checkpointing()
|
| 32 |
+
|
| 33 |
+
# Step 2: Initialize all dmd hyperparameters
|
| 34 |
+
self.num_train_timestep = args.num_train_timestep
|
| 35 |
+
self.min_step = int(0.02 * self.num_train_timestep)
|
| 36 |
+
self.max_step = int(0.98 * self.num_train_timestep)
|
| 37 |
+
if hasattr(args, "real_guidance_scale"):
|
| 38 |
+
self.real_guidance_scale = args.real_guidance_scale
|
| 39 |
+
self.fake_guidance_scale = args.fake_guidance_scale
|
| 40 |
+
else:
|
| 41 |
+
self.real_guidance_scale = args.guidance_scale
|
| 42 |
+
self.fake_guidance_scale = 0.0
|
| 43 |
+
self.timestep_shift = getattr(args, "timestep_shift", 1.0)
|
| 44 |
+
self.teacher_forcing = getattr(args, "teacher_forcing", False)
|
| 45 |
+
|
| 46 |
+
if getattr(self.scheduler, "alphas_cumprod", None) is not None:
|
| 47 |
+
self.scheduler.alphas_cumprod = self.scheduler.alphas_cumprod.to(device)
|
| 48 |
+
else:
|
| 49 |
+
self.scheduler.alphas_cumprod = None
|
| 50 |
+
|
| 51 |
+
def _compute_kl_grad(
|
| 52 |
+
self,
|
| 53 |
+
noisy_image_or_video: torch.Tensor,
|
| 54 |
+
estimated_clean_image_or_video: torch.Tensor,
|
| 55 |
+
timestep: torch.Tensor,
|
| 56 |
+
conditional_dict: dict,
|
| 57 |
+
unconditional_dict: dict,
|
| 58 |
+
normalization: bool = True,
|
| 59 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 60 |
+
"""
|
| 61 |
+
Compute the KL grad (eq 7 in https://arxiv.org/abs/2311.18828).
|
| 62 |
+
Input:
|
| 63 |
+
- noisy_image_or_video: a tensor with shape [B, F, C, H, W] where the number of frame is 1 for images.
|
| 64 |
+
- estimated_clean_image_or_video: a tensor with shape [B, F, C, H, W] representing the estimated clean image or video.
|
| 65 |
+
- timestep: a tensor with shape [B, F] containing the randomly generated timestep.
|
| 66 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 67 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 68 |
+
- normalization: a boolean indicating whether to normalize the gradient.
|
| 69 |
+
Output:
|
| 70 |
+
- kl_grad: a tensor representing the KL grad.
|
| 71 |
+
- kl_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 72 |
+
"""
|
| 73 |
+
# Step 1: Compute the fake score
|
| 74 |
+
_, pred_fake_image_cond = self.fake_score(
|
| 75 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 76 |
+
conditional_dict=conditional_dict,
|
| 77 |
+
timestep=timestep,
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
if self.fake_guidance_scale != 0.0:
|
| 81 |
+
_, pred_fake_image_uncond = self.fake_score(
|
| 82 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 83 |
+
conditional_dict=unconditional_dict,
|
| 84 |
+
timestep=timestep,
|
| 85 |
+
)
|
| 86 |
+
pred_fake_image = (
|
| 87 |
+
pred_fake_image_cond
|
| 88 |
+
+ (pred_fake_image_cond - pred_fake_image_uncond) * self.fake_guidance_scale
|
| 89 |
+
)
|
| 90 |
+
else:
|
| 91 |
+
pred_fake_image = pred_fake_image_cond
|
| 92 |
+
|
| 93 |
+
# Step 2: Compute the real score
|
| 94 |
+
# We compute the conditional and unconditional prediction
|
| 95 |
+
# and add them together to achieve cfg (https://arxiv.org/abs/2207.12598)
|
| 96 |
+
_, pred_real_image_cond = self.real_score(
|
| 97 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 98 |
+
conditional_dict=conditional_dict,
|
| 99 |
+
timestep=timestep,
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
_, pred_real_image_uncond = self.real_score(
|
| 103 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 104 |
+
conditional_dict=unconditional_dict,
|
| 105 |
+
timestep=timestep,
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
pred_real_image = (
|
| 109 |
+
pred_real_image_cond
|
| 110 |
+
+ (pred_real_image_cond - pred_real_image_uncond) * self.real_guidance_scale
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
# Step 3: Compute the DMD gradient (DMD paper eq. 7).
|
| 114 |
+
grad = pred_fake_image - pred_real_image
|
| 115 |
+
|
| 116 |
+
# TODO: Change the normalizer for causal teacher
|
| 117 |
+
if normalization:
|
| 118 |
+
# Step 4: Gradient normalization (DMD paper eq. 8).
|
| 119 |
+
p_real = estimated_clean_image_or_video - pred_real_image
|
| 120 |
+
normalizer = torch.abs(p_real).mean(dim=[1, 2, 3, 4], keepdim=True)
|
| 121 |
+
grad = grad / normalizer
|
| 122 |
+
grad = torch.nan_to_num(grad)
|
| 123 |
+
|
| 124 |
+
return grad, {
|
| 125 |
+
"dmdtrain_gradient_norm": torch.mean(torch.abs(grad)).detach(),
|
| 126 |
+
"timestep": timestep.detach(),
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
def compute_distribution_matching_loss(
|
| 130 |
+
self,
|
| 131 |
+
image_or_video: torch.Tensor,
|
| 132 |
+
conditional_dict: dict,
|
| 133 |
+
unconditional_dict: dict,
|
| 134 |
+
gradient_mask: torch.Tensor = None,
|
| 135 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 136 |
+
"""
|
| 137 |
+
Compute the DMD loss (eq 7 in https://arxiv.org/abs/2311.18828).
|
| 138 |
+
Input:
|
| 139 |
+
- image_or_video: a tensor with shape [B, F, C, H, W] where the number of frame is 1 for images.
|
| 140 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 141 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 142 |
+
- gradient_mask: a boolean tensor with the same shape as image_or_video indicating which pixels to compute loss .
|
| 143 |
+
Output:
|
| 144 |
+
- dmd_loss: a scalar tensor representing the DMD loss.
|
| 145 |
+
- dmd_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 146 |
+
"""
|
| 147 |
+
original_latent = image_or_video
|
| 148 |
+
|
| 149 |
+
batch_size, num_frame = image_or_video.shape[:2]
|
| 150 |
+
|
| 151 |
+
with torch.no_grad():
|
| 152 |
+
# Step 1: Randomly sample timestep based on the given schedule and corresponding noise
|
| 153 |
+
timestep = self._get_timestep(
|
| 154 |
+
0,
|
| 155 |
+
self.num_train_timestep,
|
| 156 |
+
batch_size,
|
| 157 |
+
num_frame,
|
| 158 |
+
self.num_frame_per_block,
|
| 159 |
+
uniform_timestep=True,
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
if self.timestep_shift > 1:
|
| 163 |
+
timestep = (
|
| 164 |
+
self.timestep_shift
|
| 165 |
+
* (timestep / 1000)
|
| 166 |
+
/ (1 + (self.timestep_shift - 1) * (timestep / 1000))
|
| 167 |
+
* 1000
|
| 168 |
+
)
|
| 169 |
+
timestep = timestep.clamp(self.min_step, self.max_step)
|
| 170 |
+
|
| 171 |
+
noise = torch.randn_like(image_or_video)
|
| 172 |
+
noisy_latent = (
|
| 173 |
+
self.scheduler.add_noise(
|
| 174 |
+
image_or_video.flatten(0, 1),
|
| 175 |
+
noise.flatten(0, 1),
|
| 176 |
+
timestep.flatten(0, 1),
|
| 177 |
+
)
|
| 178 |
+
.detach()
|
| 179 |
+
.unflatten(0, (batch_size, num_frame))
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
# Step 2: Compute the KL grad
|
| 183 |
+
grad, dmd_log_dict = self._compute_kl_grad(
|
| 184 |
+
noisy_image_or_video=noisy_latent,
|
| 185 |
+
estimated_clean_image_or_video=original_latent,
|
| 186 |
+
timestep=timestep,
|
| 187 |
+
conditional_dict=conditional_dict,
|
| 188 |
+
unconditional_dict=unconditional_dict,
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
if gradient_mask is not None:
|
| 192 |
+
dmd_loss = 0.5 * F.mse_loss(
|
| 193 |
+
original_latent.double()[gradient_mask],
|
| 194 |
+
(original_latent.double() - grad.double()).detach()[gradient_mask],
|
| 195 |
+
reduction="mean",
|
| 196 |
+
)
|
| 197 |
+
else:
|
| 198 |
+
dmd_loss = 0.5 * F.mse_loss(
|
| 199 |
+
original_latent.double(),
|
| 200 |
+
(original_latent.double() - grad.double()).detach(),
|
| 201 |
+
reduction="mean",
|
| 202 |
+
)
|
| 203 |
+
return dmd_loss, dmd_log_dict
|
| 204 |
+
|
| 205 |
+
def _run_generator(
|
| 206 |
+
self,
|
| 207 |
+
image_or_video_shape,
|
| 208 |
+
conditional_dict: dict,
|
| 209 |
+
clean_latent: torch.tensor,
|
| 210 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 211 |
+
"""
|
| 212 |
+
Optionally simulate the generator's input from noise using backward simulation
|
| 213 |
+
and then run the generator for one-step.
|
| 214 |
+
Input:
|
| 215 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 216 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 217 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 218 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 219 |
+
- initial_latent: a tensor containing the initial latents [B, F, C, H, W].
|
| 220 |
+
Output:
|
| 221 |
+
- pred_image: a tensor with shape [B, F, C, H, W].
|
| 222 |
+
"""
|
| 223 |
+
simulated_noisy_input = []
|
| 224 |
+
for timestep in self.denoising_step_list:
|
| 225 |
+
noise = torch.randn(image_or_video_shape, device=self.device, dtype=self.dtype)
|
| 226 |
+
|
| 227 |
+
noisy_timestep = timestep * torch.ones(
|
| 228 |
+
image_or_video_shape[:2], device=self.device, dtype=torch.long
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
if timestep != 0:
|
| 232 |
+
noisy_image = self.scheduler.add_noise(
|
| 233 |
+
clean_latent.flatten(0, 1),
|
| 234 |
+
noise.flatten(0, 1),
|
| 235 |
+
noisy_timestep.flatten(0, 1),
|
| 236 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 237 |
+
else:
|
| 238 |
+
noisy_image = clean_latent
|
| 239 |
+
|
| 240 |
+
simulated_noisy_input.append(noisy_image)
|
| 241 |
+
|
| 242 |
+
simulated_noisy_input = torch.stack(simulated_noisy_input, dim=1)
|
| 243 |
+
|
| 244 |
+
# Step 2: Randomly sample a timestep and pick the corresponding input
|
| 245 |
+
index = self._get_timestep(
|
| 246 |
+
0,
|
| 247 |
+
len(self.denoising_step_list),
|
| 248 |
+
image_or_video_shape[0],
|
| 249 |
+
image_or_video_shape[1],
|
| 250 |
+
self.num_frame_per_block,
|
| 251 |
+
uniform_timestep=False,
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
# select the corresponding timestep's noisy input from the stacked tensor [B, T, F, C, H, W]
|
| 255 |
+
noisy_input = torch.gather(
|
| 256 |
+
simulated_noisy_input,
|
| 257 |
+
dim=1,
|
| 258 |
+
index=index.reshape(index.shape[0], 1, index.shape[1], 1, 1, 1)
|
| 259 |
+
.expand(-1, -1, -1, *image_or_video_shape[2:])
|
| 260 |
+
.to(self.device),
|
| 261 |
+
).squeeze(1)
|
| 262 |
+
|
| 263 |
+
timestep = self.denoising_step_list[index].to(self.device)
|
| 264 |
+
|
| 265 |
+
_, pred_image_or_video = self.generator(
|
| 266 |
+
noisy_image_or_video=noisy_input,
|
| 267 |
+
conditional_dict=conditional_dict,
|
| 268 |
+
timestep=timestep,
|
| 269 |
+
clean_x=clean_latent if self.teacher_forcing else None,
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
gradient_mask = None # timestep != 0
|
| 273 |
+
|
| 274 |
+
pred_image_or_video = pred_image_or_video.type_as(noisy_input)
|
| 275 |
+
|
| 276 |
+
return pred_image_or_video, gradient_mask
|
| 277 |
+
|
| 278 |
+
def generator_loss(
|
| 279 |
+
self,
|
| 280 |
+
image_or_video_shape,
|
| 281 |
+
conditional_dict: dict,
|
| 282 |
+
unconditional_dict: dict,
|
| 283 |
+
clean_latent: torch.Tensor,
|
| 284 |
+
initial_latent: torch.Tensor = None,
|
| 285 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 286 |
+
"""
|
| 287 |
+
Generate image/videos from noise and compute the DMD loss.
|
| 288 |
+
The noisy input to the generator is backward simulated.
|
| 289 |
+
This removes the need of any datasets during distillation.
|
| 290 |
+
See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.
|
| 291 |
+
Input:
|
| 292 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 293 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 294 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 295 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 296 |
+
Output:
|
| 297 |
+
- loss: a scalar tensor representing the generator loss.
|
| 298 |
+
- generator_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 299 |
+
"""
|
| 300 |
+
# Step 1: Run generator on backward simulated noisy input
|
| 301 |
+
pred_image, gradient_mask = self._run_generator(
|
| 302 |
+
image_or_video_shape=image_or_video_shape,
|
| 303 |
+
conditional_dict=conditional_dict,
|
| 304 |
+
clean_latent=clean_latent,
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
# Step 2: Compute the DMD loss
|
| 308 |
+
dmd_loss, dmd_log_dict = self.compute_distribution_matching_loss(
|
| 309 |
+
image_or_video=pred_image,
|
| 310 |
+
conditional_dict=conditional_dict,
|
| 311 |
+
unconditional_dict=unconditional_dict,
|
| 312 |
+
gradient_mask=gradient_mask,
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
# Step 3: TODO: Implement the GAN loss
|
| 316 |
+
|
| 317 |
+
return dmd_loss, dmd_log_dict
|
| 318 |
+
|
| 319 |
+
def critic_loss(
|
| 320 |
+
self,
|
| 321 |
+
image_or_video_shape,
|
| 322 |
+
conditional_dict: dict,
|
| 323 |
+
unconditional_dict: dict,
|
| 324 |
+
clean_latent: torch.Tensor,
|
| 325 |
+
initial_latent: torch.Tensor = None,
|
| 326 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 327 |
+
"""
|
| 328 |
+
Generate image/videos from noise and train the critic with generated samples.
|
| 329 |
+
The noisy input to the generator is backward simulated.
|
| 330 |
+
This removes the need of any datasets during distillation.
|
| 331 |
+
See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.
|
| 332 |
+
Input:
|
| 333 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 334 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 335 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 336 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 337 |
+
Output:
|
| 338 |
+
- loss: a scalar tensor representing the generator loss.
|
| 339 |
+
- critic_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 340 |
+
"""
|
| 341 |
+
|
| 342 |
+
# Step 1: Run generator on backward simulated noisy input
|
| 343 |
+
with torch.no_grad():
|
| 344 |
+
generated_image, _ = self._run_generator(
|
| 345 |
+
image_or_video_shape=image_or_video_shape,
|
| 346 |
+
conditional_dict=conditional_dict,
|
| 347 |
+
clean_latent=clean_latent,
|
| 348 |
+
)
|
| 349 |
+
|
| 350 |
+
# Step 2: Compute the fake prediction
|
| 351 |
+
critic_timestep = self._get_timestep(
|
| 352 |
+
0,
|
| 353 |
+
self.num_train_timestep,
|
| 354 |
+
image_or_video_shape[0],
|
| 355 |
+
image_or_video_shape[1],
|
| 356 |
+
self.num_frame_per_block,
|
| 357 |
+
uniform_timestep=True,
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
if self.timestep_shift > 1:
|
| 361 |
+
critic_timestep = (
|
| 362 |
+
self.timestep_shift
|
| 363 |
+
* (critic_timestep / 1000)
|
| 364 |
+
/ (1 + (self.timestep_shift - 1) * (critic_timestep / 1000))
|
| 365 |
+
* 1000
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
critic_timestep = critic_timestep.clamp(self.min_step, self.max_step)
|
| 369 |
+
|
| 370 |
+
critic_noise = torch.randn_like(generated_image)
|
| 371 |
+
noisy_generated_image = self.scheduler.add_noise(
|
| 372 |
+
generated_image.flatten(0, 1),
|
| 373 |
+
critic_noise.flatten(0, 1),
|
| 374 |
+
critic_timestep.flatten(0, 1),
|
| 375 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 376 |
+
|
| 377 |
+
_, pred_fake_image = self.fake_score(
|
| 378 |
+
noisy_image_or_video=noisy_generated_image,
|
| 379 |
+
conditional_dict=conditional_dict,
|
| 380 |
+
timestep=critic_timestep,
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
# Step 3: Compute the denoising loss for the fake critic
|
| 384 |
+
if self.args.denoising_loss_type == "flow":
|
| 385 |
+
from utils.wan_wrapper import WanDiffusionWrapper
|
| 386 |
+
|
| 387 |
+
flow_pred = WanDiffusionWrapper._convert_x0_to_flow_pred(
|
| 388 |
+
scheduler=self.scheduler,
|
| 389 |
+
x0_pred=pred_fake_image.flatten(0, 1),
|
| 390 |
+
xt=noisy_generated_image.flatten(0, 1),
|
| 391 |
+
timestep=critic_timestep.flatten(0, 1),
|
| 392 |
+
)
|
| 393 |
+
pred_fake_noise = None
|
| 394 |
+
else:
|
| 395 |
+
flow_pred = None
|
| 396 |
+
pred_fake_noise = self.scheduler.convert_x0_to_noise(
|
| 397 |
+
x0=pred_fake_image.flatten(0, 1),
|
| 398 |
+
xt=noisy_generated_image.flatten(0, 1),
|
| 399 |
+
timestep=critic_timestep.flatten(0, 1),
|
| 400 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 401 |
+
|
| 402 |
+
denoising_loss = self.denoising_loss_func(
|
| 403 |
+
x=generated_image.flatten(0, 1),
|
| 404 |
+
x_pred=pred_fake_image.flatten(0, 1),
|
| 405 |
+
noise=critic_noise.flatten(0, 1),
|
| 406 |
+
noise_pred=pred_fake_noise,
|
| 407 |
+
alphas_cumprod=self.scheduler.alphas_cumprod,
|
| 408 |
+
timestep=critic_timestep.flatten(0, 1),
|
| 409 |
+
flow_pred=flow_pred,
|
| 410 |
+
)
|
| 411 |
+
|
| 412 |
+
# Step 4: TODO: Compute the GAN loss
|
| 413 |
+
|
| 414 |
+
# Step 5: Debugging Log
|
| 415 |
+
critic_log_dict = {"critic_timestep": critic_timestep.detach()}
|
| 416 |
+
|
| 417 |
+
return denoising_loss, critic_log_dict
|
model/diffusion.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Tuple
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
from model.base import BaseModel
|
| 5 |
+
from utils.wan_wrapper import WanDiffusionWrapper, WanTextEncoder, WanVAEWrapper
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class CausalDiffusion(BaseModel):
|
| 9 |
+
def __init__(
|
| 10 |
+
self,
|
| 11 |
+
args,
|
| 12 |
+
device,
|
| 13 |
+
):
|
| 14 |
+
"""
|
| 15 |
+
Initialize the Diffusion loss module.
|
| 16 |
+
"""
|
| 17 |
+
super().__init__(args, device)
|
| 18 |
+
self.num_frame_per_block = getattr(args, "num_frame_per_block", 1)
|
| 19 |
+
if self.num_frame_per_block > 1:
|
| 20 |
+
self.generator.model.num_frame_per_block = self.num_frame_per_block
|
| 21 |
+
self.independent_first_frame = getattr(args, "independent_first_frame", False)
|
| 22 |
+
if self.independent_first_frame:
|
| 23 |
+
self.generator.model.independent_first_frame = True
|
| 24 |
+
|
| 25 |
+
if args.gradient_checkpointing:
|
| 26 |
+
self.generator.enable_gradient_checkpointing()
|
| 27 |
+
|
| 28 |
+
# Step 2: Initialize all hyperparameters
|
| 29 |
+
self.num_train_timestep = args.num_train_timestep
|
| 30 |
+
self.min_step = int(0.02 * self.num_train_timestep)
|
| 31 |
+
self.max_step = int(0.98 * self.num_train_timestep)
|
| 32 |
+
self.guidance_scale = args.guidance_scale
|
| 33 |
+
self.timestep_shift = getattr(args, "timestep_shift", 1.0)
|
| 34 |
+
self.teacher_forcing = getattr(args, "teacher_forcing", False)
|
| 35 |
+
# Noise augmentation in teacher forcing, we add small noise to clean context latents
|
| 36 |
+
self.noise_augmentation_max_timestep = getattr(args, "noise_augmentation_max_timestep", 0)
|
| 37 |
+
|
| 38 |
+
def _initialize_models(self, args):
|
| 39 |
+
self.generator = WanDiffusionWrapper(
|
| 40 |
+
**getattr(args, "model_kwargs", {}),
|
| 41 |
+
is_causal=True,
|
| 42 |
+
)
|
| 43 |
+
self.generator.model.requires_grad_(True)
|
| 44 |
+
|
| 45 |
+
self.text_encoder = WanTextEncoder()
|
| 46 |
+
self.text_encoder.requires_grad_(False)
|
| 47 |
+
|
| 48 |
+
self.vae = WanVAEWrapper()
|
| 49 |
+
self.vae.requires_grad_(False)
|
| 50 |
+
|
| 51 |
+
def generator_loss(
|
| 52 |
+
self,
|
| 53 |
+
image_or_video_shape,
|
| 54 |
+
conditional_dict: dict,
|
| 55 |
+
unconditional_dict: dict,
|
| 56 |
+
clean_latent: torch.Tensor,
|
| 57 |
+
initial_latent: torch.Tensor = None,
|
| 58 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 59 |
+
"""
|
| 60 |
+
Generate image/videos from noise and compute the DMD loss.
|
| 61 |
+
The noisy input to the generator is backward simulated.
|
| 62 |
+
This removes the need of any datasets during distillation.
|
| 63 |
+
See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.
|
| 64 |
+
Input:
|
| 65 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 66 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 67 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 68 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 69 |
+
Output:
|
| 70 |
+
- loss: a scalar tensor representing the generator loss.
|
| 71 |
+
- generator_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 72 |
+
"""
|
| 73 |
+
noise = torch.randn_like(clean_latent)
|
| 74 |
+
batch_size, num_frame = image_or_video_shape[:2]
|
| 75 |
+
|
| 76 |
+
# Step 2: Randomly sample a timestep and add noise to denoiser inputs
|
| 77 |
+
index = self._get_timestep(
|
| 78 |
+
0,
|
| 79 |
+
self.scheduler.num_train_timesteps,
|
| 80 |
+
image_or_video_shape[0],
|
| 81 |
+
image_or_video_shape[1],
|
| 82 |
+
self.num_frame_per_block,
|
| 83 |
+
uniform_timestep=False,
|
| 84 |
+
)
|
| 85 |
+
timestep = self.scheduler.timesteps[index].to(dtype=self.dtype, device=self.device)
|
| 86 |
+
noisy_latents = self.scheduler.add_noise(
|
| 87 |
+
clean_latent.flatten(0, 1),
|
| 88 |
+
noise.flatten(0, 1),
|
| 89 |
+
timestep.flatten(0, 1),
|
| 90 |
+
).unflatten(0, (batch_size, num_frame))
|
| 91 |
+
training_target = self.scheduler.training_target(clean_latent, noise, timestep)
|
| 92 |
+
|
| 93 |
+
# Step 3: Noise augmentation, also add small noise to clean context latents
|
| 94 |
+
if self.noise_augmentation_max_timestep > 0:
|
| 95 |
+
index_clean_aug = self._get_timestep(
|
| 96 |
+
0,
|
| 97 |
+
self.noise_augmentation_max_timestep,
|
| 98 |
+
image_or_video_shape[0],
|
| 99 |
+
image_or_video_shape[1],
|
| 100 |
+
self.num_frame_per_block,
|
| 101 |
+
uniform_timestep=False,
|
| 102 |
+
)
|
| 103 |
+
timestep_clean_aug = self.scheduler.timesteps[index_clean_aug].to(
|
| 104 |
+
dtype=self.dtype, device=self.device
|
| 105 |
+
)
|
| 106 |
+
clean_latent_aug = self.scheduler.add_noise(
|
| 107 |
+
clean_latent.flatten(0, 1),
|
| 108 |
+
noise.flatten(0, 1),
|
| 109 |
+
timestep_clean_aug.flatten(0, 1),
|
| 110 |
+
).unflatten(0, (batch_size, num_frame))
|
| 111 |
+
else:
|
| 112 |
+
clean_latent_aug = clean_latent
|
| 113 |
+
timestep_clean_aug = None
|
| 114 |
+
|
| 115 |
+
# Compute loss
|
| 116 |
+
flow_pred, x0_pred = self.generator(
|
| 117 |
+
noisy_image_or_video=noisy_latents,
|
| 118 |
+
conditional_dict=conditional_dict,
|
| 119 |
+
timestep=timestep,
|
| 120 |
+
clean_x=clean_latent_aug if self.teacher_forcing else None,
|
| 121 |
+
aug_t=timestep_clean_aug if self.teacher_forcing else None,
|
| 122 |
+
)
|
| 123 |
+
# loss = torch.nn.functional.mse_loss(flow_pred.float(), training_target.float())
|
| 124 |
+
loss = torch.nn.functional.mse_loss(
|
| 125 |
+
flow_pred.float(), training_target.float(), reduction="none"
|
| 126 |
+
).mean(dim=(2, 3, 4))
|
| 127 |
+
loss = loss * self.scheduler.training_weight(timestep).unflatten(0, (batch_size, num_frame))
|
| 128 |
+
loss = loss.mean()
|
| 129 |
+
|
| 130 |
+
log_dict = {"x0": clean_latent.detach(), "x0_pred": x0_pred.detach()}
|
| 131 |
+
return loss, log_dict
|
model/dmd.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pipeline import SelfForcingTrainingPipeline
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
from typing import Optional, Tuple
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from model.base import SelfForcingModel
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class DMD(SelfForcingModel):
|
| 10 |
+
def __init__(
|
| 11 |
+
self,
|
| 12 |
+
args,
|
| 13 |
+
device,
|
| 14 |
+
):
|
| 15 |
+
"""
|
| 16 |
+
Initialize the DMD (Distribution Matching Distillation) module.
|
| 17 |
+
This class is self-contained and compute generator and fake score losses
|
| 18 |
+
in the forward pass.
|
| 19 |
+
"""
|
| 20 |
+
super().__init__(args, device)
|
| 21 |
+
self.num_frame_per_block = getattr(args, "num_frame_per_block", 1)
|
| 22 |
+
self.same_step_across_blocks = getattr(args, "same_step_across_blocks", True)
|
| 23 |
+
self.num_training_frames = getattr(args, "num_training_frames", 21)
|
| 24 |
+
|
| 25 |
+
if self.num_frame_per_block > 1:
|
| 26 |
+
self.generator.model.num_frame_per_block = self.num_frame_per_block
|
| 27 |
+
|
| 28 |
+
self.independent_first_frame = getattr(args, "independent_first_frame", False)
|
| 29 |
+
if self.independent_first_frame:
|
| 30 |
+
self.generator.model.independent_first_frame = True
|
| 31 |
+
if args.gradient_checkpointing:
|
| 32 |
+
self.generator.enable_gradient_checkpointing()
|
| 33 |
+
self.fake_score.enable_gradient_checkpointing()
|
| 34 |
+
|
| 35 |
+
# this will be init later with fsdp-wrapped modules
|
| 36 |
+
self.inference_pipeline: SelfForcingTrainingPipeline = None
|
| 37 |
+
|
| 38 |
+
# Step 2: Initialize all dmd hyperparameters
|
| 39 |
+
self.num_train_timestep = args.num_train_timestep
|
| 40 |
+
self.min_step = int(0.02 * self.num_train_timestep)
|
| 41 |
+
self.max_step = int(0.98 * self.num_train_timestep)
|
| 42 |
+
if hasattr(args, "real_guidance_scale"):
|
| 43 |
+
self.real_guidance_scale = args.real_guidance_scale
|
| 44 |
+
self.fake_guidance_scale = args.fake_guidance_scale
|
| 45 |
+
else:
|
| 46 |
+
self.real_guidance_scale = args.guidance_scale
|
| 47 |
+
self.fake_guidance_scale = 0.0
|
| 48 |
+
self.timestep_shift = getattr(args, "timestep_shift", 1.0)
|
| 49 |
+
self.ts_schedule = getattr(args, "ts_schedule", True)
|
| 50 |
+
self.ts_schedule_max = getattr(args, "ts_schedule_max", False)
|
| 51 |
+
self.min_score_timestep = getattr(args, "min_score_timestep", 0)
|
| 52 |
+
|
| 53 |
+
if getattr(self.scheduler, "alphas_cumprod", None) is not None:
|
| 54 |
+
self.scheduler.alphas_cumprod = self.scheduler.alphas_cumprod.to(device)
|
| 55 |
+
else:
|
| 56 |
+
self.scheduler.alphas_cumprod = None
|
| 57 |
+
|
| 58 |
+
def _compute_kl_grad(
|
| 59 |
+
self,
|
| 60 |
+
noisy_image_or_video: torch.Tensor,
|
| 61 |
+
estimated_clean_image_or_video: torch.Tensor,
|
| 62 |
+
timestep: torch.Tensor,
|
| 63 |
+
conditional_dict: dict,
|
| 64 |
+
unconditional_dict: dict,
|
| 65 |
+
normalization: bool = True,
|
| 66 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 67 |
+
"""
|
| 68 |
+
Compute the KL grad (eq 7 in https://arxiv.org/abs/2311.18828).
|
| 69 |
+
Input:
|
| 70 |
+
- noisy_image_or_video: a tensor with shape [B, F, C, H, W] where the number of frame is 1 for images.
|
| 71 |
+
- estimated_clean_image_or_video: a tensor with shape [B, F, C, H, W] representing the estimated clean image or video.
|
| 72 |
+
- timestep: a tensor with shape [B, F] containing the randomly generated timestep.
|
| 73 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 74 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 75 |
+
- normalization: a boolean indicating whether to normalize the gradient.
|
| 76 |
+
Output:
|
| 77 |
+
- kl_grad: a tensor representing the KL grad.
|
| 78 |
+
- kl_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 79 |
+
"""
|
| 80 |
+
# Step 1: Compute the fake score
|
| 81 |
+
_, pred_fake_image_cond = self.fake_score(
|
| 82 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 83 |
+
conditional_dict=conditional_dict,
|
| 84 |
+
timestep=timestep,
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
if self.fake_guidance_scale != 0.0:
|
| 88 |
+
_, pred_fake_image_uncond = self.fake_score(
|
| 89 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 90 |
+
conditional_dict=unconditional_dict,
|
| 91 |
+
timestep=timestep,
|
| 92 |
+
)
|
| 93 |
+
pred_fake_image = (
|
| 94 |
+
pred_fake_image_cond
|
| 95 |
+
+ (pred_fake_image_cond - pred_fake_image_uncond) * self.fake_guidance_scale
|
| 96 |
+
)
|
| 97 |
+
else:
|
| 98 |
+
pred_fake_image = pred_fake_image_cond
|
| 99 |
+
|
| 100 |
+
# Step 2: Compute the real score
|
| 101 |
+
# We compute the conditional and unconditional prediction
|
| 102 |
+
# and add them together to achieve cfg (https://arxiv.org/abs/2207.12598)
|
| 103 |
+
_, pred_real_image_cond = self.real_score(
|
| 104 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 105 |
+
conditional_dict=conditional_dict,
|
| 106 |
+
timestep=timestep,
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
_, pred_real_image_uncond = self.real_score(
|
| 110 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 111 |
+
conditional_dict=unconditional_dict,
|
| 112 |
+
timestep=timestep,
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
pred_real_image = (
|
| 116 |
+
pred_real_image_cond
|
| 117 |
+
+ (pred_real_image_cond - pred_real_image_uncond) * self.real_guidance_scale
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
# Step 3: Compute the DMD gradient (DMD paper eq. 7).
|
| 121 |
+
grad = pred_fake_image - pred_real_image
|
| 122 |
+
|
| 123 |
+
# TODO: Change the normalizer for causal teacher
|
| 124 |
+
if normalization:
|
| 125 |
+
# Step 4: Gradient normalization (DMD paper eq. 8).
|
| 126 |
+
p_real = estimated_clean_image_or_video - pred_real_image
|
| 127 |
+
normalizer = torch.abs(p_real).mean(dim=[1, 2, 3, 4], keepdim=True)
|
| 128 |
+
grad = grad / normalizer
|
| 129 |
+
grad = torch.nan_to_num(grad)
|
| 130 |
+
|
| 131 |
+
return grad, {
|
| 132 |
+
"dmdtrain_gradient_norm": torch.mean(torch.abs(grad)).detach(),
|
| 133 |
+
"timestep": timestep.detach(),
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
def compute_distribution_matching_loss(
|
| 137 |
+
self,
|
| 138 |
+
image_or_video: torch.Tensor,
|
| 139 |
+
conditional_dict: dict,
|
| 140 |
+
unconditional_dict: dict,
|
| 141 |
+
gradient_mask: Optional[torch.Tensor] = None,
|
| 142 |
+
denoised_timestep_from: int = 0,
|
| 143 |
+
denoised_timestep_to: int = 0,
|
| 144 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 145 |
+
"""
|
| 146 |
+
Compute the DMD loss (eq 7 in https://arxiv.org/abs/2311.18828).
|
| 147 |
+
Input:
|
| 148 |
+
- image_or_video: a tensor with shape [B, F, C, H, W] where the number of frame is 1 for images.
|
| 149 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 150 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 151 |
+
- gradient_mask: a boolean tensor with the same shape as image_or_video indicating which pixels to compute loss .
|
| 152 |
+
Output:
|
| 153 |
+
- dmd_loss: a scalar tensor representing the DMD loss.
|
| 154 |
+
- dmd_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 155 |
+
"""
|
| 156 |
+
original_latent = image_or_video
|
| 157 |
+
|
| 158 |
+
batch_size, num_frame = image_or_video.shape[:2]
|
| 159 |
+
|
| 160 |
+
with torch.no_grad():
|
| 161 |
+
# Step 1: Randomly sample timestep based on the given schedule and corresponding noise
|
| 162 |
+
min_timestep = (
|
| 163 |
+
denoised_timestep_to
|
| 164 |
+
if self.ts_schedule and denoised_timestep_to is not None
|
| 165 |
+
else self.min_score_timestep
|
| 166 |
+
)
|
| 167 |
+
max_timestep = (
|
| 168 |
+
denoised_timestep_from
|
| 169 |
+
if self.ts_schedule_max and denoised_timestep_from is not None
|
| 170 |
+
else self.num_train_timestep
|
| 171 |
+
)
|
| 172 |
+
timestep = self._get_timestep(
|
| 173 |
+
min_timestep,
|
| 174 |
+
max_timestep,
|
| 175 |
+
batch_size,
|
| 176 |
+
num_frame,
|
| 177 |
+
self.num_frame_per_block,
|
| 178 |
+
uniform_timestep=True,
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
# TODO:should we change it to `timestep = self.scheduler.timesteps[timestep]`?
|
| 182 |
+
if self.timestep_shift > 1:
|
| 183 |
+
timestep = (
|
| 184 |
+
self.timestep_shift
|
| 185 |
+
* (timestep / 1000)
|
| 186 |
+
/ (1 + (self.timestep_shift - 1) * (timestep / 1000))
|
| 187 |
+
* 1000
|
| 188 |
+
)
|
| 189 |
+
timestep = timestep.clamp(self.min_step, self.max_step)
|
| 190 |
+
|
| 191 |
+
noise = torch.randn_like(image_or_video)
|
| 192 |
+
noisy_latent = (
|
| 193 |
+
self.scheduler.add_noise(
|
| 194 |
+
image_or_video.flatten(0, 1),
|
| 195 |
+
noise.flatten(0, 1),
|
| 196 |
+
timestep.flatten(0, 1),
|
| 197 |
+
)
|
| 198 |
+
.detach()
|
| 199 |
+
.unflatten(0, (batch_size, num_frame))
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
# Step 2: Compute the KL grad
|
| 203 |
+
grad, dmd_log_dict = self._compute_kl_grad(
|
| 204 |
+
noisy_image_or_video=noisy_latent,
|
| 205 |
+
estimated_clean_image_or_video=original_latent,
|
| 206 |
+
timestep=timestep,
|
| 207 |
+
conditional_dict=conditional_dict,
|
| 208 |
+
unconditional_dict=unconditional_dict,
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
if gradient_mask is not None:
|
| 212 |
+
dmd_loss = 0.5 * F.mse_loss(
|
| 213 |
+
original_latent.double()[gradient_mask],
|
| 214 |
+
(original_latent.double() - grad.double()).detach()[gradient_mask],
|
| 215 |
+
reduction="mean",
|
| 216 |
+
)
|
| 217 |
+
else:
|
| 218 |
+
dmd_loss = 0.5 * F.mse_loss(
|
| 219 |
+
original_latent.double(),
|
| 220 |
+
(original_latent.double() - grad.double()).detach(),
|
| 221 |
+
reduction="mean",
|
| 222 |
+
)
|
| 223 |
+
return dmd_loss, dmd_log_dict
|
| 224 |
+
|
| 225 |
+
def generator_loss(
|
| 226 |
+
self,
|
| 227 |
+
image_or_video_shape,
|
| 228 |
+
conditional_dict: dict,
|
| 229 |
+
unconditional_dict: dict,
|
| 230 |
+
clean_latent: torch.Tensor,
|
| 231 |
+
initial_latent: torch.Tensor = None,
|
| 232 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 233 |
+
"""
|
| 234 |
+
Generate image/videos from noise and compute the DMD loss.
|
| 235 |
+
The noisy input to the generator is backward simulated.
|
| 236 |
+
This removes the need of any datasets during distillation.
|
| 237 |
+
See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.
|
| 238 |
+
Input:
|
| 239 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 240 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 241 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 242 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 243 |
+
Output:
|
| 244 |
+
- loss: a scalar tensor representing the generator loss.
|
| 245 |
+
- generator_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 246 |
+
"""
|
| 247 |
+
# Step 1: Unroll generator to obtain fake videos
|
| 248 |
+
(
|
| 249 |
+
pred_image,
|
| 250 |
+
gradient_mask,
|
| 251 |
+
denoised_timestep_from,
|
| 252 |
+
denoised_timestep_to,
|
| 253 |
+
) = self._run_generator(
|
| 254 |
+
image_or_video_shape=image_or_video_shape,
|
| 255 |
+
conditional_dict=conditional_dict,
|
| 256 |
+
initial_latent=initial_latent,
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
# Step 2: Compute the DMD loss
|
| 260 |
+
dmd_loss, dmd_log_dict = self.compute_distribution_matching_loss(
|
| 261 |
+
image_or_video=pred_image,
|
| 262 |
+
conditional_dict=conditional_dict,
|
| 263 |
+
unconditional_dict=unconditional_dict,
|
| 264 |
+
gradient_mask=gradient_mask,
|
| 265 |
+
denoised_timestep_from=denoised_timestep_from,
|
| 266 |
+
denoised_timestep_to=denoised_timestep_to,
|
| 267 |
+
)
|
| 268 |
+
|
| 269 |
+
return dmd_loss, dmd_log_dict
|
| 270 |
+
|
| 271 |
+
def critic_loss(
|
| 272 |
+
self,
|
| 273 |
+
image_or_video_shape,
|
| 274 |
+
conditional_dict: dict,
|
| 275 |
+
unconditional_dict: dict,
|
| 276 |
+
clean_latent: torch.Tensor,
|
| 277 |
+
initial_latent: torch.Tensor = None,
|
| 278 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 279 |
+
"""
|
| 280 |
+
Generate image/videos from noise and train the critic with generated samples.
|
| 281 |
+
The noisy input to the generator is backward simulated.
|
| 282 |
+
This removes the need of any datasets during distillation.
|
| 283 |
+
See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.
|
| 284 |
+
Input:
|
| 285 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 286 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 287 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 288 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 289 |
+
Output:
|
| 290 |
+
- loss: a scalar tensor representing the generator loss.
|
| 291 |
+
- critic_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 292 |
+
"""
|
| 293 |
+
|
| 294 |
+
# Step 1: Run generator on backward simulated noisy input
|
| 295 |
+
with torch.no_grad():
|
| 296 |
+
generated_image, _, denoised_timestep_from, denoised_timestep_to = self._run_generator(
|
| 297 |
+
image_or_video_shape=image_or_video_shape,
|
| 298 |
+
conditional_dict=conditional_dict,
|
| 299 |
+
initial_latent=initial_latent,
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
# Step 2: Compute the fake prediction
|
| 303 |
+
min_timestep = (
|
| 304 |
+
denoised_timestep_to
|
| 305 |
+
if self.ts_schedule and denoised_timestep_to is not None
|
| 306 |
+
else self.min_score_timestep
|
| 307 |
+
)
|
| 308 |
+
max_timestep = (
|
| 309 |
+
denoised_timestep_from
|
| 310 |
+
if self.ts_schedule_max and denoised_timestep_from is not None
|
| 311 |
+
else self.num_train_timestep
|
| 312 |
+
)
|
| 313 |
+
critic_timestep = self._get_timestep(
|
| 314 |
+
min_timestep,
|
| 315 |
+
max_timestep,
|
| 316 |
+
image_or_video_shape[0],
|
| 317 |
+
image_or_video_shape[1],
|
| 318 |
+
self.num_frame_per_block,
|
| 319 |
+
uniform_timestep=True,
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
if self.timestep_shift > 1:
|
| 323 |
+
critic_timestep = (
|
| 324 |
+
self.timestep_shift
|
| 325 |
+
* (critic_timestep / 1000)
|
| 326 |
+
/ (1 + (self.timestep_shift - 1) * (critic_timestep / 1000))
|
| 327 |
+
* 1000
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
critic_timestep = critic_timestep.clamp(self.min_step, self.max_step)
|
| 331 |
+
|
| 332 |
+
critic_noise = torch.randn_like(generated_image)
|
| 333 |
+
noisy_generated_image = self.scheduler.add_noise(
|
| 334 |
+
generated_image.flatten(0, 1),
|
| 335 |
+
critic_noise.flatten(0, 1),
|
| 336 |
+
critic_timestep.flatten(0, 1),
|
| 337 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 338 |
+
|
| 339 |
+
_, pred_fake_image = self.fake_score(
|
| 340 |
+
noisy_image_or_video=noisy_generated_image,
|
| 341 |
+
conditional_dict=conditional_dict,
|
| 342 |
+
timestep=critic_timestep,
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
# Step 3: Compute the denoising loss for the fake critic
|
| 346 |
+
if self.args.denoising_loss_type == "flow":
|
| 347 |
+
from utils.wan_wrapper import WanDiffusionWrapper
|
| 348 |
+
|
| 349 |
+
flow_pred = WanDiffusionWrapper._convert_x0_to_flow_pred(
|
| 350 |
+
scheduler=self.scheduler,
|
| 351 |
+
x0_pred=pred_fake_image.flatten(0, 1),
|
| 352 |
+
xt=noisy_generated_image.flatten(0, 1),
|
| 353 |
+
timestep=critic_timestep.flatten(0, 1),
|
| 354 |
+
)
|
| 355 |
+
pred_fake_noise = None
|
| 356 |
+
else:
|
| 357 |
+
flow_pred = None
|
| 358 |
+
pred_fake_noise = self.scheduler.convert_x0_to_noise(
|
| 359 |
+
x0=pred_fake_image.flatten(0, 1),
|
| 360 |
+
xt=noisy_generated_image.flatten(0, 1),
|
| 361 |
+
timestep=critic_timestep.flatten(0, 1),
|
| 362 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 363 |
+
|
| 364 |
+
denoising_loss = self.denoising_loss_func(
|
| 365 |
+
x=generated_image.flatten(0, 1),
|
| 366 |
+
x_pred=pred_fake_image.flatten(0, 1),
|
| 367 |
+
noise=critic_noise.flatten(0, 1),
|
| 368 |
+
noise_pred=pred_fake_noise,
|
| 369 |
+
alphas_cumprod=self.scheduler.alphas_cumprod,
|
| 370 |
+
timestep=critic_timestep.flatten(0, 1),
|
| 371 |
+
flow_pred=flow_pred,
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
# Step 5: Debugging Log
|
| 375 |
+
critic_log_dict = {"critic_timestep": critic_timestep.detach()}
|
| 376 |
+
|
| 377 |
+
return denoising_loss, critic_log_dict
|
model/gan.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
from pipeline import SelfForcingTrainingPipeline
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
from typing import Tuple
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from model.base import SelfForcingModel
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class GAN(SelfForcingModel):
|
| 11 |
+
def __init__(
|
| 12 |
+
self,
|
| 13 |
+
args,
|
| 14 |
+
device,
|
| 15 |
+
):
|
| 16 |
+
"""
|
| 17 |
+
Initialize the GAN module.
|
| 18 |
+
This class is self-contained and compute generator and fake score losses
|
| 19 |
+
in the forward pass.
|
| 20 |
+
"""
|
| 21 |
+
super().__init__(args, device)
|
| 22 |
+
self.num_frame_per_block = getattr(args, "num_frame_per_block", 1)
|
| 23 |
+
self.same_step_across_blocks = getattr(args, "same_step_across_blocks", True)
|
| 24 |
+
self.concat_time_embeddings = getattr(args, "concat_time_embeddings", False)
|
| 25 |
+
self.num_class = args.num_class
|
| 26 |
+
self.relativistic_discriminator = getattr(args, "relativistic_discriminator", False)
|
| 27 |
+
|
| 28 |
+
if self.num_frame_per_block > 1:
|
| 29 |
+
self.generator.model.num_frame_per_block = self.num_frame_per_block
|
| 30 |
+
|
| 31 |
+
self.fake_score.adding_cls_branch(
|
| 32 |
+
atten_dim=1536,
|
| 33 |
+
num_class=args.num_class,
|
| 34 |
+
time_embed_dim=1536 if self.concat_time_embeddings else 0,
|
| 35 |
+
)
|
| 36 |
+
self.fake_score.model.requires_grad_(True)
|
| 37 |
+
|
| 38 |
+
self.independent_first_frame = getattr(args, "independent_first_frame", False)
|
| 39 |
+
if self.independent_first_frame:
|
| 40 |
+
self.generator.model.independent_first_frame = True
|
| 41 |
+
if args.gradient_checkpointing:
|
| 42 |
+
self.generator.enable_gradient_checkpointing()
|
| 43 |
+
self.fake_score.enable_gradient_checkpointing()
|
| 44 |
+
|
| 45 |
+
# this will be init later with fsdp-wrapped modules
|
| 46 |
+
self.inference_pipeline: SelfForcingTrainingPipeline = None
|
| 47 |
+
|
| 48 |
+
# Step 2: Initialize all dmd hyperparameters
|
| 49 |
+
self.num_train_timestep = args.num_train_timestep
|
| 50 |
+
self.min_step = int(0.02 * self.num_train_timestep)
|
| 51 |
+
self.max_step = int(0.98 * self.num_train_timestep)
|
| 52 |
+
if hasattr(args, "real_guidance_scale"):
|
| 53 |
+
self.real_guidance_scale = args.real_guidance_scale
|
| 54 |
+
self.fake_guidance_scale = args.fake_guidance_scale
|
| 55 |
+
else:
|
| 56 |
+
self.real_guidance_scale = args.guidance_scale
|
| 57 |
+
self.fake_guidance_scale = 0.0
|
| 58 |
+
self.timestep_shift = getattr(args, "timestep_shift", 1.0)
|
| 59 |
+
self.critic_timestep_shift = getattr(args, "critic_timestep_shift", self.timestep_shift)
|
| 60 |
+
self.ts_schedule = getattr(args, "ts_schedule", True)
|
| 61 |
+
self.ts_schedule_max = getattr(args, "ts_schedule_max", False)
|
| 62 |
+
self.min_score_timestep = getattr(args, "min_score_timestep", 0)
|
| 63 |
+
|
| 64 |
+
self.gan_g_weight = getattr(args, "gan_g_weight", 1e-2)
|
| 65 |
+
self.gan_d_weight = getattr(args, "gan_d_weight", 1e-2)
|
| 66 |
+
self.r1_weight = getattr(args, "r1_weight", 0.0)
|
| 67 |
+
self.r2_weight = getattr(args, "r2_weight", 0.0)
|
| 68 |
+
self.r1_sigma = getattr(args, "r1_sigma", 0.01)
|
| 69 |
+
self.r2_sigma = getattr(args, "r2_sigma", 0.01)
|
| 70 |
+
|
| 71 |
+
if getattr(self.scheduler, "alphas_cumprod", None) is not None:
|
| 72 |
+
self.scheduler.alphas_cumprod = self.scheduler.alphas_cumprod.to(device)
|
| 73 |
+
else:
|
| 74 |
+
self.scheduler.alphas_cumprod = None
|
| 75 |
+
|
| 76 |
+
def _run_cls_pred_branch(
|
| 77 |
+
self,
|
| 78 |
+
noisy_image_or_video: torch.Tensor,
|
| 79 |
+
conditional_dict: dict,
|
| 80 |
+
timestep: torch.Tensor,
|
| 81 |
+
) -> torch.Tensor:
|
| 82 |
+
"""
|
| 83 |
+
Run the classifier prediction branch on the generated image or video.
|
| 84 |
+
Input:
|
| 85 |
+
- image_or_video: a tensor with shape [B, F, C, H, W].
|
| 86 |
+
Output:
|
| 87 |
+
- cls_pred: a tensor with shape [B, 1, 1, 1, 1] representing the feature map for classification.
|
| 88 |
+
"""
|
| 89 |
+
_, _, noisy_logit = self.fake_score(
|
| 90 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 91 |
+
conditional_dict=conditional_dict,
|
| 92 |
+
timestep=timestep,
|
| 93 |
+
classify_mode=True,
|
| 94 |
+
concat_time_embeddings=self.concat_time_embeddings,
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
return noisy_logit
|
| 98 |
+
|
| 99 |
+
def generator_loss(
|
| 100 |
+
self,
|
| 101 |
+
image_or_video_shape,
|
| 102 |
+
conditional_dict: dict,
|
| 103 |
+
unconditional_dict: dict,
|
| 104 |
+
clean_latent: torch.Tensor,
|
| 105 |
+
initial_latent: torch.Tensor = None,
|
| 106 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 107 |
+
"""
|
| 108 |
+
Generate image/videos from noise and compute the DMD loss.
|
| 109 |
+
The noisy input to the generator is backward simulated.
|
| 110 |
+
This removes the need of any datasets during distillation.
|
| 111 |
+
See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.
|
| 112 |
+
Input:
|
| 113 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 114 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 115 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 116 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 117 |
+
Output:
|
| 118 |
+
- loss: a scalar tensor representing the generator loss.
|
| 119 |
+
- generator_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 120 |
+
"""
|
| 121 |
+
# Step 1: Unroll generator to obtain fake videos
|
| 122 |
+
(
|
| 123 |
+
pred_image,
|
| 124 |
+
gradient_mask,
|
| 125 |
+
denoised_timestep_from,
|
| 126 |
+
denoised_timestep_to,
|
| 127 |
+
) = self._run_generator(
|
| 128 |
+
image_or_video_shape=image_or_video_shape,
|
| 129 |
+
conditional_dict=conditional_dict,
|
| 130 |
+
initial_latent=initial_latent,
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
# Step 2: Get timestep and add noise to generated/real latents
|
| 134 |
+
min_timestep = (
|
| 135 |
+
denoised_timestep_to
|
| 136 |
+
if self.ts_schedule and denoised_timestep_to is not None
|
| 137 |
+
else self.min_score_timestep
|
| 138 |
+
)
|
| 139 |
+
max_timestep = (
|
| 140 |
+
denoised_timestep_from
|
| 141 |
+
if self.ts_schedule_max and denoised_timestep_from is not None
|
| 142 |
+
else self.num_train_timestep
|
| 143 |
+
)
|
| 144 |
+
critic_timestep = self._get_timestep(
|
| 145 |
+
min_timestep,
|
| 146 |
+
max_timestep,
|
| 147 |
+
image_or_video_shape[0],
|
| 148 |
+
image_or_video_shape[1],
|
| 149 |
+
self.num_frame_per_block,
|
| 150 |
+
uniform_timestep=True,
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
if self.critic_timestep_shift > 1:
|
| 154 |
+
critic_timestep = (
|
| 155 |
+
self.critic_timestep_shift
|
| 156 |
+
* (critic_timestep / 1000)
|
| 157 |
+
/ (1 + (self.critic_timestep_shift - 1) * (critic_timestep / 1000))
|
| 158 |
+
* 1000
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
critic_timestep = critic_timestep.clamp(self.min_step, self.max_step)
|
| 162 |
+
|
| 163 |
+
critic_noise = torch.randn_like(pred_image)
|
| 164 |
+
noisy_fake_latent = self.scheduler.add_noise(
|
| 165 |
+
pred_image.flatten(0, 1),
|
| 166 |
+
critic_noise.flatten(0, 1),
|
| 167 |
+
critic_timestep.flatten(0, 1),
|
| 168 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 169 |
+
|
| 170 |
+
# Step 4: Compute the real GAN discriminator loss
|
| 171 |
+
real_image_or_video = clean_latent.clone()
|
| 172 |
+
critic_noise = torch.randn_like(real_image_or_video)
|
| 173 |
+
noisy_real_latent = self.scheduler.add_noise(
|
| 174 |
+
real_image_or_video.flatten(0, 1),
|
| 175 |
+
critic_noise.flatten(0, 1),
|
| 176 |
+
critic_timestep.flatten(0, 1),
|
| 177 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 178 |
+
|
| 179 |
+
conditional_dict["prompt_embeds"] = torch.concatenate(
|
| 180 |
+
(
|
| 181 |
+
conditional_dict["prompt_embeds"],
|
| 182 |
+
conditional_dict["prompt_embeds"],
|
| 183 |
+
),
|
| 184 |
+
dim=0,
|
| 185 |
+
)
|
| 186 |
+
critic_timestep = torch.concatenate((critic_timestep, critic_timestep), dim=0)
|
| 187 |
+
noisy_latent = torch.concatenate((noisy_fake_latent, noisy_real_latent), dim=0)
|
| 188 |
+
_, _, noisy_logit = self.fake_score(
|
| 189 |
+
noisy_image_or_video=noisy_latent,
|
| 190 |
+
conditional_dict=conditional_dict,
|
| 191 |
+
timestep=critic_timestep,
|
| 192 |
+
classify_mode=True,
|
| 193 |
+
concat_time_embeddings=self.concat_time_embeddings,
|
| 194 |
+
)
|
| 195 |
+
noisy_fake_logit, noisy_real_logit = noisy_logit.chunk(2, dim=0)
|
| 196 |
+
|
| 197 |
+
if not self.relativistic_discriminator:
|
| 198 |
+
gan_G_loss = F.softplus(-noisy_fake_logit.float()).mean() * self.gan_g_weight
|
| 199 |
+
else:
|
| 200 |
+
relative_fake_logit = noisy_fake_logit - noisy_real_logit
|
| 201 |
+
gan_G_loss = F.softplus(-relative_fake_logit.float()).mean() * self.gan_g_weight
|
| 202 |
+
|
| 203 |
+
return gan_G_loss
|
| 204 |
+
|
| 205 |
+
def critic_loss(
|
| 206 |
+
self,
|
| 207 |
+
image_or_video_shape,
|
| 208 |
+
conditional_dict: dict,
|
| 209 |
+
unconditional_dict: dict,
|
| 210 |
+
clean_latent: torch.Tensor,
|
| 211 |
+
real_image_or_video: torch.Tensor,
|
| 212 |
+
initial_latent: torch.Tensor = None,
|
| 213 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 214 |
+
"""
|
| 215 |
+
Generate image/videos from noise and train the critic with generated samples.
|
| 216 |
+
The noisy input to the generator is backward simulated.
|
| 217 |
+
This removes the need of any datasets during distillation.
|
| 218 |
+
See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.
|
| 219 |
+
Input:
|
| 220 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 221 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 222 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 223 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 224 |
+
Output:
|
| 225 |
+
- loss: a scalar tensor representing the generator loss.
|
| 226 |
+
- critic_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 227 |
+
"""
|
| 228 |
+
|
| 229 |
+
# Step 1: Run generator on backward simulated noisy input
|
| 230 |
+
with torch.no_grad():
|
| 231 |
+
(
|
| 232 |
+
generated_image,
|
| 233 |
+
_,
|
| 234 |
+
denoised_timestep_from,
|
| 235 |
+
denoised_timestep_to,
|
| 236 |
+
num_sim_steps,
|
| 237 |
+
) = self._run_generator(
|
| 238 |
+
image_or_video_shape=image_or_video_shape,
|
| 239 |
+
conditional_dict=conditional_dict,
|
| 240 |
+
initial_latent=initial_latent,
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
# Step 2: Get timestep and add noise to generated/real latents
|
| 244 |
+
min_timestep = (
|
| 245 |
+
denoised_timestep_to
|
| 246 |
+
if self.ts_schedule and denoised_timestep_to is not None
|
| 247 |
+
else self.min_score_timestep
|
| 248 |
+
)
|
| 249 |
+
max_timestep = (
|
| 250 |
+
denoised_timestep_from
|
| 251 |
+
if self.ts_schedule_max and denoised_timestep_from is not None
|
| 252 |
+
else self.num_train_timestep
|
| 253 |
+
)
|
| 254 |
+
critic_timestep = self._get_timestep(
|
| 255 |
+
min_timestep,
|
| 256 |
+
max_timestep,
|
| 257 |
+
image_or_video_shape[0],
|
| 258 |
+
image_or_video_shape[1],
|
| 259 |
+
self.num_frame_per_block,
|
| 260 |
+
uniform_timestep=True,
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
if self.critic_timestep_shift > 1:
|
| 264 |
+
critic_timestep = (
|
| 265 |
+
self.critic_timestep_shift
|
| 266 |
+
* (critic_timestep / 1000)
|
| 267 |
+
/ (1 + (self.critic_timestep_shift - 1) * (critic_timestep / 1000))
|
| 268 |
+
* 1000
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
critic_timestep = critic_timestep.clamp(self.min_step, self.max_step)
|
| 272 |
+
|
| 273 |
+
critic_noise = torch.randn_like(generated_image)
|
| 274 |
+
noisy_fake_latent = self.scheduler.add_noise(
|
| 275 |
+
generated_image.flatten(0, 1),
|
| 276 |
+
critic_noise.flatten(0, 1),
|
| 277 |
+
critic_timestep.flatten(0, 1),
|
| 278 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 279 |
+
|
| 280 |
+
# Step 4: Compute the real GAN discriminator loss
|
| 281 |
+
noisy_real_latent = self.scheduler.add_noise(
|
| 282 |
+
real_image_or_video.flatten(0, 1),
|
| 283 |
+
critic_noise.flatten(0, 1),
|
| 284 |
+
critic_timestep.flatten(0, 1),
|
| 285 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 286 |
+
|
| 287 |
+
conditional_dict_cloned = copy.deepcopy(conditional_dict)
|
| 288 |
+
conditional_dict_cloned["prompt_embeds"] = torch.concatenate(
|
| 289 |
+
(
|
| 290 |
+
conditional_dict_cloned["prompt_embeds"],
|
| 291 |
+
conditional_dict_cloned["prompt_embeds"],
|
| 292 |
+
),
|
| 293 |
+
dim=0,
|
| 294 |
+
)
|
| 295 |
+
_, _, noisy_logit = self.fake_score(
|
| 296 |
+
noisy_image_or_video=torch.concatenate((noisy_fake_latent, noisy_real_latent), dim=0),
|
| 297 |
+
conditional_dict=conditional_dict_cloned,
|
| 298 |
+
timestep=torch.concatenate((critic_timestep, critic_timestep), dim=0),
|
| 299 |
+
classify_mode=True,
|
| 300 |
+
concat_time_embeddings=self.concat_time_embeddings,
|
| 301 |
+
)
|
| 302 |
+
noisy_fake_logit, noisy_real_logit = noisy_logit.chunk(2, dim=0)
|
| 303 |
+
|
| 304 |
+
if not self.relativistic_discriminator:
|
| 305 |
+
gan_D_loss = (
|
| 306 |
+
F.softplus(-noisy_real_logit.float()).mean()
|
| 307 |
+
+ F.softplus(noisy_fake_logit.float()).mean()
|
| 308 |
+
)
|
| 309 |
+
else:
|
| 310 |
+
relative_real_logit = noisy_real_logit - noisy_fake_logit
|
| 311 |
+
gan_D_loss = F.softplus(-relative_real_logit.float()).mean()
|
| 312 |
+
gan_D_loss = gan_D_loss * self.gan_d_weight
|
| 313 |
+
|
| 314 |
+
# R1 regularization
|
| 315 |
+
if self.r1_weight > 0.0:
|
| 316 |
+
noisy_real_latent_perturbed = noisy_real_latent.clone()
|
| 317 |
+
epison_real = self.r1_sigma * torch.randn_like(noisy_real_latent_perturbed)
|
| 318 |
+
noisy_real_latent_perturbed = noisy_real_latent_perturbed + epison_real
|
| 319 |
+
noisy_real_logit_perturbed = self._run_cls_pred_branch(
|
| 320 |
+
noisy_image_or_video=noisy_real_latent_perturbed,
|
| 321 |
+
conditional_dict=conditional_dict,
|
| 322 |
+
timestep=critic_timestep,
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
r1_grad = (noisy_real_logit_perturbed - noisy_real_logit) / self.r1_sigma
|
| 326 |
+
r1_loss = self.r1_weight * torch.mean((r1_grad) ** 2)
|
| 327 |
+
else:
|
| 328 |
+
r1_loss = torch.zeros_like(gan_D_loss)
|
| 329 |
+
|
| 330 |
+
# R2 regularization
|
| 331 |
+
if self.r2_weight > 0.0:
|
| 332 |
+
noisy_fake_latent_perturbed = noisy_fake_latent.clone()
|
| 333 |
+
epison_generated = self.r2_sigma * torch.randn_like(noisy_fake_latent_perturbed)
|
| 334 |
+
noisy_fake_latent_perturbed = noisy_fake_latent_perturbed + epison_generated
|
| 335 |
+
noisy_fake_logit_perturbed = self._run_cls_pred_branch(
|
| 336 |
+
noisy_image_or_video=noisy_fake_latent_perturbed,
|
| 337 |
+
conditional_dict=conditional_dict,
|
| 338 |
+
timestep=critic_timestep,
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
r2_grad = (noisy_fake_logit_perturbed - noisy_fake_logit) / self.r2_sigma
|
| 342 |
+
r2_loss = self.r2_weight * torch.mean((r2_grad) ** 2)
|
| 343 |
+
else:
|
| 344 |
+
r2_loss = torch.zeros_like(r2_loss)
|
| 345 |
+
|
| 346 |
+
critic_log_dict = {
|
| 347 |
+
"critic_timestep": critic_timestep.detach(),
|
| 348 |
+
"noisy_real_logit": noisy_real_logit.detach(),
|
| 349 |
+
"noisy_fake_logit": noisy_fake_logit.detach(),
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
return (gan_D_loss, r1_loss, r2_loss), critic_log_dict
|
model/ode_regression.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn.functional as F
|
| 2 |
+
from typing import Tuple
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from model.base import BaseModel
|
| 6 |
+
from utils.wan_wrapper import WanDiffusionWrapper, WanTextEncoder, WanVAEWrapper
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class ODERegression(BaseModel):
|
| 10 |
+
def __init__(
|
| 11 |
+
self,
|
| 12 |
+
args,
|
| 13 |
+
device,
|
| 14 |
+
):
|
| 15 |
+
"""
|
| 16 |
+
Initialize the ODERegression module.
|
| 17 |
+
This class is self-contained and compute generator losses
|
| 18 |
+
in the forward pass given precomputed ode solution pairs.
|
| 19 |
+
This class supports the ode regression loss for both causal and bidirectional models.
|
| 20 |
+
See Sec 4.3 of CausVid https://arxiv.org/abs/2412.07772 for details
|
| 21 |
+
"""
|
| 22 |
+
super().__init__(args, device)
|
| 23 |
+
|
| 24 |
+
# Step 1: Initialize all models
|
| 25 |
+
|
| 26 |
+
self.generator = WanDiffusionWrapper(
|
| 27 |
+
**getattr(args, "model_kwargs", {}),
|
| 28 |
+
is_causal=True,
|
| 29 |
+
)
|
| 30 |
+
self.generator.model.requires_grad_(True)
|
| 31 |
+
if getattr(args, "generator_ckpt", False):
|
| 32 |
+
print(f"Loading pretrained generator from {args.generator_ckpt}")
|
| 33 |
+
state_dict = torch.load(args.generator_ckpt, map_location="cpu")["generator"]
|
| 34 |
+
self.generator.load_state_dict(state_dict, strict=True)
|
| 35 |
+
|
| 36 |
+
self.num_frame_per_block = getattr(args, "num_frame_per_block", 1)
|
| 37 |
+
|
| 38 |
+
if self.num_frame_per_block > 1:
|
| 39 |
+
self.generator.model.num_frame_per_block = self.num_frame_per_block
|
| 40 |
+
|
| 41 |
+
self.independent_first_frame = getattr(
|
| 42 |
+
args,
|
| 43 |
+
"independent_first_frame",
|
| 44 |
+
False,
|
| 45 |
+
)
|
| 46 |
+
if self.independent_first_frame:
|
| 47 |
+
self.generator.model.independent_first_frame = True
|
| 48 |
+
if args.gradient_checkpointing:
|
| 49 |
+
self.generator.enable_gradient_checkpointing()
|
| 50 |
+
|
| 51 |
+
# Step 2: Initialize all hyperparameters
|
| 52 |
+
self.timestep_shift = getattr(args, "timestep_shift", 1.0)
|
| 53 |
+
|
| 54 |
+
def _initialize_models(self, args):
|
| 55 |
+
self.generator = WanDiffusionWrapper(**getattr(args, "model_kwargs", {}), is_causal=True)
|
| 56 |
+
self.generator.model.requires_grad_(True)
|
| 57 |
+
|
| 58 |
+
self.text_encoder = WanTextEncoder()
|
| 59 |
+
self.text_encoder.requires_grad_(False)
|
| 60 |
+
|
| 61 |
+
self.vae = WanVAEWrapper()
|
| 62 |
+
self.vae.requires_grad_(False)
|
| 63 |
+
|
| 64 |
+
@torch.no_grad()
|
| 65 |
+
def _prepare_generator_input(
|
| 66 |
+
self,
|
| 67 |
+
ode_latent: torch.Tensor,
|
| 68 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 69 |
+
"""
|
| 70 |
+
Given a tensor containing the whole ODE sampling trajectories,
|
| 71 |
+
randomly choose an intermediate timestep and return the latent as well as the corresponding timestep.
|
| 72 |
+
Input:
|
| 73 |
+
- ode_latent: a tensor containing the whole ODE sampling trajectories [batch_size, num_denoising_steps, num_frames, num_channels, height, width].
|
| 74 |
+
Output:
|
| 75 |
+
- noisy_input: a tensor containing the selected latent [batch_size, num_frames, num_channels, height, width].
|
| 76 |
+
- timestep: a tensor containing the corresponding timestep [batch_size].
|
| 77 |
+
"""
|
| 78 |
+
(
|
| 79 |
+
batch_size,
|
| 80 |
+
num_denoising_steps,
|
| 81 |
+
num_frames,
|
| 82 |
+
num_channels,
|
| 83 |
+
height,
|
| 84 |
+
width,
|
| 85 |
+
) = ode_latent.shape
|
| 86 |
+
|
| 87 |
+
# Step 1: Randomly choose a timestep for each frame
|
| 88 |
+
index = self._get_timestep(
|
| 89 |
+
0,
|
| 90 |
+
len(self.denoising_step_list),
|
| 91 |
+
batch_size,
|
| 92 |
+
num_frames,
|
| 93 |
+
self.num_frame_per_block,
|
| 94 |
+
uniform_timestep=False,
|
| 95 |
+
)
|
| 96 |
+
if self.args.i2v:
|
| 97 |
+
index[:, 0] = len(self.denoising_step_list) - 1
|
| 98 |
+
|
| 99 |
+
noisy_input = torch.gather(
|
| 100 |
+
ode_latent,
|
| 101 |
+
dim=1,
|
| 102 |
+
index=index.reshape(batch_size, 1, num_frames, 1, 1, 1)
|
| 103 |
+
.expand(-1, -1, -1, num_channels, height, width)
|
| 104 |
+
.to(self.device),
|
| 105 |
+
).squeeze(1)
|
| 106 |
+
|
| 107 |
+
timestep = self.denoising_step_list[index].to(self.device)
|
| 108 |
+
|
| 109 |
+
# if self.extra_noise_step > 0:
|
| 110 |
+
# random_timestep = torch.randint(0, self.extra_noise_step, [
|
| 111 |
+
# batch_size, num_frames], device=self.device, dtype=torch.long)
|
| 112 |
+
# perturbed_noisy_input = self.scheduler.add_noise(
|
| 113 |
+
# noisy_input.flatten(0, 1),
|
| 114 |
+
# torch.randn_like(noisy_input.flatten(0, 1)),
|
| 115 |
+
# random_timestep.flatten(0, 1)
|
| 116 |
+
# ).detach().unflatten(0, (batch_size, num_frames)).type_as(noisy_input)
|
| 117 |
+
|
| 118 |
+
# noisy_input[timestep == 0] = perturbed_noisy_input[timestep == 0]
|
| 119 |
+
|
| 120 |
+
return noisy_input, timestep
|
| 121 |
+
|
| 122 |
+
def generator_loss(
|
| 123 |
+
self,
|
| 124 |
+
ode_latent: torch.Tensor,
|
| 125 |
+
conditional_dict: dict,
|
| 126 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 127 |
+
"""
|
| 128 |
+
Generate image/videos from noisy latents and compute the ODE regression loss.
|
| 129 |
+
Input:
|
| 130 |
+
- ode_latent: a tensor containing the ODE latents [batch_size, num_denoising_steps, num_frames, num_channels, height, width].
|
| 131 |
+
They are ordered from most noisy to clean latents.
|
| 132 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 133 |
+
Output:
|
| 134 |
+
- loss: a scalar tensor representing the generator loss.
|
| 135 |
+
- log_dict: a dictionary containing additional information for loss timestep breakdown.
|
| 136 |
+
"""
|
| 137 |
+
# Step 1: Run generator on noisy latents
|
| 138 |
+
target_latent = ode_latent[:, -1]
|
| 139 |
+
|
| 140 |
+
noisy_input, timestep = self._prepare_generator_input(ode_latent=ode_latent)
|
| 141 |
+
|
| 142 |
+
_, pred_image_or_video = self.generator(
|
| 143 |
+
noisy_image_or_video=noisy_input,
|
| 144 |
+
conditional_dict=conditional_dict,
|
| 145 |
+
timestep=timestep,
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
# Step 2: Compute the regression loss
|
| 149 |
+
mask = timestep != 0
|
| 150 |
+
|
| 151 |
+
loss = F.mse_loss(pred_image_or_video[mask], target_latent[mask], reduction="mean")
|
| 152 |
+
|
| 153 |
+
log_dict = {
|
| 154 |
+
"unnormalized_loss": F.mse_loss(pred_image_or_video, target_latent, reduction="none")
|
| 155 |
+
.mean(dim=[1, 2, 3, 4])
|
| 156 |
+
.detach(),
|
| 157 |
+
"timestep": timestep.float().mean(dim=1).detach(),
|
| 158 |
+
"input": noisy_input.detach(),
|
| 159 |
+
"output": pred_image_or_video.detach(),
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
return loss, log_dict
|
model/sid.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pipeline import SelfForcingTrainingPipeline
|
| 2 |
+
from typing import Optional, Tuple
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from model.base import SelfForcingModel
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class SiD(SelfForcingModel):
|
| 9 |
+
def __init__(
|
| 10 |
+
self,
|
| 11 |
+
args,
|
| 12 |
+
device,
|
| 13 |
+
):
|
| 14 |
+
"""
|
| 15 |
+
Initialize the DMD (Distribution Matching Distillation) module.
|
| 16 |
+
This class is self-contained and compute generator and fake score losses
|
| 17 |
+
in the forward pass.
|
| 18 |
+
"""
|
| 19 |
+
super().__init__(args, device)
|
| 20 |
+
self.num_frame_per_block = getattr(args, "num_frame_per_block", 1)
|
| 21 |
+
|
| 22 |
+
if self.num_frame_per_block > 1:
|
| 23 |
+
self.generator.model.num_frame_per_block = self.num_frame_per_block
|
| 24 |
+
|
| 25 |
+
if args.gradient_checkpointing:
|
| 26 |
+
self.generator.enable_gradient_checkpointing()
|
| 27 |
+
self.fake_score.enable_gradient_checkpointing()
|
| 28 |
+
self.real_score.enable_gradient_checkpointing()
|
| 29 |
+
|
| 30 |
+
# this will be init later with fsdp-wrapped modules
|
| 31 |
+
self.inference_pipeline: SelfForcingTrainingPipeline = None
|
| 32 |
+
|
| 33 |
+
# Step 2: Initialize all dmd hyperparameters
|
| 34 |
+
self.num_train_timestep = args.num_train_timestep
|
| 35 |
+
self.min_step = int(0.02 * self.num_train_timestep)
|
| 36 |
+
self.max_step = int(0.98 * self.num_train_timestep)
|
| 37 |
+
if hasattr(args, "real_guidance_scale"):
|
| 38 |
+
self.real_guidance_scale = args.real_guidance_scale
|
| 39 |
+
else:
|
| 40 |
+
self.real_guidance_scale = args.guidance_scale
|
| 41 |
+
self.timestep_shift = getattr(args, "timestep_shift", 1.0)
|
| 42 |
+
self.sid_alpha = getattr(args, "sid_alpha", 1.0)
|
| 43 |
+
self.ts_schedule = getattr(args, "ts_schedule", True)
|
| 44 |
+
self.ts_schedule_max = getattr(args, "ts_schedule_max", False)
|
| 45 |
+
|
| 46 |
+
if getattr(self.scheduler, "alphas_cumprod", None) is not None:
|
| 47 |
+
self.scheduler.alphas_cumprod = self.scheduler.alphas_cumprod.to(device)
|
| 48 |
+
else:
|
| 49 |
+
self.scheduler.alphas_cumprod = None
|
| 50 |
+
|
| 51 |
+
def compute_distribution_matching_loss(
|
| 52 |
+
self,
|
| 53 |
+
image_or_video: torch.Tensor,
|
| 54 |
+
conditional_dict: dict,
|
| 55 |
+
unconditional_dict: dict,
|
| 56 |
+
gradient_mask: Optional[torch.Tensor] = None,
|
| 57 |
+
denoised_timestep_from: int = 0,
|
| 58 |
+
denoised_timestep_to: int = 0,
|
| 59 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 60 |
+
"""
|
| 61 |
+
Compute the DMD loss (eq 7 in https://arxiv.org/abs/2311.18828).
|
| 62 |
+
Input:
|
| 63 |
+
- image_or_video: a tensor with shape [B, F, C, H, W] where the number of frame is 1 for images.
|
| 64 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 65 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 66 |
+
- gradient_mask: a boolean tensor with the same shape as image_or_video indicating which pixels to compute loss .
|
| 67 |
+
Output:
|
| 68 |
+
- dmd_loss: a scalar tensor representing the DMD loss.
|
| 69 |
+
- dmd_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 70 |
+
"""
|
| 71 |
+
original_latent = image_or_video
|
| 72 |
+
|
| 73 |
+
batch_size, num_frame = image_or_video.shape[:2]
|
| 74 |
+
|
| 75 |
+
# Step 1: Randomly sample timestep based on the given schedule and corresponding noise
|
| 76 |
+
min_timestep = (
|
| 77 |
+
denoised_timestep_to
|
| 78 |
+
if self.ts_schedule and denoised_timestep_to is not None
|
| 79 |
+
else self.min_score_timestep
|
| 80 |
+
)
|
| 81 |
+
max_timestep = (
|
| 82 |
+
denoised_timestep_from
|
| 83 |
+
if self.ts_schedule_max and denoised_timestep_from is not None
|
| 84 |
+
else self.num_train_timestep
|
| 85 |
+
)
|
| 86 |
+
timestep = self._get_timestep(
|
| 87 |
+
min_timestep,
|
| 88 |
+
max_timestep,
|
| 89 |
+
batch_size,
|
| 90 |
+
num_frame,
|
| 91 |
+
self.num_frame_per_block,
|
| 92 |
+
uniform_timestep=True,
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
if self.timestep_shift > 1:
|
| 96 |
+
timestep = (
|
| 97 |
+
self.timestep_shift
|
| 98 |
+
* (timestep / 1000)
|
| 99 |
+
/ (1 + (self.timestep_shift - 1) * (timestep / 1000))
|
| 100 |
+
* 1000
|
| 101 |
+
)
|
| 102 |
+
timestep = timestep.clamp(self.min_step, self.max_step)
|
| 103 |
+
|
| 104 |
+
noise = torch.randn_like(image_or_video)
|
| 105 |
+
noisy_latent = self.scheduler.add_noise(
|
| 106 |
+
image_or_video.flatten(0, 1),
|
| 107 |
+
noise.flatten(0, 1),
|
| 108 |
+
timestep.flatten(0, 1),
|
| 109 |
+
).unflatten(0, (batch_size, num_frame))
|
| 110 |
+
|
| 111 |
+
# Step 2: SiD (May be wrap it?)
|
| 112 |
+
noisy_image_or_video = noisy_latent
|
| 113 |
+
# Step 2.1: Compute the fake score
|
| 114 |
+
_, pred_fake_image = self.fake_score(
|
| 115 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 116 |
+
conditional_dict=conditional_dict,
|
| 117 |
+
timestep=timestep,
|
| 118 |
+
)
|
| 119 |
+
# Step 2.2: Compute the real score
|
| 120 |
+
# We compute the conditional and unconditional prediction
|
| 121 |
+
# and add them together to achieve cfg (https://arxiv.org/abs/2207.12598)
|
| 122 |
+
# NOTE: This step may cause OOM issue, which can be addressed by the CFG-free technique
|
| 123 |
+
|
| 124 |
+
_, pred_real_image_cond = self.real_score(
|
| 125 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 126 |
+
conditional_dict=conditional_dict,
|
| 127 |
+
timestep=timestep,
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
_, pred_real_image_uncond = self.real_score(
|
| 131 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 132 |
+
conditional_dict=unconditional_dict,
|
| 133 |
+
timestep=timestep,
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
pred_real_image = (
|
| 137 |
+
pred_real_image_cond
|
| 138 |
+
+ (pred_real_image_cond - pred_real_image_uncond) * self.real_guidance_scale
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# Step 2.3: SiD Loss
|
| 142 |
+
# TODO: Add alpha
|
| 143 |
+
# TODO: Double?
|
| 144 |
+
sid_loss = (pred_real_image.double() - pred_fake_image.double()) * (
|
| 145 |
+
(pred_real_image.double() - original_latent.double())
|
| 146 |
+
- self.sid_alpha * (pred_real_image.double() - pred_fake_image.double())
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
# Step 2.4: Loss normalizer
|
| 150 |
+
with torch.no_grad():
|
| 151 |
+
p_real = original_latent - pred_real_image
|
| 152 |
+
normalizer = torch.abs(p_real).mean(dim=[1, 2, 3, 4], keepdim=True)
|
| 153 |
+
sid_loss = sid_loss / normalizer
|
| 154 |
+
|
| 155 |
+
sid_loss = torch.nan_to_num(sid_loss)
|
| 156 |
+
num_frame = sid_loss.shape[1]
|
| 157 |
+
sid_loss = sid_loss.mean()
|
| 158 |
+
|
| 159 |
+
sid_log_dict = {
|
| 160 |
+
"dmdtrain_gradient_norm": torch.zeros_like(sid_loss),
|
| 161 |
+
"timestep": timestep.detach(),
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
return sid_loss, sid_log_dict
|
| 165 |
+
|
| 166 |
+
def generator_loss(
|
| 167 |
+
self,
|
| 168 |
+
image_or_video_shape,
|
| 169 |
+
conditional_dict: dict,
|
| 170 |
+
unconditional_dict: dict,
|
| 171 |
+
clean_latent: torch.Tensor,
|
| 172 |
+
initial_latent: torch.Tensor = None,
|
| 173 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 174 |
+
"""
|
| 175 |
+
Generate image/videos from noise and compute the DMD loss.
|
| 176 |
+
The noisy input to the generator is backward simulated.
|
| 177 |
+
This removes the need of any datasets during distillation.
|
| 178 |
+
See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.
|
| 179 |
+
Input:
|
| 180 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 181 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 182 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 183 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 184 |
+
Output:
|
| 185 |
+
- loss: a scalar tensor representing the generator loss.
|
| 186 |
+
- generator_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 187 |
+
"""
|
| 188 |
+
# Step 1: Unroll generator to obtain fake videos
|
| 189 |
+
(
|
| 190 |
+
pred_image,
|
| 191 |
+
gradient_mask,
|
| 192 |
+
denoised_timestep_from,
|
| 193 |
+
denoised_timestep_to,
|
| 194 |
+
) = self._run_generator(
|
| 195 |
+
image_or_video_shape=image_or_video_shape,
|
| 196 |
+
conditional_dict=conditional_dict,
|
| 197 |
+
initial_latent=initial_latent,
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
# Step 2: Compute the DMD loss
|
| 201 |
+
dmd_loss, dmd_log_dict = self.compute_distribution_matching_loss(
|
| 202 |
+
image_or_video=pred_image,
|
| 203 |
+
conditional_dict=conditional_dict,
|
| 204 |
+
unconditional_dict=unconditional_dict,
|
| 205 |
+
gradient_mask=gradient_mask,
|
| 206 |
+
denoised_timestep_from=denoised_timestep_from,
|
| 207 |
+
denoised_timestep_to=denoised_timestep_to,
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
return dmd_loss, dmd_log_dict
|
| 211 |
+
|
| 212 |
+
def critic_loss(
|
| 213 |
+
self,
|
| 214 |
+
image_or_video_shape,
|
| 215 |
+
conditional_dict: dict,
|
| 216 |
+
unconditional_dict: dict,
|
| 217 |
+
clean_latent: torch.Tensor,
|
| 218 |
+
initial_latent: torch.Tensor = None,
|
| 219 |
+
) -> Tuple[torch.Tensor, dict]:
|
| 220 |
+
"""
|
| 221 |
+
Generate image/videos from noise and train the critic with generated samples.
|
| 222 |
+
The noisy input to the generator is backward simulated.
|
| 223 |
+
This removes the need of any datasets during distillation.
|
| 224 |
+
See Sec 4.5 of the DMD2 paper (https://arxiv.org/abs/2405.14867) for details.
|
| 225 |
+
Input:
|
| 226 |
+
- image_or_video_shape: a list containing the shape of the image or video [B, F, C, H, W].
|
| 227 |
+
- conditional_dict: a dictionary containing the conditional information (e.g. text embeddings, image embeddings).
|
| 228 |
+
- unconditional_dict: a dictionary containing the unconditional information (e.g. null/negative text embeddings, null/negative image embeddings).
|
| 229 |
+
- clean_latent: a tensor containing the clean latents [B, F, C, H, W]. Need to be passed when no backward simulation is used.
|
| 230 |
+
Output:
|
| 231 |
+
- loss: a scalar tensor representing the generator loss.
|
| 232 |
+
- critic_log_dict: a dictionary containing the intermediate tensors for logging.
|
| 233 |
+
"""
|
| 234 |
+
|
| 235 |
+
# Step 1: Run generator on backward simulated noisy input
|
| 236 |
+
with torch.no_grad():
|
| 237 |
+
generated_image, _, denoised_timestep_from, denoised_timestep_to = self._run_generator(
|
| 238 |
+
image_or_video_shape=image_or_video_shape,
|
| 239 |
+
conditional_dict=conditional_dict,
|
| 240 |
+
initial_latent=initial_latent,
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
# Step 2: Compute the fake prediction
|
| 244 |
+
min_timestep = (
|
| 245 |
+
denoised_timestep_to
|
| 246 |
+
if self.ts_schedule and denoised_timestep_to is not None
|
| 247 |
+
else self.min_score_timestep
|
| 248 |
+
)
|
| 249 |
+
max_timestep = (
|
| 250 |
+
denoised_timestep_from
|
| 251 |
+
if self.ts_schedule_max and denoised_timestep_from is not None
|
| 252 |
+
else self.num_train_timestep
|
| 253 |
+
)
|
| 254 |
+
critic_timestep = self._get_timestep(
|
| 255 |
+
min_timestep,
|
| 256 |
+
max_timestep,
|
| 257 |
+
image_or_video_shape[0],
|
| 258 |
+
image_or_video_shape[1],
|
| 259 |
+
self.num_frame_per_block,
|
| 260 |
+
uniform_timestep=True,
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
if self.timestep_shift > 1:
|
| 264 |
+
critic_timestep = (
|
| 265 |
+
self.timestep_shift
|
| 266 |
+
* (critic_timestep / 1000)
|
| 267 |
+
/ (1 + (self.timestep_shift - 1) * (critic_timestep / 1000))
|
| 268 |
+
* 1000
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
critic_timestep = critic_timestep.clamp(self.min_step, self.max_step)
|
| 272 |
+
|
| 273 |
+
critic_noise = torch.randn_like(generated_image)
|
| 274 |
+
noisy_generated_image = self.scheduler.add_noise(
|
| 275 |
+
generated_image.flatten(0, 1),
|
| 276 |
+
critic_noise.flatten(0, 1),
|
| 277 |
+
critic_timestep.flatten(0, 1),
|
| 278 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 279 |
+
|
| 280 |
+
_, pred_fake_image = self.fake_score(
|
| 281 |
+
noisy_image_or_video=noisy_generated_image,
|
| 282 |
+
conditional_dict=conditional_dict,
|
| 283 |
+
timestep=critic_timestep,
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
# Step 3: Compute the denoising loss for the fake critic
|
| 287 |
+
if self.args.denoising_loss_type == "flow":
|
| 288 |
+
from utils.wan_wrapper import WanDiffusionWrapper
|
| 289 |
+
|
| 290 |
+
flow_pred = WanDiffusionWrapper._convert_x0_to_flow_pred(
|
| 291 |
+
scheduler=self.scheduler,
|
| 292 |
+
x0_pred=pred_fake_image.flatten(0, 1),
|
| 293 |
+
xt=noisy_generated_image.flatten(0, 1),
|
| 294 |
+
timestep=critic_timestep.flatten(0, 1),
|
| 295 |
+
)
|
| 296 |
+
pred_fake_noise = None
|
| 297 |
+
else:
|
| 298 |
+
flow_pred = None
|
| 299 |
+
pred_fake_noise = self.scheduler.convert_x0_to_noise(
|
| 300 |
+
x0=pred_fake_image.flatten(0, 1),
|
| 301 |
+
xt=noisy_generated_image.flatten(0, 1),
|
| 302 |
+
timestep=critic_timestep.flatten(0, 1),
|
| 303 |
+
).unflatten(0, image_or_video_shape[:2])
|
| 304 |
+
|
| 305 |
+
denoising_loss = self.denoising_loss_func(
|
| 306 |
+
x=generated_image.flatten(0, 1),
|
| 307 |
+
x_pred=pred_fake_image.flatten(0, 1),
|
| 308 |
+
noise=critic_noise.flatten(0, 1),
|
| 309 |
+
noise_pred=pred_fake_noise,
|
| 310 |
+
alphas_cumprod=self.scheduler.alphas_cumprod,
|
| 311 |
+
timestep=critic_timestep.flatten(0, 1),
|
| 312 |
+
flow_pred=flow_pred,
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
# Step 5: Debugging Log
|
| 316 |
+
critic_log_dict = {"critic_timestep": critic_timestep.detach()}
|
| 317 |
+
|
| 318 |
+
return denoising_loss, critic_log_dict
|
offline_run.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from hydra import compose, initialize
|
| 6 |
+
from hydra.core.global_hydra import GlobalHydra
|
| 7 |
+
from torchvision.io import write_video
|
| 8 |
+
|
| 9 |
+
from optimize_utils import MultiTrajectory
|
| 10 |
+
from stream_drag_inference_wrapper import StreamDragInferenceWrapper
|
| 11 |
+
from utils.misc import set_seed
|
| 12 |
+
from video_operations import run_optimization, save_videos
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def build_stream_drag_inference(
|
| 16 |
+
config_dir: str,
|
| 17 |
+
config_name: str,
|
| 18 |
+
checkpoint_path: str,
|
| 19 |
+
total_generate_block_number: int,
|
| 20 |
+
use_ema: bool,
|
| 21 |
+
seed: int,
|
| 22 |
+
) -> StreamDragInferenceWrapper:
|
| 23 |
+
if GlobalHydra.instance().is_initialized():
|
| 24 |
+
GlobalHydra.instance().clear()
|
| 25 |
+
|
| 26 |
+
with initialize(version_base=None, config_path=config_dir):
|
| 27 |
+
stream_config = compose(config_name=config_name)
|
| 28 |
+
|
| 29 |
+
return StreamDragInferenceWrapper(
|
| 30 |
+
stream_model_config=stream_config,
|
| 31 |
+
checkpoint_path=checkpoint_path,
|
| 32 |
+
total_generate_block_number=total_generate_block_number,
|
| 33 |
+
use_ema=use_ema,
|
| 34 |
+
seed=seed,
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def main() -> None:
|
| 39 |
+
prompt_index = 4
|
| 40 |
+
trajectory_dir = "./saved_labels/self_forcing_dmd_vsink_stream_drag-seed42/0004-A_close-up_3D_animated_scene_of_a_short,_fluffy_mo"
|
| 41 |
+
start_block_index = 3
|
| 42 |
+
trajectory_prefix = "block_3_Animation"
|
| 43 |
+
config_dir = "configs"
|
| 44 |
+
config_name = "self_forcing_dmd_vsink_stream_drag"
|
| 45 |
+
checkpoint_path = "./checkpoints/self_forcing_dmd.pt"
|
| 46 |
+
total_generate_block_number = 36
|
| 47 |
+
seed = 42
|
| 48 |
+
fps = 8
|
| 49 |
+
output_dir = "outputs-editing/self_forcing_dmd_vsink_stream_drag-seed42"
|
| 50 |
+
use_ema = True
|
| 51 |
+
|
| 52 |
+
torch.set_grad_enabled(False)
|
| 53 |
+
|
| 54 |
+
trajectory = MultiTrajectory.load(
|
| 55 |
+
save_dir=trajectory_dir,
|
| 56 |
+
prefix=trajectory_prefix,
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
model = build_stream_drag_inference(
|
| 60 |
+
config_dir=config_dir,
|
| 61 |
+
config_name=config_name,
|
| 62 |
+
checkpoint_path=checkpoint_path,
|
| 63 |
+
total_generate_block_number=total_generate_block_number,
|
| 64 |
+
use_ema=use_ema,
|
| 65 |
+
seed=seed,
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
set_seed(seed)
|
| 69 |
+
model.reset()
|
| 70 |
+
model.inference(
|
| 71 |
+
start_block_index=0,
|
| 72 |
+
end_block_index=start_block_index,
|
| 73 |
+
prompt=trajectory.prompt,
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
all_video, current_video, end_block_index = run_optimization(
|
| 77 |
+
model=model,
|
| 78 |
+
trajectory=trajectory,
|
| 79 |
+
start_block_index=start_block_index,
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
saved_path = save_videos(
|
| 83 |
+
all_video=all_video,
|
| 84 |
+
current_video=current_video,
|
| 85 |
+
output_dir=Path(output_dir),
|
| 86 |
+
prompt_index=prompt_index,
|
| 87 |
+
prompt=trajectory.prompt,
|
| 88 |
+
start_block_index=start_block_index,
|
| 89 |
+
end_block_index=end_block_index,
|
| 90 |
+
mode=trajectory.drag_or_animation_select,
|
| 91 |
+
fps=fps,
|
| 92 |
+
)
|
| 93 |
+
print(str(saved_path))
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
if __name__ == "__main__":
|
| 97 |
+
main()
|
optimize_utils.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from attr import dataclass
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from tensor_utils import (
|
| 8 |
+
calculate_angle_from_points,
|
| 9 |
+
read_mask_from_file,
|
| 10 |
+
save_mask_to_file,
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dataclass
|
| 15 |
+
class Trajectory:
|
| 16 |
+
original_trajectory: dict[str, bool | list[torch.Tensor]] = None
|
| 17 |
+
"""
|
| 18 |
+
trajectory is dict, keys include 'is_rotation', 'points', if translation also has 'control_points'
|
| 19 |
+
"""
|
| 20 |
+
block_trajectories: list[dict[str, bool | list[torch.Tensor]]] = []
|
| 21 |
+
"""block_num x trajectory
|
| 22 |
+
trajectory has keys 'is_rotation' 'deltas' 'start_point'
|
| 23 |
+
if is_rotation: trajectory also has 'rotation_center'
|
| 24 |
+
"""
|
| 25 |
+
mask: np.ndarray = None
|
| 26 |
+
"""
|
| 27 |
+
target mask for the trajectory
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(
|
| 31 |
+
self,
|
| 32 |
+
original_trajectory: dict[str, bool | list[torch.Tensor]] = None,
|
| 33 |
+
mask: np.ndarray = None,
|
| 34 |
+
):
|
| 35 |
+
self.original_trajectory = original_trajectory
|
| 36 |
+
self.mask = mask
|
| 37 |
+
if original_trajectory is not None:
|
| 38 |
+
self.block_trajectories = self.original_to_block_trajectories(original_trajectory)
|
| 39 |
+
else:
|
| 40 |
+
self.block_trajectories = []
|
| 41 |
+
|
| 42 |
+
@staticmethod
|
| 43 |
+
def original_to_block_trajectories(
|
| 44 |
+
original_trajectory: dict[str, bool | list[torch.Tensor]],
|
| 45 |
+
block_length: int = 3,
|
| 46 |
+
) -> list[dict[str, bool | list[torch.Tensor]]]:
|
| 47 |
+
"""Convert an original trajectory (with 'points') into per-block trajectories (with 'deltas').
|
| 48 |
+
|
| 49 |
+
For translation:
|
| 50 |
+
deltas[i] = points[i+1] - points[0] (displacement from start)
|
| 51 |
+
Each block gets `block_length` consecutive deltas.
|
| 52 |
+
|
| 53 |
+
For rotation:
|
| 54 |
+
points[0] is the rotation center.
|
| 55 |
+
deltas[i] = angle(center, points[1], points[i+2])
|
| 56 |
+
Each block gets `block_length` consecutive deltas,
|
| 57 |
+
plus 'rotation_center' and 'start_point'.
|
| 58 |
+
"""
|
| 59 |
+
is_rotation = original_trajectory.get("is_rotation", False)
|
| 60 |
+
points = original_trajectory.get("points", [])
|
| 61 |
+
|
| 62 |
+
if is_rotation:
|
| 63 |
+
# points[0] = rotation center, points[1] = start arm, points[2:] = subsequent arms
|
| 64 |
+
if len(points) < 2:
|
| 65 |
+
return []
|
| 66 |
+
rotation_center = points[0]
|
| 67 |
+
start_point = points[1]
|
| 68 |
+
deltas = [
|
| 69 |
+
calculate_angle_from_points(
|
| 70 |
+
rotation_center,
|
| 71 |
+
start_point,
|
| 72 |
+
point,
|
| 73 |
+
)
|
| 74 |
+
for point in points[2:]
|
| 75 |
+
]
|
| 76 |
+
else:
|
| 77 |
+
# Translation: points[0] = start, points[1:] = subsequent positions
|
| 78 |
+
if len(points) < 1:
|
| 79 |
+
return []
|
| 80 |
+
start_point = points[0]
|
| 81 |
+
deltas = [torch.Tensor(point) - torch.Tensor(start_point) for point in points[1:]]
|
| 82 |
+
|
| 83 |
+
block_trajectories = []
|
| 84 |
+
for i in range(0, len(deltas), block_length):
|
| 85 |
+
block_traj = {
|
| 86 |
+
"is_rotation": is_rotation,
|
| 87 |
+
"deltas": deltas[i : i + block_length],
|
| 88 |
+
"start_point": start_point,
|
| 89 |
+
}
|
| 90 |
+
if is_rotation:
|
| 91 |
+
block_traj["rotation_center"] = rotation_center
|
| 92 |
+
block_trajectories.append(block_traj)
|
| 93 |
+
return block_trajectories
|
| 94 |
+
|
| 95 |
+
def set_original_trajectory(
|
| 96 |
+
self,
|
| 97 |
+
original_trajectory: dict[str, bool | list[torch.Tensor]] = None,
|
| 98 |
+
):
|
| 99 |
+
self.original_trajectory = original_trajectory
|
| 100 |
+
if original_trajectory is not None:
|
| 101 |
+
self.block_trajectories = self.original_to_block_trajectories(original_trajectory)
|
| 102 |
+
else:
|
| 103 |
+
self.block_trajectories = []
|
| 104 |
+
|
| 105 |
+
@staticmethod
|
| 106 |
+
def _serialize_value(
|
| 107 |
+
v,
|
| 108 |
+
):
|
| 109 |
+
"""Recursively serialize a value to JSON-compatible types."""
|
| 110 |
+
if isinstance(v, torch.Tensor):
|
| 111 |
+
return v.tolist()
|
| 112 |
+
elif isinstance(v, np.ndarray):
|
| 113 |
+
return v.tolist()
|
| 114 |
+
elif isinstance(v, dict):
|
| 115 |
+
return {k: Trajectory._serialize_value(val) for k, val in v.items()}
|
| 116 |
+
elif isinstance(v, list):
|
| 117 |
+
return [Trajectory._serialize_value(item) for item in v]
|
| 118 |
+
else:
|
| 119 |
+
return v
|
| 120 |
+
|
| 121 |
+
def to_dict(
|
| 122 |
+
self,
|
| 123 |
+
mask_filename: str = None,
|
| 124 |
+
) -> dict:
|
| 125 |
+
"""Convert the Trajectory to a JSON-serializable dictionary.
|
| 126 |
+
|
| 127 |
+
Args:
|
| 128 |
+
mask_filename: If provided, store this filename instead of the mask array.
|
| 129 |
+
"""
|
| 130 |
+
result = {}
|
| 131 |
+
|
| 132 |
+
if self.original_trajectory is not None:
|
| 133 |
+
result["original_trajectory"] = self._serialize_value(self.original_trajectory)
|
| 134 |
+
else:
|
| 135 |
+
result["original_trajectory"] = None
|
| 136 |
+
|
| 137 |
+
result["block_trajectories"] = self._serialize_value(self.block_trajectories)
|
| 138 |
+
|
| 139 |
+
if mask_filename is not None:
|
| 140 |
+
result["mask_file"] = mask_filename
|
| 141 |
+
|
| 142 |
+
return result
|
| 143 |
+
|
| 144 |
+
def save_mask(
|
| 145 |
+
self,
|
| 146 |
+
save_path: Path,
|
| 147 |
+
) -> None:
|
| 148 |
+
"""Save the mask as a PNG image."""
|
| 149 |
+
if self.mask is not None:
|
| 150 |
+
save_mask_to_file(self.mask, save_path)
|
| 151 |
+
|
| 152 |
+
@staticmethod
|
| 153 |
+
def load(
|
| 154 |
+
data: dict,
|
| 155 |
+
save_dir: Path,
|
| 156 |
+
) -> "Trajectory":
|
| 157 |
+
"""Load a Trajectory from a dictionary and directory."""
|
| 158 |
+
traj = Trajectory()
|
| 159 |
+
traj.original_trajectory = data.get("original_trajectory", None)
|
| 160 |
+
traj.block_trajectories = data.get("block_trajectories", [])
|
| 161 |
+
mask_file = data.get("mask_file", None)
|
| 162 |
+
if mask_file is not None:
|
| 163 |
+
traj.mask = read_mask_from_file(save_dir / mask_file)
|
| 164 |
+
return traj
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
@dataclass
|
| 168 |
+
class MultiTrajectory:
|
| 169 |
+
block_number: int = 1
|
| 170 |
+
prompt: str = ""
|
| 171 |
+
drag_or_animation_select: str = "Drag"
|
| 172 |
+
trajectories: list[Trajectory] = []
|
| 173 |
+
"""
|
| 174 |
+
multiple trajectories for a single prompt, each trajectory has its own mask
|
| 175 |
+
"""
|
| 176 |
+
movable_mask: np.ndarray = None
|
| 177 |
+
"""
|
| 178 |
+
the movable area mask for the whole image
|
| 179 |
+
"""
|
| 180 |
+
|
| 181 |
+
def save(
|
| 182 |
+
self,
|
| 183 |
+
save_dir: str | Path,
|
| 184 |
+
prefix: str = "multi_traj",
|
| 185 |
+
) -> Path:
|
| 186 |
+
"""Save the MultiTrajectory to a directory.
|
| 187 |
+
|
| 188 |
+
Masks are saved as PNG images, and metadata is saved as a JSON file.
|
| 189 |
+
|
| 190 |
+
Args:
|
| 191 |
+
save_dir: Directory to save files into.
|
| 192 |
+
prefix: Filename prefix for all saved files.
|
| 193 |
+
|
| 194 |
+
Returns:
|
| 195 |
+
Path to the saved JSON file.
|
| 196 |
+
"""
|
| 197 |
+
save_dir = Path(save_dir)
|
| 198 |
+
save_dir.mkdir(parents=True, exist_ok=True)
|
| 199 |
+
|
| 200 |
+
result = {
|
| 201 |
+
"block_number": self.block_number,
|
| 202 |
+
"prompt": self.prompt,
|
| 203 |
+
"drag_or_animation_select": self.drag_or_animation_select,
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
# Save movable_mask
|
| 207 |
+
if self.movable_mask is not None:
|
| 208 |
+
movable_mask_filename = f"{prefix}_movable_mask.png"
|
| 209 |
+
save_mask_to_file(self.movable_mask, save_dir / movable_mask_filename)
|
| 210 |
+
result["movable_area_mask_file"] = movable_mask_filename
|
| 211 |
+
else:
|
| 212 |
+
result["movable_area_mask_file"] = None
|
| 213 |
+
|
| 214 |
+
# Save each trajectory and its mask
|
| 215 |
+
traj_dicts = []
|
| 216 |
+
if self.trajectories is not None:
|
| 217 |
+
for i, traj in enumerate(self.trajectories):
|
| 218 |
+
mask_filename = None
|
| 219 |
+
if traj.mask is not None:
|
| 220 |
+
mask_filename = f"{prefix}_traj_{i}_mask.png"
|
| 221 |
+
traj.save_mask(save_dir / mask_filename)
|
| 222 |
+
traj_dicts.append(traj.to_dict(mask_filename=mask_filename))
|
| 223 |
+
result["trajectories"] = traj_dicts
|
| 224 |
+
|
| 225 |
+
# Write JSON
|
| 226 |
+
json_path = save_dir / f"{prefix}_trajectory.json"
|
| 227 |
+
with open(json_path, "w") as f:
|
| 228 |
+
json.dump(result, f, indent=2)
|
| 229 |
+
|
| 230 |
+
return json_path
|
| 231 |
+
|
| 232 |
+
@staticmethod
|
| 233 |
+
def load(
|
| 234 |
+
save_dir: str | Path,
|
| 235 |
+
prefix: str = "multi_traj",
|
| 236 |
+
) -> "MultiTrajectory":
|
| 237 |
+
"""Load a MultiTrajectory from a directory.
|
| 238 |
+
|
| 239 |
+
Args:
|
| 240 |
+
save_dir: Directory containing the saved files.
|
| 241 |
+
prefix: Filename prefix used when saving.
|
| 242 |
+
|
| 243 |
+
Returns:
|
| 244 |
+
The loaded MultiTrajectory instance.
|
| 245 |
+
"""
|
| 246 |
+
save_dir = Path(save_dir)
|
| 247 |
+
json_path = save_dir / f"{prefix}_trajectory.json"
|
| 248 |
+
|
| 249 |
+
with open(json_path, "r") as f:
|
| 250 |
+
data = json.load(f)
|
| 251 |
+
|
| 252 |
+
mt = MultiTrajectory()
|
| 253 |
+
mt.block_number = data.get("block_number", 1)
|
| 254 |
+
mt.prompt = data.get("prompt", "")
|
| 255 |
+
mt.drag_or_animation_select = data.get("drag_or_animation_select", "Drag")
|
| 256 |
+
# Load movable_mask
|
| 257 |
+
movable_file = data.get("movable_area_mask_file", None)
|
| 258 |
+
if movable_file is not None:
|
| 259 |
+
mt.movable_mask = read_mask_from_file(save_dir / movable_file)
|
| 260 |
+
|
| 261 |
+
# Load trajectories
|
| 262 |
+
mt.trajectories = []
|
| 263 |
+
for traj_data in data.get("trajectories", []):
|
| 264 |
+
mt.trajectories.append(Trajectory.load(traj_data, save_dir))
|
| 265 |
+
|
| 266 |
+
return mt
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def transpose_dict_2d(d):
|
| 270 |
+
"""Transpose a 2D dict: dict[key1][key2] -> dict[key2][key1]."""
|
| 271 |
+
result = {}
|
| 272 |
+
for key1, inner in d.items():
|
| 273 |
+
for key2, item in inner.items():
|
| 274 |
+
result.setdefault(key2, {})[key1] = item
|
| 275 |
+
return result
|
palette.py
ADDED
|
@@ -0,0 +1,774 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
_palette = [
|
| 4 |
+
0,
|
| 5 |
+
0,
|
| 6 |
+
0,
|
| 7 |
+
128,
|
| 8 |
+
0,
|
| 9 |
+
0,
|
| 10 |
+
0,
|
| 11 |
+
128,
|
| 12 |
+
0,
|
| 13 |
+
128,
|
| 14 |
+
128,
|
| 15 |
+
0,
|
| 16 |
+
0,
|
| 17 |
+
0,
|
| 18 |
+
128,
|
| 19 |
+
128,
|
| 20 |
+
0,
|
| 21 |
+
128,
|
| 22 |
+
0,
|
| 23 |
+
128,
|
| 24 |
+
128,
|
| 25 |
+
128,
|
| 26 |
+
128,
|
| 27 |
+
128,
|
| 28 |
+
64,
|
| 29 |
+
0,
|
| 30 |
+
0,
|
| 31 |
+
191,
|
| 32 |
+
0,
|
| 33 |
+
0,
|
| 34 |
+
64,
|
| 35 |
+
128,
|
| 36 |
+
0,
|
| 37 |
+
191,
|
| 38 |
+
128,
|
| 39 |
+
0,
|
| 40 |
+
64,
|
| 41 |
+
0,
|
| 42 |
+
128,
|
| 43 |
+
191,
|
| 44 |
+
0,
|
| 45 |
+
128,
|
| 46 |
+
64,
|
| 47 |
+
128,
|
| 48 |
+
128,
|
| 49 |
+
191,
|
| 50 |
+
128,
|
| 51 |
+
128,
|
| 52 |
+
0,
|
| 53 |
+
64,
|
| 54 |
+
0,
|
| 55 |
+
128,
|
| 56 |
+
64,
|
| 57 |
+
0,
|
| 58 |
+
0,
|
| 59 |
+
191,
|
| 60 |
+
0,
|
| 61 |
+
128,
|
| 62 |
+
191,
|
| 63 |
+
0,
|
| 64 |
+
0,
|
| 65 |
+
64,
|
| 66 |
+
128,
|
| 67 |
+
128,
|
| 68 |
+
64,
|
| 69 |
+
128,
|
| 70 |
+
22,
|
| 71 |
+
22,
|
| 72 |
+
22,
|
| 73 |
+
23,
|
| 74 |
+
23,
|
| 75 |
+
23,
|
| 76 |
+
24,
|
| 77 |
+
24,
|
| 78 |
+
24,
|
| 79 |
+
25,
|
| 80 |
+
25,
|
| 81 |
+
25,
|
| 82 |
+
26,
|
| 83 |
+
26,
|
| 84 |
+
26,
|
| 85 |
+
27,
|
| 86 |
+
27,
|
| 87 |
+
27,
|
| 88 |
+
28,
|
| 89 |
+
28,
|
| 90 |
+
28,
|
| 91 |
+
29,
|
| 92 |
+
29,
|
| 93 |
+
29,
|
| 94 |
+
30,
|
| 95 |
+
30,
|
| 96 |
+
30,
|
| 97 |
+
31,
|
| 98 |
+
31,
|
| 99 |
+
31,
|
| 100 |
+
32,
|
| 101 |
+
32,
|
| 102 |
+
32,
|
| 103 |
+
33,
|
| 104 |
+
33,
|
| 105 |
+
33,
|
| 106 |
+
34,
|
| 107 |
+
34,
|
| 108 |
+
34,
|
| 109 |
+
35,
|
| 110 |
+
35,
|
| 111 |
+
35,
|
| 112 |
+
36,
|
| 113 |
+
36,
|
| 114 |
+
36,
|
| 115 |
+
37,
|
| 116 |
+
37,
|
| 117 |
+
37,
|
| 118 |
+
38,
|
| 119 |
+
38,
|
| 120 |
+
38,
|
| 121 |
+
39,
|
| 122 |
+
39,
|
| 123 |
+
39,
|
| 124 |
+
40,
|
| 125 |
+
40,
|
| 126 |
+
40,
|
| 127 |
+
41,
|
| 128 |
+
41,
|
| 129 |
+
41,
|
| 130 |
+
42,
|
| 131 |
+
42,
|
| 132 |
+
42,
|
| 133 |
+
43,
|
| 134 |
+
43,
|
| 135 |
+
43,
|
| 136 |
+
44,
|
| 137 |
+
44,
|
| 138 |
+
44,
|
| 139 |
+
45,
|
| 140 |
+
45,
|
| 141 |
+
45,
|
| 142 |
+
46,
|
| 143 |
+
46,
|
| 144 |
+
46,
|
| 145 |
+
47,
|
| 146 |
+
47,
|
| 147 |
+
47,
|
| 148 |
+
48,
|
| 149 |
+
48,
|
| 150 |
+
48,
|
| 151 |
+
49,
|
| 152 |
+
49,
|
| 153 |
+
49,
|
| 154 |
+
50,
|
| 155 |
+
50,
|
| 156 |
+
50,
|
| 157 |
+
51,
|
| 158 |
+
51,
|
| 159 |
+
51,
|
| 160 |
+
52,
|
| 161 |
+
52,
|
| 162 |
+
52,
|
| 163 |
+
53,
|
| 164 |
+
53,
|
| 165 |
+
53,
|
| 166 |
+
54,
|
| 167 |
+
54,
|
| 168 |
+
54,
|
| 169 |
+
55,
|
| 170 |
+
55,
|
| 171 |
+
55,
|
| 172 |
+
56,
|
| 173 |
+
56,
|
| 174 |
+
56,
|
| 175 |
+
57,
|
| 176 |
+
57,
|
| 177 |
+
57,
|
| 178 |
+
58,
|
| 179 |
+
58,
|
| 180 |
+
58,
|
| 181 |
+
59,
|
| 182 |
+
59,
|
| 183 |
+
59,
|
| 184 |
+
60,
|
| 185 |
+
60,
|
| 186 |
+
60,
|
| 187 |
+
61,
|
| 188 |
+
61,
|
| 189 |
+
61,
|
| 190 |
+
62,
|
| 191 |
+
62,
|
| 192 |
+
62,
|
| 193 |
+
63,
|
| 194 |
+
63,
|
| 195 |
+
63,
|
| 196 |
+
64,
|
| 197 |
+
64,
|
| 198 |
+
64,
|
| 199 |
+
65,
|
| 200 |
+
65,
|
| 201 |
+
65,
|
| 202 |
+
66,
|
| 203 |
+
66,
|
| 204 |
+
66,
|
| 205 |
+
67,
|
| 206 |
+
67,
|
| 207 |
+
67,
|
| 208 |
+
68,
|
| 209 |
+
68,
|
| 210 |
+
68,
|
| 211 |
+
69,
|
| 212 |
+
69,
|
| 213 |
+
69,
|
| 214 |
+
70,
|
| 215 |
+
70,
|
| 216 |
+
70,
|
| 217 |
+
71,
|
| 218 |
+
71,
|
| 219 |
+
71,
|
| 220 |
+
72,
|
| 221 |
+
72,
|
| 222 |
+
72,
|
| 223 |
+
73,
|
| 224 |
+
73,
|
| 225 |
+
73,
|
| 226 |
+
74,
|
| 227 |
+
74,
|
| 228 |
+
74,
|
| 229 |
+
75,
|
| 230 |
+
75,
|
| 231 |
+
75,
|
| 232 |
+
76,
|
| 233 |
+
76,
|
| 234 |
+
76,
|
| 235 |
+
77,
|
| 236 |
+
77,
|
| 237 |
+
77,
|
| 238 |
+
78,
|
| 239 |
+
78,
|
| 240 |
+
78,
|
| 241 |
+
79,
|
| 242 |
+
79,
|
| 243 |
+
79,
|
| 244 |
+
80,
|
| 245 |
+
80,
|
| 246 |
+
80,
|
| 247 |
+
81,
|
| 248 |
+
81,
|
| 249 |
+
81,
|
| 250 |
+
82,
|
| 251 |
+
82,
|
| 252 |
+
82,
|
| 253 |
+
83,
|
| 254 |
+
83,
|
| 255 |
+
83,
|
| 256 |
+
84,
|
| 257 |
+
84,
|
| 258 |
+
84,
|
| 259 |
+
85,
|
| 260 |
+
85,
|
| 261 |
+
85,
|
| 262 |
+
86,
|
| 263 |
+
86,
|
| 264 |
+
86,
|
| 265 |
+
87,
|
| 266 |
+
87,
|
| 267 |
+
87,
|
| 268 |
+
88,
|
| 269 |
+
88,
|
| 270 |
+
88,
|
| 271 |
+
89,
|
| 272 |
+
89,
|
| 273 |
+
89,
|
| 274 |
+
90,
|
| 275 |
+
90,
|
| 276 |
+
90,
|
| 277 |
+
91,
|
| 278 |
+
91,
|
| 279 |
+
91,
|
| 280 |
+
92,
|
| 281 |
+
92,
|
| 282 |
+
92,
|
| 283 |
+
93,
|
| 284 |
+
93,
|
| 285 |
+
93,
|
| 286 |
+
94,
|
| 287 |
+
94,
|
| 288 |
+
94,
|
| 289 |
+
95,
|
| 290 |
+
95,
|
| 291 |
+
95,
|
| 292 |
+
96,
|
| 293 |
+
96,
|
| 294 |
+
96,
|
| 295 |
+
97,
|
| 296 |
+
97,
|
| 297 |
+
97,
|
| 298 |
+
98,
|
| 299 |
+
98,
|
| 300 |
+
98,
|
| 301 |
+
99,
|
| 302 |
+
99,
|
| 303 |
+
99,
|
| 304 |
+
100,
|
| 305 |
+
100,
|
| 306 |
+
100,
|
| 307 |
+
101,
|
| 308 |
+
101,
|
| 309 |
+
101,
|
| 310 |
+
102,
|
| 311 |
+
102,
|
| 312 |
+
102,
|
| 313 |
+
103,
|
| 314 |
+
103,
|
| 315 |
+
103,
|
| 316 |
+
104,
|
| 317 |
+
104,
|
| 318 |
+
104,
|
| 319 |
+
105,
|
| 320 |
+
105,
|
| 321 |
+
105,
|
| 322 |
+
106,
|
| 323 |
+
106,
|
| 324 |
+
106,
|
| 325 |
+
107,
|
| 326 |
+
107,
|
| 327 |
+
107,
|
| 328 |
+
108,
|
| 329 |
+
108,
|
| 330 |
+
108,
|
| 331 |
+
109,
|
| 332 |
+
109,
|
| 333 |
+
109,
|
| 334 |
+
110,
|
| 335 |
+
110,
|
| 336 |
+
110,
|
| 337 |
+
111,
|
| 338 |
+
111,
|
| 339 |
+
111,
|
| 340 |
+
112,
|
| 341 |
+
112,
|
| 342 |
+
112,
|
| 343 |
+
113,
|
| 344 |
+
113,
|
| 345 |
+
113,
|
| 346 |
+
114,
|
| 347 |
+
114,
|
| 348 |
+
114,
|
| 349 |
+
115,
|
| 350 |
+
115,
|
| 351 |
+
115,
|
| 352 |
+
116,
|
| 353 |
+
116,
|
| 354 |
+
116,
|
| 355 |
+
117,
|
| 356 |
+
117,
|
| 357 |
+
117,
|
| 358 |
+
118,
|
| 359 |
+
118,
|
| 360 |
+
118,
|
| 361 |
+
119,
|
| 362 |
+
119,
|
| 363 |
+
119,
|
| 364 |
+
120,
|
| 365 |
+
120,
|
| 366 |
+
120,
|
| 367 |
+
121,
|
| 368 |
+
121,
|
| 369 |
+
121,
|
| 370 |
+
122,
|
| 371 |
+
122,
|
| 372 |
+
122,
|
| 373 |
+
123,
|
| 374 |
+
123,
|
| 375 |
+
123,
|
| 376 |
+
124,
|
| 377 |
+
124,
|
| 378 |
+
124,
|
| 379 |
+
125,
|
| 380 |
+
125,
|
| 381 |
+
125,
|
| 382 |
+
126,
|
| 383 |
+
126,
|
| 384 |
+
126,
|
| 385 |
+
127,
|
| 386 |
+
127,
|
| 387 |
+
127,
|
| 388 |
+
128,
|
| 389 |
+
128,
|
| 390 |
+
128,
|
| 391 |
+
129,
|
| 392 |
+
129,
|
| 393 |
+
129,
|
| 394 |
+
130,
|
| 395 |
+
130,
|
| 396 |
+
130,
|
| 397 |
+
131,
|
| 398 |
+
131,
|
| 399 |
+
131,
|
| 400 |
+
132,
|
| 401 |
+
132,
|
| 402 |
+
132,
|
| 403 |
+
133,
|
| 404 |
+
133,
|
| 405 |
+
133,
|
| 406 |
+
134,
|
| 407 |
+
134,
|
| 408 |
+
134,
|
| 409 |
+
135,
|
| 410 |
+
135,
|
| 411 |
+
135,
|
| 412 |
+
136,
|
| 413 |
+
136,
|
| 414 |
+
136,
|
| 415 |
+
137,
|
| 416 |
+
137,
|
| 417 |
+
137,
|
| 418 |
+
138,
|
| 419 |
+
138,
|
| 420 |
+
138,
|
| 421 |
+
139,
|
| 422 |
+
139,
|
| 423 |
+
139,
|
| 424 |
+
140,
|
| 425 |
+
140,
|
| 426 |
+
140,
|
| 427 |
+
141,
|
| 428 |
+
141,
|
| 429 |
+
141,
|
| 430 |
+
142,
|
| 431 |
+
142,
|
| 432 |
+
142,
|
| 433 |
+
143,
|
| 434 |
+
143,
|
| 435 |
+
143,
|
| 436 |
+
144,
|
| 437 |
+
144,
|
| 438 |
+
144,
|
| 439 |
+
145,
|
| 440 |
+
145,
|
| 441 |
+
145,
|
| 442 |
+
146,
|
| 443 |
+
146,
|
| 444 |
+
146,
|
| 445 |
+
147,
|
| 446 |
+
147,
|
| 447 |
+
147,
|
| 448 |
+
148,
|
| 449 |
+
148,
|
| 450 |
+
148,
|
| 451 |
+
149,
|
| 452 |
+
149,
|
| 453 |
+
149,
|
| 454 |
+
150,
|
| 455 |
+
150,
|
| 456 |
+
150,
|
| 457 |
+
151,
|
| 458 |
+
151,
|
| 459 |
+
151,
|
| 460 |
+
152,
|
| 461 |
+
152,
|
| 462 |
+
152,
|
| 463 |
+
153,
|
| 464 |
+
153,
|
| 465 |
+
153,
|
| 466 |
+
154,
|
| 467 |
+
154,
|
| 468 |
+
154,
|
| 469 |
+
155,
|
| 470 |
+
155,
|
| 471 |
+
155,
|
| 472 |
+
156,
|
| 473 |
+
156,
|
| 474 |
+
156,
|
| 475 |
+
157,
|
| 476 |
+
157,
|
| 477 |
+
157,
|
| 478 |
+
158,
|
| 479 |
+
158,
|
| 480 |
+
158,
|
| 481 |
+
159,
|
| 482 |
+
159,
|
| 483 |
+
159,
|
| 484 |
+
160,
|
| 485 |
+
160,
|
| 486 |
+
160,
|
| 487 |
+
161,
|
| 488 |
+
161,
|
| 489 |
+
161,
|
| 490 |
+
162,
|
| 491 |
+
162,
|
| 492 |
+
162,
|
| 493 |
+
163,
|
| 494 |
+
163,
|
| 495 |
+
163,
|
| 496 |
+
164,
|
| 497 |
+
164,
|
| 498 |
+
164,
|
| 499 |
+
165,
|
| 500 |
+
165,
|
| 501 |
+
165,
|
| 502 |
+
166,
|
| 503 |
+
166,
|
| 504 |
+
166,
|
| 505 |
+
167,
|
| 506 |
+
167,
|
| 507 |
+
167,
|
| 508 |
+
168,
|
| 509 |
+
168,
|
| 510 |
+
168,
|
| 511 |
+
169,
|
| 512 |
+
169,
|
| 513 |
+
169,
|
| 514 |
+
170,
|
| 515 |
+
170,
|
| 516 |
+
170,
|
| 517 |
+
171,
|
| 518 |
+
171,
|
| 519 |
+
171,
|
| 520 |
+
172,
|
| 521 |
+
172,
|
| 522 |
+
172,
|
| 523 |
+
173,
|
| 524 |
+
173,
|
| 525 |
+
173,
|
| 526 |
+
174,
|
| 527 |
+
174,
|
| 528 |
+
174,
|
| 529 |
+
175,
|
| 530 |
+
175,
|
| 531 |
+
175,
|
| 532 |
+
176,
|
| 533 |
+
176,
|
| 534 |
+
176,
|
| 535 |
+
177,
|
| 536 |
+
177,
|
| 537 |
+
177,
|
| 538 |
+
178,
|
| 539 |
+
178,
|
| 540 |
+
178,
|
| 541 |
+
179,
|
| 542 |
+
179,
|
| 543 |
+
179,
|
| 544 |
+
180,
|
| 545 |
+
180,
|
| 546 |
+
180,
|
| 547 |
+
181,
|
| 548 |
+
181,
|
| 549 |
+
181,
|
| 550 |
+
182,
|
| 551 |
+
182,
|
| 552 |
+
182,
|
| 553 |
+
183,
|
| 554 |
+
183,
|
| 555 |
+
183,
|
| 556 |
+
184,
|
| 557 |
+
184,
|
| 558 |
+
184,
|
| 559 |
+
185,
|
| 560 |
+
185,
|
| 561 |
+
185,
|
| 562 |
+
186,
|
| 563 |
+
186,
|
| 564 |
+
186,
|
| 565 |
+
187,
|
| 566 |
+
187,
|
| 567 |
+
187,
|
| 568 |
+
188,
|
| 569 |
+
188,
|
| 570 |
+
188,
|
| 571 |
+
189,
|
| 572 |
+
189,
|
| 573 |
+
189,
|
| 574 |
+
190,
|
| 575 |
+
190,
|
| 576 |
+
190,
|
| 577 |
+
191,
|
| 578 |
+
191,
|
| 579 |
+
191,
|
| 580 |
+
192,
|
| 581 |
+
192,
|
| 582 |
+
192,
|
| 583 |
+
193,
|
| 584 |
+
193,
|
| 585 |
+
193,
|
| 586 |
+
194,
|
| 587 |
+
194,
|
| 588 |
+
194,
|
| 589 |
+
195,
|
| 590 |
+
195,
|
| 591 |
+
195,
|
| 592 |
+
196,
|
| 593 |
+
196,
|
| 594 |
+
196,
|
| 595 |
+
197,
|
| 596 |
+
197,
|
| 597 |
+
197,
|
| 598 |
+
198,
|
| 599 |
+
198,
|
| 600 |
+
198,
|
| 601 |
+
199,
|
| 602 |
+
199,
|
| 603 |
+
199,
|
| 604 |
+
200,
|
| 605 |
+
200,
|
| 606 |
+
200,
|
| 607 |
+
201,
|
| 608 |
+
201,
|
| 609 |
+
201,
|
| 610 |
+
202,
|
| 611 |
+
202,
|
| 612 |
+
202,
|
| 613 |
+
203,
|
| 614 |
+
203,
|
| 615 |
+
203,
|
| 616 |
+
204,
|
| 617 |
+
204,
|
| 618 |
+
204,
|
| 619 |
+
205,
|
| 620 |
+
205,
|
| 621 |
+
205,
|
| 622 |
+
206,
|
| 623 |
+
206,
|
| 624 |
+
206,
|
| 625 |
+
207,
|
| 626 |
+
207,
|
| 627 |
+
207,
|
| 628 |
+
208,
|
| 629 |
+
208,
|
| 630 |
+
208,
|
| 631 |
+
209,
|
| 632 |
+
209,
|
| 633 |
+
209,
|
| 634 |
+
210,
|
| 635 |
+
210,
|
| 636 |
+
210,
|
| 637 |
+
211,
|
| 638 |
+
211,
|
| 639 |
+
211,
|
| 640 |
+
212,
|
| 641 |
+
212,
|
| 642 |
+
212,
|
| 643 |
+
213,
|
| 644 |
+
213,
|
| 645 |
+
213,
|
| 646 |
+
214,
|
| 647 |
+
214,
|
| 648 |
+
214,
|
| 649 |
+
215,
|
| 650 |
+
215,
|
| 651 |
+
215,
|
| 652 |
+
216,
|
| 653 |
+
216,
|
| 654 |
+
216,
|
| 655 |
+
217,
|
| 656 |
+
217,
|
| 657 |
+
217,
|
| 658 |
+
218,
|
| 659 |
+
218,
|
| 660 |
+
218,
|
| 661 |
+
219,
|
| 662 |
+
219,
|
| 663 |
+
219,
|
| 664 |
+
220,
|
| 665 |
+
220,
|
| 666 |
+
220,
|
| 667 |
+
221,
|
| 668 |
+
221,
|
| 669 |
+
221,
|
| 670 |
+
222,
|
| 671 |
+
222,
|
| 672 |
+
222,
|
| 673 |
+
223,
|
| 674 |
+
223,
|
| 675 |
+
223,
|
| 676 |
+
224,
|
| 677 |
+
224,
|
| 678 |
+
224,
|
| 679 |
+
225,
|
| 680 |
+
225,
|
| 681 |
+
225,
|
| 682 |
+
226,
|
| 683 |
+
226,
|
| 684 |
+
226,
|
| 685 |
+
227,
|
| 686 |
+
227,
|
| 687 |
+
227,
|
| 688 |
+
228,
|
| 689 |
+
228,
|
| 690 |
+
228,
|
| 691 |
+
229,
|
| 692 |
+
229,
|
| 693 |
+
229,
|
| 694 |
+
230,
|
| 695 |
+
230,
|
| 696 |
+
230,
|
| 697 |
+
231,
|
| 698 |
+
231,
|
| 699 |
+
231,
|
| 700 |
+
232,
|
| 701 |
+
232,
|
| 702 |
+
232,
|
| 703 |
+
233,
|
| 704 |
+
233,
|
| 705 |
+
233,
|
| 706 |
+
234,
|
| 707 |
+
234,
|
| 708 |
+
234,
|
| 709 |
+
235,
|
| 710 |
+
235,
|
| 711 |
+
235,
|
| 712 |
+
236,
|
| 713 |
+
236,
|
| 714 |
+
236,
|
| 715 |
+
237,
|
| 716 |
+
237,
|
| 717 |
+
237,
|
| 718 |
+
238,
|
| 719 |
+
238,
|
| 720 |
+
238,
|
| 721 |
+
239,
|
| 722 |
+
239,
|
| 723 |
+
239,
|
| 724 |
+
240,
|
| 725 |
+
240,
|
| 726 |
+
240,
|
| 727 |
+
241,
|
| 728 |
+
241,
|
| 729 |
+
241,
|
| 730 |
+
242,
|
| 731 |
+
242,
|
| 732 |
+
242,
|
| 733 |
+
243,
|
| 734 |
+
243,
|
| 735 |
+
243,
|
| 736 |
+
244,
|
| 737 |
+
244,
|
| 738 |
+
244,
|
| 739 |
+
245,
|
| 740 |
+
245,
|
| 741 |
+
245,
|
| 742 |
+
246,
|
| 743 |
+
246,
|
| 744 |
+
246,
|
| 745 |
+
247,
|
| 746 |
+
247,
|
| 747 |
+
247,
|
| 748 |
+
248,
|
| 749 |
+
248,
|
| 750 |
+
248,
|
| 751 |
+
249,
|
| 752 |
+
249,
|
| 753 |
+
249,
|
| 754 |
+
250,
|
| 755 |
+
250,
|
| 756 |
+
250,
|
| 757 |
+
251,
|
| 758 |
+
251,
|
| 759 |
+
251,
|
| 760 |
+
252,
|
| 761 |
+
252,
|
| 762 |
+
252,
|
| 763 |
+
253,
|
| 764 |
+
253,
|
| 765 |
+
253,
|
| 766 |
+
254,
|
| 767 |
+
254,
|
| 768 |
+
254,
|
| 769 |
+
255,
|
| 770 |
+
255,
|
| 771 |
+
255,
|
| 772 |
+
]
|
| 773 |
+
_palette = np.array(_palette).reshape((-1, 3))
|
| 774 |
+
_palette = _palette.tolist()
|
pipeline/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .bidirectional_diffusion_inference import (
|
| 2 |
+
BidirectionalDiffusionInferencePipeline,
|
| 3 |
+
)
|
| 4 |
+
from .bidirectional_inference import BidirectionalInferencePipeline
|
| 5 |
+
from .causal_diffusion_inference import CausalDiffusionInferencePipeline
|
| 6 |
+
from .causal_inference import CausalInferencePipeline
|
| 7 |
+
from .self_forcing_training import SelfForcingTrainingPipeline
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"BidirectionalDiffusionInferencePipeline",
|
| 11 |
+
"BidirectionalInferencePipeline",
|
| 12 |
+
"CausalDiffusionInferencePipeline",
|
| 13 |
+
"CausalInferencePipeline",
|
| 14 |
+
"SelfForcingTrainingPipeline",
|
| 15 |
+
]
|
pipeline/bidirectional_diffusion_inference.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from tqdm import tqdm
|
| 2 |
+
from typing import List
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from wan.utils.fm_solvers import (
|
| 6 |
+
FlowDPMSolverMultistepScheduler,
|
| 7 |
+
get_sampling_sigmas,
|
| 8 |
+
retrieve_timesteps,
|
| 9 |
+
)
|
| 10 |
+
from wan.utils.fm_solvers_unipc import FlowUniPCMultistepScheduler
|
| 11 |
+
from utils.wan_wrapper import (
|
| 12 |
+
WanDiffusionWrapper,
|
| 13 |
+
WanTextEncoder,
|
| 14 |
+
WanVAEWrapper,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class BidirectionalDiffusionInferencePipeline(torch.nn.Module):
|
| 19 |
+
def __init__(
|
| 20 |
+
self,
|
| 21 |
+
args,
|
| 22 |
+
device,
|
| 23 |
+
generator=None,
|
| 24 |
+
text_encoder=None,
|
| 25 |
+
vae=None,
|
| 26 |
+
):
|
| 27 |
+
super().__init__()
|
| 28 |
+
# Step 1: Initialize all models
|
| 29 |
+
self.generator = (
|
| 30 |
+
WanDiffusionWrapper(
|
| 31 |
+
**getattr(args, "model_kwargs", {}),
|
| 32 |
+
is_causal=False,
|
| 33 |
+
)
|
| 34 |
+
if generator is None
|
| 35 |
+
else generator
|
| 36 |
+
)
|
| 37 |
+
self.text_encoder = WanTextEncoder() if text_encoder is None else text_encoder
|
| 38 |
+
self.vae = WanVAEWrapper() if vae is None else vae
|
| 39 |
+
|
| 40 |
+
# Step 2: Initialize scheduler
|
| 41 |
+
self.num_train_timesteps = args.num_train_timestep
|
| 42 |
+
self.sampling_steps = 50
|
| 43 |
+
self.sample_solver = "unipc"
|
| 44 |
+
self.shift = 8.0
|
| 45 |
+
|
| 46 |
+
self.args = args
|
| 47 |
+
|
| 48 |
+
def inference(
|
| 49 |
+
self,
|
| 50 |
+
noise: torch.Tensor,
|
| 51 |
+
text_prompts: List[str],
|
| 52 |
+
return_latents=False,
|
| 53 |
+
) -> torch.Tensor:
|
| 54 |
+
"""
|
| 55 |
+
Perform inference on the given noise and text prompts.
|
| 56 |
+
Inputs:
|
| 57 |
+
noise (torch.Tensor): The input noise tensor of shape
|
| 58 |
+
(batch_size, num_frames, num_channels, height, width).
|
| 59 |
+
text_prompts (List[str]): The list of text prompts.
|
| 60 |
+
Outputs:
|
| 61 |
+
video (torch.Tensor): The generated video tensor of shape
|
| 62 |
+
(batch_size, num_frames, num_channels, height, width). It is normalized to be in the range [0, 1].
|
| 63 |
+
"""
|
| 64 |
+
|
| 65 |
+
conditional_dict = self.text_encoder(text_prompts=text_prompts)
|
| 66 |
+
unconditional_dict = self.text_encoder(
|
| 67 |
+
text_prompts=[self.args.negative_prompt] * len(text_prompts)
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
latents = noise
|
| 71 |
+
|
| 72 |
+
sample_scheduler = self._initialize_sample_scheduler(noise)
|
| 73 |
+
for _, t in enumerate(tqdm(sample_scheduler.timesteps)):
|
| 74 |
+
latent_model_input = latents
|
| 75 |
+
timestep = t * torch.ones(
|
| 76 |
+
[latents.shape[0], 21], device=noise.device, dtype=torch.float32
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
flow_pred_cond, _ = self.generator(latent_model_input, conditional_dict, timestep)
|
| 80 |
+
flow_pred_uncond, _ = self.generator(latent_model_input, unconditional_dict, timestep)
|
| 81 |
+
|
| 82 |
+
flow_pred = flow_pred_uncond + self.args.guidance_scale * (
|
| 83 |
+
flow_pred_cond - flow_pred_uncond
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
temp_x0 = sample_scheduler.step(
|
| 87 |
+
flow_pred.unsqueeze(0),
|
| 88 |
+
t,
|
| 89 |
+
latents.unsqueeze(0),
|
| 90 |
+
return_dict=False,
|
| 91 |
+
)[0]
|
| 92 |
+
latents = temp_x0.squeeze(0)
|
| 93 |
+
|
| 94 |
+
x0 = latents
|
| 95 |
+
video = self.vae.decode_to_pixel(x0)
|
| 96 |
+
video = (video * 0.5 + 0.5).clamp(0, 1)
|
| 97 |
+
|
| 98 |
+
del sample_scheduler
|
| 99 |
+
|
| 100 |
+
if return_latents:
|
| 101 |
+
return video, latents
|
| 102 |
+
else:
|
| 103 |
+
return video
|
| 104 |
+
|
| 105 |
+
def _initialize_sample_scheduler(
|
| 106 |
+
self,
|
| 107 |
+
noise,
|
| 108 |
+
):
|
| 109 |
+
if self.sample_solver == "unipc":
|
| 110 |
+
sample_scheduler = FlowUniPCMultistepScheduler(
|
| 111 |
+
num_train_timesteps=self.num_train_timesteps,
|
| 112 |
+
shift=1,
|
| 113 |
+
use_dynamic_shifting=False,
|
| 114 |
+
)
|
| 115 |
+
sample_scheduler.set_timesteps(
|
| 116 |
+
self.sampling_steps, device=noise.device, shift=self.shift
|
| 117 |
+
)
|
| 118 |
+
self.timesteps = sample_scheduler.timesteps
|
| 119 |
+
elif self.sample_solver == "dpm++":
|
| 120 |
+
sample_scheduler = FlowDPMSolverMultistepScheduler(
|
| 121 |
+
num_train_timesteps=self.num_train_timesteps,
|
| 122 |
+
shift=1,
|
| 123 |
+
use_dynamic_shifting=False,
|
| 124 |
+
)
|
| 125 |
+
sampling_sigmas = get_sampling_sigmas(self.sampling_steps, self.shift)
|
| 126 |
+
self.timesteps, _ = retrieve_timesteps(
|
| 127 |
+
sample_scheduler, device=noise.device, sigmas=sampling_sigmas
|
| 128 |
+
)
|
| 129 |
+
else:
|
| 130 |
+
raise NotImplementedError("Unsupported solver.")
|
| 131 |
+
return sample_scheduler
|
pipeline/bidirectional_inference.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
from utils.wan_wrapper import WanDiffusionWrapper, WanTextEncoder, WanVAEWrapper
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class BidirectionalInferencePipeline(torch.nn.Module):
|
| 8 |
+
def __init__(
|
| 9 |
+
self,
|
| 10 |
+
args,
|
| 11 |
+
device,
|
| 12 |
+
generator=None,
|
| 13 |
+
text_encoder=None,
|
| 14 |
+
vae=None,
|
| 15 |
+
):
|
| 16 |
+
super().__init__()
|
| 17 |
+
# Step 1: Initialize all models
|
| 18 |
+
self.generator = (
|
| 19 |
+
WanDiffusionWrapper(**getattr(args, "model_kwargs", {}), is_causal=False)
|
| 20 |
+
if generator is None
|
| 21 |
+
else generator
|
| 22 |
+
)
|
| 23 |
+
self.text_encoder = WanTextEncoder() if text_encoder is None else text_encoder
|
| 24 |
+
self.vae = WanVAEWrapper() if vae is None else vae
|
| 25 |
+
|
| 26 |
+
# Step 2: Initialize all bidirectional wan hyperparmeters
|
| 27 |
+
self.scheduler = self.generator.get_scheduler()
|
| 28 |
+
self.denoising_step_list = torch.tensor(
|
| 29 |
+
args.denoising_step_list, dtype=torch.long, device=device
|
| 30 |
+
)
|
| 31 |
+
if self.denoising_step_list[-1] == 0:
|
| 32 |
+
self.denoising_step_list = self.denoising_step_list[
|
| 33 |
+
:-1
|
| 34 |
+
] # remove the zero timestep for inference
|
| 35 |
+
if args.warp_denoising_step:
|
| 36 |
+
timesteps = torch.cat(
|
| 37 |
+
(
|
| 38 |
+
self.scheduler.timesteps.cpu(),
|
| 39 |
+
torch.tensor([0], dtype=torch.float32),
|
| 40 |
+
)
|
| 41 |
+
)
|
| 42 |
+
self.denoising_step_list = timesteps[1000 - self.denoising_step_list]
|
| 43 |
+
|
| 44 |
+
def inference(
|
| 45 |
+
self,
|
| 46 |
+
noise: torch.Tensor,
|
| 47 |
+
text_prompts: List[str],
|
| 48 |
+
) -> torch.Tensor:
|
| 49 |
+
"""
|
| 50 |
+
Perform inference on the given noise and text prompts.
|
| 51 |
+
Inputs:
|
| 52 |
+
noise (torch.Tensor): The input noise tensor of shape
|
| 53 |
+
(batch_size, num_frames, num_channels, height, width).
|
| 54 |
+
text_prompts (List[str]): The list of text prompts.
|
| 55 |
+
Outputs:
|
| 56 |
+
video (torch.Tensor): The generated video tensor of shape
|
| 57 |
+
(batch_size, num_frames, num_channels, height, width). It is normalized to be in the range [0, 1].
|
| 58 |
+
"""
|
| 59 |
+
conditional_dict = self.text_encoder(text_prompts=text_prompts)
|
| 60 |
+
|
| 61 |
+
# initial point
|
| 62 |
+
noisy_image_or_video = noise
|
| 63 |
+
|
| 64 |
+
# use the last n-1 timesteps to simulate the generator's input
|
| 65 |
+
for index, current_timestep in enumerate(self.denoising_step_list[:-1]):
|
| 66 |
+
_, pred_image_or_video = self.generator(
|
| 67 |
+
noisy_image_or_video=noisy_image_or_video,
|
| 68 |
+
conditional_dict=conditional_dict,
|
| 69 |
+
timestep=torch.ones(noise.shape[:2], dtype=torch.long, device=noise.device)
|
| 70 |
+
* current_timestep,
|
| 71 |
+
) # [B, F, C, H, W]
|
| 72 |
+
|
| 73 |
+
next_timestep = self.denoising_step_list[index + 1] * torch.ones(
|
| 74 |
+
noise.shape[:2], dtype=torch.long, device=noise.device
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
noisy_image_or_video = self.scheduler.add_noise(
|
| 78 |
+
pred_image_or_video.flatten(0, 1),
|
| 79 |
+
torch.randn_like(pred_image_or_video.flatten(0, 1)),
|
| 80 |
+
next_timestep.flatten(0, 1),
|
| 81 |
+
).unflatten(0, noise.shape[:2])
|
| 82 |
+
|
| 83 |
+
video = self.vae.decode_to_pixel(pred_image_or_video)
|
| 84 |
+
video = (video * 0.5 + 0.5).clamp(0, 1)
|
| 85 |
+
return video
|
pipeline/causal_diffusion_inference.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from tqdm import tqdm
|
| 2 |
+
from typing import List, Optional
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from wan.utils.fm_solvers import (
|
| 6 |
+
FlowDPMSolverMultistepScheduler,
|
| 7 |
+
get_sampling_sigmas,
|
| 8 |
+
retrieve_timesteps,
|
| 9 |
+
)
|
| 10 |
+
from wan.utils.fm_solvers_unipc import FlowUniPCMultistepScheduler
|
| 11 |
+
from utils.wan_wrapper import (
|
| 12 |
+
WanDiffusionWrapper,
|
| 13 |
+
WanTextEncoder,
|
| 14 |
+
WanVAEWrapper,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class CausalDiffusionInferencePipeline(torch.nn.Module):
|
| 19 |
+
def __init__(
|
| 20 |
+
self,
|
| 21 |
+
args,
|
| 22 |
+
device,
|
| 23 |
+
generator=None,
|
| 24 |
+
text_encoder=None,
|
| 25 |
+
vae=None,
|
| 26 |
+
):
|
| 27 |
+
super().__init__()
|
| 28 |
+
# Step 1: Initialize all models
|
| 29 |
+
self.generator = (
|
| 30 |
+
WanDiffusionWrapper(
|
| 31 |
+
**getattr(args, "model_kwargs", {}),
|
| 32 |
+
is_causal=True,
|
| 33 |
+
)
|
| 34 |
+
if generator is None
|
| 35 |
+
else generator
|
| 36 |
+
)
|
| 37 |
+
self.text_encoder = WanTextEncoder() if text_encoder is None else text_encoder
|
| 38 |
+
self.vae = WanVAEWrapper() if vae is None else vae
|
| 39 |
+
|
| 40 |
+
# Step 2: Initialize scheduler
|
| 41 |
+
self.num_train_timesteps = args.num_train_timestep
|
| 42 |
+
self.sampling_steps = 50
|
| 43 |
+
self.sample_solver = "unipc"
|
| 44 |
+
self.shift = args.timestep_shift
|
| 45 |
+
|
| 46 |
+
self.num_transformer_blocks = 30
|
| 47 |
+
self.frame_seq_length = 1560
|
| 48 |
+
|
| 49 |
+
self.kv_cache_pos = None
|
| 50 |
+
self.kv_cache_neg = None
|
| 51 |
+
self.crossattn_cache_pos = None
|
| 52 |
+
self.crossattn_cache_neg = None
|
| 53 |
+
self.args = args
|
| 54 |
+
self.num_frame_per_block = getattr(args, "num_frame_per_block", 1)
|
| 55 |
+
self.independent_first_frame = args.independent_first_frame
|
| 56 |
+
self.local_attn_size = self.generator.model.local_attn_size
|
| 57 |
+
|
| 58 |
+
print(f"KV inference with {self.num_frame_per_block} frames per block")
|
| 59 |
+
|
| 60 |
+
if self.num_frame_per_block > 1:
|
| 61 |
+
self.generator.model.num_frame_per_block = self.num_frame_per_block
|
| 62 |
+
|
| 63 |
+
def inference(
|
| 64 |
+
self,
|
| 65 |
+
noise: torch.Tensor,
|
| 66 |
+
text_prompts: List[str],
|
| 67 |
+
initial_latent: Optional[torch.Tensor] = None,
|
| 68 |
+
return_latents: bool = False,
|
| 69 |
+
start_frame_index: Optional[int] = 0,
|
| 70 |
+
) -> torch.Tensor:
|
| 71 |
+
"""
|
| 72 |
+
Perform inference on the given noise and text prompts.
|
| 73 |
+
Inputs:
|
| 74 |
+
noise (torch.Tensor): The input noise tensor of shape
|
| 75 |
+
(batch_size, num_output_frames, num_channels, height, width).
|
| 76 |
+
text_prompts (List[str]): The list of text prompts.
|
| 77 |
+
initial_latent (torch.Tensor): The initial latent tensor of shape
|
| 78 |
+
(batch_size, num_input_frames, num_channels, height, width).
|
| 79 |
+
If num_input_frames is 1, perform image to video.
|
| 80 |
+
If num_input_frames is greater than 1, perform video extension.
|
| 81 |
+
return_latents (bool): Whether to return the latents.
|
| 82 |
+
start_frame_index (int): In long video generation, where does the current window start?
|
| 83 |
+
Outputs:
|
| 84 |
+
video (torch.Tensor): The generated video tensor of shape
|
| 85 |
+
(batch_size, num_frames, num_channels, height, width). It is normalized to be in the range [0, 1].
|
| 86 |
+
"""
|
| 87 |
+
batch_size, num_frames, num_channels, height, width = noise.shape
|
| 88 |
+
if not self.independent_first_frame or (
|
| 89 |
+
self.independent_first_frame and initial_latent is not None
|
| 90 |
+
):
|
| 91 |
+
# If the first frame is independent and the first frame is provided, then the number of frames in the
|
| 92 |
+
# noise should still be a multiple of num_frame_per_block
|
| 93 |
+
assert num_frames % self.num_frame_per_block == 0
|
| 94 |
+
num_blocks = num_frames // self.num_frame_per_block
|
| 95 |
+
elif self.independent_first_frame and initial_latent is None:
|
| 96 |
+
# Using a [1, 4, 4, 4, 4, 4] model to generate a video without image conditioning
|
| 97 |
+
assert (num_frames - 1) % self.num_frame_per_block == 0
|
| 98 |
+
num_blocks = (num_frames - 1) // self.num_frame_per_block
|
| 99 |
+
num_input_frames = initial_latent.shape[1] if initial_latent is not None else 0
|
| 100 |
+
num_output_frames = num_frames + num_input_frames # add the initial latent frames
|
| 101 |
+
conditional_dict = self.text_encoder(text_prompts=text_prompts)
|
| 102 |
+
unconditional_dict = self.text_encoder(
|
| 103 |
+
text_prompts=[self.args.negative_prompt] * len(text_prompts)
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
output = torch.zeros(
|
| 107 |
+
[batch_size, num_output_frames, num_channels, height, width],
|
| 108 |
+
device=noise.device,
|
| 109 |
+
dtype=noise.dtype,
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
# Step 1: Initialize KV cache to all zeros
|
| 113 |
+
if self.kv_cache_pos is None:
|
| 114 |
+
self._initialize_kv_cache(batch_size=batch_size, dtype=noise.dtype, device=noise.device)
|
| 115 |
+
self._initialize_crossattn_cache(
|
| 116 |
+
batch_size=batch_size, dtype=noise.dtype, device=noise.device
|
| 117 |
+
)
|
| 118 |
+
else:
|
| 119 |
+
# reset cross attn cache
|
| 120 |
+
for block_index in range(self.num_transformer_blocks):
|
| 121 |
+
self.crossattn_cache_pos[block_index]["is_init"] = False
|
| 122 |
+
self.crossattn_cache_neg[block_index]["is_init"] = False
|
| 123 |
+
# reset kv cache
|
| 124 |
+
for block_index in range(len(self.kv_cache_pos)):
|
| 125 |
+
self.kv_cache_pos[block_index]["global_end_index"] = torch.tensor(
|
| 126 |
+
[0], dtype=torch.long, device=noise.device
|
| 127 |
+
)
|
| 128 |
+
self.kv_cache_pos[block_index]["local_end_index"] = torch.tensor(
|
| 129 |
+
[0], dtype=torch.long, device=noise.device
|
| 130 |
+
)
|
| 131 |
+
self.kv_cache_neg[block_index]["global_end_index"] = torch.tensor(
|
| 132 |
+
[0], dtype=torch.long, device=noise.device
|
| 133 |
+
)
|
| 134 |
+
self.kv_cache_neg[block_index]["local_end_index"] = torch.tensor(
|
| 135 |
+
[0], dtype=torch.long, device=noise.device
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
# Step 2: Cache context feature
|
| 139 |
+
current_start_frame = start_frame_index
|
| 140 |
+
cache_start_frame = 0
|
| 141 |
+
if initial_latent is not None:
|
| 142 |
+
timestep = torch.ones([batch_size, 1], device=noise.device, dtype=torch.int64) * 0
|
| 143 |
+
if self.independent_first_frame:
|
| 144 |
+
# Assume num_input_frames is 1 + self.num_frame_per_block * num_input_blocks
|
| 145 |
+
assert (num_input_frames - 1) % self.num_frame_per_block == 0
|
| 146 |
+
num_input_blocks = (num_input_frames - 1) // self.num_frame_per_block
|
| 147 |
+
output[:, :1] = initial_latent[:, :1]
|
| 148 |
+
self.generator(
|
| 149 |
+
noisy_image_or_video=initial_latent[:, :1],
|
| 150 |
+
conditional_dict=conditional_dict,
|
| 151 |
+
timestep=timestep * 0,
|
| 152 |
+
kv_cache=self.kv_cache_pos,
|
| 153 |
+
crossattn_cache=self.crossattn_cache_pos,
|
| 154 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 155 |
+
cache_start=cache_start_frame * self.frame_seq_length,
|
| 156 |
+
)
|
| 157 |
+
self.generator(
|
| 158 |
+
noisy_image_or_video=initial_latent[:, :1],
|
| 159 |
+
conditional_dict=unconditional_dict,
|
| 160 |
+
timestep=timestep * 0,
|
| 161 |
+
kv_cache=self.kv_cache_neg,
|
| 162 |
+
crossattn_cache=self.crossattn_cache_neg,
|
| 163 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 164 |
+
cache_start=cache_start_frame * self.frame_seq_length,
|
| 165 |
+
)
|
| 166 |
+
current_start_frame += 1
|
| 167 |
+
cache_start_frame += 1
|
| 168 |
+
else:
|
| 169 |
+
# Assume num_input_frames is self.num_frame_per_block * num_input_blocks
|
| 170 |
+
assert num_input_frames % self.num_frame_per_block == 0
|
| 171 |
+
num_input_blocks = num_input_frames // self.num_frame_per_block
|
| 172 |
+
|
| 173 |
+
for block_index in range(num_input_blocks):
|
| 174 |
+
current_ref_latents = initial_latent[
|
| 175 |
+
:,
|
| 176 |
+
cache_start_frame : cache_start_frame + self.num_frame_per_block,
|
| 177 |
+
]
|
| 178 |
+
output[
|
| 179 |
+
:,
|
| 180 |
+
cache_start_frame : cache_start_frame + self.num_frame_per_block,
|
| 181 |
+
] = current_ref_latents
|
| 182 |
+
self.generator(
|
| 183 |
+
noisy_image_or_video=current_ref_latents,
|
| 184 |
+
conditional_dict=conditional_dict,
|
| 185 |
+
timestep=timestep * 0,
|
| 186 |
+
kv_cache=self.kv_cache_pos,
|
| 187 |
+
crossattn_cache=self.crossattn_cache_pos,
|
| 188 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 189 |
+
cache_start=cache_start_frame * self.frame_seq_length,
|
| 190 |
+
)
|
| 191 |
+
self.generator(
|
| 192 |
+
noisy_image_or_video=current_ref_latents,
|
| 193 |
+
conditional_dict=unconditional_dict,
|
| 194 |
+
timestep=timestep * 0,
|
| 195 |
+
kv_cache=self.kv_cache_neg,
|
| 196 |
+
crossattn_cache=self.crossattn_cache_neg,
|
| 197 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 198 |
+
cache_start=cache_start_frame * self.frame_seq_length,
|
| 199 |
+
)
|
| 200 |
+
current_start_frame += self.num_frame_per_block
|
| 201 |
+
cache_start_frame += self.num_frame_per_block
|
| 202 |
+
|
| 203 |
+
# Step 3: Temporal denoising loop
|
| 204 |
+
all_num_frames = [self.num_frame_per_block] * num_blocks
|
| 205 |
+
if self.independent_first_frame and initial_latent is None:
|
| 206 |
+
all_num_frames = [1] + all_num_frames
|
| 207 |
+
for current_num_frames in all_num_frames:
|
| 208 |
+
noisy_input = noise[
|
| 209 |
+
:,
|
| 210 |
+
cache_start_frame
|
| 211 |
+
- num_input_frames : cache_start_frame
|
| 212 |
+
+ current_num_frames
|
| 213 |
+
- num_input_frames,
|
| 214 |
+
]
|
| 215 |
+
latents = noisy_input
|
| 216 |
+
|
| 217 |
+
# Step 3.1: Spatial denoising loop
|
| 218 |
+
sample_scheduler = self._initialize_sample_scheduler(noise)
|
| 219 |
+
for _, t in enumerate(tqdm(sample_scheduler.timesteps)):
|
| 220 |
+
latent_model_input = latents
|
| 221 |
+
timestep = t * torch.ones(
|
| 222 |
+
[batch_size, current_num_frames],
|
| 223 |
+
device=noise.device,
|
| 224 |
+
dtype=torch.float32,
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
flow_pred_cond, _ = self.generator(
|
| 228 |
+
noisy_image_or_video=latent_model_input,
|
| 229 |
+
conditional_dict=conditional_dict,
|
| 230 |
+
timestep=timestep,
|
| 231 |
+
kv_cache=self.kv_cache_pos,
|
| 232 |
+
crossattn_cache=self.crossattn_cache_pos,
|
| 233 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 234 |
+
cache_start=cache_start_frame * self.frame_seq_length,
|
| 235 |
+
)
|
| 236 |
+
flow_pred_uncond, _ = self.generator(
|
| 237 |
+
noisy_image_or_video=latent_model_input,
|
| 238 |
+
conditional_dict=unconditional_dict,
|
| 239 |
+
timestep=timestep,
|
| 240 |
+
kv_cache=self.kv_cache_neg,
|
| 241 |
+
crossattn_cache=self.crossattn_cache_neg,
|
| 242 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 243 |
+
cache_start=cache_start_frame * self.frame_seq_length,
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
flow_pred = flow_pred_uncond + self.args.guidance_scale * (
|
| 247 |
+
flow_pred_cond - flow_pred_uncond
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
temp_x0 = sample_scheduler.step(flow_pred, t, latents, return_dict=False)[0]
|
| 251 |
+
latents = temp_x0
|
| 252 |
+
print(f"kv_cache['local_end_index']: {self.kv_cache_pos[0]['local_end_index']}")
|
| 253 |
+
print(f"kv_cache['global_end_index']: {self.kv_cache_pos[0]['global_end_index']}")
|
| 254 |
+
|
| 255 |
+
# Step 3.2: record the model's output
|
| 256 |
+
output[:, cache_start_frame : cache_start_frame + current_num_frames] = latents
|
| 257 |
+
|
| 258 |
+
# Step 3.3: rerun with timestep zero to update KV cache using clean context
|
| 259 |
+
self.generator(
|
| 260 |
+
noisy_image_or_video=latents,
|
| 261 |
+
conditional_dict=conditional_dict,
|
| 262 |
+
timestep=timestep * 0,
|
| 263 |
+
kv_cache=self.kv_cache_pos,
|
| 264 |
+
crossattn_cache=self.crossattn_cache_pos,
|
| 265 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 266 |
+
cache_start=cache_start_frame * self.frame_seq_length,
|
| 267 |
+
)
|
| 268 |
+
self.generator(
|
| 269 |
+
noisy_image_or_video=latents,
|
| 270 |
+
conditional_dict=unconditional_dict,
|
| 271 |
+
timestep=timestep * 0,
|
| 272 |
+
kv_cache=self.kv_cache_neg,
|
| 273 |
+
crossattn_cache=self.crossattn_cache_neg,
|
| 274 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 275 |
+
cache_start=cache_start_frame * self.frame_seq_length,
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
# Step 3.4: update the start and end frame indices
|
| 279 |
+
current_start_frame += current_num_frames
|
| 280 |
+
cache_start_frame += current_num_frames
|
| 281 |
+
|
| 282 |
+
# Step 4: Decode the output
|
| 283 |
+
video = self.vae.decode_to_pixel(output)
|
| 284 |
+
video = (video * 0.5 + 0.5).clamp(0, 1)
|
| 285 |
+
|
| 286 |
+
if return_latents:
|
| 287 |
+
return video, output
|
| 288 |
+
else:
|
| 289 |
+
return video
|
| 290 |
+
|
| 291 |
+
def _initialize_kv_cache(
|
| 292 |
+
self,
|
| 293 |
+
batch_size,
|
| 294 |
+
dtype,
|
| 295 |
+
device,
|
| 296 |
+
):
|
| 297 |
+
"""
|
| 298 |
+
Initialize a Per-GPU KV cache for the Wan model.
|
| 299 |
+
"""
|
| 300 |
+
kv_cache_pos = []
|
| 301 |
+
kv_cache_neg = []
|
| 302 |
+
if self.local_attn_size != -1:
|
| 303 |
+
# Use the local attention size to compute the KV cache size
|
| 304 |
+
kv_cache_size = self.local_attn_size * self.frame_seq_length
|
| 305 |
+
else:
|
| 306 |
+
# Use the default KV cache size
|
| 307 |
+
kv_cache_size = 32760
|
| 308 |
+
|
| 309 |
+
for _ in range(self.num_transformer_blocks):
|
| 310 |
+
kv_cache_pos.append(
|
| 311 |
+
{
|
| 312 |
+
"k": torch.zeros(
|
| 313 |
+
[batch_size, kv_cache_size, 12, 128],
|
| 314 |
+
dtype=dtype,
|
| 315 |
+
device=device,
|
| 316 |
+
),
|
| 317 |
+
"v": torch.zeros(
|
| 318 |
+
[batch_size, kv_cache_size, 12, 128],
|
| 319 |
+
dtype=dtype,
|
| 320 |
+
device=device,
|
| 321 |
+
),
|
| 322 |
+
"global_end_index": torch.tensor([0], dtype=torch.long, device=device),
|
| 323 |
+
"local_end_index": torch.tensor([0], dtype=torch.long, device=device),
|
| 324 |
+
}
|
| 325 |
+
)
|
| 326 |
+
kv_cache_neg.append(
|
| 327 |
+
{
|
| 328 |
+
"k": torch.zeros(
|
| 329 |
+
[batch_size, kv_cache_size, 12, 128],
|
| 330 |
+
dtype=dtype,
|
| 331 |
+
device=device,
|
| 332 |
+
),
|
| 333 |
+
"v": torch.zeros(
|
| 334 |
+
[batch_size, kv_cache_size, 12, 128],
|
| 335 |
+
dtype=dtype,
|
| 336 |
+
device=device,
|
| 337 |
+
),
|
| 338 |
+
"global_end_index": torch.tensor([0], dtype=torch.long, device=device),
|
| 339 |
+
"local_end_index": torch.tensor([0], dtype=torch.long, device=device),
|
| 340 |
+
}
|
| 341 |
+
)
|
| 342 |
+
|
| 343 |
+
self.kv_cache_pos = kv_cache_pos # always store the clean cache
|
| 344 |
+
self.kv_cache_neg = kv_cache_neg # always store the clean cache
|
| 345 |
+
|
| 346 |
+
def _initialize_crossattn_cache(
|
| 347 |
+
self,
|
| 348 |
+
batch_size,
|
| 349 |
+
dtype,
|
| 350 |
+
device,
|
| 351 |
+
):
|
| 352 |
+
"""
|
| 353 |
+
Initialize a Per-GPU cross-attention cache for the Wan model.
|
| 354 |
+
"""
|
| 355 |
+
crossattn_cache_pos = []
|
| 356 |
+
crossattn_cache_neg = []
|
| 357 |
+
for _ in range(self.num_transformer_blocks):
|
| 358 |
+
crossattn_cache_pos.append(
|
| 359 |
+
{
|
| 360 |
+
"k": torch.zeros([batch_size, 512, 12, 128], dtype=dtype, device=device),
|
| 361 |
+
"v": torch.zeros([batch_size, 512, 12, 128], dtype=dtype, device=device),
|
| 362 |
+
"is_init": False,
|
| 363 |
+
}
|
| 364 |
+
)
|
| 365 |
+
crossattn_cache_neg.append(
|
| 366 |
+
{
|
| 367 |
+
"k": torch.zeros([batch_size, 512, 12, 128], dtype=dtype, device=device),
|
| 368 |
+
"v": torch.zeros([batch_size, 512, 12, 128], dtype=dtype, device=device),
|
| 369 |
+
"is_init": False,
|
| 370 |
+
}
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
self.crossattn_cache_pos = crossattn_cache_pos # always store the clean cache
|
| 374 |
+
self.crossattn_cache_neg = crossattn_cache_neg # always store the clean cache
|
| 375 |
+
|
| 376 |
+
def _initialize_sample_scheduler(
|
| 377 |
+
self,
|
| 378 |
+
noise,
|
| 379 |
+
):
|
| 380 |
+
if self.sample_solver == "unipc":
|
| 381 |
+
sample_scheduler = FlowUniPCMultistepScheduler(
|
| 382 |
+
num_train_timesteps=self.num_train_timesteps,
|
| 383 |
+
shift=1,
|
| 384 |
+
use_dynamic_shifting=False,
|
| 385 |
+
)
|
| 386 |
+
sample_scheduler.set_timesteps(
|
| 387 |
+
self.sampling_steps, device=noise.device, shift=self.shift
|
| 388 |
+
)
|
| 389 |
+
self.timesteps = sample_scheduler.timesteps
|
| 390 |
+
elif self.sample_solver == "dpm++":
|
| 391 |
+
sample_scheduler = FlowDPMSolverMultistepScheduler(
|
| 392 |
+
num_train_timesteps=self.num_train_timesteps,
|
| 393 |
+
shift=1,
|
| 394 |
+
use_dynamic_shifting=False,
|
| 395 |
+
)
|
| 396 |
+
sampling_sigmas = get_sampling_sigmas(self.sampling_steps, self.shift)
|
| 397 |
+
self.timesteps, _ = retrieve_timesteps(
|
| 398 |
+
sample_scheduler, device=noise.device, sigmas=sampling_sigmas
|
| 399 |
+
)
|
| 400 |
+
else:
|
| 401 |
+
raise NotImplementedError("Unsupported solver.")
|
| 402 |
+
return sample_scheduler
|
pipeline/causal_inference.py
ADDED
|
@@ -0,0 +1,1193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gc
|
| 2 |
+
import random
|
| 3 |
+
import time
|
| 4 |
+
from typing import List, Optional
|
| 5 |
+
import numpy as np
|
| 6 |
+
from omegaconf import DictConfig, OmegaConf
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
import torch.nn.functional as F
|
| 10 |
+
from torch.nn.utils import clip_grad_norm_
|
| 11 |
+
from tqdm import tqdm
|
| 12 |
+
|
| 13 |
+
from optimize_utils import transpose_dict_2d
|
| 14 |
+
from tensor_utils import (
|
| 15 |
+
build_anisotropic_gaussian_from_mask,
|
| 16 |
+
combine_gaussian_maps,
|
| 17 |
+
combine_masks_or,
|
| 18 |
+
normalize_tensor_to_match_tensor,
|
| 19 |
+
resize_tensor,
|
| 20 |
+
warp_tensor,
|
| 21 |
+
warp_tensor_sequence,
|
| 22 |
+
)
|
| 23 |
+
from utils.wan_wrapper import (
|
| 24 |
+
WanDiffusionWrapper,
|
| 25 |
+
WanTextEncoder,
|
| 26 |
+
WanVAEWrapper,
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
from demo_utils.memory import (
|
| 30 |
+
gpu,
|
| 31 |
+
get_cuda_free_memory_gb,
|
| 32 |
+
DynamicSwapInstaller,
|
| 33 |
+
move_model_to_device_with_memory_preservation,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
IMAGE_HEIGHT = 480.0
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def split_trajectories_segments(
|
| 40 |
+
trajectories: list[dict[str, bool | list[torch.Tensor]]],
|
| 41 |
+
translation_step: float,
|
| 42 |
+
rotation_step: float,
|
| 43 |
+
) -> List[list[dict[str, bool | list[torch.Tensor]]]]:
|
| 44 |
+
"""
|
| 45 |
+
Split drag trajectories into evenly spaced intermediate segments for
|
| 46 |
+
progressive (coarse-to-fine) optimization.
|
| 47 |
+
|
| 48 |
+
Given N trajectories (each with per-frame deltas), this function:
|
| 49 |
+
1. Determines the maximum number of segments needed across all
|
| 50 |
+
trajectories based on the magnitude of their deltas and the
|
| 51 |
+
provided step sizes.
|
| 52 |
+
2. Divides every trajectory's deltas uniformly into that many segments.
|
| 53 |
+
3. Produces a list of cumulative intermediate trajectory snapshots,
|
| 54 |
+
where segment k contains deltas scaled by (k / max_segments).
|
| 55 |
+
|
| 56 |
+
:param trajectories:
|
| 57 |
+
N x trajectory dicts.
|
| 58 |
+
Each dict has keys:
|
| 59 |
+
- 'is_rotation' (bool): Whether this trajectory is a rotation.
|
| 60 |
+
- 'deltas' (list): Per-frame displacement values.
|
| 61 |
+
For translation: each delta is a 2D vector (dx, dy).
|
| 62 |
+
For rotation: each delta is a scalar angle.
|
| 63 |
+
- 'start_point': The starting pixel coordinate of the drag.
|
| 64 |
+
- 'rotation_center' (only if is_rotation): The center of rotation.
|
| 65 |
+
|
| 66 |
+
:param translation_step:
|
| 67 |
+
The pixel distance that defines one segment for translation
|
| 68 |
+
trajectories. Larger values produce fewer segments.
|
| 69 |
+
|
| 70 |
+
:param rotation_step:
|
| 71 |
+
The angle (in the same units as deltas) that defines one segment
|
| 72 |
+
for rotation trajectories. Larger values produce fewer segments.
|
| 73 |
+
|
| 74 |
+
:returns:
|
| 75 |
+
segment_num x N x trajectory dicts.
|
| 76 |
+
A list of length `max_segment_number`, where each element is a list
|
| 77 |
+
of N trajectory dicts. The k-th element (1-indexed) contains
|
| 78 |
+
trajectories whose deltas are scaled to (k / max_segment_number)
|
| 79 |
+
of the original deltas — i.e., cumulative intermediate waypoints.
|
| 80 |
+
"""
|
| 81 |
+
# -------------------------------------------------------------------------
|
| 82 |
+
# Phase 1: Convert raw deltas to torch tensors (ensure uniform type)
|
| 83 |
+
# -------------------------------------------------------------------------
|
| 84 |
+
for trajectory in trajectories:
|
| 85 |
+
trajectory["deltas"] = [torch.tensor(delta, device="cpu") for delta in trajectory["deltas"]]
|
| 86 |
+
|
| 87 |
+
# -------------------------------------------------------------------------
|
| 88 |
+
# Phase 2: Determine the maximum number of segments across all trajectories.
|
| 89 |
+
# - For rotations: segment count = |angle_delta| // rotation_step
|
| 90 |
+
# - For translations: segment count = ||displacement_delta||₂ // translation_step
|
| 91 |
+
# - We take the global maximum so every trajectory is split into the
|
| 92 |
+
# same number of segments (ensuring synchronized progressive steps).
|
| 93 |
+
# -------------------------------------------------------------------------
|
| 94 |
+
max_segment_number = 1 # at least one segment
|
| 95 |
+
for trajectory in trajectories:
|
| 96 |
+
print(f"{trajectory['is_rotation'] = }")
|
| 97 |
+
for delta in trajectory["deltas"]:
|
| 98 |
+
if trajectory["is_rotation"]:
|
| 99 |
+
magnitude = abs(delta)
|
| 100 |
+
step = rotation_step
|
| 101 |
+
else:
|
| 102 |
+
magnitude = abs(torch.norm(delta))
|
| 103 |
+
step = translation_step
|
| 104 |
+
segment_number = int(magnitude // step)
|
| 105 |
+
print(f"{delta = } {magnitude = } {segment_number = }")
|
| 106 |
+
max_segment_number = max(max_segment_number, segment_number)
|
| 107 |
+
print(f"{max_segment_number = }")
|
| 108 |
+
|
| 109 |
+
# -------------------------------------------------------------------------
|
| 110 |
+
# Phase 3: Compute per-segment step sizes for each trajectory.
|
| 111 |
+
# Each trajectory's deltas are divided by max_segment_number to get
|
| 112 |
+
# the uniform per-segment increment.
|
| 113 |
+
# -------------------------------------------------------------------------
|
| 114 |
+
split_trajectory_steps = []
|
| 115 |
+
for trajectory in trajectories:
|
| 116 |
+
print(f"{trajectory['is_rotation'] = }")
|
| 117 |
+
# Divide each frame's delta by the total number of segments
|
| 118 |
+
trajectory_steps = [delta / float(max_segment_number) for delta in trajectory["deltas"]]
|
| 119 |
+
print(f"{trajectory_steps = }")
|
| 120 |
+
# Build the per-trajectory step metadata
|
| 121 |
+
split_trajectory_step = {
|
| 122 |
+
"is_rotation": trajectory["is_rotation"],
|
| 123 |
+
"steps": trajectory_steps, # per-segment increment per frame
|
| 124 |
+
"start_point": trajectory["start_point"],
|
| 125 |
+
}
|
| 126 |
+
if trajectory["is_rotation"]:
|
| 127 |
+
split_trajectory_step["rotation_center"] = trajectory["rotation_center"]
|
| 128 |
+
split_trajectory_steps.append(split_trajectory_step)
|
| 129 |
+
|
| 130 |
+
# -------------------------------------------------------------------------
|
| 131 |
+
# Phase 4: Build cumulative intermediate trajectory lists.
|
| 132 |
+
# For segment_index k (1-indexed from 1 to max_segment_number):
|
| 133 |
+
# delta_k = step * k
|
| 134 |
+
# This produces progressively larger displacements, enabling the
|
| 135 |
+
# optimizer to move features gradually toward the final target.
|
| 136 |
+
# -------------------------------------------------------------------------
|
| 137 |
+
new_trajectories_list = []
|
| 138 |
+
for segment_index in range(max_segment_number):
|
| 139 |
+
segment_index += 1 # 1-indexed: cumulative scale factor
|
| 140 |
+
new_trajectories = []
|
| 141 |
+
for trajectory_step in split_trajectory_steps:
|
| 142 |
+
new_trajectory = {
|
| 143 |
+
"is_rotation": trajectory_step["is_rotation"],
|
| 144 |
+
"deltas": [step * segment_index for step in trajectory_step["steps"]],
|
| 145 |
+
"start_point": trajectory_step["start_point"],
|
| 146 |
+
}
|
| 147 |
+
if trajectory_step["is_rotation"]:
|
| 148 |
+
new_trajectory["rotation_center"] = trajectory_step["rotation_center"]
|
| 149 |
+
new_trajectories.append(new_trajectory)
|
| 150 |
+
print(f"{new_trajectories = }")
|
| 151 |
+
new_trajectories_list.append(new_trajectories)
|
| 152 |
+
|
| 153 |
+
# Return: list of length max_segment_number, each containing N trajectory dicts
|
| 154 |
+
# with cumulatively scaled deltas (segment 1 = smallest, last = full original delta)
|
| 155 |
+
return new_trajectories_list
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
class CausalInferencePipeline(torch.nn.Module):
|
| 159 |
+
def __init__(
|
| 160 |
+
self,
|
| 161 |
+
args,
|
| 162 |
+
device,
|
| 163 |
+
generator=None,
|
| 164 |
+
text_encoder=None,
|
| 165 |
+
vae=None,
|
| 166 |
+
):
|
| 167 |
+
super().__init__()
|
| 168 |
+
# Step 1: Initialize all models
|
| 169 |
+
self.generator = (
|
| 170 |
+
WanDiffusionWrapper(
|
| 171 |
+
**getattr(args, "model_kwargs", {}),
|
| 172 |
+
is_causal=True,
|
| 173 |
+
)
|
| 174 |
+
if generator is None
|
| 175 |
+
else generator
|
| 176 |
+
)
|
| 177 |
+
self.text_encoder = WanTextEncoder() if text_encoder is None else text_encoder
|
| 178 |
+
self.vae = WanVAEWrapper() if vae is None else vae
|
| 179 |
+
|
| 180 |
+
# Step 2: Initialize all causal hyperparmeters
|
| 181 |
+
self.scheduler = self.generator.get_scheduler()
|
| 182 |
+
self.denoising_step_list = torch.tensor(args.denoising_step_list, dtype=torch.long)
|
| 183 |
+
if args.warp_denoising_step:
|
| 184 |
+
timesteps = torch.cat(
|
| 185 |
+
(
|
| 186 |
+
self.scheduler.timesteps.cpu(),
|
| 187 |
+
torch.tensor([0], dtype=torch.float32),
|
| 188 |
+
)
|
| 189 |
+
)
|
| 190 |
+
self.denoising_step_list = timesteps[1000 - self.denoising_step_list]
|
| 191 |
+
|
| 192 |
+
self.num_transformer_blocks = 30
|
| 193 |
+
self.frame_seq_length = 1560
|
| 194 |
+
|
| 195 |
+
self.kv_cache1 = None
|
| 196 |
+
self.args = args
|
| 197 |
+
self.num_frame_per_block = getattr(args, "num_frame_per_block", 1)
|
| 198 |
+
self.independent_first_frame = args.independent_first_frame
|
| 199 |
+
self.local_attn_size = self.generator.model.local_attn_size
|
| 200 |
+
|
| 201 |
+
print(f"KV inference with {self.num_frame_per_block} frames per block")
|
| 202 |
+
|
| 203 |
+
if self.num_frame_per_block > 1:
|
| 204 |
+
self.generator.model.num_frame_per_block = self.num_frame_per_block
|
| 205 |
+
|
| 206 |
+
def stack_features(
|
| 207 |
+
self,
|
| 208 |
+
record_features: dict[int, torch.Tensor],
|
| 209 |
+
# dict[block_index] -> Tensor[1, 3, 30, 52, 1536]
|
| 210 |
+
):
|
| 211 |
+
"""
|
| 212 |
+
Reorder and stack per-block attention features into one tensor.
|
| 213 |
+
|
| 214 |
+
Input:
|
| 215 |
+
record_features: Mapping `block_index -> Tensor` with shape
|
| 216 |
+
`[1, 3, 30, 52, 1536]`.
|
| 217 |
+
|
| 218 |
+
Returns:
|
| 219 |
+
Tensor with shape `[1536 * L, 3, 30, 52]`,
|
| 220 |
+
where `L = len(record_features)`.
|
| 221 |
+
"""
|
| 222 |
+
attention_values = {
|
| 223 |
+
k: v.permute(0, 4, 1, 2, 3).contiguous() for k, v in record_features.items()
|
| 224 |
+
}
|
| 225 |
+
# [ [1, 1536, 3, 30, 52] ]
|
| 226 |
+
|
| 227 |
+
features = []
|
| 228 |
+
for fet in attention_values.values():
|
| 229 |
+
# [1536, 3, 30, 52]
|
| 230 |
+
features.append(fet.squeeze(0).contiguous())
|
| 231 |
+
del fet
|
| 232 |
+
|
| 233 |
+
# Concatenate all features
|
| 234 |
+
feature = torch.cat(features, dim=0)
|
| 235 |
+
# [1536 * L, 3, 30, 52], L is the number of blocks
|
| 236 |
+
return feature
|
| 237 |
+
|
| 238 |
+
def generate_features(
|
| 239 |
+
self,
|
| 240 |
+
latents,
|
| 241 |
+
conditional_dict,
|
| 242 |
+
timestep,
|
| 243 |
+
kv_cache,
|
| 244 |
+
crossattn_cache,
|
| 245 |
+
current_start,
|
| 246 |
+
# below are for drag optimization
|
| 247 |
+
model_config: DictConfig = None,
|
| 248 |
+
):
|
| 249 |
+
"""
|
| 250 |
+
Run one generator forward pass and return prediction + stacked features.
|
| 251 |
+
|
| 252 |
+
Notes:
|
| 253 |
+
- KV cache is deep-cloned/detached before forward to avoid mutating
|
| 254 |
+
the caller's cache during optimization.
|
| 255 |
+
- Forward runs under CUDA bfloat16 autocast.
|
| 256 |
+
- Returned `record_features` are converted via `stack_features(...)`.
|
| 257 |
+
|
| 258 |
+
Returns:
|
| 259 |
+
denoised_pred:
|
| 260 |
+
Model denoised prediction tensor.
|
| 261 |
+
record_features:
|
| 262 |
+
Dict `variant_key -> Tensor[1536 * L, 3, 30, 52]`. L is the number of blocks.
|
| 263 |
+
"""
|
| 264 |
+
temp_kv_cache = [
|
| 265 |
+
{
|
| 266 |
+
"k": kv_cache[block_index]["k"].clone().detach(),
|
| 267 |
+
"v": kv_cache[block_index]["v"].clone().detach(),
|
| 268 |
+
"global_end_index": kv_cache[block_index]["global_end_index"].clone().detach(),
|
| 269 |
+
"local_end_index": kv_cache[block_index]["local_end_index"].clone().detach(),
|
| 270 |
+
}
|
| 271 |
+
for block_index in range(self.num_transformer_blocks)
|
| 272 |
+
]
|
| 273 |
+
# print(f"{temp_kv_cache[0]['k'].shape = }")
|
| 274 |
+
# Forward pass through the transformer with user-specified autocast dtype
|
| 275 |
+
with torch.autocast(device_type="cuda", dtype=torch.bfloat16):
|
| 276 |
+
# Extract features during forward pass
|
| 277 |
+
_, denoised_pred = self.generator(
|
| 278 |
+
noisy_image_or_video=latents,
|
| 279 |
+
conditional_dict=conditional_dict,
|
| 280 |
+
timestep=timestep,
|
| 281 |
+
kv_cache=temp_kv_cache,
|
| 282 |
+
crossattn_cache=crossattn_cache,
|
| 283 |
+
current_start=current_start,
|
| 284 |
+
# below are for drag optimization
|
| 285 |
+
model_config=model_config,
|
| 286 |
+
)
|
| 287 |
+
denoised_pred, record_features = denoised_pred
|
| 288 |
+
# record_features: dict[block_index] -> dict[key -> Tensor] [1, 3, 30, 52, 1536]
|
| 289 |
+
|
| 290 |
+
record_features = transpose_dict_2d(record_features)
|
| 291 |
+
# record_features: Dict[key] -> Dict[block_index -> Tensor] [1, 3, 30, 52, 1536]
|
| 292 |
+
record_features = {k: self.stack_features(v) for k, v in record_features.items()}
|
| 293 |
+
# record_features: Dict[key] -> Tensor [1536 * L, 3, 30, 52]
|
| 294 |
+
return denoised_pred, record_features
|
| 295 |
+
|
| 296 |
+
@staticmethod
|
| 297 |
+
def precompute_conditions(
|
| 298 |
+
trajectories: list[dict[str, bool | list[torch.Tensor]]],
|
| 299 |
+
# N x trajectory
|
| 300 |
+
masks: list[np.ndarray], # N x mask
|
| 301 |
+
dtype: torch.dtype,
|
| 302 |
+
device: torch.device,
|
| 303 |
+
model_config: DictConfig,
|
| 304 |
+
downsample_movable_mask: torch.Tensor,
|
| 305 |
+
previous_record_features: dict[int, dict[str, torch.Tensor]],
|
| 306 |
+
latent_spatial_size: tuple[int, int], # (H_lat, W_lat)
|
| 307 |
+
):
|
| 308 |
+
with torch.no_grad():
|
| 309 |
+
_any_prev_feat = next(iter(previous_record_features.values()))
|
| 310 |
+
feat_spatial_size = tuple(_any_prev_feat.shape[-2:]) # (Hf, Wf)
|
| 311 |
+
|
| 312 |
+
# 1) Warped masks in image space
|
| 313 |
+
warped_masks = [
|
| 314 |
+
warp_tensor_sequence(
|
| 315 |
+
tensor=torch.tensor(mask, device=device).float(),
|
| 316 |
+
is_rotation=trajectory["is_rotation"],
|
| 317 |
+
deltas=trajectory["deltas"],
|
| 318 |
+
rotation_center=trajectory.get("rotation_center", None),
|
| 319 |
+
original_height=IMAGE_HEIGHT,
|
| 320 |
+
mode="nearest",
|
| 321 |
+
cumulative=False,
|
| 322 |
+
)
|
| 323 |
+
for trajectory, mask in zip(trajectories, masks)
|
| 324 |
+
] # N x frame x [H_img, W_img]
|
| 325 |
+
# print(f"{len(warped_masks) = }, {len(warped_masks[0]) = }, {warped_masks[0][0].shape = }")
|
| 326 |
+
|
| 327 |
+
# 2) Downsampled warped masks in feature space
|
| 328 |
+
down_warp_masks = [
|
| 329 |
+
[
|
| 330 |
+
resize_tensor(
|
| 331 |
+
warped_mask.detach(),
|
| 332 |
+
size=feat_spatial_size,
|
| 333 |
+
mode="nearest",
|
| 334 |
+
).detach()
|
| 335 |
+
for warped_mask in traj_warped_masks
|
| 336 |
+
]
|
| 337 |
+
for traj_warped_masks in warped_masks
|
| 338 |
+
] # N x frame x [Hf, Wf]
|
| 339 |
+
|
| 340 |
+
# 3) Gaussian heatmaps per trajectory
|
| 341 |
+
gaussian_heatmaps_per_traj = [
|
| 342 |
+
[
|
| 343 |
+
build_anisotropic_gaussian_from_mask(
|
| 344 |
+
warped_mask,
|
| 345 |
+
padding_scale=model_config.drag_optim_config.gradient_gaussian_padding,
|
| 346 |
+
sigma_scale=model_config.drag_optim_config.gradient_gaussian_sigma,
|
| 347 |
+
).detach()
|
| 348 |
+
for warped_mask in traj_warped_masks
|
| 349 |
+
]
|
| 350 |
+
for traj_warped_masks in warped_masks
|
| 351 |
+
] # N x frame x [H_img, W_img]
|
| 352 |
+
|
| 353 |
+
# 4) Combined downsampled movable mask (OR of all warped + original)
|
| 354 |
+
all_down_warp_masks = [
|
| 355 |
+
dwm for traj_down_masks in down_warp_masks for dwm in traj_down_masks
|
| 356 |
+
]
|
| 357 |
+
all_down_warp_masks.append(downsample_movable_mask.clone())
|
| 358 |
+
combined_downsample_movable_mask = combine_masks_or(all_down_warp_masks)
|
| 359 |
+
|
| 360 |
+
# 5) Precompute warped attention values per variant
|
| 361 |
+
warped_for_prev: dict[str | int, List[List[torch.Tensor]]] = {
|
| 362 |
+
key: [
|
| 363 |
+
warp_tensor_sequence(
|
| 364 |
+
tensor=prev_feat.to(dtype=dtype, device=device),
|
| 365 |
+
is_rotation=trajectory["is_rotation"],
|
| 366 |
+
deltas=[
|
| 367 |
+
d.to(
|
| 368 |
+
dtype=dtype,
|
| 369 |
+
device=device,
|
| 370 |
+
)
|
| 371 |
+
for d in trajectory["deltas"]
|
| 372 |
+
],
|
| 373 |
+
rotation_center=trajectory.get("rotation_center", None),
|
| 374 |
+
original_height=IMAGE_HEIGHT,
|
| 375 |
+
mode="nearest",
|
| 376 |
+
cumulative=False,
|
| 377 |
+
)
|
| 378 |
+
for trajectory in trajectories
|
| 379 |
+
]
|
| 380 |
+
for key, prev_feat in previous_record_features.items()
|
| 381 |
+
}
|
| 382 |
+
# warped_for_prev: dict[key] -> list[traj_index] -> list[frame_index] -> Tensor [1536 * L, 30 * scaling, 52 * scaling]
|
| 383 |
+
|
| 384 |
+
# 6) Combined Gaussian heatmaps in latent space
|
| 385 |
+
combined_gaussian_heatmaps = None
|
| 386 |
+
num_frames = len(gaussian_heatmaps_per_traj[0])
|
| 387 |
+
num_trajs = len(gaussian_heatmaps_per_traj)
|
| 388 |
+
combined_gaussian_heatmaps = torch.stack(
|
| 389 |
+
[
|
| 390 |
+
combine_gaussian_maps(
|
| 391 |
+
[
|
| 392 |
+
gaussian_heatmaps_per_traj[traj_idx][frame_idx]
|
| 393 |
+
for traj_idx in range(num_trajs)
|
| 394 |
+
]
|
| 395 |
+
) # [H_img, W_img]
|
| 396 |
+
for frame_idx in range(num_frames)
|
| 397 |
+
],
|
| 398 |
+
dim=0,
|
| 399 |
+
).to(
|
| 400 |
+
device=device
|
| 401 |
+
) # [F, H_img, W_img]
|
| 402 |
+
|
| 403 |
+
combined_gaussian_heatmaps = resize_tensor(
|
| 404 |
+
combined_gaussian_heatmaps,
|
| 405 |
+
size=latent_spatial_size,
|
| 406 |
+
mode="bilinear",
|
| 407 |
+
).detach() # [F, H_lat, W_lat]
|
| 408 |
+
combined_gaussian_heatmaps = combined_gaussian_heatmaps.to(dtype=dtype)
|
| 409 |
+
return (
|
| 410 |
+
warped_masks,
|
| 411 |
+
down_warp_masks,
|
| 412 |
+
gaussian_heatmaps_per_traj,
|
| 413 |
+
combined_downsample_movable_mask,
|
| 414 |
+
warped_for_prev,
|
| 415 |
+
combined_gaussian_heatmaps,
|
| 416 |
+
)
|
| 417 |
+
|
| 418 |
+
def optimize_latent(
|
| 419 |
+
self,
|
| 420 |
+
latents,
|
| 421 |
+
conditional_dict,
|
| 422 |
+
timestep,
|
| 423 |
+
kv_cache,
|
| 424 |
+
crossattn_cache,
|
| 425 |
+
current_start,
|
| 426 |
+
# below are for drag optimization
|
| 427 |
+
trajectories: list[dict[str, bool | list[torch.Tensor]]],
|
| 428 |
+
# N x trajectory,
|
| 429 |
+
# trajectory has keys 'is_rotation' 'deltas' 'start_point'
|
| 430 |
+
# if is_rotation: trajectory also has 'rotation_center'
|
| 431 |
+
masks: list[np.ndarray], # N x mask
|
| 432 |
+
movable_mask: np.ndarray,
|
| 433 |
+
clean_previous_record_feature: dict[int, dict[str, torch.Tensor]],
|
| 434 |
+
# dict[block_index] -> dict[key -> Tensor] [1, 3, 30, 52, 1536]
|
| 435 |
+
noisy_previous_record_feature: dict[int, dict[str, torch.Tensor]],
|
| 436 |
+
# dict[block_index] -> dict[key -> Tensor] [1, 3, 30, 52, 1536]
|
| 437 |
+
model_config: DictConfig,
|
| 438 |
+
optimize_target_latent_index: int = -1,
|
| 439 |
+
):
|
| 440 |
+
"""
|
| 441 |
+
:param trajectories:
|
| 442 |
+
N x trajectory,
|
| 443 |
+
trajectory has keys 'is_rotation' 'deltas' 'start_point'
|
| 444 |
+
if is_rotation: trajectory also has 'rotation_center'
|
| 445 |
+
:param masks:
|
| 446 |
+
N x mask
|
| 447 |
+
"""
|
| 448 |
+
assert isinstance(model_config.drag_optim_config.optimize_iter, int)
|
| 449 |
+
assert isinstance(model_config.drag_optim_config.optimize_lr, float)
|
| 450 |
+
assert len(model_config.drag_optim_config.record_feature_block_indexes) > 0
|
| 451 |
+
assert len(trajectories) == len(masks)
|
| 452 |
+
if len(trajectories) == 0:
|
| 453 |
+
return latents
|
| 454 |
+
|
| 455 |
+
print(f"{trajectories = }")
|
| 456 |
+
print(f"{len(masks) = }")
|
| 457 |
+
|
| 458 |
+
original_latents = latents.clone().detach()
|
| 459 |
+
|
| 460 |
+
original_denoised_pred = self.generate_features(
|
| 461 |
+
latents=latents,
|
| 462 |
+
conditional_dict=conditional_dict,
|
| 463 |
+
timestep=timestep,
|
| 464 |
+
kv_cache=kv_cache,
|
| 465 |
+
crossattn_cache=crossattn_cache,
|
| 466 |
+
current_start=current_start,
|
| 467 |
+
# below are for drag optimization
|
| 468 |
+
model_config=model_config,
|
| 469 |
+
)
|
| 470 |
+
original_denoised_pred, _ = original_denoised_pred
|
| 471 |
+
|
| 472 |
+
# Check if optimization is enabled
|
| 473 |
+
if model_config.drag_optim_config.optimize_iter <= 0:
|
| 474 |
+
return latents
|
| 475 |
+
# Cast latents to configured dtype for optimization
|
| 476 |
+
latent_original_dtype = latents.dtype
|
| 477 |
+
latents = latents.to(dtype=torch.bfloat16)
|
| 478 |
+
timestep_original_dtype = timestep.dtype
|
| 479 |
+
timestep = timestep.to(dtype=latents.dtype)
|
| 480 |
+
|
| 481 |
+
for param in self.generator.parameters():
|
| 482 |
+
generator_original_dtype = param.dtype
|
| 483 |
+
break
|
| 484 |
+
self.generator = self.generator.to(dtype=latents.dtype)
|
| 485 |
+
# self.generator.train(True)
|
| 486 |
+
for param in self.generator.parameters():
|
| 487 |
+
param.requires_grad = False
|
| 488 |
+
|
| 489 |
+
split_trajectories_list = split_trajectories_segments(
|
| 490 |
+
trajectories=trajectories,
|
| 491 |
+
translation_step=model_config.drag_optim_config.translation_step,
|
| 492 |
+
rotation_step=model_config.drag_optim_config.rotation_step,
|
| 493 |
+
)
|
| 494 |
+
# split_trajectories_list: list[segment_index] -> list[trajectory_index] -> trajectory dict
|
| 495 |
+
|
| 496 |
+
def _select_variant(feat):
|
| 497 |
+
if isinstance(feat, dict):
|
| 498 |
+
keys = list(feat.keys())
|
| 499 |
+
if not keys:
|
| 500 |
+
raise ValueError("Empty feature dict provided.")
|
| 501 |
+
non_orig = [k for k in keys if str(k) != "original"]
|
| 502 |
+
key = random.choice(keys)
|
| 503 |
+
print(f"Selected feature variant {key = } from {keys = }")
|
| 504 |
+
# key = "original" if "original" in feat else keys[0]
|
| 505 |
+
return feat[key]
|
| 506 |
+
return feat
|
| 507 |
+
|
| 508 |
+
def get_previous_last(
|
| 509 |
+
prev: dict[int, dict[str, torch.Tensor]],
|
| 510 |
+
# dict[block_index] -> dict[key -> Tensor] [1, 3, 30, 52, 1536]
|
| 511 |
+
) -> dict[str, torch.Tensor]:
|
| 512 |
+
out = transpose_dict_2d(prev)
|
| 513 |
+
# out: dict[key] -> dict[block_index -> Tensor] [1, 3, 30, 52, 1536]
|
| 514 |
+
out = {k: self.stack_features(v) for k, v in out.items()}
|
| 515 |
+
# out: dict[key] -> Tensor [1536 * L, 3, 30, 52]
|
| 516 |
+
out = {k: v[:, -1, ...].detach() for k, v in out.items()}
|
| 517 |
+
# out: dict[key] -> Tensor [1536 * L, 30, 52]
|
| 518 |
+
out = {
|
| 519 |
+
k: resize_tensor(
|
| 520 |
+
v.detach(),
|
| 521 |
+
scale_factor=model_config.drag_optim_config.feature_scaling_factor,
|
| 522 |
+
mode="bilinear",
|
| 523 |
+
).detach()
|
| 524 |
+
for k, v in out.items()
|
| 525 |
+
}
|
| 526 |
+
# out: dict[key] -> Tensor [1536 * L, 30 * scaling, 52 * scaling]
|
| 527 |
+
return out
|
| 528 |
+
|
| 529 |
+
previous_record_features: dict[str, torch.Tensor] = get_previous_last(
|
| 530 |
+
noisy_previous_record_feature
|
| 531 |
+
)
|
| 532 |
+
# previous_record_features: dict[key] -> Tensor [1536 * L, 30 * scaling, 52 * scaling]
|
| 533 |
+
|
| 534 |
+
movable_mask_torch = torch.tensor(movable_mask, device=latents.device).float()
|
| 535 |
+
downsample_movable_mask = resize_tensor(
|
| 536 |
+
movable_mask_torch.detach(),
|
| 537 |
+
size=tuple(original_denoised_pred.shape[-2:]),
|
| 538 |
+
mode="nearest",
|
| 539 |
+
).detach()
|
| 540 |
+
# print(f"{downsample_movable_mask.shape = }") # [60, 104]
|
| 541 |
+
|
| 542 |
+
with torch.enable_grad():
|
| 543 |
+
|
| 544 |
+
latents.requires_grad_(True)
|
| 545 |
+
optimizer = torch.optim.AdamW(
|
| 546 |
+
[latents],
|
| 547 |
+
lr=model_config.drag_optim_config.optimize_lr,
|
| 548 |
+
)
|
| 549 |
+
|
| 550 |
+
for split_traj_idx, split_trajectories in enumerate(split_trajectories_list):
|
| 551 |
+
# split_trajectories: N x trajectory, list[trajectory_index] -> trajectory dict
|
| 552 |
+
(
|
| 553 |
+
warped_masks,
|
| 554 |
+
down_warp_masks,
|
| 555 |
+
gaussian_heatmaps_per_traj,
|
| 556 |
+
combined_downsample_movable_mask,
|
| 557 |
+
warped_previous_record_features,
|
| 558 |
+
combined_gaussian_heatmaps,
|
| 559 |
+
) = CausalInferencePipeline.precompute_conditions(
|
| 560 |
+
trajectories=split_trajectories,
|
| 561 |
+
masks=masks,
|
| 562 |
+
dtype=latents.dtype,
|
| 563 |
+
device=latents.device,
|
| 564 |
+
model_config=model_config,
|
| 565 |
+
downsample_movable_mask=downsample_movable_mask,
|
| 566 |
+
previous_record_features=previous_record_features,
|
| 567 |
+
latent_spatial_size=tuple(latents.shape[-2:]),
|
| 568 |
+
)
|
| 569 |
+
# -------------------------
|
| 570 |
+
# Optimization iterations (reuse precomputed items)
|
| 571 |
+
# -------------------------
|
| 572 |
+
for optimize_iter_idx in range(model_config.drag_optim_config.optimize_iter):
|
| 573 |
+
print(f"{optimize_iter_idx = }")
|
| 574 |
+
print(f"{latents.mean((0, 2, 3, 4)) = }")
|
| 575 |
+
print(f"{latents.std((0, 2, 3, 4)) = }")
|
| 576 |
+
|
| 577 |
+
denoised_pred, record_features = self.generate_features(
|
| 578 |
+
latents=latents,
|
| 579 |
+
conditional_dict=conditional_dict,
|
| 580 |
+
timestep=timestep,
|
| 581 |
+
kv_cache=kv_cache,
|
| 582 |
+
crossattn_cache=crossattn_cache,
|
| 583 |
+
current_start=current_start,
|
| 584 |
+
model_config=model_config,
|
| 585 |
+
)
|
| 586 |
+
# denoised_pred.shape [1, 3, 16, 60, 104]
|
| 587 |
+
# record_features: dict[key] -> Tensor [1536 * L, 3, 30, 52]
|
| 588 |
+
|
| 589 |
+
print(f"selecting features for optimization")
|
| 590 |
+
record_features_selected = _select_variant(
|
| 591 |
+
record_features,
|
| 592 |
+
)
|
| 593 |
+
# print(f"{record_features_selected.shape = }")
|
| 594 |
+
# record_features_selected: Tensor [1536 * L, 3, 30, 52]
|
| 595 |
+
del record_features
|
| 596 |
+
|
| 597 |
+
if optimize_target_latent_index >= 0:
|
| 598 |
+
record_features_selected = record_features_selected[
|
| 599 |
+
:,
|
| 600 |
+
optimize_target_latent_index : optimize_target_latent_index + 1,
|
| 601 |
+
]
|
| 602 |
+
|
| 603 |
+
record_features_selected = resize_tensor(
|
| 604 |
+
record_features_selected,
|
| 605 |
+
scale_factor=model_config.drag_optim_config.feature_scaling_factor,
|
| 606 |
+
mode="bilinear",
|
| 607 |
+
)
|
| 608 |
+
# print(
|
| 609 |
+
# f"{record_features_selected.shape = }"
|
| 610 |
+
# ) # [1536 * L, 3, 30 * scaling, 52 * scaling]
|
| 611 |
+
|
| 612 |
+
print(f"selecting warped previous features")
|
| 613 |
+
warped_previous_feature_selected = _select_variant(
|
| 614 |
+
warped_previous_record_features,
|
| 615 |
+
) # list[traj_index] -> list[frame_index] -> Tensor [1536 * L, 30 * scaling, 52 * scaling]
|
| 616 |
+
|
| 617 |
+
loss = 0
|
| 618 |
+
loss_cnt = 0
|
| 619 |
+
|
| 620 |
+
# Iterate over each trajectory point
|
| 621 |
+
for trajectory_index, trajectory in enumerate(split_trajectories):
|
| 622 |
+
assert record_features_selected.shape[1] == len(trajectory["deltas"])
|
| 623 |
+
|
| 624 |
+
for frame_index in range(len(trajectory["deltas"])):
|
| 625 |
+
warped_attention_values = warped_previous_feature_selected[
|
| 626 |
+
trajectory_index
|
| 627 |
+
][frame_index]
|
| 628 |
+
|
| 629 |
+
downsample_warped_mask = down_warp_masks[trajectory_index][frame_index]
|
| 630 |
+
|
| 631 |
+
pixel_wise_loss = F.mse_loss(
|
| 632 |
+
warped_attention_values * downsample_warped_mask,
|
| 633 |
+
record_features_selected[:, frame_index] * downsample_warped_mask,
|
| 634 |
+
reduction="none",
|
| 635 |
+
).mean(dim=0)
|
| 636 |
+
# print(f"{pixel_wise_loss.shape = }") # [60, 104]
|
| 637 |
+
|
| 638 |
+
# Add weighted loss
|
| 639 |
+
loss = loss + (downsample_warped_mask * pixel_wise_loss).sum()
|
| 640 |
+
loss_cnt += downsample_warped_mask.sum()
|
| 641 |
+
|
| 642 |
+
print(f"{loss = } / {loss_cnt = }")
|
| 643 |
+
loss = loss / max(1e-8, loss_cnt)
|
| 644 |
+
print(f"{loss = }")
|
| 645 |
+
|
| 646 |
+
unchanged_mask = 1.0 - combined_downsample_movable_mask
|
| 647 |
+
unchanged_loss = F.mse_loss(
|
| 648 |
+
denoised_pred * unchanged_mask.detach(),
|
| 649 |
+
original_denoised_pred.detach() * unchanged_mask.detach(),
|
| 650 |
+
)
|
| 651 |
+
print(f"{unchanged_loss = }")
|
| 652 |
+
loss = loss + unchanged_loss * 1.0
|
| 653 |
+
|
| 654 |
+
# Update latents
|
| 655 |
+
self.generator.zero_grad()
|
| 656 |
+
optimizer.zero_grad()
|
| 657 |
+
if loss_cnt > 0:
|
| 658 |
+
loss.backward()
|
| 659 |
+
assert (
|
| 660 |
+
combined_gaussian_heatmaps.shape[0] == 1
|
| 661 |
+
or combined_gaussian_heatmaps.shape[0] == latents.shape[-4]
|
| 662 |
+
)
|
| 663 |
+
assert combined_gaussian_heatmaps.shape[-2:] == latents.shape[-2:]
|
| 664 |
+
latents.grad.mul_(combined_gaussian_heatmaps[:, None, :, :])
|
| 665 |
+
# Clip gradients
|
| 666 |
+
clip_grad_norm_(
|
| 667 |
+
[latents],
|
| 668 |
+
max_norm=1.0,
|
| 669 |
+
norm_type=2,
|
| 670 |
+
)
|
| 671 |
+
optimizer.step()
|
| 672 |
+
if model_config.drag_optim_config.normalize_latent_after_drag_optimize:
|
| 673 |
+
print(f"Normalizing latents after optimize iteration")
|
| 674 |
+
latents = (
|
| 675 |
+
normalize_tensor_to_match_tensor(
|
| 676 |
+
latents.detach().clone(),
|
| 677 |
+
dim=(0, 3, 4),
|
| 678 |
+
reference_tensor=original_latents.to(dtype=latents.dtype),
|
| 679 |
+
)
|
| 680 |
+
.detach()
|
| 681 |
+
.clone()
|
| 682 |
+
)
|
| 683 |
+
# latents = latents.clamp(
|
| 684 |
+
# min=latents_min,
|
| 685 |
+
# max=latents_max,
|
| 686 |
+
# ).detach().clone()
|
| 687 |
+
latents.requires_grad_(True)
|
| 688 |
+
optimizer = torch.optim.AdamW(
|
| 689 |
+
[latents],
|
| 690 |
+
lr=model_config.drag_optim_config.optimize_lr,
|
| 691 |
+
)
|
| 692 |
+
# Clean up to save memory
|
| 693 |
+
gc.collect()
|
| 694 |
+
torch.cuda.empty_cache()
|
| 695 |
+
|
| 696 |
+
latents = latents.detach().requires_grad_(False)
|
| 697 |
+
|
| 698 |
+
if model_config.drag_optim_config.normalize_latent_after_post_merge:
|
| 699 |
+
latents = (
|
| 700 |
+
normalize_tensor_to_match_tensor(
|
| 701 |
+
latents,
|
| 702 |
+
dim=None,
|
| 703 |
+
reference_tensor=original_latents,
|
| 704 |
+
)
|
| 705 |
+
.detach()
|
| 706 |
+
.clone()
|
| 707 |
+
)
|
| 708 |
+
|
| 709 |
+
# Convert back to original dtype
|
| 710 |
+
self.generator = self.generator.to(dtype=generator_original_dtype)
|
| 711 |
+
self.generator.train(False)
|
| 712 |
+
latents = latents.to(dtype=latent_original_dtype)
|
| 713 |
+
timestep = timestep.to(dtype=timestep_original_dtype)
|
| 714 |
+
|
| 715 |
+
# Detach latents and remove gradient
|
| 716 |
+
latents = latents.detach().requires_grad_(False)
|
| 717 |
+
return latents
|
| 718 |
+
|
| 719 |
+
def inference(
|
| 720 |
+
self,
|
| 721 |
+
noise: torch.Tensor,
|
| 722 |
+
text_prompts: List[str],
|
| 723 |
+
initial_latent: Optional[torch.Tensor] = None,
|
| 724 |
+
return_latents: bool = False,
|
| 725 |
+
profile: bool = False,
|
| 726 |
+
low_memory: bool = False,
|
| 727 |
+
do_not_decode_video: bool = False,
|
| 728 |
+
do_not_recompute_initial_latents: bool = False,
|
| 729 |
+
# below are for drag optimization
|
| 730 |
+
model_config: DictConfig = None,
|
| 731 |
+
previous_record_feature_list: dict[int, dict[int, dict[str, torch.Tensor]]] = None,
|
| 732 |
+
# dict[denoising_step] -> dict[block_index] -> dict[key -> Tensor] [1, 3, 30, 52, 1536]
|
| 733 |
+
is_drag_optimize: bool = False,
|
| 734 |
+
block_trajectories: list[list[dict[str, bool | list[torch.Tensor]]]] = [],
|
| 735 |
+
masks: list[np.ndarray] = [],
|
| 736 |
+
movable_mask: np.ndarray = None,
|
| 737 |
+
drag_optimize_target_latent_index: int = -1,
|
| 738 |
+
) -> torch.Tensor:
|
| 739 |
+
"""
|
| 740 |
+
Perform inference on the given noise and text prompts.
|
| 741 |
+
Inputs:
|
| 742 |
+
noise (torch.Tensor): The input noise tensor of shape
|
| 743 |
+
(batch_size, num_output_frames, num_channels, height, width).
|
| 744 |
+
text_prompts (List[str]): The list of text prompts.
|
| 745 |
+
initial_latent (torch.Tensor): The initial latent tensor of shape
|
| 746 |
+
(batch_size, num_input_frames, num_channels, height, width).
|
| 747 |
+
If num_input_frames is 1, perform image to video.
|
| 748 |
+
If num_input_frames is greater than 1, perform video extension.
|
| 749 |
+
return_latents (bool): Whether to return the latents.
|
| 750 |
+
:param block_trajectories:
|
| 751 |
+
block_num x N x trajectory,
|
| 752 |
+
trajectory has keys 'is_rotation' 'deltas' 'start_point'
|
| 753 |
+
if is_rotation: trajectory also has 'rotation_center'
|
| 754 |
+
:param masks:
|
| 755 |
+
N x mask
|
| 756 |
+
Outputs:
|
| 757 |
+
video (torch.Tensor): The generated video tensor of shape
|
| 758 |
+
(batch_size, num_output_frames, num_channels, height, width).
|
| 759 |
+
It is normalized to be in the range [0, 1].
|
| 760 |
+
"""
|
| 761 |
+
batch_size, num_frames, num_channels, height, width = noise.shape
|
| 762 |
+
if not self.independent_first_frame or (
|
| 763 |
+
self.independent_first_frame and initial_latent is not None
|
| 764 |
+
):
|
| 765 |
+
# If the first frame is independent and the first frame is provided, then the number of frames in the
|
| 766 |
+
# noise should still be a multiple of num_frame_per_block
|
| 767 |
+
assert num_frames % self.num_frame_per_block == 0
|
| 768 |
+
num_blocks = num_frames // self.num_frame_per_block
|
| 769 |
+
else:
|
| 770 |
+
# Using a [1, 4, 4, 4, 4, 4, ...] model to generate a video without image conditioning
|
| 771 |
+
assert (num_frames - 1) % self.num_frame_per_block == 0
|
| 772 |
+
num_blocks = (num_frames - 1) // self.num_frame_per_block
|
| 773 |
+
num_input_frames = initial_latent.shape[1] if initial_latent is not None else 0
|
| 774 |
+
num_output_frames = num_frames + num_input_frames # add the initial latent frames
|
| 775 |
+
conditional_dict = self.text_encoder(text_prompts=text_prompts)
|
| 776 |
+
|
| 777 |
+
if low_memory:
|
| 778 |
+
gpu_memory_preservation = get_cuda_free_memory_gb(gpu) + 5
|
| 779 |
+
move_model_to_device_with_memory_preservation(
|
| 780 |
+
self.text_encoder,
|
| 781 |
+
target_device=gpu,
|
| 782 |
+
preserved_memory_gb=gpu_memory_preservation,
|
| 783 |
+
)
|
| 784 |
+
|
| 785 |
+
output = torch.zeros(
|
| 786 |
+
[batch_size, num_output_frames, num_channels, height, width],
|
| 787 |
+
device=noise.device,
|
| 788 |
+
dtype=noise.dtype,
|
| 789 |
+
)
|
| 790 |
+
|
| 791 |
+
# Set up profiling if requested
|
| 792 |
+
if profile:
|
| 793 |
+
init_start = torch.cuda.Event(enable_timing=True)
|
| 794 |
+
init_end = torch.cuda.Event(enable_timing=True)
|
| 795 |
+
diffusion_start = torch.cuda.Event(enable_timing=True)
|
| 796 |
+
diffusion_end = torch.cuda.Event(enable_timing=True)
|
| 797 |
+
vae_start = torch.cuda.Event(enable_timing=True)
|
| 798 |
+
vae_end = torch.cuda.Event(enable_timing=True)
|
| 799 |
+
block_times = []
|
| 800 |
+
block_start = torch.cuda.Event(enable_timing=True)
|
| 801 |
+
block_end = torch.cuda.Event(enable_timing=True)
|
| 802 |
+
init_start.record()
|
| 803 |
+
|
| 804 |
+
# Step 1: Initialize KV cache to all zeros
|
| 805 |
+
if self.kv_cache1 is None:
|
| 806 |
+
self._initialize_kv_cache(batch_size=batch_size, dtype=noise.dtype, device=noise.device)
|
| 807 |
+
self._initialize_crossattn_cache(
|
| 808 |
+
batch_size=batch_size, dtype=noise.dtype, device=noise.device
|
| 809 |
+
)
|
| 810 |
+
else:
|
| 811 |
+
if do_not_recompute_initial_latents:
|
| 812 |
+
pass
|
| 813 |
+
else:
|
| 814 |
+
print(f"Resetting caches")
|
| 815 |
+
self._reset_crossattn_cache()
|
| 816 |
+
self._reset_kv_cache()
|
| 817 |
+
|
| 818 |
+
# Step 2: Cache context feature
|
| 819 |
+
current_start_frame = 0
|
| 820 |
+
if initial_latent is not None:
|
| 821 |
+
timestep = torch.ones([batch_size, 1], device=noise.device, dtype=torch.int64) * 0
|
| 822 |
+
if self.independent_first_frame:
|
| 823 |
+
# Assume num_input_frames is 1 + self.num_frame_per_block * num_input_blocks
|
| 824 |
+
assert (num_input_frames - 1) % self.num_frame_per_block == 0
|
| 825 |
+
num_input_blocks = (num_input_frames - 1) // self.num_frame_per_block
|
| 826 |
+
output[:, :1] = initial_latent[:, :1]
|
| 827 |
+
if do_not_recompute_initial_latents:
|
| 828 |
+
pass
|
| 829 |
+
else:
|
| 830 |
+
print(f"Recompute KV cache based on Initial Latents")
|
| 831 |
+
self.generator(
|
| 832 |
+
noisy_image_or_video=initial_latent[:, :1],
|
| 833 |
+
conditional_dict=conditional_dict,
|
| 834 |
+
timestep=timestep * 0,
|
| 835 |
+
kv_cache=self.kv_cache1,
|
| 836 |
+
crossattn_cache=self.crossattn_cache,
|
| 837 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 838 |
+
)
|
| 839 |
+
current_start_frame += 1
|
| 840 |
+
else:
|
| 841 |
+
# Assume num_input_frames is self.num_frame_per_block * num_input_blocks
|
| 842 |
+
assert num_input_frames % self.num_frame_per_block == 0
|
| 843 |
+
num_input_blocks = num_input_frames // self.num_frame_per_block
|
| 844 |
+
|
| 845 |
+
for _ in range(num_input_blocks):
|
| 846 |
+
current_ref_latents = initial_latent[
|
| 847 |
+
:,
|
| 848 |
+
current_start_frame : current_start_frame + self.num_frame_per_block,
|
| 849 |
+
]
|
| 850 |
+
output[
|
| 851 |
+
:,
|
| 852 |
+
current_start_frame : current_start_frame + self.num_frame_per_block,
|
| 853 |
+
] = current_ref_latents
|
| 854 |
+
if do_not_recompute_initial_latents:
|
| 855 |
+
pass
|
| 856 |
+
else:
|
| 857 |
+
print(f"Recompute KV cache based on Initial Latents")
|
| 858 |
+
self.generator(
|
| 859 |
+
noisy_image_or_video=current_ref_latents,
|
| 860 |
+
conditional_dict=conditional_dict,
|
| 861 |
+
timestep=timestep * 0,
|
| 862 |
+
kv_cache=self.kv_cache1,
|
| 863 |
+
crossattn_cache=self.crossattn_cache,
|
| 864 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 865 |
+
)
|
| 866 |
+
current_start_frame += self.num_frame_per_block
|
| 867 |
+
|
| 868 |
+
if profile:
|
| 869 |
+
init_end.record()
|
| 870 |
+
torch.cuda.synchronize()
|
| 871 |
+
diffusion_start.record()
|
| 872 |
+
|
| 873 |
+
# Step 3: Temporal denoising loop
|
| 874 |
+
all_num_frames = [self.num_frame_per_block] * num_blocks
|
| 875 |
+
if self.independent_first_frame and initial_latent is None:
|
| 876 |
+
all_num_frames = [1] + all_num_frames
|
| 877 |
+
for current_chunk_index, current_num_frames in enumerate(
|
| 878 |
+
tqdm(all_num_frames),
|
| 879 |
+
start=num_input_blocks if initial_latent is not None else 0,
|
| 880 |
+
):
|
| 881 |
+
print(f"\n{current_chunk_index = } ; {current_start_frame = }")
|
| 882 |
+
if profile:
|
| 883 |
+
block_start.record()
|
| 884 |
+
|
| 885 |
+
noisy_input = noise[
|
| 886 |
+
:,
|
| 887 |
+
current_start_frame
|
| 888 |
+
- num_input_frames : current_start_frame
|
| 889 |
+
+ current_num_frames
|
| 890 |
+
- num_input_frames,
|
| 891 |
+
]
|
| 892 |
+
|
| 893 |
+
if model_config is not None and OmegaConf.select(
|
| 894 |
+
model_config, "drag_optim_config.record_feature_block_indexes"
|
| 895 |
+
):
|
| 896 |
+
record_attention_values_list = {}
|
| 897 |
+
# Step 3.1: Spatial denoising loop
|
| 898 |
+
for time_step_index, current_timestep in enumerate(self.denoising_step_list):
|
| 899 |
+
print(f"{time_step_index = } ; {current_timestep = }")
|
| 900 |
+
# set current timestep
|
| 901 |
+
timestep = (
|
| 902 |
+
torch.ones(
|
| 903 |
+
[batch_size, current_num_frames],
|
| 904 |
+
device=noise.device,
|
| 905 |
+
dtype=torch.int64,
|
| 906 |
+
)
|
| 907 |
+
* current_timestep
|
| 908 |
+
)
|
| 909 |
+
|
| 910 |
+
if (
|
| 911 |
+
is_drag_optimize
|
| 912 |
+
and time_step_index
|
| 913 |
+
in model_config.drag_optim_config.optimize_denoising_steps_indexes
|
| 914 |
+
):
|
| 915 |
+
|
| 916 |
+
noisy_input = self.optimize_latent(
|
| 917 |
+
latents=noisy_input,
|
| 918 |
+
conditional_dict=conditional_dict,
|
| 919 |
+
timestep=timestep,
|
| 920 |
+
kv_cache=self.kv_cache1,
|
| 921 |
+
crossattn_cache=self.crossattn_cache,
|
| 922 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 923 |
+
# below are for drag optimization
|
| 924 |
+
trajectories=block_trajectories[current_chunk_index - num_input_blocks],
|
| 925 |
+
masks=masks,
|
| 926 |
+
movable_mask=movable_mask,
|
| 927 |
+
clean_previous_record_feature=previous_record_feature_list[-1],
|
| 928 |
+
noisy_previous_record_feature=previous_record_feature_list[time_step_index],
|
| 929 |
+
model_config=model_config,
|
| 930 |
+
optimize_target_latent_index=drag_optimize_target_latent_index,
|
| 931 |
+
)
|
| 932 |
+
print(f"{noisy_input.mean() = }")
|
| 933 |
+
print(f"{noisy_input.std() = }")
|
| 934 |
+
|
| 935 |
+
_, denoised_pred = self.generator(
|
| 936 |
+
noisy_image_or_video=noisy_input,
|
| 937 |
+
conditional_dict=conditional_dict,
|
| 938 |
+
timestep=timestep,
|
| 939 |
+
kv_cache=self.kv_cache1,
|
| 940 |
+
crossattn_cache=self.crossattn_cache,
|
| 941 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 942 |
+
model_config=model_config,
|
| 943 |
+
)
|
| 944 |
+
if model_config is not None and OmegaConf.select(
|
| 945 |
+
model_config,
|
| 946 |
+
"drag_optim_config.record_feature_block_indexes",
|
| 947 |
+
):
|
| 948 |
+
denoised_pred, record_features = denoised_pred
|
| 949 |
+
if (
|
| 950 |
+
time_step_index
|
| 951 |
+
in model_config.drag_optim_config.optimize_denoising_steps_indexes
|
| 952 |
+
):
|
| 953 |
+
record_attention_values_list[time_step_index] = record_features
|
| 954 |
+
|
| 955 |
+
if (
|
| 956 |
+
model_config is not None
|
| 957 |
+
and OmegaConf.select(
|
| 958 |
+
model_config,
|
| 959 |
+
"drag_optim_config.dynamic_chunk_normalization_block_number",
|
| 960 |
+
default=0,
|
| 961 |
+
)
|
| 962 |
+
> 0
|
| 963 |
+
):
|
| 964 |
+
num_norm_blocks = (
|
| 965 |
+
model_config.drag_optim_config.dynamic_chunk_normalization_block_number
|
| 966 |
+
)
|
| 967 |
+
# Exclude the first chunk (independent first frame latent) by starting no earlier than num_frame_per_block
|
| 968 |
+
dynamic_normalize_start_frame_index = max(
|
| 969 |
+
self.num_frame_per_block,
|
| 970 |
+
(current_chunk_index - num_norm_blocks) * self.num_frame_per_block,
|
| 971 |
+
)
|
| 972 |
+
# print(f"{dynamic_normalize_start_frame_index = }")
|
| 973 |
+
if dynamic_normalize_start_frame_index < current_start_frame:
|
| 974 |
+
reference_tensor = torch.cat(
|
| 975 |
+
[
|
| 976 |
+
output[
|
| 977 |
+
:,
|
| 978 |
+
dynamic_normalize_start_frame_index:current_start_frame,
|
| 979 |
+
],
|
| 980 |
+
denoised_pred,
|
| 981 |
+
],
|
| 982 |
+
dim=1,
|
| 983 |
+
)
|
| 984 |
+
denoised_pred = normalize_tensor_to_match_tensor(
|
| 985 |
+
denoised_pred,
|
| 986 |
+
dim=None,
|
| 987 |
+
reference_tensor=reference_tensor,
|
| 988 |
+
)
|
| 989 |
+
# print(f"{denoised_pred.mean() = }")
|
| 990 |
+
# print(f"{denoised_pred.std() = }")
|
| 991 |
+
|
| 992 |
+
if time_step_index < len(self.denoising_step_list) - 1:
|
| 993 |
+
next_timestep = self.denoising_step_list[time_step_index + 1]
|
| 994 |
+
noisy_input = self.scheduler.add_noise(
|
| 995 |
+
denoised_pred.flatten(0, 1),
|
| 996 |
+
torch.randn_like(denoised_pred.flatten(0, 1)),
|
| 997 |
+
next_timestep
|
| 998 |
+
* torch.ones(
|
| 999 |
+
[batch_size * current_num_frames],
|
| 1000 |
+
device=noise.device,
|
| 1001 |
+
dtype=torch.long,
|
| 1002 |
+
),
|
| 1003 |
+
).unflatten(0, denoised_pred.shape[:2])
|
| 1004 |
+
|
| 1005 |
+
# Step 3.2: record the model's output
|
| 1006 |
+
output[
|
| 1007 |
+
:,
|
| 1008 |
+
current_start_frame : current_start_frame + current_num_frames,
|
| 1009 |
+
] = denoised_pred
|
| 1010 |
+
|
| 1011 |
+
# Step 3.3: rerun with timestep zero to update KV cache using clean context
|
| 1012 |
+
context_timestep = torch.ones_like(timestep) * self.args.context_noise
|
| 1013 |
+
_, denoised_pred = self.generator(
|
| 1014 |
+
noisy_image_or_video=denoised_pred,
|
| 1015 |
+
conditional_dict=conditional_dict,
|
| 1016 |
+
timestep=context_timestep,
|
| 1017 |
+
kv_cache=self.kv_cache1,
|
| 1018 |
+
crossattn_cache=self.crossattn_cache,
|
| 1019 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 1020 |
+
model_config=model_config,
|
| 1021 |
+
)
|
| 1022 |
+
if model_config is not None and OmegaConf.select(
|
| 1023 |
+
model_config, "drag_optim_config.record_feature_block_indexes"
|
| 1024 |
+
):
|
| 1025 |
+
denoised_pred, record_features = denoised_pred
|
| 1026 |
+
record_attention_values_list[-1] = record_features
|
| 1027 |
+
|
| 1028 |
+
if profile:
|
| 1029 |
+
block_end.record()
|
| 1030 |
+
torch.cuda.synchronize()
|
| 1031 |
+
block_time = block_start.elapsed_time(block_end)
|
| 1032 |
+
block_times.append(block_time)
|
| 1033 |
+
|
| 1034 |
+
# Step 3.4: update the start and end frame indices
|
| 1035 |
+
current_start_frame += current_num_frames
|
| 1036 |
+
|
| 1037 |
+
if profile:
|
| 1038 |
+
# End diffusion timing and synchronize CUDA
|
| 1039 |
+
diffusion_end.record()
|
| 1040 |
+
torch.cuda.synchronize()
|
| 1041 |
+
diffusion_time = diffusion_start.elapsed_time(diffusion_end)
|
| 1042 |
+
init_time = init_start.elapsed_time(init_end)
|
| 1043 |
+
vae_start.record()
|
| 1044 |
+
|
| 1045 |
+
# Step 4: Decode the output
|
| 1046 |
+
if not do_not_decode_video:
|
| 1047 |
+
start_decode_time = time.time()
|
| 1048 |
+
video = self.vae.decode_to_pixel(output, use_cache=False)
|
| 1049 |
+
video = (video * 0.5 + 0.5).clamp(0, 1)
|
| 1050 |
+
print(
|
| 1051 |
+
f"{self.__class__.__name__}.inference() VAE decode time: {time.time() - start_decode_time:.2f} seconds"
|
| 1052 |
+
)
|
| 1053 |
+
|
| 1054 |
+
if profile:
|
| 1055 |
+
# End VAE timing and synchronize CUDA
|
| 1056 |
+
vae_end.record()
|
| 1057 |
+
torch.cuda.synchronize()
|
| 1058 |
+
vae_time = vae_start.elapsed_time(vae_end)
|
| 1059 |
+
total_time = init_time + diffusion_time + vae_time
|
| 1060 |
+
|
| 1061 |
+
print("Profiling results:")
|
| 1062 |
+
print(
|
| 1063 |
+
f" - Initialization/caching time: {init_time:.2f} ms ({100 * init_time / total_time:.2f}%)"
|
| 1064 |
+
)
|
| 1065 |
+
print(
|
| 1066 |
+
f" - Diffusion generation time: {diffusion_time:.2f} ms ({100 * diffusion_time / total_time:.2f}%)"
|
| 1067 |
+
)
|
| 1068 |
+
for i, block_time in enumerate(block_times):
|
| 1069 |
+
print(
|
| 1070 |
+
f" - Block {i} generation time: {block_time:.2f} ms ({100 * block_time / diffusion_time:.2f}% of diffusion)"
|
| 1071 |
+
)
|
| 1072 |
+
print(f" - VAE decoding time: {vae_time:.2f} ms ({100 * vae_time / total_time:.2f}%)")
|
| 1073 |
+
print(f" - Total time: {total_time:.2f} ms")
|
| 1074 |
+
|
| 1075 |
+
return_values = []
|
| 1076 |
+
if not do_not_decode_video:
|
| 1077 |
+
return_values.append(video)
|
| 1078 |
+
if return_latents:
|
| 1079 |
+
return_values.append(output)
|
| 1080 |
+
if model_config is not None and OmegaConf.select(
|
| 1081 |
+
model_config, "drag_optim_config.record_feature_block_indexes"
|
| 1082 |
+
):
|
| 1083 |
+
return_values.append(record_attention_values_list)
|
| 1084 |
+
|
| 1085 |
+
if len(return_values) == 0:
|
| 1086 |
+
return
|
| 1087 |
+
elif len(return_values) == 1:
|
| 1088 |
+
return return_values[0]
|
| 1089 |
+
else:
|
| 1090 |
+
return tuple(return_values)
|
| 1091 |
+
|
| 1092 |
+
def _initialize_kv_cache(
|
| 1093 |
+
self,
|
| 1094 |
+
batch_size,
|
| 1095 |
+
dtype,
|
| 1096 |
+
device,
|
| 1097 |
+
):
|
| 1098 |
+
"""
|
| 1099 |
+
Initialize a Per-GPU KV cache for the Wan model.
|
| 1100 |
+
"""
|
| 1101 |
+
print(
|
| 1102 |
+
f"""
|
| 1103 |
+
{type(self).__name__}._initialize_kv_cache
|
| 1104 |
+
{batch_size = }
|
| 1105 |
+
{dtype = }
|
| 1106 |
+
{device = }
|
| 1107 |
+
"""
|
| 1108 |
+
)
|
| 1109 |
+
kv_cache1 = []
|
| 1110 |
+
if self.local_attn_size != -1:
|
| 1111 |
+
print(f"use {self.local_attn_size = }")
|
| 1112 |
+
# Use the local attention size to compute the KV cache size
|
| 1113 |
+
kv_cache_size = self.local_attn_size * self.frame_seq_length
|
| 1114 |
+
else:
|
| 1115 |
+
# Use the default KV cache size
|
| 1116 |
+
kv_cache_size = 32760
|
| 1117 |
+
print(f"{kv_cache_size = }")
|
| 1118 |
+
|
| 1119 |
+
for _ in range(self.num_transformer_blocks):
|
| 1120 |
+
kv_cache1.append(
|
| 1121 |
+
{
|
| 1122 |
+
"k": torch.zeros(
|
| 1123 |
+
[batch_size, kv_cache_size, 12, 128],
|
| 1124 |
+
dtype=dtype,
|
| 1125 |
+
device=device,
|
| 1126 |
+
),
|
| 1127 |
+
"v": torch.zeros(
|
| 1128 |
+
[batch_size, kv_cache_size, 12, 128],
|
| 1129 |
+
dtype=dtype,
|
| 1130 |
+
device=device,
|
| 1131 |
+
),
|
| 1132 |
+
"global_end_index": torch.tensor([0], dtype=torch.long, device=device),
|
| 1133 |
+
"local_end_index": torch.tensor([0], dtype=torch.long, device=device),
|
| 1134 |
+
}
|
| 1135 |
+
)
|
| 1136 |
+
|
| 1137 |
+
self.kv_cache1 = kv_cache1 # always store the clean cache
|
| 1138 |
+
|
| 1139 |
+
def _initialize_crossattn_cache(
|
| 1140 |
+
self,
|
| 1141 |
+
batch_size,
|
| 1142 |
+
dtype,
|
| 1143 |
+
device,
|
| 1144 |
+
):
|
| 1145 |
+
"""
|
| 1146 |
+
Initialize a Per-GPU cross-attention cache for the Wan model.
|
| 1147 |
+
"""
|
| 1148 |
+
print(
|
| 1149 |
+
f"""
|
| 1150 |
+
{type(self).__name__}._initialize_crossattn_cache
|
| 1151 |
+
{batch_size = }
|
| 1152 |
+
{dtype = }
|
| 1153 |
+
{device = }
|
| 1154 |
+
"""
|
| 1155 |
+
)
|
| 1156 |
+
crossattn_cache = []
|
| 1157 |
+
|
| 1158 |
+
for _ in range(self.num_transformer_blocks):
|
| 1159 |
+
crossattn_cache.append(
|
| 1160 |
+
{
|
| 1161 |
+
"k": torch.zeros([batch_size, 512, 12, 128], dtype=dtype, device=device),
|
| 1162 |
+
"v": torch.zeros([batch_size, 512, 12, 128], dtype=dtype, device=device),
|
| 1163 |
+
"is_init": False,
|
| 1164 |
+
}
|
| 1165 |
+
)
|
| 1166 |
+
self.crossattn_cache = crossattn_cache
|
| 1167 |
+
|
| 1168 |
+
def _reset_crossattn_cache(self):
|
| 1169 |
+
# reset cross attn cache
|
| 1170 |
+
print(f"{type(self).__name__}._reset_crossattn_cache")
|
| 1171 |
+
for block_index in range(self.num_transformer_blocks):
|
| 1172 |
+
self.crossattn_cache[block_index]["is_init"] = False
|
| 1173 |
+
|
| 1174 |
+
def _reset_kv_cache(self):
|
| 1175 |
+
# reset kv cache
|
| 1176 |
+
print(f"{type(self).__name__}._reset_kv_cache")
|
| 1177 |
+
for block_index in range(len(self.kv_cache1)):
|
| 1178 |
+
self.kv_cache1[block_index]["global_end_index"] = torch.tensor(
|
| 1179 |
+
[0],
|
| 1180 |
+
dtype=torch.long,
|
| 1181 |
+
device=self.kv_cache1[block_index]["global_end_index"].device,
|
| 1182 |
+
)
|
| 1183 |
+
self.kv_cache1[block_index]["local_end_index"] = torch.tensor(
|
| 1184 |
+
[0],
|
| 1185 |
+
dtype=torch.long,
|
| 1186 |
+
device=self.kv_cache1[block_index]["local_end_index"].device,
|
| 1187 |
+
)
|
| 1188 |
+
|
| 1189 |
+
def is_kv_cache_initialized(self):
|
| 1190 |
+
return hasattr(self, "kv_cache1") and self.kv_cache1 is not None
|
| 1191 |
+
|
| 1192 |
+
def is_crossattn_cache_initialized(self):
|
| 1193 |
+
return hasattr(self, "crossattn_cache") and self.crossattn_cache is not None
|
pipeline/self_forcing_training.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from utils.wan_wrapper import WanDiffusionWrapper
|
| 2 |
+
from utils.scheduler import SchedulerInterface
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
import torch
|
| 5 |
+
import torch.distributed as dist
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class SelfForcingTrainingPipeline:
|
| 9 |
+
def __init__(
|
| 10 |
+
self,
|
| 11 |
+
denoising_step_list: List[int],
|
| 12 |
+
scheduler: SchedulerInterface,
|
| 13 |
+
generator: WanDiffusionWrapper,
|
| 14 |
+
num_frame_per_block=3,
|
| 15 |
+
independent_first_frame: bool = False,
|
| 16 |
+
same_step_across_blocks: bool = False,
|
| 17 |
+
last_step_only: bool = False,
|
| 18 |
+
num_max_frames: int = 21,
|
| 19 |
+
context_noise: int = 0,
|
| 20 |
+
**kwargs
|
| 21 |
+
):
|
| 22 |
+
super().__init__()
|
| 23 |
+
self.scheduler = scheduler
|
| 24 |
+
self.generator = generator
|
| 25 |
+
self.denoising_step_list = denoising_step_list
|
| 26 |
+
if self.denoising_step_list[-1] == 0:
|
| 27 |
+
self.denoising_step_list = self.denoising_step_list[
|
| 28 |
+
:-1
|
| 29 |
+
] # remove the zero timestep for inference
|
| 30 |
+
|
| 31 |
+
# Wan specific hyperparameters
|
| 32 |
+
self.num_transformer_blocks = 30
|
| 33 |
+
self.frame_seq_length = 1560
|
| 34 |
+
self.num_frame_per_block = num_frame_per_block
|
| 35 |
+
self.context_noise = context_noise
|
| 36 |
+
self.i2v = False
|
| 37 |
+
|
| 38 |
+
self.kv_cache1 = None
|
| 39 |
+
self.kv_cache2 = None
|
| 40 |
+
self.independent_first_frame = independent_first_frame
|
| 41 |
+
self.same_step_across_blocks = same_step_across_blocks
|
| 42 |
+
self.last_step_only = last_step_only
|
| 43 |
+
self.kv_cache_size = num_max_frames * self.frame_seq_length
|
| 44 |
+
|
| 45 |
+
def generate_and_sync_list(
|
| 46 |
+
self,
|
| 47 |
+
num_blocks,
|
| 48 |
+
num_denoising_steps,
|
| 49 |
+
device,
|
| 50 |
+
):
|
| 51 |
+
rank = dist.get_rank() if dist.is_initialized() else 0
|
| 52 |
+
|
| 53 |
+
if rank == 0:
|
| 54 |
+
# Generate random indices
|
| 55 |
+
indices = torch.randint(
|
| 56 |
+
low=0,
|
| 57 |
+
high=num_denoising_steps,
|
| 58 |
+
size=(num_blocks,),
|
| 59 |
+
device=device,
|
| 60 |
+
)
|
| 61 |
+
if self.last_step_only:
|
| 62 |
+
indices = torch.ones_like(indices) * (num_denoising_steps - 1)
|
| 63 |
+
else:
|
| 64 |
+
indices = torch.empty(num_blocks, dtype=torch.long, device=device)
|
| 65 |
+
|
| 66 |
+
dist.broadcast(indices, src=0) # Broadcast the random indices to all ranks
|
| 67 |
+
return indices.tolist()
|
| 68 |
+
|
| 69 |
+
def inference_with_trajectory(
|
| 70 |
+
self,
|
| 71 |
+
noise: torch.Tensor,
|
| 72 |
+
initial_latent: Optional[torch.Tensor] = None,
|
| 73 |
+
return_sim_step: bool = False,
|
| 74 |
+
**conditional_dict
|
| 75 |
+
) -> torch.Tensor:
|
| 76 |
+
batch_size, num_frames, num_channels, height, width = noise.shape
|
| 77 |
+
if not self.independent_first_frame or (
|
| 78 |
+
self.independent_first_frame and initial_latent is not None
|
| 79 |
+
):
|
| 80 |
+
# If the first frame is independent and the first frame is provided, then the number of frames in the
|
| 81 |
+
# noise should still be a multiple of num_frame_per_block
|
| 82 |
+
assert num_frames % self.num_frame_per_block == 0
|
| 83 |
+
num_blocks = num_frames // self.num_frame_per_block
|
| 84 |
+
else:
|
| 85 |
+
# Using a [1, 4, 4, 4, 4, 4, ...] model to generate a video without image conditioning
|
| 86 |
+
assert (num_frames - 1) % self.num_frame_per_block == 0
|
| 87 |
+
num_blocks = (num_frames - 1) // self.num_frame_per_block
|
| 88 |
+
num_input_frames = initial_latent.shape[1] if initial_latent is not None else 0
|
| 89 |
+
num_output_frames = num_frames + num_input_frames # add the initial latent frames
|
| 90 |
+
output = torch.zeros(
|
| 91 |
+
[batch_size, num_output_frames, num_channels, height, width],
|
| 92 |
+
device=noise.device,
|
| 93 |
+
dtype=noise.dtype,
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
# Step 1: Initialize KV cache to all zeros
|
| 97 |
+
self._initialize_kv_cache(batch_size=batch_size, dtype=noise.dtype, device=noise.device)
|
| 98 |
+
self._initialize_crossattn_cache(
|
| 99 |
+
batch_size=batch_size, dtype=noise.dtype, device=noise.device
|
| 100 |
+
)
|
| 101 |
+
# if self.kv_cache1 is None:
|
| 102 |
+
# self._initialize_kv_cache(
|
| 103 |
+
# batch_size=batch_size,
|
| 104 |
+
# dtype=noise.dtype,
|
| 105 |
+
# device=noise.device,
|
| 106 |
+
# )
|
| 107 |
+
# self._initialize_crossattn_cache(
|
| 108 |
+
# batch_size=batch_size,
|
| 109 |
+
# dtype=noise.dtype,
|
| 110 |
+
# device=noise.device
|
| 111 |
+
# )
|
| 112 |
+
# else:
|
| 113 |
+
# # reset cross attn cache
|
| 114 |
+
# for block_index in range(self.num_transformer_blocks):
|
| 115 |
+
# self.crossattn_cache[block_index]["is_init"] = False
|
| 116 |
+
# # reset kv cache
|
| 117 |
+
# for block_index in range(len(self.kv_cache1)):
|
| 118 |
+
# self.kv_cache1[block_index]["global_end_index"] = torch.tensor(
|
| 119 |
+
# [0], dtype=torch.long, device=noise.device)
|
| 120 |
+
# self.kv_cache1[block_index]["local_end_index"] = torch.tensor(
|
| 121 |
+
# [0], dtype=torch.long, device=noise.device)
|
| 122 |
+
|
| 123 |
+
# Step 2: Cache context feature
|
| 124 |
+
current_start_frame = 0
|
| 125 |
+
if initial_latent is not None:
|
| 126 |
+
timestep = torch.ones([batch_size, 1], device=noise.device, dtype=torch.int64) * 0
|
| 127 |
+
# Assume num_input_frames is 1 + self.num_frame_per_block * num_input_blocks
|
| 128 |
+
output[:, :1] = initial_latent
|
| 129 |
+
with torch.no_grad():
|
| 130 |
+
self.generator(
|
| 131 |
+
noisy_image_or_video=initial_latent,
|
| 132 |
+
conditional_dict=conditional_dict,
|
| 133 |
+
timestep=timestep * 0,
|
| 134 |
+
kv_cache=self.kv_cache1,
|
| 135 |
+
crossattn_cache=self.crossattn_cache,
|
| 136 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 137 |
+
)
|
| 138 |
+
current_start_frame += 1
|
| 139 |
+
|
| 140 |
+
# Step 3: Temporal denoising loop
|
| 141 |
+
all_num_frames = [self.num_frame_per_block] * num_blocks
|
| 142 |
+
if self.independent_first_frame and initial_latent is None:
|
| 143 |
+
all_num_frames = [1] + all_num_frames
|
| 144 |
+
num_denoising_steps = len(self.denoising_step_list)
|
| 145 |
+
exit_flags = self.generate_and_sync_list(
|
| 146 |
+
len(all_num_frames), num_denoising_steps, device=noise.device
|
| 147 |
+
)
|
| 148 |
+
start_gradient_frame_index = num_output_frames - 21
|
| 149 |
+
|
| 150 |
+
# for block_index in range(num_blocks):
|
| 151 |
+
for block_index, current_num_frames in enumerate(all_num_frames):
|
| 152 |
+
noisy_input = noise[
|
| 153 |
+
:,
|
| 154 |
+
current_start_frame
|
| 155 |
+
- num_input_frames : current_start_frame
|
| 156 |
+
+ current_num_frames
|
| 157 |
+
- num_input_frames,
|
| 158 |
+
]
|
| 159 |
+
|
| 160 |
+
# Step 3.1: Spatial denoising loop
|
| 161 |
+
for index, current_timestep in enumerate(self.denoising_step_list):
|
| 162 |
+
if self.same_step_across_blocks:
|
| 163 |
+
exit_flag = index == exit_flags[0]
|
| 164 |
+
else:
|
| 165 |
+
exit_flag = (
|
| 166 |
+
index == exit_flags[block_index]
|
| 167 |
+
) # Only backprop at the randomly selected timestep (consistent across all ranks)
|
| 168 |
+
timestep = (
|
| 169 |
+
torch.ones(
|
| 170 |
+
[batch_size, current_num_frames],
|
| 171 |
+
device=noise.device,
|
| 172 |
+
dtype=torch.int64,
|
| 173 |
+
)
|
| 174 |
+
* current_timestep
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
if not exit_flag:
|
| 178 |
+
with torch.no_grad():
|
| 179 |
+
_, denoised_pred = self.generator(
|
| 180 |
+
noisy_image_or_video=noisy_input,
|
| 181 |
+
conditional_dict=conditional_dict,
|
| 182 |
+
timestep=timestep,
|
| 183 |
+
kv_cache=self.kv_cache1,
|
| 184 |
+
crossattn_cache=self.crossattn_cache,
|
| 185 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 186 |
+
)
|
| 187 |
+
next_timestep = self.denoising_step_list[index + 1]
|
| 188 |
+
noisy_input = self.scheduler.add_noise(
|
| 189 |
+
denoised_pred.flatten(0, 1),
|
| 190 |
+
torch.randn_like(denoised_pred.flatten(0, 1)),
|
| 191 |
+
next_timestep
|
| 192 |
+
* torch.ones(
|
| 193 |
+
[batch_size * current_num_frames],
|
| 194 |
+
device=noise.device,
|
| 195 |
+
dtype=torch.long,
|
| 196 |
+
),
|
| 197 |
+
).unflatten(0, denoised_pred.shape[:2])
|
| 198 |
+
else:
|
| 199 |
+
# for getting real output
|
| 200 |
+
# with torch.set_grad_enabled(current_start_frame >= start_gradient_frame_index):
|
| 201 |
+
if current_start_frame < start_gradient_frame_index:
|
| 202 |
+
with torch.no_grad():
|
| 203 |
+
_, denoised_pred = self.generator(
|
| 204 |
+
noisy_image_or_video=noisy_input,
|
| 205 |
+
conditional_dict=conditional_dict,
|
| 206 |
+
timestep=timestep,
|
| 207 |
+
kv_cache=self.kv_cache1,
|
| 208 |
+
crossattn_cache=self.crossattn_cache,
|
| 209 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 210 |
+
)
|
| 211 |
+
else:
|
| 212 |
+
_, denoised_pred = self.generator(
|
| 213 |
+
noisy_image_or_video=noisy_input,
|
| 214 |
+
conditional_dict=conditional_dict,
|
| 215 |
+
timestep=timestep,
|
| 216 |
+
kv_cache=self.kv_cache1,
|
| 217 |
+
crossattn_cache=self.crossattn_cache,
|
| 218 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 219 |
+
)
|
| 220 |
+
break
|
| 221 |
+
|
| 222 |
+
# Step 3.2: record the model's output
|
| 223 |
+
output[
|
| 224 |
+
:,
|
| 225 |
+
current_start_frame : current_start_frame + current_num_frames,
|
| 226 |
+
] = denoised_pred
|
| 227 |
+
|
| 228 |
+
# Step 3.3: rerun with timestep zero to update the cache
|
| 229 |
+
context_timestep = torch.ones_like(timestep) * self.context_noise
|
| 230 |
+
# add context noise
|
| 231 |
+
denoised_pred = self.scheduler.add_noise(
|
| 232 |
+
denoised_pred.flatten(0, 1),
|
| 233 |
+
torch.randn_like(denoised_pred.flatten(0, 1)),
|
| 234 |
+
context_timestep
|
| 235 |
+
* torch.ones(
|
| 236 |
+
[batch_size * current_num_frames],
|
| 237 |
+
device=noise.device,
|
| 238 |
+
dtype=torch.long,
|
| 239 |
+
),
|
| 240 |
+
).unflatten(0, denoised_pred.shape[:2])
|
| 241 |
+
with torch.no_grad():
|
| 242 |
+
self.generator(
|
| 243 |
+
noisy_image_or_video=denoised_pred,
|
| 244 |
+
conditional_dict=conditional_dict,
|
| 245 |
+
timestep=context_timestep,
|
| 246 |
+
kv_cache=self.kv_cache1,
|
| 247 |
+
crossattn_cache=self.crossattn_cache,
|
| 248 |
+
current_start=current_start_frame * self.frame_seq_length,
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
# Step 3.4: update the start and end frame indices
|
| 252 |
+
current_start_frame += current_num_frames
|
| 253 |
+
|
| 254 |
+
# Step 3.5: Return the denoised timestep
|
| 255 |
+
if not self.same_step_across_blocks:
|
| 256 |
+
denoised_timestep_from, denoised_timestep_to = None, None
|
| 257 |
+
elif exit_flags[0] == len(self.denoising_step_list) - 1:
|
| 258 |
+
denoised_timestep_to = 0
|
| 259 |
+
denoised_timestep_from = (
|
| 260 |
+
1000
|
| 261 |
+
- torch.argmin(
|
| 262 |
+
(
|
| 263 |
+
self.scheduler.timesteps.cuda()
|
| 264 |
+
- self.denoising_step_list[exit_flags[0]].cuda()
|
| 265 |
+
).abs(),
|
| 266 |
+
dim=0,
|
| 267 |
+
).item()
|
| 268 |
+
)
|
| 269 |
+
else:
|
| 270 |
+
denoised_timestep_to = (
|
| 271 |
+
1000
|
| 272 |
+
- torch.argmin(
|
| 273 |
+
(
|
| 274 |
+
self.scheduler.timesteps.cuda()
|
| 275 |
+
- self.denoising_step_list[exit_flags[0] + 1].cuda()
|
| 276 |
+
).abs(),
|
| 277 |
+
dim=0,
|
| 278 |
+
).item()
|
| 279 |
+
)
|
| 280 |
+
denoised_timestep_from = (
|
| 281 |
+
1000
|
| 282 |
+
- torch.argmin(
|
| 283 |
+
(
|
| 284 |
+
self.scheduler.timesteps.cuda()
|
| 285 |
+
- self.denoising_step_list[exit_flags[0]].cuda()
|
| 286 |
+
).abs(),
|
| 287 |
+
dim=0,
|
| 288 |
+
).item()
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
if return_sim_step:
|
| 292 |
+
return (
|
| 293 |
+
output,
|
| 294 |
+
denoised_timestep_from,
|
| 295 |
+
denoised_timestep_to,
|
| 296 |
+
exit_flags[0] + 1,
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
return output, denoised_timestep_from, denoised_timestep_to
|
| 300 |
+
|
| 301 |
+
def _initialize_kv_cache(
|
| 302 |
+
self,
|
| 303 |
+
batch_size,
|
| 304 |
+
dtype,
|
| 305 |
+
device,
|
| 306 |
+
):
|
| 307 |
+
"""
|
| 308 |
+
Initialize a Per-GPU KV cache for the Wan model.
|
| 309 |
+
"""
|
| 310 |
+
kv_cache1 = []
|
| 311 |
+
|
| 312 |
+
for _ in range(self.num_transformer_blocks):
|
| 313 |
+
kv_cache1.append(
|
| 314 |
+
{
|
| 315 |
+
"k": torch.zeros(
|
| 316 |
+
[batch_size, self.kv_cache_size, 12, 128],
|
| 317 |
+
dtype=dtype,
|
| 318 |
+
device=device,
|
| 319 |
+
),
|
| 320 |
+
"v": torch.zeros(
|
| 321 |
+
[batch_size, self.kv_cache_size, 12, 128],
|
| 322 |
+
dtype=dtype,
|
| 323 |
+
device=device,
|
| 324 |
+
),
|
| 325 |
+
"global_end_index": torch.tensor([0], dtype=torch.long, device=device),
|
| 326 |
+
"local_end_index": torch.tensor([0], dtype=torch.long, device=device),
|
| 327 |
+
}
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
self.kv_cache1 = kv_cache1 # always store the clean cache
|
| 331 |
+
|
| 332 |
+
def _initialize_crossattn_cache(
|
| 333 |
+
self,
|
| 334 |
+
batch_size,
|
| 335 |
+
dtype,
|
| 336 |
+
device,
|
| 337 |
+
):
|
| 338 |
+
"""
|
| 339 |
+
Initialize a Per-GPU cross-attention cache for the Wan model.
|
| 340 |
+
"""
|
| 341 |
+
crossattn_cache = []
|
| 342 |
+
|
| 343 |
+
for _ in range(self.num_transformer_blocks):
|
| 344 |
+
crossattn_cache.append(
|
| 345 |
+
{
|
| 346 |
+
"k": torch.zeros([batch_size, 512, 12, 128], dtype=dtype, device=device),
|
| 347 |
+
"v": torch.zeros([batch_size, 512, 12, 128], dtype=dtype, device=device),
|
| 348 |
+
"is_init": False,
|
| 349 |
+
}
|
| 350 |
+
)
|
| 351 |
+
self.crossattn_cache = crossattn_cache
|
prompts/MovieGenVideoBench.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
prompts/MovieGenVideoBench_extended.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
prompts/vbench/all_dimension.txt
ADDED
|
@@ -0,0 +1,946 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
In a still frame, a stop sign
|
| 2 |
+
a toilet, frozen in time
|
| 3 |
+
a laptop, frozen in time
|
| 4 |
+
A tranquil tableau of alley
|
| 5 |
+
A tranquil tableau of bar
|
| 6 |
+
A tranquil tableau of barn
|
| 7 |
+
A tranquil tableau of bathroom
|
| 8 |
+
A tranquil tableau of bedroom
|
| 9 |
+
A tranquil tableau of cliff
|
| 10 |
+
In a still frame, courtyard
|
| 11 |
+
In a still frame, gas station
|
| 12 |
+
A tranquil tableau of house
|
| 13 |
+
indoor gymnasium, frozen in time
|
| 14 |
+
A tranquil tableau of indoor library
|
| 15 |
+
A tranquil tableau of kitchen
|
| 16 |
+
A tranquil tableau of palace
|
| 17 |
+
In a still frame, parking lot
|
| 18 |
+
In a still frame, phone booth
|
| 19 |
+
A tranquil tableau of restaurant
|
| 20 |
+
A tranquil tableau of tower
|
| 21 |
+
A tranquil tableau of a bowl
|
| 22 |
+
A tranquil tableau of an apple
|
| 23 |
+
A tranquil tableau of a bench
|
| 24 |
+
A tranquil tableau of a bed
|
| 25 |
+
A tranquil tableau of a chair
|
| 26 |
+
A tranquil tableau of a cup
|
| 27 |
+
A tranquil tableau of a dining table
|
| 28 |
+
In a still frame, a pear
|
| 29 |
+
A tranquil tableau of a bunch of grapes
|
| 30 |
+
A tranquil tableau of a bowl on the kitchen counter
|
| 31 |
+
A tranquil tableau of a beautiful, handcrafted ceramic bowl
|
| 32 |
+
A tranquil tableau of an antique bowl
|
| 33 |
+
A tranquil tableau of an exquisite mahogany dining table
|
| 34 |
+
A tranquil tableau of a wooden bench in the park
|
| 35 |
+
A tranquil tableau of a beautiful wrought-iron bench surrounded by blooming flowers
|
| 36 |
+
In a still frame, a park bench with a view of the lake
|
| 37 |
+
A tranquil tableau of a vintage rocking chair was placed on the porch
|
| 38 |
+
A tranquil tableau of the jail cell was small and dimly lit, with cold, steel bars
|
| 39 |
+
A tranquil tableau of the phone booth was tucked away in a quiet alley
|
| 40 |
+
a dilapidated phone booth stood as a relic of a bygone era on the sidewalk, frozen in time
|
| 41 |
+
A tranquil tableau of the old red barn stood weathered and iconic against the backdrop of the countryside
|
| 42 |
+
A tranquil tableau of a picturesque barn was painted a warm shade of red and nestled in a picturesque meadow
|
| 43 |
+
In a still frame, within the desolate desert, an oasis unfolded, characterized by the stoic presence of palm trees and a motionless, glassy pool of water
|
| 44 |
+
In a still frame, the Parthenon's majestic Doric columns stand in serene solitude atop the Acropolis, framed by the tranquil Athenian landscape
|
| 45 |
+
In a still frame, the Temple of Hephaestus, with its timeless Doric grace, stands stoically against the backdrop of a quiet Athens
|
| 46 |
+
In a still frame, the ornate Victorian streetlamp stands solemnly, adorned with intricate ironwork and stained glass panels
|
| 47 |
+
A tranquil tableau of the Stonehenge presented itself as an enigmatic puzzle, each colossal stone meticulously placed against the backdrop of tranquility
|
| 48 |
+
In a still frame, in the vast desert, an oasis nestled among dunes, featuring tall palm trees and an air of serenity
|
| 49 |
+
static view on a desert scene with an oasis, palm trees, and a clear, calm pool of water
|
| 50 |
+
A tranquil tableau of an ornate Victorian streetlamp standing on a cobblestone street corner, illuminating the empty night
|
| 51 |
+
A tranquil tableau of a tranquil lakeside cabin nestled among tall pines, its reflection mirrored perfectly in the calm water
|
| 52 |
+
In a still frame, a vintage gas lantern, adorned with intricate details, gracing a historic cobblestone square
|
| 53 |
+
In a still frame, a tranquil Japanese tea ceremony room, with tatami mats, a delicate tea set, and a bonsai tree in the corner
|
| 54 |
+
A tranquil tableau of the Parthenon stands resolute in its classical elegance, a timeless symbol of Athens' cultural legacy
|
| 55 |
+
A tranquil tableau of in the heart of Plaka, the neoclassical architecture of the old city harmonizes with the ancient ruins
|
| 56 |
+
A tranquil tableau of in the desolate beauty of the American Southwest, Chaco Canyon's ancient ruins whispered tales of an enigmatic civilization that once thrived amidst the arid landscapes
|
| 57 |
+
A tranquil tableau of at the edge of the Arabian Desert, the ancient city of Petra beckoned with its enigmatic rock-carved façades
|
| 58 |
+
In a still frame, amidst the cobblestone streets, an Art Nouveau lamppost stood tall
|
| 59 |
+
A tranquil tableau of in the quaint village square, a traditional wrought-iron streetlamp featured delicate filigree patterns and amber-hued glass panels
|
| 60 |
+
A tranquil tableau of the lampposts were adorned with Art Deco motifs, their geometric shapes and frosted glass creating a sense of vintage glamour
|
| 61 |
+
In a still frame, in the picturesque square, a Gothic-style lamppost adorned with intricate stone carvings added a touch of medieval charm to the setting
|
| 62 |
+
In a still frame, in the heart of the old city, a row of ornate lantern-style streetlamps bathed the narrow alleyway in a warm, welcoming light
|
| 63 |
+
A tranquil tableau of in the heart of the Utah desert, a massive sandstone arch spanned the horizon
|
| 64 |
+
A tranquil tableau of in the Arizona desert, a massive stone bridge arched across a rugged canyon
|
| 65 |
+
A tranquil tableau of in the corner of the minimalist tea room, a bonsai tree added a touch of nature's beauty to the otherwise simple and elegant space
|
| 66 |
+
In a still frame, amidst the hushed ambiance of the traditional tea room, a meticulously arranged tea set awaited, with porcelain cups, a bamboo whisk
|
| 67 |
+
In a still frame, nestled in the Zen garden, a rustic teahouse featured tatami seating and a traditional charcoal brazier
|
| 68 |
+
A tranquil tableau of a country estate's library featured elegant wooden shelves
|
| 69 |
+
A tranquil tableau of beneath the shade of a solitary oak tree, an old wooden park bench sat patiently
|
| 70 |
+
A tranquil tableau of beside a tranquil pond, a weeping willow tree draped its branches gracefully over the water's surface, creating a serene tableau of reflection and calm
|
| 71 |
+
A tranquil tableau of in the Zen garden, a perfectly raked gravel path led to a serene rock garden
|
| 72 |
+
In a still frame, a tranquil pond was fringed by weeping cherry trees, their blossoms drifting lazily onto the glassy surface
|
| 73 |
+
In a still frame, within the historic library's reading room, rows of antique leather chairs and mahogany tables offered a serene haven for literary contemplation
|
| 74 |
+
A tranquil tableau of a peaceful orchid garden showcased a variety of delicate blooms
|
| 75 |
+
A tranquil tableau of in the serene courtyard, a centuries-old stone well stood as a symbol of a bygone era, its mossy stones bearing witness to the passage of time
|
| 76 |
+
a bird and a cat
|
| 77 |
+
a cat and a dog
|
| 78 |
+
a dog and a horse
|
| 79 |
+
a horse and a sheep
|
| 80 |
+
a sheep and a cow
|
| 81 |
+
a cow and an elephant
|
| 82 |
+
an elephant and a bear
|
| 83 |
+
a bear and a zebra
|
| 84 |
+
a zebra and a giraffe
|
| 85 |
+
a giraffe and a bird
|
| 86 |
+
a chair and a couch
|
| 87 |
+
a couch and a potted plant
|
| 88 |
+
a potted plant and a tv
|
| 89 |
+
a tv and a laptop
|
| 90 |
+
a laptop and a remote
|
| 91 |
+
a remote and a keyboard
|
| 92 |
+
a keyboard and a cell phone
|
| 93 |
+
a cell phone and a book
|
| 94 |
+
a book and a clock
|
| 95 |
+
a clock and a backpack
|
| 96 |
+
a backpack and an umbrella
|
| 97 |
+
an umbrella and a handbag
|
| 98 |
+
a handbag and a tie
|
| 99 |
+
a tie and a suitcase
|
| 100 |
+
a suitcase and a vase
|
| 101 |
+
a vase and scissors
|
| 102 |
+
scissors and a teddy bear
|
| 103 |
+
a teddy bear and a frisbee
|
| 104 |
+
a frisbee and skis
|
| 105 |
+
skis and a snowboard
|
| 106 |
+
a snowboard and a sports ball
|
| 107 |
+
a sports ball and a kite
|
| 108 |
+
a kite and a baseball bat
|
| 109 |
+
a baseball bat and a baseball glove
|
| 110 |
+
a baseball glove and a skateboard
|
| 111 |
+
a skateboard and a surfboard
|
| 112 |
+
a surfboard and a tennis racket
|
| 113 |
+
a tennis racket and a bottle
|
| 114 |
+
a bottle and a chair
|
| 115 |
+
an airplane and a train
|
| 116 |
+
a train and a boat
|
| 117 |
+
a boat and an airplane
|
| 118 |
+
a bicycle and a car
|
| 119 |
+
a car and a motorcycle
|
| 120 |
+
a motorcycle and a bus
|
| 121 |
+
a bus and a traffic light
|
| 122 |
+
a traffic light and a fire hydrant
|
| 123 |
+
a fire hydrant and a stop sign
|
| 124 |
+
a stop sign and a parking meter
|
| 125 |
+
a parking meter and a truck
|
| 126 |
+
a truck and a bicycle
|
| 127 |
+
a toilet and a hair drier
|
| 128 |
+
a hair drier and a toothbrush
|
| 129 |
+
a toothbrush and a sink
|
| 130 |
+
a sink and a toilet
|
| 131 |
+
a wine glass and a chair
|
| 132 |
+
a cup and a couch
|
| 133 |
+
a fork and a potted plant
|
| 134 |
+
a knife and a tv
|
| 135 |
+
a spoon and a laptop
|
| 136 |
+
a bowl and a remote
|
| 137 |
+
a banana and a keyboard
|
| 138 |
+
an apple and a cell phone
|
| 139 |
+
a sandwich and a book
|
| 140 |
+
an orange and a clock
|
| 141 |
+
broccoli and a backpack
|
| 142 |
+
a carrot and an umbrella
|
| 143 |
+
a hot dog and a handbag
|
| 144 |
+
a pizza and a tie
|
| 145 |
+
a donut and a suitcase
|
| 146 |
+
a cake and a vase
|
| 147 |
+
an oven and scissors
|
| 148 |
+
a toaster and a teddy bear
|
| 149 |
+
a microwave and a frisbee
|
| 150 |
+
a refrigerator and skis
|
| 151 |
+
a bicycle and an airplane
|
| 152 |
+
a car and a train
|
| 153 |
+
a motorcycle and a boat
|
| 154 |
+
a person and a toilet
|
| 155 |
+
a person and a hair drier
|
| 156 |
+
a person and a toothbrush
|
| 157 |
+
a person and a sink
|
| 158 |
+
A person is riding a bike
|
| 159 |
+
A person is marching
|
| 160 |
+
A person is roller skating
|
| 161 |
+
A person is tasting beer
|
| 162 |
+
A person is clapping
|
| 163 |
+
A person is drawing
|
| 164 |
+
A person is petting animal (not cat)
|
| 165 |
+
A person is eating watermelon
|
| 166 |
+
A person is playing harp
|
| 167 |
+
A person is wrestling
|
| 168 |
+
A person is riding scooter
|
| 169 |
+
A person is sweeping floor
|
| 170 |
+
A person is skateboarding
|
| 171 |
+
A person is dunking basketball
|
| 172 |
+
A person is playing flute
|
| 173 |
+
A person is stretching leg
|
| 174 |
+
A person is tying tie
|
| 175 |
+
A person is skydiving
|
| 176 |
+
A person is shooting goal (soccer)
|
| 177 |
+
A person is playing piano
|
| 178 |
+
A person is finger snapping
|
| 179 |
+
A person is canoeing or kayaking
|
| 180 |
+
A person is laughing
|
| 181 |
+
A person is digging
|
| 182 |
+
A person is clay pottery making
|
| 183 |
+
A person is shooting basketball
|
| 184 |
+
A person is bending back
|
| 185 |
+
A person is shaking hands
|
| 186 |
+
A person is bandaging
|
| 187 |
+
A person is push up
|
| 188 |
+
A person is catching or throwing frisbee
|
| 189 |
+
A person is playing trumpet
|
| 190 |
+
A person is flying kite
|
| 191 |
+
A person is filling eyebrows
|
| 192 |
+
A person is shuffling cards
|
| 193 |
+
A person is folding clothes
|
| 194 |
+
A person is smoking
|
| 195 |
+
A person is tai chi
|
| 196 |
+
A person is squat
|
| 197 |
+
A person is playing controller
|
| 198 |
+
A person is throwing axe
|
| 199 |
+
A person is giving or receiving award
|
| 200 |
+
A person is air drumming
|
| 201 |
+
A person is taking a shower
|
| 202 |
+
A person is planting trees
|
| 203 |
+
A person is sharpening knives
|
| 204 |
+
A person is robot dancing
|
| 205 |
+
A person is rock climbing
|
| 206 |
+
A person is hula hooping
|
| 207 |
+
A person is writing
|
| 208 |
+
A person is bungee jumping
|
| 209 |
+
A person is pushing cart
|
| 210 |
+
A person is cleaning windows
|
| 211 |
+
A person is cutting watermelon
|
| 212 |
+
A person is cheerleading
|
| 213 |
+
A person is washing hands
|
| 214 |
+
A person is ironing
|
| 215 |
+
A person is cutting nails
|
| 216 |
+
A person is hugging
|
| 217 |
+
A person is trimming or shaving beard
|
| 218 |
+
A person is jogging
|
| 219 |
+
A person is making bed
|
| 220 |
+
A person is washing dishes
|
| 221 |
+
A person is grooming dog
|
| 222 |
+
A person is doing laundry
|
| 223 |
+
A person is knitting
|
| 224 |
+
A person is reading book
|
| 225 |
+
A person is baby waking up
|
| 226 |
+
A person is massaging legs
|
| 227 |
+
A person is brushing teeth
|
| 228 |
+
A person is crawling baby
|
| 229 |
+
A person is motorcycling
|
| 230 |
+
A person is driving car
|
| 231 |
+
A person is sticking tongue out
|
| 232 |
+
A person is shaking head
|
| 233 |
+
A person is sword fighting
|
| 234 |
+
A person is doing aerobics
|
| 235 |
+
A person is strumming guitar
|
| 236 |
+
A person is riding or walking with horse
|
| 237 |
+
A person is archery
|
| 238 |
+
A person is catching or throwing baseball
|
| 239 |
+
A person is playing chess
|
| 240 |
+
A person is rock scissors paper
|
| 241 |
+
A person is using computer
|
| 242 |
+
A person is arranging flowers
|
| 243 |
+
A person is bending metal
|
| 244 |
+
A person is ice skating
|
| 245 |
+
A person is climbing a rope
|
| 246 |
+
A person is crying
|
| 247 |
+
A person is dancing ballet
|
| 248 |
+
A person is getting a haircut
|
| 249 |
+
A person is running on treadmill
|
| 250 |
+
A person is kissing
|
| 251 |
+
A person is counting money
|
| 252 |
+
A person is barbequing
|
| 253 |
+
A person is peeling apples
|
| 254 |
+
A person is milking cow
|
| 255 |
+
A person is shining shoes
|
| 256 |
+
A person is making snowman
|
| 257 |
+
A person is sailing
|
| 258 |
+
a person swimming in ocean
|
| 259 |
+
a person giving a presentation to a room full of colleagues
|
| 260 |
+
a person washing the dishes
|
| 261 |
+
a person eating a burger
|
| 262 |
+
a person walking in the snowstorm
|
| 263 |
+
a person drinking coffee in a cafe
|
| 264 |
+
a person playing guitar
|
| 265 |
+
a bicycle leaning against a tree
|
| 266 |
+
a bicycle gliding through a snowy field
|
| 267 |
+
a bicycle slowing down to stop
|
| 268 |
+
a bicycle accelerating to gain speed
|
| 269 |
+
a car stuck in traffic during rush hour
|
| 270 |
+
a car turning a corner
|
| 271 |
+
a car slowing down to stop
|
| 272 |
+
a car accelerating to gain speed
|
| 273 |
+
a motorcycle cruising along a coastal highway
|
| 274 |
+
a motorcycle turning a corner
|
| 275 |
+
a motorcycle slowing down to stop
|
| 276 |
+
a motorcycle gliding through a snowy field
|
| 277 |
+
a motorcycle accelerating to gain speed
|
| 278 |
+
an airplane soaring through a clear blue sky
|
| 279 |
+
an airplane taking off
|
| 280 |
+
an airplane landing smoothly on a runway
|
| 281 |
+
an airplane accelerating to gain speed
|
| 282 |
+
a bus turning a corner
|
| 283 |
+
a bus stuck in traffic during rush hour
|
| 284 |
+
a bus accelerating to gain speed
|
| 285 |
+
a train speeding down the tracks
|
| 286 |
+
a train crossing over a tall bridge
|
| 287 |
+
a train accelerating to gain speed
|
| 288 |
+
a truck turning a corner
|
| 289 |
+
a truck anchored in a tranquil bay
|
| 290 |
+
a truck stuck in traffic during rush hour
|
| 291 |
+
a truck slowing down to stop
|
| 292 |
+
a truck accelerating to gain speed
|
| 293 |
+
a boat sailing smoothly on a calm lake
|
| 294 |
+
a boat slowing down to stop
|
| 295 |
+
a boat accelerating to gain speed
|
| 296 |
+
a bird soaring gracefully in the sky
|
| 297 |
+
a bird building a nest from twigs and leaves
|
| 298 |
+
a bird flying over a snowy forest
|
| 299 |
+
a cat grooming itself meticulously with its tongue
|
| 300 |
+
a cat playing in park
|
| 301 |
+
a cat drinking water
|
| 302 |
+
a cat running happily
|
| 303 |
+
a dog enjoying a peaceful walk
|
| 304 |
+
a dog playing in park
|
| 305 |
+
a dog drinking water
|
| 306 |
+
a dog running happily
|
| 307 |
+
a horse bending down to drink water from a river
|
| 308 |
+
a horse galloping across an open field
|
| 309 |
+
a horse taking a peaceful walk
|
| 310 |
+
a horse running to join a herd of its kind
|
| 311 |
+
a sheep bending down to drink water from a river
|
| 312 |
+
a sheep taking a peaceful walk
|
| 313 |
+
a sheep running to join a herd of its kind
|
| 314 |
+
a cow bending down to drink water from a river
|
| 315 |
+
a cow chewing cud while resting in a tranquil barn
|
| 316 |
+
a cow running to join a herd of its kind
|
| 317 |
+
an elephant spraying itself with water using its trunk to cool down
|
| 318 |
+
an elephant taking a peaceful walk
|
| 319 |
+
an elephant running to join a herd of its kind
|
| 320 |
+
a bear catching a salmon in its powerful jaws
|
| 321 |
+
a bear sniffing the air for scents of food
|
| 322 |
+
a bear climbing a tree
|
| 323 |
+
a bear hunting for prey
|
| 324 |
+
a zebra bending down to drink water from a river
|
| 325 |
+
a zebra running to join a herd of its kind
|
| 326 |
+
a zebra taking a peaceful walk
|
| 327 |
+
a giraffe bending down to drink water from a river
|
| 328 |
+
a giraffe taking a peaceful walk
|
| 329 |
+
a giraffe running to join a herd of its kind
|
| 330 |
+
a person
|
| 331 |
+
a bicycle
|
| 332 |
+
a car
|
| 333 |
+
a motorcycle
|
| 334 |
+
an airplane
|
| 335 |
+
a bus
|
| 336 |
+
a train
|
| 337 |
+
a truck
|
| 338 |
+
a boat
|
| 339 |
+
a traffic light
|
| 340 |
+
a fire hydrant
|
| 341 |
+
a stop sign
|
| 342 |
+
a parking meter
|
| 343 |
+
a bench
|
| 344 |
+
a bird
|
| 345 |
+
a cat
|
| 346 |
+
a dog
|
| 347 |
+
a horse
|
| 348 |
+
a sheep
|
| 349 |
+
a cow
|
| 350 |
+
an elephant
|
| 351 |
+
a bear
|
| 352 |
+
a zebra
|
| 353 |
+
a giraffe
|
| 354 |
+
a backpack
|
| 355 |
+
an umbrella
|
| 356 |
+
a handbag
|
| 357 |
+
a tie
|
| 358 |
+
a suitcase
|
| 359 |
+
a frisbee
|
| 360 |
+
skis
|
| 361 |
+
a snowboard
|
| 362 |
+
a sports ball
|
| 363 |
+
a kite
|
| 364 |
+
a baseball bat
|
| 365 |
+
a baseball glove
|
| 366 |
+
a skateboard
|
| 367 |
+
a surfboard
|
| 368 |
+
a tennis racket
|
| 369 |
+
a bottle
|
| 370 |
+
a wine glass
|
| 371 |
+
a cup
|
| 372 |
+
a fork
|
| 373 |
+
a knife
|
| 374 |
+
a spoon
|
| 375 |
+
a bowl
|
| 376 |
+
a banana
|
| 377 |
+
an apple
|
| 378 |
+
a sandwich
|
| 379 |
+
an orange
|
| 380 |
+
broccoli
|
| 381 |
+
a carrot
|
| 382 |
+
a hot dog
|
| 383 |
+
a pizza
|
| 384 |
+
a donut
|
| 385 |
+
a cake
|
| 386 |
+
a chair
|
| 387 |
+
a couch
|
| 388 |
+
a potted plant
|
| 389 |
+
a bed
|
| 390 |
+
a dining table
|
| 391 |
+
a toilet
|
| 392 |
+
a tv
|
| 393 |
+
a laptop
|
| 394 |
+
a remote
|
| 395 |
+
a keyboard
|
| 396 |
+
a cell phone
|
| 397 |
+
a microwave
|
| 398 |
+
an oven
|
| 399 |
+
a toaster
|
| 400 |
+
a sink
|
| 401 |
+
a refrigerator
|
| 402 |
+
a book
|
| 403 |
+
a clock
|
| 404 |
+
a vase
|
| 405 |
+
scissors
|
| 406 |
+
a teddy bear
|
| 407 |
+
a hair drier
|
| 408 |
+
a toothbrush
|
| 409 |
+
a red bicycle
|
| 410 |
+
a green bicycle
|
| 411 |
+
a blue bicycle
|
| 412 |
+
a yellow bicycle
|
| 413 |
+
an orange bicycle
|
| 414 |
+
a purple bicycle
|
| 415 |
+
a pink bicycle
|
| 416 |
+
a black bicycle
|
| 417 |
+
a white bicycle
|
| 418 |
+
a red car
|
| 419 |
+
a green car
|
| 420 |
+
a blue car
|
| 421 |
+
a yellow car
|
| 422 |
+
an orange car
|
| 423 |
+
a purple car
|
| 424 |
+
a pink car
|
| 425 |
+
a black car
|
| 426 |
+
a white car
|
| 427 |
+
a red bird
|
| 428 |
+
a green bird
|
| 429 |
+
a blue bird
|
| 430 |
+
a yellow bird
|
| 431 |
+
an orange bird
|
| 432 |
+
a purple bird
|
| 433 |
+
a pink bird
|
| 434 |
+
a black bird
|
| 435 |
+
a white bird
|
| 436 |
+
a black cat
|
| 437 |
+
a white cat
|
| 438 |
+
an orange cat
|
| 439 |
+
a yellow cat
|
| 440 |
+
a red umbrella
|
| 441 |
+
a green umbrella
|
| 442 |
+
a blue umbrella
|
| 443 |
+
a yellow umbrella
|
| 444 |
+
an orange umbrella
|
| 445 |
+
a purple umbrella
|
| 446 |
+
a pink umbrella
|
| 447 |
+
a black umbrella
|
| 448 |
+
a white umbrella
|
| 449 |
+
a red suitcase
|
| 450 |
+
a green suitcase
|
| 451 |
+
a blue suitcase
|
| 452 |
+
a yellow suitcase
|
| 453 |
+
an orange suitcase
|
| 454 |
+
a purple suitcase
|
| 455 |
+
a pink suitcase
|
| 456 |
+
a black suitcase
|
| 457 |
+
a white suitcase
|
| 458 |
+
a red bowl
|
| 459 |
+
a green bowl
|
| 460 |
+
a blue bowl
|
| 461 |
+
a yellow bowl
|
| 462 |
+
an orange bowl
|
| 463 |
+
a purple bowl
|
| 464 |
+
a pink bowl
|
| 465 |
+
a black bowl
|
| 466 |
+
a white bowl
|
| 467 |
+
a red chair
|
| 468 |
+
a green chair
|
| 469 |
+
a blue chair
|
| 470 |
+
a yellow chair
|
| 471 |
+
an orange chair
|
| 472 |
+
a purple chair
|
| 473 |
+
a pink chair
|
| 474 |
+
a black chair
|
| 475 |
+
a white chair
|
| 476 |
+
a red clock
|
| 477 |
+
a green clock
|
| 478 |
+
a blue clock
|
| 479 |
+
a yellow clock
|
| 480 |
+
an orange clock
|
| 481 |
+
a purple clock
|
| 482 |
+
a pink clock
|
| 483 |
+
a black clock
|
| 484 |
+
a white clock
|
| 485 |
+
a red vase
|
| 486 |
+
a green vase
|
| 487 |
+
a blue vase
|
| 488 |
+
a yellow vase
|
| 489 |
+
an orange vase
|
| 490 |
+
a purple vase
|
| 491 |
+
a pink vase
|
| 492 |
+
a black vase
|
| 493 |
+
a white vase
|
| 494 |
+
A beautiful coastal beach in spring, waves lapping on sand, Van Gogh style
|
| 495 |
+
A beautiful coastal beach in spring, waves lapping on sand, oil painting
|
| 496 |
+
A beautiful coastal beach in spring, waves lapping on sand by Hokusai, in the style of Ukiyo
|
| 497 |
+
A beautiful coastal beach in spring, waves lapping on sand, black and white
|
| 498 |
+
A beautiful coastal beach in spring, waves lapping on sand, pixel art
|
| 499 |
+
A beautiful coastal beach in spring, waves lapping on sand, in cyberpunk style
|
| 500 |
+
A beautiful coastal beach in spring, waves lapping on sand, animated style
|
| 501 |
+
A beautiful coastal beach in spring, waves lapping on sand, watercolor painting
|
| 502 |
+
A beautiful coastal beach in spring, waves lapping on sand, surrealism style
|
| 503 |
+
The bund Shanghai, Van Gogh style
|
| 504 |
+
The bund Shanghai, oil painting
|
| 505 |
+
The bund Shanghai by Hokusai, in the style of Ukiyo
|
| 506 |
+
The bund Shanghai, black and white
|
| 507 |
+
The bund Shanghai, pixel art
|
| 508 |
+
The bund Shanghai, in cyberpunk style
|
| 509 |
+
The bund Shanghai, animated style
|
| 510 |
+
The bund Shanghai, watercolor painting
|
| 511 |
+
The bund Shanghai, surrealism style
|
| 512 |
+
a shark is swimming in the ocean, Van Gogh style
|
| 513 |
+
a shark is swimming in the ocean, oil painting
|
| 514 |
+
a shark is swimming in the ocean by Hokusai, in the style of Ukiyo
|
| 515 |
+
a shark is swimming in the ocean, black and white
|
| 516 |
+
a shark is swimming in the ocean, pixel art
|
| 517 |
+
a shark is swimming in the ocean, in cyberpunk style
|
| 518 |
+
a shark is swimming in the ocean, animated style
|
| 519 |
+
a shark is swimming in the ocean, watercolor painting
|
| 520 |
+
a shark is swimming in the ocean, surrealism style
|
| 521 |
+
A panda drinking coffee in a cafe in Paris, Van Gogh style
|
| 522 |
+
A panda drinking coffee in a cafe in Paris, oil painting
|
| 523 |
+
A panda drinking coffee in a cafe in Paris by Hokusai, in the style of Ukiyo
|
| 524 |
+
A panda drinking coffee in a cafe in Paris, black and white
|
| 525 |
+
A panda drinking coffee in a cafe in Paris, pixel art
|
| 526 |
+
A panda drinking coffee in a cafe in Paris, in cyberpunk style
|
| 527 |
+
A panda drinking coffee in a cafe in Paris, animated style
|
| 528 |
+
A panda drinking coffee in a cafe in Paris, watercolor painting
|
| 529 |
+
A panda drinking coffee in a cafe in Paris, surrealism style
|
| 530 |
+
A cute happy Corgi playing in park, sunset, Van Gogh style
|
| 531 |
+
A cute happy Corgi playing in park, sunset, oil painting
|
| 532 |
+
A cute happy Corgi playing in park, sunset by Hokusai, in the style of Ukiyo
|
| 533 |
+
A cute happy Corgi playing in park, sunset, black and white
|
| 534 |
+
A cute happy Corgi playing in park, sunset, pixel art
|
| 535 |
+
A cute happy Corgi playing in park, sunset, in cyberpunk style
|
| 536 |
+
A cute happy Corgi playing in park, sunset, animated style
|
| 537 |
+
A cute happy Corgi playing in park, sunset, watercolor painting
|
| 538 |
+
A cute happy Corgi playing in park, sunset, surrealism style
|
| 539 |
+
Gwen Stacy reading a book, Van Gogh style
|
| 540 |
+
Gwen Stacy reading a book, oil painting
|
| 541 |
+
Gwen Stacy reading a book by Hokusai, in the style of Ukiyo
|
| 542 |
+
Gwen Stacy reading a book, black and white
|
| 543 |
+
Gwen Stacy reading a book, pixel art
|
| 544 |
+
Gwen Stacy reading a book, in cyberpunk style
|
| 545 |
+
Gwen Stacy reading a book, animated style
|
| 546 |
+
Gwen Stacy reading a book, watercolor painting
|
| 547 |
+
Gwen Stacy reading a book, surrealism style
|
| 548 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, Van Gogh style
|
| 549 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, oil painting
|
| 550 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background by Hokusai, in the style of Ukiyo
|
| 551 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, black and white
|
| 552 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, pixel art
|
| 553 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, in cyberpunk style
|
| 554 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, animated style
|
| 555 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, watercolor painting
|
| 556 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, surrealism style
|
| 557 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, Van Gogh style
|
| 558 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, oil painting
|
| 559 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas by Hokusai, in the style of Ukiyo
|
| 560 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, black and white
|
| 561 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, pixel art
|
| 562 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, in cyberpunk style
|
| 563 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, animated style
|
| 564 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, watercolor painting
|
| 565 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, surrealism style
|
| 566 |
+
An astronaut flying in space, Van Gogh style
|
| 567 |
+
An astronaut flying in space, oil painting
|
| 568 |
+
An astronaut flying in space by Hokusai, in the style of Ukiyo
|
| 569 |
+
An astronaut flying in space, black and white
|
| 570 |
+
An astronaut flying in space, pixel art
|
| 571 |
+
An astronaut flying in space, in cyberpunk style
|
| 572 |
+
An astronaut flying in space, animated style
|
| 573 |
+
An astronaut flying in space, watercolor painting
|
| 574 |
+
An astronaut flying in space, surrealism style
|
| 575 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, Van Gogh style
|
| 576 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, oil painting
|
| 577 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks by Hokusai, in the style of Ukiyo
|
| 578 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, black and white
|
| 579 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, pixel art
|
| 580 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, in cyberpunk style
|
| 581 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, animated style
|
| 582 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, watercolor painting
|
| 583 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, surrealism style
|
| 584 |
+
A beautiful coastal beach in spring, waves lapping on sand, in super slow motion
|
| 585 |
+
A beautiful coastal beach in spring, waves lapping on sand, zoom in
|
| 586 |
+
A beautiful coastal beach in spring, waves lapping on sand, zoom out
|
| 587 |
+
A beautiful coastal beach in spring, waves lapping on sand, pan left
|
| 588 |
+
A beautiful coastal beach in spring, waves lapping on sand, pan right
|
| 589 |
+
A beautiful coastal beach in spring, waves lapping on sand, tilt up
|
| 590 |
+
A beautiful coastal beach in spring, waves lapping on sand, tilt down
|
| 591 |
+
A beautiful coastal beach in spring, waves lapping on sand, with an intense shaking effect
|
| 592 |
+
A beautiful coastal beach in spring, waves lapping on sand, featuring a steady and smooth perspective
|
| 593 |
+
A beautiful coastal beach in spring, waves lapping on sand, racking focus
|
| 594 |
+
The bund Shanghai, in super slow motion
|
| 595 |
+
The bund Shanghai, zoom in
|
| 596 |
+
The bund Shanghai, zoom out
|
| 597 |
+
The bund Shanghai, pan left
|
| 598 |
+
The bund Shanghai, pan right
|
| 599 |
+
The bund Shanghai, tilt up
|
| 600 |
+
The bund Shanghai, tilt down
|
| 601 |
+
The bund Shanghai, with an intense shaking effect
|
| 602 |
+
The bund Shanghai, featuring a steady and smooth perspective
|
| 603 |
+
The bund Shanghai, racking focus
|
| 604 |
+
a shark is swimming in the ocean, in super slow motion
|
| 605 |
+
a shark is swimming in the ocean, zoom in
|
| 606 |
+
a shark is swimming in the ocean, zoom out
|
| 607 |
+
a shark is swimming in the ocean, pan left
|
| 608 |
+
a shark is swimming in the ocean, pan right
|
| 609 |
+
a shark is swimming in the ocean, tilt up
|
| 610 |
+
a shark is swimming in the ocean, tilt down
|
| 611 |
+
a shark is swimming in the ocean, with an intense shaking effect
|
| 612 |
+
a shark is swimming in the ocean, featuring a steady and smooth perspective
|
| 613 |
+
a shark is swimming in the ocean, racking focus
|
| 614 |
+
A panda drinking coffee in a cafe in Paris, in super slow motion
|
| 615 |
+
A panda drinking coffee in a cafe in Paris, zoom in
|
| 616 |
+
A panda drinking coffee in a cafe in Paris, zoom out
|
| 617 |
+
A panda drinking coffee in a cafe in Paris, pan left
|
| 618 |
+
A panda drinking coffee in a cafe in Paris, pan right
|
| 619 |
+
A panda drinking coffee in a cafe in Paris, tilt up
|
| 620 |
+
A panda drinking coffee in a cafe in Paris, tilt down
|
| 621 |
+
A panda drinking coffee in a cafe in Paris, with an intense shaking effect
|
| 622 |
+
A panda drinking coffee in a cafe in Paris, featuring a steady and smooth perspective
|
| 623 |
+
A panda drinking coffee in a cafe in Paris, racking focus
|
| 624 |
+
A cute happy Corgi playing in park, sunset, in super slow motion
|
| 625 |
+
A cute happy Corgi playing in park, sunset, zoom in
|
| 626 |
+
A cute happy Corgi playing in park, sunset, zoom out
|
| 627 |
+
A cute happy Corgi playing in park, sunset, pan left
|
| 628 |
+
A cute happy Corgi playing in park, sunset, pan right
|
| 629 |
+
A cute happy Corgi playing in park, sunset, tilt up
|
| 630 |
+
A cute happy Corgi playing in park, sunset, tilt down
|
| 631 |
+
A cute happy Corgi playing in park, sunset, with an intense shaking effect
|
| 632 |
+
A cute happy Corgi playing in park, sunset, featuring a steady and smooth perspective
|
| 633 |
+
A cute happy Corgi playing in park, sunset, racking focus
|
| 634 |
+
Gwen Stacy reading a book, in super slow motion
|
| 635 |
+
Gwen Stacy reading a book, zoom in
|
| 636 |
+
Gwen Stacy reading a book, zoom out
|
| 637 |
+
Gwen Stacy reading a book, pan left
|
| 638 |
+
Gwen Stacy reading a book, pan right
|
| 639 |
+
Gwen Stacy reading a book, tilt up
|
| 640 |
+
Gwen Stacy reading a book, tilt down
|
| 641 |
+
Gwen Stacy reading a book, with an intense shaking effect
|
| 642 |
+
Gwen Stacy reading a book, featuring a steady and smooth perspective
|
| 643 |
+
Gwen Stacy reading a book, racking focus
|
| 644 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, in super slow motion
|
| 645 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, zoom in
|
| 646 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, zoom out
|
| 647 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, pan left
|
| 648 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, pan right
|
| 649 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, tilt up
|
| 650 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, tilt down
|
| 651 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, with an intense shaking effect
|
| 652 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, featuring a steady and smooth perspective
|
| 653 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background, racking focus
|
| 654 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, in super slow motion
|
| 655 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, zoom in
|
| 656 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, zoom out
|
| 657 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, pan left
|
| 658 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, pan right
|
| 659 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, tilt up
|
| 660 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, tilt down
|
| 661 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, with an intense shaking effect
|
| 662 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, featuring a steady and smooth perspective
|
| 663 |
+
A couple in formal evening wear going home get caught in a heavy downpour with umbrellas, racking focus
|
| 664 |
+
An astronaut flying in space, in super slow motion
|
| 665 |
+
An astronaut flying in space, zoom in
|
| 666 |
+
An astronaut flying in space, zoom out
|
| 667 |
+
An astronaut flying in space, pan left
|
| 668 |
+
An astronaut flying in space, pan right
|
| 669 |
+
An astronaut flying in space, tilt up
|
| 670 |
+
An astronaut flying in space, tilt down
|
| 671 |
+
An astronaut flying in space, with an intense shaking effect
|
| 672 |
+
An astronaut flying in space, featuring a steady and smooth perspective
|
| 673 |
+
An astronaut flying in space, racking focus
|
| 674 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, in super slow motion
|
| 675 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, zoom in
|
| 676 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, zoom out
|
| 677 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, pan left
|
| 678 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, pan right
|
| 679 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, tilt up
|
| 680 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, tilt down
|
| 681 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, with an intense shaking effect
|
| 682 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, featuring a steady and smooth perspective
|
| 683 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks, racking focus
|
| 684 |
+
Close up of grapes on a rotating table.
|
| 685 |
+
Turtle swimming in ocean.
|
| 686 |
+
A storm trooper vacuuming the beach.
|
| 687 |
+
A panda standing on a surfboard in the ocean in sunset.
|
| 688 |
+
An astronaut feeding ducks on a sunny afternoon, reflection from the water.
|
| 689 |
+
Two pandas discussing an academic paper.
|
| 690 |
+
Sunset time lapse at the beach with moving clouds and colors in the sky.
|
| 691 |
+
A fat rabbit wearing a purple robe walking through a fantasy landscape.
|
| 692 |
+
A koala bear playing piano in the forest.
|
| 693 |
+
An astronaut flying in space.
|
| 694 |
+
Fireworks.
|
| 695 |
+
An animated painting of fluffy white clouds moving in sky.
|
| 696 |
+
Flying through fantasy landscapes.
|
| 697 |
+
A bigfoot walking in the snowstorm.
|
| 698 |
+
A squirrel eating a burger.
|
| 699 |
+
A cat wearing sunglasses and working as a lifeguard at a pool.
|
| 700 |
+
Snow rocky mountains peaks canyon. snow blanketed rocky mountains surround and shadow deep canyons. the canyons twist and bend through the high elevated mountain peaks.
|
| 701 |
+
Splash of turquoise water in extreme slow motion, alpha channel included.
|
| 702 |
+
an ice cream is melting on the table.
|
| 703 |
+
a drone flying over a snowy forest.
|
| 704 |
+
a shark is swimming in the ocean.
|
| 705 |
+
Aerial panoramic video from a drone of a fantasy land.
|
| 706 |
+
a teddy bear is swimming in the ocean.
|
| 707 |
+
time lapse of sunrise on mars.
|
| 708 |
+
golden fish swimming in the ocean.
|
| 709 |
+
An artist brush painting on a canvas close up.
|
| 710 |
+
A drone view of celebration with Christmas tree and fireworks, starry sky - background.
|
| 711 |
+
happy dog wearing a yellow turtleneck, studio, portrait, facing camera, dark background
|
| 712 |
+
Origami dancers in white paper, 3D render, on white background, studio shot, dancing modern dance.
|
| 713 |
+
Campfire at night in a snowy forest with starry sky in the background.
|
| 714 |
+
a fantasy landscape
|
| 715 |
+
A 3D model of a 1800s victorian house.
|
| 716 |
+
this is how I do makeup in the morning.
|
| 717 |
+
A raccoon that looks like a turtle, digital art.
|
| 718 |
+
Robot dancing in Times Square.
|
| 719 |
+
Busy freeway at night.
|
| 720 |
+
Balloon full of water exploding in extreme slow motion.
|
| 721 |
+
An astronaut is riding a horse in the space in a photorealistic style.
|
| 722 |
+
Macro slo-mo. Slow motion cropped closeup of roasted coffee beans falling into an empty bowl.
|
| 723 |
+
Sewing machine, old sewing machine working.
|
| 724 |
+
Motion colour drop in water, ink swirling in water, colourful ink in water, abstraction fancy dream cloud of ink.
|
| 725 |
+
Few big purple plums rotating on the turntable. water drops appear on the skin during rotation. isolated on the white background. close-up. macro.
|
| 726 |
+
Vampire makeup face of beautiful girl, red contact lenses.
|
| 727 |
+
Ashtray full of butts on table, smoke flowing on black background, close-up
|
| 728 |
+
Pacific coast, carmel by the sea ocean and waves.
|
| 729 |
+
A teddy bear is playing drum kit in NYC Times Square.
|
| 730 |
+
A corgi is playing drum kit.
|
| 731 |
+
An Iron man is playing the electronic guitar, high electronic guitar.
|
| 732 |
+
A raccoon is playing the electronic guitar.
|
| 733 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background by Vincent van Gogh
|
| 734 |
+
A corgi's head depicted as an explosion of a nebula
|
| 735 |
+
A fantasy landscape
|
| 736 |
+
A future where humans have achieved teleportation technology
|
| 737 |
+
A jellyfish floating through the ocean, with bioluminescent tentacles
|
| 738 |
+
A Mars rover moving on Mars
|
| 739 |
+
A panda drinking coffee in a cafe in Paris
|
| 740 |
+
A space shuttle launching into orbit, with flames and smoke billowing out from the engines
|
| 741 |
+
A steam train moving on a mountainside
|
| 742 |
+
A super cool giant robot in Cyberpunk Beijing
|
| 743 |
+
A tropical beach at sunrise, with palm trees and crystal-clear water in the foreground
|
| 744 |
+
Cinematic shot of Van Gogh's selfie, Van Gogh style
|
| 745 |
+
Gwen Stacy reading a book
|
| 746 |
+
Iron Man flying in the sky
|
| 747 |
+
The bund Shanghai, oil painting
|
| 748 |
+
Yoda playing guitar on the stage
|
| 749 |
+
A beautiful coastal beach in spring, waves lapping on sand by Hokusai, in the style of Ukiyo
|
| 750 |
+
A beautiful coastal beach in spring, waves lapping on sand by Vincent van Gogh
|
| 751 |
+
A boat sailing leisurely along the Seine River with the Eiffel Tower in background
|
| 752 |
+
A car moving slowly on an empty street, rainy evening
|
| 753 |
+
A cat eating food out of a bowl
|
| 754 |
+
A cat wearing sunglasses at a pool
|
| 755 |
+
A confused panda in calculus class
|
| 756 |
+
A cute fluffy panda eating Chinese food in a restaurant
|
| 757 |
+
A cute happy Corgi playing in park, sunset
|
| 758 |
+
A cute raccoon playing guitar in a boat on the ocean
|
| 759 |
+
A happy fuzzy panda playing guitar nearby a campfire, snow mountain in the background
|
| 760 |
+
A lightning striking atop of eiffel tower, dark clouds in the sky
|
| 761 |
+
A modern art museum, with colorful paintings
|
| 762 |
+
A panda cooking in the kitchen
|
| 763 |
+
A panda playing on a swing set
|
| 764 |
+
A polar bear is playing guitar
|
| 765 |
+
A raccoon dressed in suit playing the trumpet, stage background
|
| 766 |
+
A robot DJ is playing the turntable, in heavy raining futuristic tokyo rooftop cyberpunk night, sci-fi, fantasy
|
| 767 |
+
A shark swimming in clear Caribbean ocean
|
| 768 |
+
A super robot protecting city
|
| 769 |
+
A teddy bear washing the dishes
|
| 770 |
+
An epic tornado attacking above a glowing city at night, the tornado is made of smoke
|
| 771 |
+
An oil painting of a couple in formal evening wear going home get caught in a heavy downpour with umbrellas
|
| 772 |
+
Clown fish swimming through the coral reef
|
| 773 |
+
Hyper-realistic spaceship landing on Mars
|
| 774 |
+
The bund Shanghai, vibrant color
|
| 775 |
+
Vincent van Gogh is painting in the room
|
| 776 |
+
Yellow flowers swing in the wind
|
| 777 |
+
alley
|
| 778 |
+
amusement park
|
| 779 |
+
aquarium
|
| 780 |
+
arch
|
| 781 |
+
art gallery
|
| 782 |
+
bathroom
|
| 783 |
+
bakery shop
|
| 784 |
+
ballroom
|
| 785 |
+
bar
|
| 786 |
+
barn
|
| 787 |
+
basement
|
| 788 |
+
beach
|
| 789 |
+
bedroom
|
| 790 |
+
bridge
|
| 791 |
+
botanical garden
|
| 792 |
+
cafeteria
|
| 793 |
+
campsite
|
| 794 |
+
campus
|
| 795 |
+
carrousel
|
| 796 |
+
castle
|
| 797 |
+
cemetery
|
| 798 |
+
classroom
|
| 799 |
+
cliff
|
| 800 |
+
crosswalk
|
| 801 |
+
construction site
|
| 802 |
+
corridor
|
| 803 |
+
courtyard
|
| 804 |
+
desert
|
| 805 |
+
downtown
|
| 806 |
+
driveway
|
| 807 |
+
farm
|
| 808 |
+
food court
|
| 809 |
+
football field
|
| 810 |
+
forest road
|
| 811 |
+
fountain
|
| 812 |
+
gas station
|
| 813 |
+
glacier
|
| 814 |
+
golf course
|
| 815 |
+
indoor gymnasium
|
| 816 |
+
harbor
|
| 817 |
+
highway
|
| 818 |
+
hospital
|
| 819 |
+
house
|
| 820 |
+
iceberg
|
| 821 |
+
industrial area
|
| 822 |
+
jail cell
|
| 823 |
+
junkyard
|
| 824 |
+
kitchen
|
| 825 |
+
indoor library
|
| 826 |
+
lighthouse
|
| 827 |
+
laboratory
|
| 828 |
+
mansion
|
| 829 |
+
marsh
|
| 830 |
+
mountain
|
| 831 |
+
indoor movie theater
|
| 832 |
+
indoor museum
|
| 833 |
+
music studio
|
| 834 |
+
nursery
|
| 835 |
+
ocean
|
| 836 |
+
office
|
| 837 |
+
palace
|
| 838 |
+
parking lot
|
| 839 |
+
pharmacy
|
| 840 |
+
phone booth
|
| 841 |
+
raceway
|
| 842 |
+
restaurant
|
| 843 |
+
river
|
| 844 |
+
science museum
|
| 845 |
+
shower
|
| 846 |
+
ski slope
|
| 847 |
+
sky
|
| 848 |
+
skyscraper
|
| 849 |
+
baseball stadium
|
| 850 |
+
staircase
|
| 851 |
+
street
|
| 852 |
+
supermarket
|
| 853 |
+
indoor swimming pool
|
| 854 |
+
tower
|
| 855 |
+
outdoor track
|
| 856 |
+
train railway
|
| 857 |
+
train station platform
|
| 858 |
+
underwater coral reef
|
| 859 |
+
valley
|
| 860 |
+
volcano
|
| 861 |
+
waterfall
|
| 862 |
+
windmill
|
| 863 |
+
a bicycle on the left of a car, front view
|
| 864 |
+
a car on the right of a motorcycle, front view
|
| 865 |
+
a motorcycle on the left of a bus, front view
|
| 866 |
+
a bus on the right of a traffic light, front view
|
| 867 |
+
a traffic light on the left of a fire hydrant, front view
|
| 868 |
+
a fire hydrant on the right of a stop sign, front view
|
| 869 |
+
a stop sign on the left of a parking meter, front view
|
| 870 |
+
a parking meter on the right of a bench, front view
|
| 871 |
+
a bench on the left of a truck, front view
|
| 872 |
+
a truck on the right of a bicycle, front view
|
| 873 |
+
a bird on the left of a cat, front view
|
| 874 |
+
a cat on the right of a dog, front view
|
| 875 |
+
a dog on the left of a horse, front view
|
| 876 |
+
a horse on the right of a sheep, front view
|
| 877 |
+
a sheep on the left of a cow, front view
|
| 878 |
+
a cow on the right of an elephant, front view
|
| 879 |
+
an elephant on the left of a bear, front view
|
| 880 |
+
a bear on the right of a zebra, front view
|
| 881 |
+
a zebra on the left of a giraffe, front view
|
| 882 |
+
a giraffe on the right of a bird, front view
|
| 883 |
+
a bottle on the left of a wine glass, front view
|
| 884 |
+
a wine glass on the right of a cup, front view
|
| 885 |
+
a cup on the left of a fork, front view
|
| 886 |
+
a fork on the right of a knife, front view
|
| 887 |
+
a knife on the left of a spoon, front view
|
| 888 |
+
a spoon on the right of a bowl, front view
|
| 889 |
+
a bowl on the left of a bottle, front view
|
| 890 |
+
a potted plant on the left of a remote, front view
|
| 891 |
+
a remote on the right of a clock, front view
|
| 892 |
+
a clock on the left of a vase, front view
|
| 893 |
+
a vase on the right of scissors, front view
|
| 894 |
+
scissors on the left of a teddy bear, front view
|
| 895 |
+
a teddy bear on the right of a potted plant, front view
|
| 896 |
+
a frisbee on the left of a sports ball, front view
|
| 897 |
+
a sports ball on the right of a baseball bat, front view
|
| 898 |
+
a baseball bat on the left of a baseball glove, front view
|
| 899 |
+
a baseball glove on the right of a tennis racket, front view
|
| 900 |
+
a tennis racket on the left of a frisbee, front view
|
| 901 |
+
a toilet on the left of a hair drier, front view
|
| 902 |
+
a hair drier on the right of a toothbrush, front view
|
| 903 |
+
a toothbrush on the left of a sink, front view
|
| 904 |
+
a sink on the right of a toilet, front view
|
| 905 |
+
a chair on the left of a couch, front view
|
| 906 |
+
a couch on the right of a bed, front view
|
| 907 |
+
a bed on the left of a tv, front view
|
| 908 |
+
a tv on the right of a dining table, front view
|
| 909 |
+
a dining table on the left of a chair, front view
|
| 910 |
+
an airplane on the left of a train, front view
|
| 911 |
+
a train on the right of a boat, front view
|
| 912 |
+
a boat on the left of an airplane, front view
|
| 913 |
+
an oven on the top of a toaster, front view
|
| 914 |
+
an oven on the bottom of a toaster, front view
|
| 915 |
+
a toaster on the top of a microwave, front view
|
| 916 |
+
a toaster on the bottom of a microwave, front view
|
| 917 |
+
a microwave on the top of an oven, front view
|
| 918 |
+
a microwave on the bottom of an oven, front view
|
| 919 |
+
a banana on the top of an apple, front view
|
| 920 |
+
a banana on the bottom of an apple, front view
|
| 921 |
+
an apple on the top of a sandwich, front view
|
| 922 |
+
an apple on the bottom of a sandwich, front view
|
| 923 |
+
a sandwich on the top of an orange, front view
|
| 924 |
+
a sandwich on the bottom of an orange, front view
|
| 925 |
+
an orange on the top of a carrot, front view
|
| 926 |
+
an orange on the bottom of a carrot, front view
|
| 927 |
+
a carrot on the top of a hot dog, front view
|
| 928 |
+
a carrot on the bottom of a hot dog, front view
|
| 929 |
+
a hot dog on the top of a pizza, front view
|
| 930 |
+
a hot dog on the bottom of a pizza, front view
|
| 931 |
+
a pizza on the top of a donut, front view
|
| 932 |
+
a pizza on the bottom of a donut, front view
|
| 933 |
+
a donut on the top of broccoli, front view
|
| 934 |
+
a donut on the bottom of broccoli, front view
|
| 935 |
+
broccoli on the top of a banana, front view
|
| 936 |
+
broccoli on the bottom of a banana, front view
|
| 937 |
+
skis on the top of a snowboard, front view
|
| 938 |
+
skis on the bottom of a snowboard, front view
|
| 939 |
+
a snowboard on the top of a kite, front view
|
| 940 |
+
a snowboard on the bottom of a kite, front view
|
| 941 |
+
a kite on the top of a skateboard, front view
|
| 942 |
+
a kite on the bottom of a skateboard, front view
|
| 943 |
+
a skateboard on the top of a surfboard, front view
|
| 944 |
+
a skateboard on the bottom of a surfboard, front view
|
| 945 |
+
a surfboard on the top of skis, front view
|
| 946 |
+
a surfboard on the bottom of skis, front view
|
prompts/vbench/all_dimension_extended.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch>=2.4.0
|
| 2 |
+
torchvision>=0.19.0
|
| 3 |
+
opencv-python>=4.9.0.80
|
| 4 |
+
diffusers==0.31.0
|
| 5 |
+
transformers>=4.49.0
|
| 6 |
+
tokenizers>=0.20.3
|
| 7 |
+
accelerate>=1.1.1
|
| 8 |
+
tqdm
|
| 9 |
+
imageio
|
| 10 |
+
easydict
|
| 11 |
+
ftfy
|
| 12 |
+
dashscope
|
| 13 |
+
imageio-ffmpeg
|
| 14 |
+
numpy==1.24.4
|
| 15 |
+
wandb
|
| 16 |
+
omegaconf
|
| 17 |
+
einops
|
| 18 |
+
av==13.1.0
|
| 19 |
+
opencv-python
|
| 20 |
+
git+https://github.com/openai/CLIP.git
|
| 21 |
+
open_clip_torch
|
| 22 |
+
starlette
|
| 23 |
+
pycocotools
|
| 24 |
+
lmdb
|
| 25 |
+
matplotlib
|
| 26 |
+
sentencepiece
|
| 27 |
+
pydantic==2.10.6
|
| 28 |
+
scikit-image
|
| 29 |
+
huggingface_hub[cli]
|
| 30 |
+
dominate
|
| 31 |
+
nvidia-pyindex
|
| 32 |
+
nvidia-tensorrt
|
| 33 |
+
pycuda
|
| 34 |
+
onnx
|
| 35 |
+
onnxruntime
|
| 36 |
+
onnxscript
|
| 37 |
+
onnxconverter_common
|
| 38 |
+
flask
|
| 39 |
+
flask-socketio
|
| 40 |
+
torchao
|
| 41 |
+
hydra-core
|
| 42 |
+
git+https://github.com/facebookresearch/segment-anything.git
|
| 43 |
+
|
scripts/create_lmdb_14b_shards.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
python create_lmdb_14b_shards.py \
|
| 3 |
+
--data_path /mnt/localssd/wanx_14b_data \
|
| 4 |
+
--lmdb_path /mnt/localssd/wanx_14B_shift-3.0_cfg-5.0_lmdb
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
import numpy as np
|
| 9 |
+
import argparse
|
| 10 |
+
import torch
|
| 11 |
+
import lmdb
|
| 12 |
+
import glob
|
| 13 |
+
import os
|
| 14 |
+
|
| 15 |
+
from utils.lmdb import store_arrays_to_lmdb, process_data_dict
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def main():
|
| 19 |
+
"""
|
| 20 |
+
Aggregate all ode pairs inside a folder into a lmdb dataset.
|
| 21 |
+
Each pt file should contain a (key, value) pair representing a
|
| 22 |
+
video's ODE trajectories.
|
| 23 |
+
"""
|
| 24 |
+
parser = argparse.ArgumentParser()
|
| 25 |
+
parser.add_argument("--data_path", type=str, required=True, help="path to ode pairs")
|
| 26 |
+
parser.add_argument("--lmdb_path", type=str, required=True, help="path to lmdb")
|
| 27 |
+
parser.add_argument("--num_shards", type=int, default=16, help="num_shards")
|
| 28 |
+
|
| 29 |
+
args = parser.parse_args()
|
| 30 |
+
|
| 31 |
+
all_dirs = sorted(os.listdir(args.data_path))
|
| 32 |
+
|
| 33 |
+
# figure out the maximum map size needed
|
| 34 |
+
map_size = int(1e12) # adapt to your need, set to 1TB by default
|
| 35 |
+
os.makedirs(args.lmdb_path, exist_ok=True)
|
| 36 |
+
# 1) Open one LMDB env per shard
|
| 37 |
+
envs = []
|
| 38 |
+
num_shards = args.num_shards
|
| 39 |
+
for shard_id in range(num_shards):
|
| 40 |
+
print("shard_id ", shard_id)
|
| 41 |
+
path = os.path.join(args.lmdb_path, f"shard_{shard_id}")
|
| 42 |
+
env = lmdb.open(
|
| 43 |
+
path,
|
| 44 |
+
map_size=map_size,
|
| 45 |
+
subdir=True, # set to True if you want a directory per env
|
| 46 |
+
readonly=False,
|
| 47 |
+
metasync=True,
|
| 48 |
+
sync=True,
|
| 49 |
+
lock=True,
|
| 50 |
+
readahead=False,
|
| 51 |
+
meminit=False,
|
| 52 |
+
)
|
| 53 |
+
envs.append(env)
|
| 54 |
+
|
| 55 |
+
counters = [0] * num_shards
|
| 56 |
+
seen_prompts = set() # for deduplication
|
| 57 |
+
total_samples = 0
|
| 58 |
+
all_files = []
|
| 59 |
+
|
| 60 |
+
for part_dir in all_dirs:
|
| 61 |
+
all_files += sorted(glob.glob(os.path.join(args.data_path, part_dir, "*.pt")))
|
| 62 |
+
|
| 63 |
+
# 2) Prepare a write transaction for each shard
|
| 64 |
+
for idx, file in tqdm(enumerate(all_files)):
|
| 65 |
+
try:
|
| 66 |
+
data_dict = torch.load(file)
|
| 67 |
+
data_dict = process_data_dict(data_dict, seen_prompts)
|
| 68 |
+
except Exception as e:
|
| 69 |
+
print(f"Error processing {file}: {e}")
|
| 70 |
+
continue
|
| 71 |
+
|
| 72 |
+
if data_dict["latents"].shape != (1, 21, 16, 60, 104):
|
| 73 |
+
continue
|
| 74 |
+
|
| 75 |
+
shard_id = idx % num_shards
|
| 76 |
+
# write to lmdb file
|
| 77 |
+
store_arrays_to_lmdb(envs[shard_id], data_dict, start_index=counters[shard_id])
|
| 78 |
+
counters[shard_id] += len(data_dict["prompts"])
|
| 79 |
+
data_shape = data_dict["latents"].shape
|
| 80 |
+
|
| 81 |
+
total_samples += len(all_files)
|
| 82 |
+
|
| 83 |
+
print(len(seen_prompts))
|
| 84 |
+
|
| 85 |
+
# save each entry's shape to lmdb
|
| 86 |
+
for shard_id, env in enumerate(envs):
|
| 87 |
+
with env.begin(write=True) as txn:
|
| 88 |
+
for key, val in data_dict.items():
|
| 89 |
+
assert len(data_shape) == 5
|
| 90 |
+
array_shape = np.array(data_shape) # val.shape)
|
| 91 |
+
array_shape[0] = counters[shard_id]
|
| 92 |
+
shape_key = f"{key}_shape".encode()
|
| 93 |
+
print(shape_key, array_shape)
|
| 94 |
+
shape_str = " ".join(map(str, array_shape))
|
| 95 |
+
txn.put(shape_key, shape_str.encode())
|
| 96 |
+
|
| 97 |
+
print(
|
| 98 |
+
f"Finished writing {total_samples} examples into {num_shards} shards under {args.lmdb_path}"
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
if __name__ == "__main__":
|
| 103 |
+
main()
|
scripts/create_lmdb_iterative.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from tqdm import tqdm
|
| 2 |
+
import numpy as np
|
| 3 |
+
import argparse
|
| 4 |
+
import torch
|
| 5 |
+
import lmdb
|
| 6 |
+
import glob
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
from utils.lmdb import store_arrays_to_lmdb, process_data_dict
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def main():
|
| 13 |
+
"""
|
| 14 |
+
Aggregate all ode pairs inside a folder into a lmdb dataset.
|
| 15 |
+
Each pt file should contain a (key, value) pair representing a
|
| 16 |
+
video's ODE trajectories.
|
| 17 |
+
"""
|
| 18 |
+
parser = argparse.ArgumentParser()
|
| 19 |
+
parser.add_argument("--data_path", type=str, required=True, help="path to ode pairs")
|
| 20 |
+
parser.add_argument("--lmdb_path", type=str, required=True, help="path to lmdb")
|
| 21 |
+
|
| 22 |
+
args = parser.parse_args()
|
| 23 |
+
|
| 24 |
+
all_files = sorted(glob.glob(os.path.join(args.data_path, "*.pt")))
|
| 25 |
+
|
| 26 |
+
# figure out the maximum map size needed
|
| 27 |
+
total_array_size = 5000000000000 # adapt to your need, set to 5TB by default
|
| 28 |
+
|
| 29 |
+
env = lmdb.open(args.lmdb_path, map_size=total_array_size * 2)
|
| 30 |
+
|
| 31 |
+
counter = 0
|
| 32 |
+
|
| 33 |
+
seen_prompts = set() # for deduplication
|
| 34 |
+
|
| 35 |
+
for index, file in tqdm(enumerate(all_files)):
|
| 36 |
+
# read from disk
|
| 37 |
+
data_dict = torch.load(file)
|
| 38 |
+
|
| 39 |
+
data_dict = process_data_dict(data_dict, seen_prompts)
|
| 40 |
+
|
| 41 |
+
# write to lmdb file
|
| 42 |
+
store_arrays_to_lmdb(env, data_dict, start_index=counter)
|
| 43 |
+
counter += len(data_dict["prompts"])
|
| 44 |
+
|
| 45 |
+
# save each entry's shape to lmdb
|
| 46 |
+
with env.begin(write=True) as txn:
|
| 47 |
+
for key, val in data_dict.items():
|
| 48 |
+
print(key, val)
|
| 49 |
+
array_shape = np.array(val.shape)
|
| 50 |
+
array_shape[0] = counter
|
| 51 |
+
|
| 52 |
+
shape_key = f"{key}_shape".encode()
|
| 53 |
+
shape_str = " ".join(map(str, array_shape))
|
| 54 |
+
txn.put(shape_key, shape_str.encode())
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
if __name__ == "__main__":
|
| 58 |
+
main()
|
scripts/generate_ode_pairs.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from utils.distributed import launch_distributed_job
|
| 2 |
+
from utils.scheduler import FlowMatchScheduler
|
| 3 |
+
from utils.wan_wrapper import WanDiffusionWrapper, WanTextEncoder
|
| 4 |
+
from utils.dataset import TextDataset
|
| 5 |
+
import torch.distributed as dist
|
| 6 |
+
from tqdm import tqdm
|
| 7 |
+
import argparse
|
| 8 |
+
import torch
|
| 9 |
+
import math
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def init_model(
|
| 14 |
+
device,
|
| 15 |
+
):
|
| 16 |
+
model = WanDiffusionWrapper().to(device).to(torch.float32)
|
| 17 |
+
encoder = WanTextEncoder().to(device).to(torch.float32)
|
| 18 |
+
model.model.requires_grad_(False)
|
| 19 |
+
|
| 20 |
+
scheduler = FlowMatchScheduler(shift=8.0, sigma_min=0.0, extra_one_step=True)
|
| 21 |
+
scheduler.set_timesteps(num_inference_steps=48, denoising_strength=1.0)
|
| 22 |
+
scheduler.sigmas = scheduler.sigmas.to(device)
|
| 23 |
+
|
| 24 |
+
sample_neg_prompt = "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
|
| 25 |
+
|
| 26 |
+
unconditional_dict = encoder(text_prompts=[sample_neg_prompt])
|
| 27 |
+
|
| 28 |
+
return model, encoder, scheduler, unconditional_dict
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def main():
|
| 32 |
+
parser = argparse.ArgumentParser()
|
| 33 |
+
parser.add_argument("--local_rank", type=int, default=-1)
|
| 34 |
+
parser.add_argument("--output_folder", type=str)
|
| 35 |
+
parser.add_argument("--caption_path", type=str)
|
| 36 |
+
parser.add_argument("--guidance_scale", type=float, default=6.0)
|
| 37 |
+
|
| 38 |
+
args = parser.parse_args()
|
| 39 |
+
|
| 40 |
+
# launch_distributed_job()
|
| 41 |
+
launch_distributed_job()
|
| 42 |
+
|
| 43 |
+
device = torch.cuda.current_device()
|
| 44 |
+
|
| 45 |
+
torch.set_grad_enabled(False)
|
| 46 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 47 |
+
torch.backends.cudnn.allow_tf32 = True
|
| 48 |
+
|
| 49 |
+
model, encoder, scheduler, unconditional_dict = init_model(device=device)
|
| 50 |
+
|
| 51 |
+
dataset = TextDataset(args.caption_path)
|
| 52 |
+
|
| 53 |
+
# if global_rank == 0:
|
| 54 |
+
os.makedirs(args.output_folder, exist_ok=True)
|
| 55 |
+
|
| 56 |
+
for index in tqdm(
|
| 57 |
+
range(int(math.ceil(len(dataset) / dist.get_world_size()))),
|
| 58 |
+
disable=dist.get_rank() != 0,
|
| 59 |
+
):
|
| 60 |
+
prompt_index = index * dist.get_world_size() + dist.get_rank()
|
| 61 |
+
if prompt_index >= len(dataset):
|
| 62 |
+
continue
|
| 63 |
+
prompt = dataset[prompt_index]
|
| 64 |
+
|
| 65 |
+
conditional_dict = encoder(text_prompts=prompt)
|
| 66 |
+
|
| 67 |
+
latents = torch.randn([1, 21, 16, 60, 104], dtype=torch.float32, device=device)
|
| 68 |
+
|
| 69 |
+
noisy_input = []
|
| 70 |
+
|
| 71 |
+
for progress_id, t in enumerate(tqdm(scheduler.timesteps)):
|
| 72 |
+
timestep = t * torch.ones([1, 21], device=device, dtype=torch.float32)
|
| 73 |
+
|
| 74 |
+
noisy_input.append(latents)
|
| 75 |
+
|
| 76 |
+
_, x0_pred_cond = model(latents, conditional_dict, timestep)
|
| 77 |
+
|
| 78 |
+
_, x0_pred_uncond = model(latents, unconditional_dict, timestep)
|
| 79 |
+
|
| 80 |
+
x0_pred = x0_pred_uncond + args.guidance_scale * (x0_pred_cond - x0_pred_uncond)
|
| 81 |
+
|
| 82 |
+
flow_pred = model._convert_x0_to_flow_pred(
|
| 83 |
+
scheduler=scheduler,
|
| 84 |
+
x0_pred=x0_pred.flatten(0, 1),
|
| 85 |
+
xt=latents.flatten(0, 1),
|
| 86 |
+
timestep=timestep.flatten(0, 1),
|
| 87 |
+
).unflatten(0, x0_pred.shape[:2])
|
| 88 |
+
|
| 89 |
+
latents = scheduler.step(
|
| 90 |
+
flow_pred.flatten(0, 1),
|
| 91 |
+
scheduler.timesteps[progress_id]
|
| 92 |
+
* torch.ones([1, 21], device=device, dtype=torch.long).flatten(0, 1),
|
| 93 |
+
latents.flatten(0, 1),
|
| 94 |
+
).unflatten(dim=0, sizes=flow_pred.shape[:2])
|
| 95 |
+
|
| 96 |
+
noisy_input.append(latents)
|
| 97 |
+
|
| 98 |
+
noisy_inputs = torch.stack(noisy_input, dim=1)
|
| 99 |
+
|
| 100 |
+
noisy_inputs = noisy_inputs[:, [0, 12, 24, 36, -1]]
|
| 101 |
+
|
| 102 |
+
stored_data = noisy_inputs
|
| 103 |
+
|
| 104 |
+
torch.save(
|
| 105 |
+
{prompt: stored_data.cpu().detach()},
|
| 106 |
+
os.path.join(args.output_folder, f"{prompt_index:05d}.pt"),
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
dist.barrier()
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
if __name__ == "__main__":
|
| 113 |
+
main()
|
setup.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from setuptools import setup, find_packages
|
| 2 |
+
|
| 3 |
+
setup(
|
| 4 |
+
name="self_forcing",
|
| 5 |
+
version="0.0.1",
|
| 6 |
+
packages=find_packages(),
|
| 7 |
+
)
|
stream_drag_inference_wrapper.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
from omegaconf import DictConfig
|
| 4 |
+
from optimize_utils import MultiTrajectory
|
| 5 |
+
from stream_inference_wrapper import StreamInferenceWrapper
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def _extract_block_trajectories(
|
| 9 |
+
multi_traj: MultiTrajectory,
|
| 10 |
+
) -> tuple[
|
| 11 |
+
list[list[dict[str, bool | list[torch.Tensor]]]],
|
| 12 |
+
list[np.ndarray],
|
| 13 |
+
np.ndarray | None,
|
| 14 |
+
]:
|
| 15 |
+
"""Extract block_trajectories from a MultiTrajectory in the format expected by begin_optimize.
|
| 16 |
+
|
| 17 |
+
Returns:
|
| 18 |
+
block_trajectories: block_num x N x trajectory dict
|
| 19 |
+
Each trajectory dict has keys 'is_rotation', 'deltas', 'start_point',
|
| 20 |
+
and optionally 'rotation_center'.
|
| 21 |
+
masks: list of N masks corresponding to each trajectory
|
| 22 |
+
movable_mask: the movable area mask for the whole image
|
| 23 |
+
"""
|
| 24 |
+
if multi_traj.trajectories is None or len(multi_traj.trajectories) == 0:
|
| 25 |
+
return [], [], None
|
| 26 |
+
|
| 27 |
+
movable_mask = multi_traj.movable_mask
|
| 28 |
+
|
| 29 |
+
# Collect per-trajectory masks
|
| 30 |
+
masks = [traj.mask for traj in multi_traj.trajectories]
|
| 31 |
+
|
| 32 |
+
# Find the maximum number of blocks across all trajectories
|
| 33 |
+
max_blocks = (
|
| 34 |
+
max(
|
| 35 |
+
len(traj.block_trajectories)
|
| 36 |
+
for traj in multi_traj.trajectories
|
| 37 |
+
if traj.block_trajectories
|
| 38 |
+
)
|
| 39 |
+
if any(traj.block_trajectories for traj in multi_traj.trajectories)
|
| 40 |
+
else 0
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
if max_blocks == 0:
|
| 44 |
+
return [], masks, movable_mask
|
| 45 |
+
|
| 46 |
+
block_trajectories = []
|
| 47 |
+
for block_idx in range(max_blocks):
|
| 48 |
+
block = []
|
| 49 |
+
for traj in multi_traj.trajectories:
|
| 50 |
+
if traj.block_trajectories and block_idx < len(traj.block_trajectories):
|
| 51 |
+
block.append(traj.block_trajectories[block_idx])
|
| 52 |
+
else:
|
| 53 |
+
# Provide an empty placeholder
|
| 54 |
+
block.append(
|
| 55 |
+
{
|
| 56 |
+
"is_rotation": False,
|
| 57 |
+
"deltas": [],
|
| 58 |
+
"start_point": (0, 0),
|
| 59 |
+
}
|
| 60 |
+
)
|
| 61 |
+
block_trajectories.append(block)
|
| 62 |
+
|
| 63 |
+
# Assert: the N of each block in block_trajectories should equal the length of masks
|
| 64 |
+
for block_idx, block in enumerate(block_trajectories):
|
| 65 |
+
assert len(block) == len(masks), (
|
| 66 |
+
f"Block {block_idx} has {len(block)} trajectories, " f"but there are {len(masks)} masks"
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
assert ((len(block_trajectories) == 0) and (movable_mask is None)) or (
|
| 70 |
+
(len(block_trajectories) > 0) and (movable_mask is not None)
|
| 71 |
+
), "block_trajectories and movable_mask must both be present or both be absent"
|
| 72 |
+
|
| 73 |
+
return block_trajectories, masks, movable_mask
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class StreamDragInferenceWrapper(StreamInferenceWrapper):
|
| 77 |
+
def __init__(
|
| 78 |
+
self,
|
| 79 |
+
stream_model_config: DictConfig,
|
| 80 |
+
checkpoint_path: str,
|
| 81 |
+
total_generate_block_number: int,
|
| 82 |
+
use_ema: bool = True,
|
| 83 |
+
seed: int = 0,
|
| 84 |
+
):
|
| 85 |
+
super().__init__(
|
| 86 |
+
stream_model_config=stream_model_config,
|
| 87 |
+
checkpoint_path=checkpoint_path,
|
| 88 |
+
total_generate_block_number=total_generate_block_number,
|
| 89 |
+
use_ema=use_ema,
|
| 90 |
+
seed=seed,
|
| 91 |
+
)
|
| 92 |
+
self.previous_record_feature_list = None
|
| 93 |
+
|
| 94 |
+
def inference(
|
| 95 |
+
self,
|
| 96 |
+
start_block_index: int,
|
| 97 |
+
end_block_index: int,
|
| 98 |
+
prompt: str,
|
| 99 |
+
# below are for drag optimization
|
| 100 |
+
multiple_trajectory: MultiTrajectory = None,
|
| 101 |
+
):
|
| 102 |
+
assert start_block_index >= 0
|
| 103 |
+
assert end_block_index > start_block_index
|
| 104 |
+
print(f"""
|
| 105 |
+
{self.__class__.__name__}.inference():
|
| 106 |
+
{start_block_index = } | {end_block_index = }
|
| 107 |
+
""")
|
| 108 |
+
sampled_noise = self.get_sampled_noise(start_block_index, end_block_index)
|
| 109 |
+
prompts = [prompt]
|
| 110 |
+
|
| 111 |
+
# Extract block_trajectories, masks, and movable_mask from multiple_trajectory
|
| 112 |
+
drag_optimize_target_latent_index = -1
|
| 113 |
+
if multiple_trajectory is not None:
|
| 114 |
+
block_trajectories, masks, movable_mask = _extract_block_trajectories(
|
| 115 |
+
multiple_trajectory
|
| 116 |
+
)
|
| 117 |
+
assert multiple_trajectory.drag_or_animation_select in [
|
| 118 |
+
"Drag",
|
| 119 |
+
"Animation",
|
| 120 |
+
]
|
| 121 |
+
if multiple_trajectory.drag_or_animation_select == "Drag":
|
| 122 |
+
drag_optimize_target_latent_index = 2
|
| 123 |
+
else:
|
| 124 |
+
block_trajectories, masks, movable_mask = [], [], None
|
| 125 |
+
|
| 126 |
+
if len(block_trajectories) > 0:
|
| 127 |
+
is_drag_optimize = True
|
| 128 |
+
else:
|
| 129 |
+
is_drag_optimize = False
|
| 130 |
+
|
| 131 |
+
initial_latents = self.get_initial_latents(
|
| 132 |
+
start_block_index,
|
| 133 |
+
)
|
| 134 |
+
if initial_latents is not None:
|
| 135 |
+
print(f"{initial_latents.shape = }")
|
| 136 |
+
|
| 137 |
+
print(f"{block_trajectories = }")
|
| 138 |
+
print(f"{len(masks) = }")
|
| 139 |
+
latents_result = self.pipeline.inference(
|
| 140 |
+
noise=sampled_noise,
|
| 141 |
+
text_prompts=prompts,
|
| 142 |
+
return_latents=True,
|
| 143 |
+
initial_latent=initial_latents,
|
| 144 |
+
do_not_decode_video=True,
|
| 145 |
+
do_not_recompute_initial_latents=True,
|
| 146 |
+
# below are for drag optimization
|
| 147 |
+
model_config=self.stream_model_config,
|
| 148 |
+
previous_record_feature_list=self.previous_record_feature_list,
|
| 149 |
+
is_drag_optimize=is_drag_optimize,
|
| 150 |
+
block_trajectories=block_trajectories,
|
| 151 |
+
masks=masks,
|
| 152 |
+
movable_mask=movable_mask,
|
| 153 |
+
drag_optimize_target_latent_index=drag_optimize_target_latent_index,
|
| 154 |
+
)
|
| 155 |
+
if self.stream_model_config.drag_optim_config.record_feature_block_indexes:
|
| 156 |
+
latents, record_attention_values_list = latents_result
|
| 157 |
+
else:
|
| 158 |
+
latents = latents_result
|
| 159 |
+
record_attention_values_list = None
|
| 160 |
+
if self.recorded_latents is None:
|
| 161 |
+
self.recorded_latents = latents
|
| 162 |
+
else:
|
| 163 |
+
self.recorded_latents = torch.concat(
|
| 164 |
+
[
|
| 165 |
+
self.recorded_latents[:, :0],
|
| 166 |
+
latents,
|
| 167 |
+
],
|
| 168 |
+
dim=1,
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
if record_attention_values_list is not None:
|
| 172 |
+
|
| 173 |
+
def dict_first_value(d: dict):
|
| 174 |
+
return next(iter(d.values()))
|
| 175 |
+
|
| 176 |
+
print(f"{record_attention_values_list.keys() = }") # denoising timesteps
|
| 177 |
+
print(
|
| 178 |
+
f"{dict_first_value(record_attention_values_list).keys() = }"
|
| 179 |
+
) # attention block layers
|
| 180 |
+
print(
|
| 181 |
+
f"{dict_first_value(dict_first_value(record_attention_values_list)).keys() = }"
|
| 182 |
+
) # attention types name
|
| 183 |
+
print(
|
| 184 |
+
f"{dict_first_value(dict_first_value(dict_first_value(record_attention_values_list))).shape = }"
|
| 185 |
+
) # [1, 3, 30, 52, 1536]
|
| 186 |
+
else:
|
| 187 |
+
print(f"{record_attention_values_list = }")
|
| 188 |
+
self.previous_record_feature_list = record_attention_values_list
|
| 189 |
+
|
| 190 |
+
self.decode_and_update_video(start_block_index, end_block_index)
|
| 191 |
+
|
| 192 |
+
return (
|
| 193 |
+
self.video,
|
| 194 |
+
self.video[self.block_to_video_index(start_block_index) :],
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
def reset(
|
| 198 |
+
self,
|
| 199 |
+
):
|
| 200 |
+
super().reset()
|
| 201 |
+
self.previous_record_feature_list = None
|
stream_inference.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = "1"
|
| 4 |
+
|
| 5 |
+
from tqdm import tqdm
|
| 6 |
+
from torchvision.io import write_video
|
| 7 |
+
from torch.utils.data import DataLoader, SequentialSampler
|
| 8 |
+
|
| 9 |
+
from stream_inference_wrapper import StreamInferenceWrapper
|
| 10 |
+
|
| 11 |
+
# from stream_drag_inference_wrapper import StreamDragInferenceWrapper
|
| 12 |
+
from utils.dataset import TextDataset
|
| 13 |
+
from utils.misc import set_seed
|
| 14 |
+
from hydra import initialize, compose
|
| 15 |
+
from hydra.core.global_hydra import GlobalHydra
|
| 16 |
+
|
| 17 |
+
from demo_utils.memory import gpu, get_cuda_free_memory_gb
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def main():
|
| 21 |
+
|
| 22 |
+
output_block_number = 27
|
| 23 |
+
|
| 24 |
+
config_dir = "configs"
|
| 25 |
+
stream_config_name = "self_forcing_dmd_vsink_stream"
|
| 26 |
+
# stream_config_name = "self_forcing_dmd_vsink_stream_drag"
|
| 27 |
+
|
| 28 |
+
data_path = "prompts/MovieGenVideoBench_extended.txt"
|
| 29 |
+
|
| 30 |
+
seed = 42
|
| 31 |
+
set_seed(seed)
|
| 32 |
+
|
| 33 |
+
output_folder = "outputs-stream"
|
| 34 |
+
output_folder = f"{output_folder}/blk{output_block_number}-{stream_config_name}-seed{seed}"
|
| 35 |
+
|
| 36 |
+
print(f"Free VRAM {get_cuda_free_memory_gb(gpu)} GB")
|
| 37 |
+
# low_memory = get_cuda_free_memory_gb(gpu) < 40
|
| 38 |
+
|
| 39 |
+
# Create dataset
|
| 40 |
+
dataset = TextDataset(prompt_path=data_path)
|
| 41 |
+
num_prompts = len(dataset)
|
| 42 |
+
print(f"Number of prompts: {num_prompts}")
|
| 43 |
+
|
| 44 |
+
sampler = SequentialSampler(dataset)
|
| 45 |
+
dataloader = DataLoader(dataset, batch_size=1, sampler=sampler, num_workers=0, drop_last=False)
|
| 46 |
+
|
| 47 |
+
os.makedirs(output_folder, exist_ok=True)
|
| 48 |
+
|
| 49 |
+
if GlobalHydra.instance().is_initialized():
|
| 50 |
+
GlobalHydra.instance().clear()
|
| 51 |
+
|
| 52 |
+
with initialize(version_base=None, config_path=config_dir):
|
| 53 |
+
stream_config = compose(config_name=stream_config_name)
|
| 54 |
+
print(f"{stream_config = }")
|
| 55 |
+
|
| 56 |
+
stream_inference = StreamInferenceWrapper(
|
| 57 |
+
stream_model_config=stream_config,
|
| 58 |
+
checkpoint_path="./checkpoints/self_forcing_dmd.pt",
|
| 59 |
+
total_generate_block_number=output_block_number,
|
| 60 |
+
use_ema=True,
|
| 61 |
+
seed=seed,
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
for i, batch_data in tqdm(enumerate(dataloader)):
|
| 65 |
+
idx = batch_data["idx"].item()
|
| 66 |
+
print(f"{idx = }")
|
| 67 |
+
|
| 68 |
+
# For DataLoader batch_size=1, the batch_data is already a single item, but in a batch container
|
| 69 |
+
# Unpack the batch data for convenience
|
| 70 |
+
if isinstance(batch_data, dict):
|
| 71 |
+
batch = batch_data
|
| 72 |
+
elif isinstance(batch_data, list):
|
| 73 |
+
batch = batch_data[0] # First (and only) item in the batch
|
| 74 |
+
|
| 75 |
+
# For text-to-video, batch is just the text prompt
|
| 76 |
+
prompt = batch["prompts"][0]
|
| 77 |
+
print(f"{prompt = }")
|
| 78 |
+
extended_prompt = batch["extended_prompts"][0] if "extended_prompts" in batch else None
|
| 79 |
+
print(f"{extended_prompt = }")
|
| 80 |
+
|
| 81 |
+
set_seed(seed)
|
| 82 |
+
stream_inference.reset()
|
| 83 |
+
|
| 84 |
+
current_block_index = 0
|
| 85 |
+
block_step = 3
|
| 86 |
+
while current_block_index < output_block_number:
|
| 87 |
+
end_block_index = current_block_index + block_step
|
| 88 |
+
all_video, current_video = stream_inference.inference(
|
| 89 |
+
start_block_index=current_block_index,
|
| 90 |
+
end_block_index=end_block_index,
|
| 91 |
+
prompt=prompt,
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
# Save the video if the current prompt is not a dummy prompt
|
| 95 |
+
if idx < num_prompts:
|
| 96 |
+
current_video_output_path = os.path.join(
|
| 97 |
+
output_folder,
|
| 98 |
+
f"{idx:04d}-{prompt[:50].replace(' ', '_')}-{current_block_index:02d}-{end_block_index:02d}.mp4",
|
| 99 |
+
)
|
| 100 |
+
write_video(current_video_output_path, current_video, fps=16)
|
| 101 |
+
all_video_output_path = os.path.join(
|
| 102 |
+
output_folder,
|
| 103 |
+
f"{idx:04d}-{prompt[:50].replace(' ', '_')}-{0:02d}-{end_block_index:02d}.mp4",
|
| 104 |
+
)
|
| 105 |
+
write_video(all_video_output_path, all_video, fps=16)
|
| 106 |
+
|
| 107 |
+
current_block_index = end_block_index
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
if __name__ == "__main__":
|
| 111 |
+
main()
|