Spaces:
Running on Zero
Running on Zero
CMD chunk1-short image-to-video demo
Browse files- .gitattributes +1 -0
- LICENSE +29 -0
- LICENSES/Apache-2.0.txt +201 -0
- LICENSES/LicenseRef-NvidiaOneWayNoncommercial.txt +29 -0
- NOTICE +63 -0
- README.md +71 -6
- app.py +408 -0
- configs/cosmos/default_config.yaml +19 -0
- configs/cosmos/t24_l21_student_context_distillation.yaml +89 -0
- cosmos/__init__.py +10 -0
- cosmos/camera_conditioning.py +233 -0
- cosmos/causal_model.py +960 -0
- cosmos/kv_cache.py +169 -0
- cosmos/minimal_v1_lvg_dit.py +67 -0
- cosmos/minimal_v4_dit.py +2054 -0
- cosmos/model_weights_stats.py +64 -0
- cosmos/runtime.py +244 -0
- cosmos/selective_activation_checkpoint.py +73 -0
- cosmos/wrapper.py +680 -0
- examples/image.png +3 -0
- examples/prompt.txt +1 -0
- pipeline/__init__.py +4 -0
- pipeline/causal_inference.py +483 -0
- requirements.txt +12 -0
- utils/__init__.py +4 -0
- utils/misc.py +54 -0
- utils/scheduler.py +209 -0
- wan/__init__.py +4 -0
- wan/modules/__init__.py +4 -0
- wan/modules/vae.py +697 -0
- wan/utils/__init__.py +4 -0
- wan/utils/fm_solvers_unipc.py +814 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
examples/image.png filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## NVIDIA OneWay Noncommercial License
|
| 2 |
+
|
| 3 |
+
1. Definitions
|
| 4 |
+
|
| 5 |
+
“Licensor” means any person or entity that distributes its Work.
|
| 6 |
+
|
| 7 |
+
“Work” means (a) the original work of authorship made available under this license, which may include software, documentation, or other files, and (b) any additions to or derivative works thereof that are made available under this license.
|
| 8 |
+
The terms “reproduce,” “reproduction,” “derivative works,” and “distribution” have the meaning as provided under U.S. copyright law; provided, however, that for the purposes of this license, derivative works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work.
|
| 9 |
+
Works are “made available” under this license by including in or with the Work either (a) a copyright notice referencing the applicability of this license to the Work, or (b) a copy of this license.
|
| 10 |
+
|
| 11 |
+
2. License Grant
|
| 12 |
+
2.1 Copyright Grant. Subject to the terms and conditions of this license, each Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free, copyright license to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense and distribute its Work and any resulting derivative works in any form.
|
| 13 |
+
|
| 14 |
+
3. Limitations
|
| 15 |
+
3.1 Redistribution. You may reproduce or distribute the Work only if (a) you do so under this license, (b) you include a complete copy of this license with your distribution, and (c) you retain without modification any copyright, patent, trademark, or attribution notices that are present in the Work.
|
| 16 |
+
3.2 Derivative Works. You may specify that additional or different terms apply to the use, reproduction, and distribution of your derivative works of the Work (“Your Terms”) only if (a) Your Terms provide that the use limitation in Section 3.3 applies to your derivative works, and (b) you identify the specific derivative works that are subject to Your Terms. Notwithstanding Your Terms, this license (including the redistribution requirements in Section 3.1) will continue to apply to the Work itself.
|
| 17 |
+
3.3 Use Limitation. The Work and any derivative works thereof only may be used or intended for use non-commercially. As used herein, “non-commercially” means for non-commercial research or educational purposes only.
|
| 18 |
+
3.4 Patent Claims. If you bring or threaten to bring a patent claim against any Licensor (including any claim, cross-claim or counterclaim in a lawsuit) to enforce any patents that you allege are infringed by any Work, then your rights under this license from such Licensor (including the grant in Section 2.1) will terminate immediately.
|
| 19 |
+
3.5 Trademarks. This license does not grant any rights to use any Licensor’s or its affiliates’ names, logos, or trademarks, except as necessary to reproduce the notices described in this license.
|
| 20 |
+
|
| 21 |
+
3.6 Termination. If you violate any term of this license, then your rights under this license (including the grant in Section 2.1) will terminate immediately.
|
| 22 |
+
|
| 23 |
+
4. Disclaimer of Warranty.
|
| 24 |
+
THE WORK IS PROVIDED “AS IS” WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF
|
| 25 |
+
|
| 26 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS LICENSE.
|
| 27 |
+
|
| 28 |
+
5. Limitation of Liability.
|
| 29 |
+
EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
LICENSES/Apache-2.0.txt
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.
|
LICENSES/LicenseRef-NvidiaOneWayNoncommercial.txt
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
NVIDIA OneWay Noncommercial License
|
| 2 |
+
|
| 3 |
+
1. Definitions
|
| 4 |
+
|
| 5 |
+
“Licensor” means any person or entity that distributes its Work.
|
| 6 |
+
|
| 7 |
+
“Work” means (a) the original work of authorship made available under this license, which may include software, documentation, or other files, and (b) any additions to or derivative works thereof that are made available under this license.
|
| 8 |
+
The terms “reproduce,” “reproduction,” “derivative works,” and “distribution” have the meaning as provided under U.S. copyright law; provided, however, that for the purposes of this license, derivative works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work.
|
| 9 |
+
Works are “made available” under this license by including in or with the Work either (a) a copyright notice referencing the applicability of this license to the Work, or (b) a copy of this license.
|
| 10 |
+
|
| 11 |
+
2. License Grant
|
| 12 |
+
2.1 Copyright Grant. Subject to the terms and conditions of this license, each Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free, copyright license to use, reproduce, prepare derivative works of, publicly display, publicly perform, sublicense and distribute its Work and any resulting derivative works in any form.
|
| 13 |
+
|
| 14 |
+
3. Limitations
|
| 15 |
+
3.1 Redistribution. You may reproduce or distribute the Work only if (a) you do so under this license, (b) you include a complete copy of this license with your distribution, and (c) you retain without modification any copyright, patent, trademark, or attribution notices that are present in the Work.
|
| 16 |
+
3.2 Derivative Works. You may specify that additional or different terms apply to the use, reproduction, and distribution of your derivative works of the Work (“Your Terms”) only if (a) Your Terms provide that the use limitation in Section 3.3 applies to your derivative works, and (b) you identify the specific derivative works that are subject to Your Terms. Notwithstanding Your Terms, this license (including the redistribution requirements in Section 3.1) will continue to apply to the Work itself.
|
| 17 |
+
3.3 Use Limitation. The Work and any derivative works thereof only may be used or intended for use non-commercially. As used herein, “non-commercially” means for non-commercial research or educational purposes only.
|
| 18 |
+
3.4 Patent Claims. If you bring or threaten to bring a patent claim against any Licensor (including any claim, cross-claim or counterclaim in a lawsuit) to enforce any patents that you allege are infringed by any Work, then your rights under this license from such Licensor (including the grant in Section 2.1) will terminate immediately.
|
| 19 |
+
3.5 Trademarks. This license does not grant any rights to use any Licensor’s or its affiliates’ names, logos, or trademarks, except as necessary to reproduce the notices described in this license.
|
| 20 |
+
|
| 21 |
+
3.6 Termination. If you violate any term of this license, then your rights under this license (including the grant in Section 2.1) will terminate immediately.
|
| 22 |
+
|
| 23 |
+
4. Disclaimer of Warranty.
|
| 24 |
+
THE WORK IS PROVIDED “AS IS” WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF
|
| 25 |
+
|
| 26 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS LICENSE.
|
| 27 |
+
|
| 28 |
+
5. Limitation of Liability.
|
| 29 |
+
EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
NOTICE
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Self-Forcing-Cosmos25
|
| 2 |
+
Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
|
| 4 |
+
This distribution contains code derived from third-party open-source projects.
|
| 5 |
+
The original portions identified below remain subject to their original
|
| 6 |
+
licenses. NVIDIA modifications are separately identified in modified source
|
| 7 |
+
files and are subject to the NVIDIA OneWay Noncommercial License in LICENSE.
|
| 8 |
+
This NOTICE is informational and does not alter any applicable license.
|
| 9 |
+
|
| 10 |
+
A copy of the Apache License, Version 2.0 is provided at
|
| 11 |
+
LICENSES/Apache-2.0.txt.
|
| 12 |
+
A copy of the NVIDIA OneWay Noncommercial License used by the
|
| 13 |
+
LicenseRef-NvidiaOneWayNoncommercial SPDX identifier is provided at
|
| 14 |
+
LICENSES/LicenseRef-NvidiaOneWayNoncommercial.txt.
|
| 15 |
+
|
| 16 |
+
Wan2.1
|
| 17 |
+
-------
|
| 18 |
+
Source: https://github.com/Wan-Video/Wan2.1
|
| 19 |
+
Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
|
| 20 |
+
License: Apache License 2.0
|
| 21 |
+
Paths: wan/ (except wan/modules/causal_model.py, listed under Self-Forcing)
|
| 22 |
+
|
| 23 |
+
The Wan tree also retains its embedded provenance notices for code adapted
|
| 24 |
+
from projects such as Hugging Face Diffusers, CLIP, OpenCLIP, Transformers,
|
| 25 |
+
and qwen-vl-utils.
|
| 26 |
+
|
| 27 |
+
Self-Forcing
|
| 28 |
+
------------
|
| 29 |
+
Source: https://github.com/guandeh17/Self-Forcing
|
| 30 |
+
Copyright (c) 2025 The Self-Forcing Authors. All rights reserved.
|
| 31 |
+
License: Apache License 2.0
|
| 32 |
+
Paths containing original or modified Self-Forcing code:
|
| 33 |
+
inference.py
|
| 34 |
+
setup.py
|
| 35 |
+
train.py
|
| 36 |
+
model/
|
| 37 |
+
pipeline/
|
| 38 |
+
trainer/
|
| 39 |
+
wan/modules/causal_model.py
|
| 40 |
+
scripts/create_lmdb_14b_shards.py
|
| 41 |
+
scripts/create_lmdb_iterative.py
|
| 42 |
+
scripts/generate_ode_pairs.py
|
| 43 |
+
utils/dataset.py
|
| 44 |
+
utils/distributed.py
|
| 45 |
+
utils/lmdb.py
|
| 46 |
+
utils/loss.py
|
| 47 |
+
utils/misc.py
|
| 48 |
+
utils/scheduler.py
|
| 49 |
+
utils/wan_wrapper.py
|
| 50 |
+
|
| 51 |
+
NVIDIA Cosmos-Predict2.5
|
| 52 |
+
------------------------
|
| 53 |
+
Source: https://github.com/nvidia-cosmos/cosmos-predict2.5
|
| 54 |
+
Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 55 |
+
License: Apache License 2.0
|
| 56 |
+
Paths containing original or modified Cosmos-Predict2.5 code:
|
| 57 |
+
cosmos/causal_model.py
|
| 58 |
+
cosmos/kv_cache.py
|
| 59 |
+
cosmos/minimal_v1_lvg_dit.py
|
| 60 |
+
cosmos/minimal_v4_dit.py
|
| 61 |
+
cosmos/model_weights_stats.py
|
| 62 |
+
cosmos/runtime.py
|
| 63 |
+
cosmos/selective_activation_checkpoint.py
|
README.md
CHANGED
|
@@ -1,13 +1,78 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.24.0
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: CMD Image-to-Video
|
| 3 |
+
emoji: 🎬
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: red
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.24.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: other
|
| 11 |
+
short_description: Autoregressive 4-step image-to-video with NVIDIA CMD
|
| 12 |
+
python_version: "3.10"
|
| 13 |
+
startup_duration_timeout: 1h
|
| 14 |
+
models:
|
| 15 |
+
- nvidia/cmd
|
| 16 |
+
tags:
|
| 17 |
+
- image-to-video
|
| 18 |
+
- video-generation
|
| 19 |
+
- autoregressive
|
| 20 |
+
- cosmos
|
| 21 |
---
|
| 22 |
|
| 23 |
+
# CMD — Context-Matched Distillation (image → video)
|
| 24 |
+
|
| 25 |
+
Gradio demo for [`nvidia/cmd`](https://huggingface.co/nvidia/cmd): four-step
|
| 26 |
+
**autoregressive** image-to-video generation, distilled with Context-Matched
|
| 27 |
+
Distillation from a causal [Cosmos-Predict2.5
|
| 28 |
+
2B](https://huggingface.co/nvidia/Cosmos-Predict2.5-2B) teacher.
|
| 29 |
+
|
| 30 |
+
* Paper / project page: <https://hmrishavbandy.github.io/cmd-site/>
|
| 31 |
+
* Reference implementation: <https://github.com/nv-tlabs/cmd>
|
| 32 |
+
|
| 33 |
+
## What runs here
|
| 34 |
+
|
| 35 |
+
The Space serves the released `chunk1_short_t24_l21` student with the reference
|
| 36 |
+
`configs/cosmos/t24_l21_student_context_distillation.yaml` config, matching the
|
| 37 |
+
`chunk1-short` invocation in the upstream `examples/run_examples.sh`:
|
| 38 |
+
|
| 39 |
+
| Setting | Value |
|
| 40 |
+
|---|---|
|
| 41 |
+
| Denoising steps | 4 (`[1000, 750, 500, 250]`, warped, shift 5.0) |
|
| 42 |
+
| Autoregressive chunk | 1 latent frame |
|
| 43 |
+
| Local attention window | 21 latent frames |
|
| 44 |
+
| Context noise | 128 |
|
| 45 |
+
| Latent frames | 24 → 93 pixel frames |
|
| 46 |
+
| Resolution / fps | 832 × 480 @ 16 fps |
|
| 47 |
+
|
| 48 |
+
Two runtime-forced deviations from the reference script:
|
| 49 |
+
|
| 50 |
+
1. The gated Cosmos-Predict2.5 2B base DiT checkpoint is **not** downloaded. The
|
| 51 |
+
released CMD safetensors export contains every DiT parameter and persistent
|
| 52 |
+
buffer, so the model is constructed on the meta device and populated directly
|
| 53 |
+
from `nvidia/cmd` (upstream loads the base checkpoint first and then
|
| 54 |
+
overwrites all of it with the same export).
|
| 55 |
+
2. The Wan2.1 video tokenizer that ships inside Cosmos-Predict2.5 2B is fetched
|
| 56 |
+
from an ungated safetensors mirror of the identical 508 MB `tokenizer.pth`.
|
| 57 |
+
|
| 58 |
+
The chunk-4 variants are not offered: their multi-frame path goes through
|
| 59 |
+
`torch.compile`-wrapped FlexAttention, which ZeroGPU does not support. The
|
| 60 |
+
camera-control variants, which additionally need a camera trajectory in the CMD
|
| 61 |
+
NPZ format, are also out of scope for this demo.
|
| 62 |
+
|
| 63 |
+
## Vendored code and licensing
|
| 64 |
+
|
| 65 |
+
`cosmos/`, `pipeline/`, `utils/` and a minimal `wan/` subset are copied from
|
| 66 |
+
[nv-tlabs/cmd](https://github.com/nv-tlabs/cmd) so the demo reproduces the
|
| 67 |
+
authors' inference path exactly. The only edits are trimmed package
|
| 68 |
+
initializers (the upstream ones eagerly import training-only and multi-GPU
|
| 69 |
+
modules). See `LICENSE`, `LICENSES/` and `NOTICE` for the upstream terms:
|
| 70 |
+
|
| 71 |
+
* CMD / NVIDIA modifications — **NVIDIA OneWay Noncommercial License**
|
| 72 |
+
(non-commercial research or educational use only).
|
| 73 |
+
* Wan2.1, Self-Forcing and Cosmos-Predict2.5 portions — Apache-2.0.
|
| 74 |
+
|
| 75 |
+
`examples/image.png` and `examples/prompt.txt` are the authors' own example
|
| 76 |
+
inputs, redistributed from the CMD repository under the same license.
|
| 77 |
+
|
| 78 |
+
This demo is for research and development only.
|
app.py
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Gradio demo for NVIDIA CMD (Context-Matched Distillation) causal image-to-video.
|
| 2 |
+
|
| 3 |
+
The demo runs the released ``chunk1_short_t24_l21`` CMD student on top of the
|
| 4 |
+
Cosmos-Predict2.5 2B DiT, reproducing the reference invocation from
|
| 5 |
+
``examples/run_examples.sh`` in https://github.com/nv-tlabs/cmd:
|
| 6 |
+
|
| 7 |
+
python inference.py \
|
| 8 |
+
--config_path configs/cosmos/t24_l21_student_context_distillation.yaml \
|
| 9 |
+
--checkpoint_path checkpoints/chunk1_short_t24_l21.safetensors \
|
| 10 |
+
--i2v --num_output_frames 24 --num_frame_per_block 1 --local_attn_size 21
|
| 11 |
+
|
| 12 |
+
Only two things deviate from the reference script, both forced by the runtime:
|
| 13 |
+
|
| 14 |
+
* the base Cosmos-Predict2.5 2B DiT checkpoint is never downloaded -- the
|
| 15 |
+
released CMD safetensors already contains every DiT parameter, so the model is
|
| 16 |
+
built on the meta device and populated straight from ``nvidia/cmd``;
|
| 17 |
+
* the Wan2.1 video tokenizer is fetched from an ungated safetensors mirror of
|
| 18 |
+
the same ``tokenizer.pth`` file that ships inside Cosmos-Predict2.5 2B.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from __future__ import annotations
|
| 22 |
+
|
| 23 |
+
import os
|
| 24 |
+
import random
|
| 25 |
+
import tempfile
|
| 26 |
+
import time
|
| 27 |
+
from typing import Any, Dict, Tuple
|
| 28 |
+
|
| 29 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 30 |
+
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 31 |
+
|
| 32 |
+
import spaces # noqa: E402 -- must be imported before torch / CUDA
|
| 33 |
+
|
| 34 |
+
import gradio as gr # noqa: E402
|
| 35 |
+
import imageio.v2 as imageio # noqa: E402
|
| 36 |
+
import torch # noqa: E402
|
| 37 |
+
from einops import rearrange # noqa: E402
|
| 38 |
+
from huggingface_hub import hf_hub_download # noqa: E402
|
| 39 |
+
from omegaconf import OmegaConf # noqa: E402
|
| 40 |
+
from PIL import Image, ImageOps # noqa: E402
|
| 41 |
+
from safetensors import safe_open # noqa: E402
|
| 42 |
+
from torchvision import transforms # noqa: E402
|
| 43 |
+
|
| 44 |
+
import cosmos.wrapper as cosmos_wrapper # noqa: E402
|
| 45 |
+
from cosmos.wrapper import CosmosDiffusionWrapper, CosmosTextEncoder, CosmosVAEWrapper # noqa: E402
|
| 46 |
+
from pipeline.causal_inference import CausalInferencePipeline # noqa: E402
|
| 47 |
+
from utils.misc import set_seed # noqa: E402
|
| 48 |
+
from wan.modules.vae import WanVAE_, _video_vae as _wan_video_vae # noqa: E402
|
| 49 |
+
|
| 50 |
+
# --------------------------------------------------------------------------- #
|
| 51 |
+
# Constants
|
| 52 |
+
# --------------------------------------------------------------------------- #
|
| 53 |
+
|
| 54 |
+
CMD_REPO = "nvidia/cmd"
|
| 55 |
+
CMD_CHECKPOINT = "chunk1_short_t24_l21.safetensors"
|
| 56 |
+
CONFIG_PATH = "configs/cosmos/t24_l21_student_context_distillation.yaml"
|
| 57 |
+
DEFAULT_CONFIG_PATH = "configs/cosmos/default_config.yaml"
|
| 58 |
+
|
| 59 |
+
# Cosmos-Predict2.5 2B is gated, so the Wan2.1 tokenizer it bundles is pulled
|
| 60 |
+
# from an ungated safetensors mirror of the very same 508 MB checkpoint.
|
| 61 |
+
VAE_REPO = "KyleShao/Cosmos-Predict2.5-2B-Diffusers"
|
| 62 |
+
VAE_CHECKPOINT = "vae/tokenizer.safetensors"
|
| 63 |
+
|
| 64 |
+
HEIGHT, WIDTH = 480, 832
|
| 65 |
+
FPS = 16
|
| 66 |
+
MAX_LATENT_FRAMES = 24
|
| 67 |
+
MAX_SEED = 2**31 - 1
|
| 68 |
+
|
| 69 |
+
# Buffers that track training progress and are not part of a released export.
|
| 70 |
+
_TRAINING_STAT_BUFFERS: Dict[str, torch.Tensor] = {
|
| 71 |
+
"accum_video_sample_counter": torch.zeros((), dtype=torch.int64),
|
| 72 |
+
"accum_image_sample_counter": torch.zeros((), dtype=torch.int64),
|
| 73 |
+
"accum_iteration": torch.zeros((), dtype=torch.int64),
|
| 74 |
+
"accum_train_in_hours": torch.zeros((), dtype=torch.float32),
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
EXAMPLE_PROMPT = open("examples/prompt.txt", encoding="utf-8").read().strip()
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
# --------------------------------------------------------------------------- #
|
| 81 |
+
# Weight loading
|
| 82 |
+
# --------------------------------------------------------------------------- #
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _load_safetensors_bf16(path: str) -> Dict[str, torch.Tensor]:
|
| 86 |
+
"""Read a safetensors file tensor-by-tensor, casting floats to bfloat16.
|
| 87 |
+
|
| 88 |
+
The reference script loads an fp32 export into a bf16 model, which rounds
|
| 89 |
+
every float the same way; streaming the cast keeps host memory at ~4 GB
|
| 90 |
+
instead of ~12 GB.
|
| 91 |
+
|
| 92 |
+
Args:
|
| 93 |
+
path: Local path to the ``.safetensors`` file.
|
| 94 |
+
|
| 95 |
+
Returns:
|
| 96 |
+
Mapping from tensor name to CPU tensor.
|
| 97 |
+
"""
|
| 98 |
+
state_dict: Dict[str, torch.Tensor] = {}
|
| 99 |
+
with safe_open(path, framework="pt", device="cpu") as handle:
|
| 100 |
+
for key in handle.keys():
|
| 101 |
+
tensor = handle.get_tensor(key)
|
| 102 |
+
if tensor.is_floating_point():
|
| 103 |
+
tensor = tensor.to(torch.bfloat16)
|
| 104 |
+
state_dict[key] = tensor
|
| 105 |
+
return state_dict
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def _strip_common_prefix(state_dict: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]:
|
| 109 |
+
"""Drop a uniform checkpoint prefix so keys address the bare DiT."""
|
| 110 |
+
for prefix in ("generator.", "net_ema.", "net.", "model."):
|
| 111 |
+
if state_dict and all(key.startswith(prefix) for key in state_dict):
|
| 112 |
+
return {key[len(prefix):]: value for key, value in state_dict.items()}
|
| 113 |
+
return state_dict
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class CmdDiffusionWrapper(CosmosDiffusionWrapper):
|
| 117 |
+
"""``CosmosDiffusionWrapper`` that loads the distilled CMD DiT directly.
|
| 118 |
+
|
| 119 |
+
Upstream first materialises the gated Cosmos-Predict2.5 base checkpoint and
|
| 120 |
+
then overwrites it with the CMD export. The export is complete (it covers
|
| 121 |
+
every parameter and persistent buffer of the DiT), so this subclass skips
|
| 122 |
+
the gated download entirely and assigns the CMD tensors onto a meta-device
|
| 123 |
+
model instead.
|
| 124 |
+
"""
|
| 125 |
+
|
| 126 |
+
@classmethod
|
| 127 |
+
def _load_model(
|
| 128 |
+
cls,
|
| 129 |
+
model_name: str,
|
| 130 |
+
checkpoint_filename: str,
|
| 131 |
+
is_causal: bool,
|
| 132 |
+
local_attn_size: int,
|
| 133 |
+
sink_size: int,
|
| 134 |
+
) -> torch.nn.Module:
|
| 135 |
+
if not is_causal:
|
| 136 |
+
raise ValueError("The CMD demo only serves the causal (streaming) DiT")
|
| 137 |
+
|
| 138 |
+
from cosmos.causal_model import CausalCosmosModel
|
| 139 |
+
|
| 140 |
+
model_kwargs = cls._model_kwargs(is_causal)
|
| 141 |
+
model_kwargs.update(local_attn_size=local_attn_size, sink_size=sink_size)
|
| 142 |
+
with torch.device("meta"):
|
| 143 |
+
model = CausalCosmosModel(**model_kwargs)
|
| 144 |
+
|
| 145 |
+
checkpoint_path = hf_hub_download(repo_id=model_name, filename=checkpoint_filename)
|
| 146 |
+
state_dict = _strip_common_prefix(_load_safetensors_bf16(checkpoint_path))
|
| 147 |
+
for name, tensor in _TRAINING_STAT_BUFFERS.items():
|
| 148 |
+
state_dict.setdefault(name, tensor.clone())
|
| 149 |
+
|
| 150 |
+
report = model.load_state_dict(state_dict, strict=False, assign=True)
|
| 151 |
+
if report.unexpected_keys:
|
| 152 |
+
raise RuntimeError(
|
| 153 |
+
f"Unexpected CMD checkpoint keys: {sorted(report.unexpected_keys)[:10]}"
|
| 154 |
+
)
|
| 155 |
+
uninitialised = [
|
| 156 |
+
name
|
| 157 |
+
for name, tensor in (
|
| 158 |
+
list(model.named_parameters()) + list(model.named_buffers())
|
| 159 |
+
)
|
| 160 |
+
if tensor.is_meta
|
| 161 |
+
]
|
| 162 |
+
if uninitialised:
|
| 163 |
+
raise RuntimeError(
|
| 164 |
+
"CMD checkpoint did not initialise: " + ", ".join(uninitialised[:10])
|
| 165 |
+
)
|
| 166 |
+
print(f"Loaded {checkpoint_filename}: {len(state_dict)} DiT tensors", flush=True)
|
| 167 |
+
return model
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def _video_vae_with_safetensors(
|
| 171 |
+
pretrained_path: str | None = None,
|
| 172 |
+
z_dim: int | None = None,
|
| 173 |
+
device: str = "cpu",
|
| 174 |
+
**kwargs: Any,
|
| 175 |
+
) -> torch.nn.Module:
|
| 176 |
+
"""``wan.modules.vae._video_vae`` extended to accept safetensors weights."""
|
| 177 |
+
if pretrained_path is not None and str(pretrained_path).endswith(".safetensors"):
|
| 178 |
+
cfg = dict(
|
| 179 |
+
dim=96,
|
| 180 |
+
z_dim=z_dim,
|
| 181 |
+
dim_mult=[1, 2, 4, 4],
|
| 182 |
+
num_res_blocks=2,
|
| 183 |
+
attn_scales=[],
|
| 184 |
+
temperal_downsample=[False, True, True],
|
| 185 |
+
dropout=0.0,
|
| 186 |
+
)
|
| 187 |
+
cfg.update(**kwargs)
|
| 188 |
+
with torch.device("meta"):
|
| 189 |
+
model = WanVAE_(**cfg)
|
| 190 |
+
state_dict = _load_safetensors_bf16(pretrained_path)
|
| 191 |
+
model.load_state_dict(state_dict, assign=True)
|
| 192 |
+
return model
|
| 193 |
+
return _wan_video_vae(
|
| 194 |
+
pretrained_path=pretrained_path, z_dim=z_dim, device=device, **kwargs
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
cosmos_wrapper._video_vae = _video_vae_with_safetensors
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
# --------------------------------------------------------------------------- #
|
| 202 |
+
# Pipeline construction (module scope, eagerly on CUDA per ZeroGPU guidance)
|
| 203 |
+
# --------------------------------------------------------------------------- #
|
| 204 |
+
|
| 205 |
+
CONFIG = OmegaConf.merge(
|
| 206 |
+
OmegaConf.load(DEFAULT_CONFIG_PATH), OmegaConf.load(CONFIG_PATH)
|
| 207 |
+
)
|
| 208 |
+
# Reference chunk1-short overrides from examples/run_examples.sh.
|
| 209 |
+
CONFIG.num_frame_per_block = 1
|
| 210 |
+
CONFIG.model_kwargs.local_attn_size = 21
|
| 211 |
+
CONFIG.i2v = True
|
| 212 |
+
CONFIG.model_kwargs.model_name = CMD_REPO
|
| 213 |
+
CONFIG.model_kwargs.checkpoint_filename = CMD_CHECKPOINT
|
| 214 |
+
|
| 215 |
+
_model_kwargs = OmegaConf.to_container(CONFIG.model_kwargs, resolve=True)
|
| 216 |
+
|
| 217 |
+
print("Building the CMD generator ...", flush=True)
|
| 218 |
+
GENERATOR = CmdDiffusionWrapper(**_model_kwargs, is_causal=True)
|
| 219 |
+
print("Building the Cosmos-Reason1 text encoder ...", flush=True)
|
| 220 |
+
TEXT_ENCODER = CosmosTextEncoder(
|
| 221 |
+
model_name=str(CONFIG.text_encoder_name),
|
| 222 |
+
max_length=int(getattr(CONFIG, "text_encoder_max_length", 512)),
|
| 223 |
+
)
|
| 224 |
+
print("Building the Wan2.1 video tokenizer ...", flush=True)
|
| 225 |
+
VAE = CosmosVAEWrapper(model_name=VAE_REPO, checkpoint_filename=VAE_CHECKPOINT)
|
| 226 |
+
|
| 227 |
+
PIPELINE = CausalInferencePipeline(
|
| 228 |
+
CONFIG,
|
| 229 |
+
device="cpu",
|
| 230 |
+
generator=GENERATOR,
|
| 231 |
+
text_encoder=TEXT_ENCODER,
|
| 232 |
+
vae=VAE,
|
| 233 |
+
)
|
| 234 |
+
PIPELINE = PIPELINE.to(dtype=torch.bfloat16).eval().requires_grad_(False)
|
| 235 |
+
PIPELINE = PIPELINE.to("cuda")
|
| 236 |
+
print("CMD pipeline ready.", flush=True)
|
| 237 |
+
|
| 238 |
+
_TRANSFORM = transforms.Compose(
|
| 239 |
+
[
|
| 240 |
+
transforms.Resize((HEIGHT, WIDTH)),
|
| 241 |
+
transforms.ToTensor(),
|
| 242 |
+
transforms.Normalize([0.5], [0.5]),
|
| 243 |
+
]
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
# --------------------------------------------------------------------------- #
|
| 248 |
+
# Inference
|
| 249 |
+
# --------------------------------------------------------------------------- #
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def _preprocess(image: Image.Image, center_crop: bool) -> torch.Tensor:
|
| 253 |
+
"""Turn a PIL image into the reference ``[1, 3, 1, 480, 832]`` conditioning tensor."""
|
| 254 |
+
image = image.convert("RGB")
|
| 255 |
+
if center_crop:
|
| 256 |
+
image = ImageOps.fit(image, (WIDTH, HEIGHT), method=Image.LANCZOS, centering=(0.5, 0.5))
|
| 257 |
+
tensor = _TRANSFORM(image)
|
| 258 |
+
return tensor.unsqueeze(0).unsqueeze(2)
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
@spaces.GPU(duration=240)
|
| 262 |
+
def generate(
|
| 263 |
+
image: Image.Image,
|
| 264 |
+
prompt: str,
|
| 265 |
+
num_latent_frames: int = MAX_LATENT_FRAMES,
|
| 266 |
+
seed: int = 22,
|
| 267 |
+
randomize_seed: bool = False,
|
| 268 |
+
center_crop: bool = False,
|
| 269 |
+
) -> Tuple[str, int]:
|
| 270 |
+
"""Generate a short video that continues ``image`` following ``prompt``.
|
| 271 |
+
|
| 272 |
+
Args:
|
| 273 |
+
image: First frame of the video. Resized to 832x480.
|
| 274 |
+
prompt: Text description of the motion and scene to roll out.
|
| 275 |
+
num_latent_frames: Latent frames to produce, including the conditioning
|
| 276 |
+
frame. The tokenizer expands ``n`` latents into ``4 * (n - 1) + 1``
|
| 277 |
+
pixel frames, so 24 latents give 93 frames (~5.8 s at 16 fps).
|
| 278 |
+
seed: Random seed for the initial noise.
|
| 279 |
+
randomize_seed: Draw a fresh random seed instead of using ``seed``.
|
| 280 |
+
center_crop: Center-crop the input to 16:9 instead of stretching it.
|
| 281 |
+
|
| 282 |
+
Returns:
|
| 283 |
+
A tuple of the written mp4 path and the seed that was actually used.
|
| 284 |
+
|
| 285 |
+
Raises:
|
| 286 |
+
gradio.Error: If no image or no prompt was supplied.
|
| 287 |
+
"""
|
| 288 |
+
if image is None:
|
| 289 |
+
raise gr.Error("Please provide a conditioning image.")
|
| 290 |
+
if not prompt or not prompt.strip():
|
| 291 |
+
raise gr.Error("Please provide a text prompt.")
|
| 292 |
+
|
| 293 |
+
if randomize_seed:
|
| 294 |
+
seed = random.randint(0, MAX_SEED)
|
| 295 |
+
seed = int(seed) % (MAX_SEED + 1)
|
| 296 |
+
num_latent_frames = max(2, min(int(num_latent_frames), MAX_LATENT_FRAMES))
|
| 297 |
+
|
| 298 |
+
with torch.no_grad():
|
| 299 |
+
set_seed(seed)
|
| 300 |
+
started = time.perf_counter()
|
| 301 |
+
|
| 302 |
+
pixels = _preprocess(image, center_crop).to(device="cuda", dtype=torch.bfloat16)
|
| 303 |
+
initial_latent = PIPELINE.vae.encode_to_latent(pixels).to(
|
| 304 |
+
device="cuda", dtype=torch.bfloat16
|
| 305 |
+
)
|
| 306 |
+
noise = torch.randn(
|
| 307 |
+
[1, num_latent_frames - 1, *list(CONFIG.image_or_video_shape[2:])],
|
| 308 |
+
device="cuda",
|
| 309 |
+
dtype=torch.bfloat16,
|
| 310 |
+
)
|
| 311 |
+
encoded = time.perf_counter()
|
| 312 |
+
|
| 313 |
+
video = PIPELINE.inference(
|
| 314 |
+
noise=noise,
|
| 315 |
+
text_prompts=[prompt.strip()],
|
| 316 |
+
initial_latent=initial_latent,
|
| 317 |
+
)
|
| 318 |
+
rolled_out = time.perf_counter()
|
| 319 |
+
|
| 320 |
+
frames = (255.0 * rearrange(video, "b t c h w -> b t h w c").cpu())[0]
|
| 321 |
+
frames = frames.to(torch.uint8).numpy()
|
| 322 |
+
PIPELINE.vae.model.clear_cache()
|
| 323 |
+
|
| 324 |
+
path = os.path.join(tempfile.mkdtemp(), f"cmd_{seed}.mp4")
|
| 325 |
+
imageio.mimwrite(path, list(frames), fps=FPS, codec="libx264", quality=8, macro_block_size=1)
|
| 326 |
+
finished = time.perf_counter()
|
| 327 |
+
print(
|
| 328 |
+
f"[timing] latents={num_latent_frames} encode={encoded - started:.1f}s "
|
| 329 |
+
f"rollout={rolled_out - encoded:.1f}s encode_mp4={finished - rolled_out:.1f}s "
|
| 330 |
+
f"total={finished - started:.1f}s",
|
| 331 |
+
flush=True,
|
| 332 |
+
)
|
| 333 |
+
return path, seed
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
# --------------------------------------------------------------------------- #
|
| 337 |
+
# UI
|
| 338 |
+
# --------------------------------------------------------------------------- #
|
| 339 |
+
|
| 340 |
+
CSS = """
|
| 341 |
+
#col-container { max-width: 1100px; margin: 0 auto; }
|
| 342 |
+
.dark .gradio-container { color: var(--body-text-color); }
|
| 343 |
+
"""
|
| 344 |
+
|
| 345 |
+
with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo:
|
| 346 |
+
with gr.Column(elem_id="col-container"):
|
| 347 |
+
gr.Markdown(
|
| 348 |
+
"""
|
| 349 |
+
# CMD — Context-Matched Distillation (image → video)
|
| 350 |
+
|
| 351 |
+
Four-step **autoregressive** image-to-video generation with NVIDIA's
|
| 352 |
+
[CMD](https://huggingface.co/nvidia/cmd) `chunk1_short_t24_l21` student,
|
| 353 |
+
distilled from a causal Cosmos-Predict2.5 2B teacher. The model rolls the
|
| 354 |
+
video out one latent frame at a time behind a 21-frame local attention
|
| 355 |
+
window, so cost grows linearly with length rather than quadratically.
|
| 356 |
+
|
| 357 |
+
Output is 832×480, 93 frames at 16 fps (~5.8 s).
|
| 358 |
+
*Research / non-commercial use only — NVIDIA OneWay Noncommercial License.*
|
| 359 |
+
"""
|
| 360 |
+
)
|
| 361 |
+
with gr.Row():
|
| 362 |
+
with gr.Column():
|
| 363 |
+
image = gr.Image(label="First frame", type="pil", height=300)
|
| 364 |
+
prompt = gr.Textbox(
|
| 365 |
+
label="Prompt",
|
| 366 |
+
placeholder="Describe the scene and how the camera / subject should move…",
|
| 367 |
+
lines=4,
|
| 368 |
+
)
|
| 369 |
+
run_button = gr.Button("Generate video", variant="primary")
|
| 370 |
+
with gr.Accordion("Advanced settings", open=False):
|
| 371 |
+
num_latent_frames = gr.Slider(
|
| 372 |
+
label="Latent frames (24 → 93 pixel frames)",
|
| 373 |
+
minimum=4,
|
| 374 |
+
maximum=MAX_LATENT_FRAMES,
|
| 375 |
+
step=1,
|
| 376 |
+
value=MAX_LATENT_FRAMES,
|
| 377 |
+
)
|
| 378 |
+
seed = gr.Slider(
|
| 379 |
+
label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=22
|
| 380 |
+
)
|
| 381 |
+
randomize_seed = gr.Checkbox(label="Randomize seed", value=False)
|
| 382 |
+
center_crop = gr.Checkbox(
|
| 383 |
+
label="Center-crop the input to 16:9 (default: stretch, as in the reference script)",
|
| 384 |
+
value=False,
|
| 385 |
+
)
|
| 386 |
+
with gr.Column():
|
| 387 |
+
video_out = gr.Video(label="Generated video", autoplay=True, height=430)
|
| 388 |
+
|
| 389 |
+
gr.Examples(
|
| 390 |
+
examples=[["examples/image.png", EXAMPLE_PROMPT]],
|
| 391 |
+
inputs=[image, prompt],
|
| 392 |
+
outputs=[video_out, seed],
|
| 393 |
+
fn=generate,
|
| 394 |
+
cache_examples=True,
|
| 395 |
+
cache_mode="lazy",
|
| 396 |
+
label="Official CMD example (seed 22)",
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
gr.on(
|
| 400 |
+
triggers=[run_button.click, prompt.submit],
|
| 401 |
+
fn=generate,
|
| 402 |
+
inputs=[image, prompt, num_latent_frames, seed, randomize_seed, center_crop],
|
| 403 |
+
outputs=[video_out, seed],
|
| 404 |
+
api_name="generate",
|
| 405 |
+
)
|
| 406 |
+
|
| 407 |
+
if __name__ == "__main__":
|
| 408 |
+
demo.launch(mcp_server=True)
|
configs/cosmos/default_config.yaml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model_family: cosmos
|
| 2 |
+
independent_first_frame: true
|
| 3 |
+
warp_denoising_step: false
|
| 4 |
+
weight_decay: 0.01
|
| 5 |
+
same_step_across_blocks: true
|
| 6 |
+
discriminator_lr_multiplier: 1.0
|
| 7 |
+
last_step_only: false
|
| 8 |
+
i2v: true
|
| 9 |
+
num_training_frames: 24
|
| 10 |
+
gc_interval: 100
|
| 11 |
+
context_noise: 0
|
| 12 |
+
causal: true
|
| 13 |
+
|
| 14 |
+
ckpt_step: 0
|
| 15 |
+
eval_first_n: 64
|
| 16 |
+
num_samples: 1
|
| 17 |
+
height: 480
|
| 18 |
+
width: 832
|
| 19 |
+
num_frames: 93
|
configs/cosmos/t24_l21_student_context_distillation.yaml
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stage 2: distill the t24/l21 student from the paired causal-flow teacher.
|
| 2 |
+
generator_ckpt: checkpoints/chunk1_teacher_t24_l21.safetensors
|
| 3 |
+
teacher_ckpt: checkpoints/chunk1_teacher_t24_l21.safetensors
|
| 4 |
+
prefix_noise: 256
|
| 5 |
+
model_family: cosmos
|
| 6 |
+
real_name: nvidia/Cosmos-Predict2.5-2B
|
| 7 |
+
fake_name: nvidia/Cosmos-Predict2.5-2B
|
| 8 |
+
text_encoder_name: nvidia/Cosmos-Reason1-7B
|
| 9 |
+
vae_model_name: nvidia/Cosmos-Predict2.5-2B
|
| 10 |
+
|
| 11 |
+
generator_fsdp_wrap_strategy: size
|
| 12 |
+
real_score_fsdp_wrap_strategy: size
|
| 13 |
+
fake_score_fsdp_wrap_strategy: size
|
| 14 |
+
text_encoder_fsdp_wrap_strategy: size
|
| 15 |
+
denoising_step_list:
|
| 16 |
+
- 1000
|
| 17 |
+
- 750
|
| 18 |
+
- 500
|
| 19 |
+
- 250
|
| 20 |
+
warp_denoising_step: true
|
| 21 |
+
ts_schedule: false
|
| 22 |
+
num_train_timestep: 1000
|
| 23 |
+
timestep_shift: 5.0
|
| 24 |
+
context_noise: 128
|
| 25 |
+
validation_interval: 50
|
| 26 |
+
validation_at_start: true
|
| 27 |
+
validation_seed: 12345
|
| 28 |
+
validation_fps: 16
|
| 29 |
+
real_guidance_scale: 4.0
|
| 30 |
+
fake_guidance_scale: 0.0
|
| 31 |
+
denoising_loss_type: flow
|
| 32 |
+
mixed_precision: true
|
| 33 |
+
seed: 0
|
| 34 |
+
wandb_host: ${oc.env:WANDB_BASE_URL,https://api.wandb.ai}
|
| 35 |
+
# wandb.login(key=None) reads WANDB_API_KEY without placing the key in config.
|
| 36 |
+
wandb_key: null
|
| 37 |
+
wandb_entity: ${oc.env:WANDB_ENTITY,null}
|
| 38 |
+
wandb_project: ${oc.env:WANDB_PROJECT,causal-cosmos25}
|
| 39 |
+
sharding_strategy: hybrid_full
|
| 40 |
+
lr: 2.0e-06
|
| 41 |
+
lr_critic: 4.0e-07
|
| 42 |
+
beta1: 0.0
|
| 43 |
+
beta2: 0.999
|
| 44 |
+
beta1_critic: 0.0
|
| 45 |
+
beta2_critic: 0.999
|
| 46 |
+
|
| 47 |
+
# Sharded LMDB containing prompts and Cosmos/Wan-VAE image latents.
|
| 48 |
+
data_path: data/cosmos_i2v_lmdb_t24
|
| 49 |
+
batch_size: 1
|
| 50 |
+
dataloader_num_workers: 1
|
| 51 |
+
ema_weight: 0.0
|
| 52 |
+
ema_start_step: 200
|
| 53 |
+
log_iters: 50
|
| 54 |
+
max_steps: 10000
|
| 55 |
+
negative_prompt: >-
|
| 56 |
+
The video captures a series of frames showing ugly scenes, static with no
|
| 57 |
+
motion, motion blur, over-saturation, shaky footage, low resolution, grainy
|
| 58 |
+
texture, pixelated images, poorly lit areas, underexposed and overexposed
|
| 59 |
+
scenes, poor color balance, washed out colors, choppy sequences, jerky
|
| 60 |
+
movements, low frame rate, artifacting, color banding, unnatural transitions,
|
| 61 |
+
outdated special effects, fake elements, unconvincing visuals, poorly edited
|
| 62 |
+
content, jump cuts, visual noise, and flickering. Overall, the video is of
|
| 63 |
+
poor quality.
|
| 64 |
+
dfake_gen_update_ratio: 5
|
| 65 |
+
|
| 66 |
+
# 93 pixel frames become 24 latent frames with the checkpoint tokenizer.
|
| 67 |
+
# 480x832 becomes 60x104 latent spatially.
|
| 68 |
+
image_or_video_shape:
|
| 69 |
+
- 1
|
| 70 |
+
- 24
|
| 71 |
+
- 16
|
| 72 |
+
- 60
|
| 73 |
+
- 104
|
| 74 |
+
distribution_loss: context_matched
|
| 75 |
+
trainer: score_distillation
|
| 76 |
+
gradient_checkpointing: true
|
| 77 |
+
num_frame_per_block: 1
|
| 78 |
+
load_raw_video: false
|
| 79 |
+
i2v: true
|
| 80 |
+
independent_first_frame: true
|
| 81 |
+
# One clean I2V frame plus 23 generated/scored frames, matching t24.
|
| 82 |
+
num_training_frames: 24
|
| 83 |
+
model_kwargs:
|
| 84 |
+
model_name: nvidia/Cosmos-Predict2.5-2B
|
| 85 |
+
checkpoint_filename: base/post-trained/81edfebe-bd6a-4039-8c1d-737df1a790bf_ema_bf16.pt
|
| 86 |
+
timestep_shift: 5.0
|
| 87 |
+
i2v: true
|
| 88 |
+
local_attn_size: 21
|
| 89 |
+
sink_size: 0
|
cosmos/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: LicenseRef-NvidiaOneWayNoncommercial
|
| 3 |
+
|
| 4 |
+
from .wrapper import CosmosDiffusionWrapper, CosmosTextEncoder, CosmosVAEWrapper
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
"CosmosDiffusionWrapper",
|
| 8 |
+
"CosmosTextEncoder",
|
| 9 |
+
"CosmosVAEWrapper",
|
| 10 |
+
]
|
cosmos/camera_conditioning.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: LicenseRef-NvidiaOneWayNoncommercial
|
| 3 |
+
|
| 4 |
+
"""Camera-ray conditioning for the Cosmos video latent grid."""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
CAMERA_FEATURE_DIM = 6
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def camera_frame_indices(
|
| 15 |
+
num_pixel_frames: int,
|
| 16 |
+
frame_stride: int = 4,
|
| 17 |
+
*,
|
| 18 |
+
device: torch.device | None = None,
|
| 19 |
+
) -> torch.Tensor:
|
| 20 |
+
"""Select pixel-frame cameras aligned with temporally compressed latents."""
|
| 21 |
+
if num_pixel_frames <= 0:
|
| 22 |
+
raise ValueError("num_pixel_frames must be positive")
|
| 23 |
+
if frame_stride <= 0:
|
| 24 |
+
raise ValueError("frame_stride must be positive")
|
| 25 |
+
if (num_pixel_frames - 1) % frame_stride:
|
| 26 |
+
raise ValueError(
|
| 27 |
+
"Camera sequence length must be 1 + k * frame_stride; got "
|
| 28 |
+
f"{num_pixel_frames} frames and stride {frame_stride}"
|
| 29 |
+
)
|
| 30 |
+
return torch.arange(
|
| 31 |
+
0,
|
| 32 |
+
num_pixel_frames,
|
| 33 |
+
frame_stride,
|
| 34 |
+
device=device,
|
| 35 |
+
dtype=torch.long,
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def frame_relative_camera_to_world(
|
| 40 |
+
camera_to_world: torch.Tensor,
|
| 41 |
+
num_frame_per_block: int = 1,
|
| 42 |
+
) -> torch.Tensor:
|
| 43 |
+
"""Express each generated block relative to the prior block boundary."""
|
| 44 |
+
if camera_to_world.ndim != 4 or camera_to_world.shape[-2:] != (4, 4):
|
| 45 |
+
raise ValueError(
|
| 46 |
+
"camera_to_world must have shape [B, T, 4, 4]; got "
|
| 47 |
+
f"{tuple(camera_to_world.shape)}"
|
| 48 |
+
)
|
| 49 |
+
if camera_to_world.shape[1] == 0:
|
| 50 |
+
raise ValueError("camera_to_world must contain at least one frame")
|
| 51 |
+
if num_frame_per_block <= 0:
|
| 52 |
+
raise ValueError("num_frame_per_block must be positive")
|
| 53 |
+
|
| 54 |
+
poses = camera_to_world.to(torch.float32)
|
| 55 |
+
frame_indices = torch.arange(
|
| 56 |
+
poses.shape[1],
|
| 57 |
+
device=poses.device,
|
| 58 |
+
dtype=torch.long,
|
| 59 |
+
)
|
| 60 |
+
# With an independent I2V prefix at frame zero, frames 1..C use frame 0,
|
| 61 |
+
# frames C+1..2C use frame C, and so on. For C=1 this reduces to the
|
| 62 |
+
# original previous-frame-relative convention.
|
| 63 |
+
anchor_indices = torch.div(
|
| 64 |
+
torch.clamp(frame_indices - 1, min=0),
|
| 65 |
+
num_frame_per_block,
|
| 66 |
+
rounding_mode="floor",
|
| 67 |
+
) * num_frame_per_block
|
| 68 |
+
anchors = poses.index_select(1, anchor_indices)
|
| 69 |
+
relative = torch.linalg.solve(anchors, poses)
|
| 70 |
+
return relative
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def _per_frame_intrinsics(
|
| 74 |
+
intrinsics: torch.Tensor,
|
| 75 |
+
frame_indices: torch.Tensor,
|
| 76 |
+
num_pixel_frames: int,
|
| 77 |
+
) -> torch.Tensor:
|
| 78 |
+
if intrinsics.ndim == 3 and intrinsics.shape[-2:] == (3, 3):
|
| 79 |
+
return intrinsics[:, None].expand(-1, frame_indices.numel(), -1, -1)
|
| 80 |
+
if intrinsics.ndim == 4 and intrinsics.shape[-2:] == (3, 3):
|
| 81 |
+
if intrinsics.shape[1] == 1:
|
| 82 |
+
return intrinsics.expand(-1, frame_indices.numel(), -1, -1)
|
| 83 |
+
if intrinsics.shape[1] != num_pixel_frames:
|
| 84 |
+
raise ValueError(
|
| 85 |
+
"Per-frame intrinsics must match the pixel camera sequence; got "
|
| 86 |
+
f"{intrinsics.shape[1]} and {num_pixel_frames} frames"
|
| 87 |
+
)
|
| 88 |
+
return intrinsics.index_select(1, frame_indices)
|
| 89 |
+
raise ValueError(
|
| 90 |
+
"intrinsics must have shape [B, 3, 3] or [B, T, 3, 3]; got "
|
| 91 |
+
f"{tuple(intrinsics.shape)}"
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def camera_rays(
|
| 96 |
+
camera_to_world: torch.Tensor,
|
| 97 |
+
intrinsics: torch.Tensor,
|
| 98 |
+
image_height: int,
|
| 99 |
+
image_width: int,
|
| 100 |
+
) -> torch.Tensor:
|
| 101 |
+
"""Return ray origins and unit directions as ``[B, T, H, W, 6]``."""
|
| 102 |
+
if camera_to_world.ndim != 4 or camera_to_world.shape[-2:] != (4, 4):
|
| 103 |
+
raise ValueError("camera_to_world must have shape [B, T, 4, 4]")
|
| 104 |
+
if intrinsics.ndim != 4 or intrinsics.shape[-2:] != (3, 3):
|
| 105 |
+
raise ValueError("intrinsics must have shape [B, T, 3, 3]")
|
| 106 |
+
if camera_to_world.shape[:2] != intrinsics.shape[:2]:
|
| 107 |
+
raise ValueError("Camera poses and intrinsics must have matching B and T")
|
| 108 |
+
if image_height <= 0 or image_width <= 0:
|
| 109 |
+
raise ValueError("Camera image dimensions must be positive")
|
| 110 |
+
|
| 111 |
+
poses = camera_to_world.to(torch.float32)
|
| 112 |
+
calibration = intrinsics.to(device=poses.device, dtype=torch.float32)
|
| 113 |
+
focal_x = calibration[..., 0, 0]
|
| 114 |
+
focal_y = calibration[..., 1, 1]
|
| 115 |
+
if torch.any(focal_x <= 0) or torch.any(focal_y <= 0):
|
| 116 |
+
raise ValueError("Camera focal lengths must be positive")
|
| 117 |
+
|
| 118 |
+
pixel_y, pixel_x = torch.meshgrid(
|
| 119 |
+
torch.arange(image_height, device=poses.device, dtype=torch.float32) + 0.5,
|
| 120 |
+
torch.arange(image_width, device=poses.device, dtype=torch.float32) + 0.5,
|
| 121 |
+
indexing="ij",
|
| 122 |
+
)
|
| 123 |
+
pixel_x = pixel_x[None, None]
|
| 124 |
+
pixel_y = pixel_y[None, None]
|
| 125 |
+
direction_x = (
|
| 126 |
+
pixel_x - calibration[..., 0, 2, None, None]
|
| 127 |
+
) / focal_x[..., None, None]
|
| 128 |
+
direction_y = (
|
| 129 |
+
pixel_y - calibration[..., 1, 2, None, None]
|
| 130 |
+
) / focal_y[..., None, None]
|
| 131 |
+
camera_direction = torch.stack(
|
| 132 |
+
[direction_x, direction_y, torch.ones_like(direction_x)],
|
| 133 |
+
dim=-1,
|
| 134 |
+
)
|
| 135 |
+
camera_direction = torch.nn.functional.normalize(camera_direction, dim=-1)
|
| 136 |
+
|
| 137 |
+
rotation = poses[..., :3, :3]
|
| 138 |
+
ray_direction = torch.einsum(
|
| 139 |
+
"btij,bthwj->bthwi",
|
| 140 |
+
rotation,
|
| 141 |
+
camera_direction,
|
| 142 |
+
)
|
| 143 |
+
ray_origin = poses[..., :3, 3][..., None, None, :].expand_as(ray_direction)
|
| 144 |
+
return torch.cat([ray_origin, ray_direction], dim=-1)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def patchify_camera_rays(
|
| 148 |
+
rays: torch.Tensor,
|
| 149 |
+
patch_size: int = 16,
|
| 150 |
+
) -> torch.Tensor:
|
| 151 |
+
"""Flatten each spatial ray patch into the camera token channels."""
|
| 152 |
+
if rays.ndim != 5 or rays.shape[-1] != CAMERA_FEATURE_DIM:
|
| 153 |
+
raise ValueError("rays must have shape [B, T, H, W, 6]")
|
| 154 |
+
if patch_size <= 0:
|
| 155 |
+
raise ValueError("patch_size must be positive")
|
| 156 |
+
batch, frames, height, width, channels = rays.shape
|
| 157 |
+
if height % patch_size or width % patch_size:
|
| 158 |
+
raise ValueError(
|
| 159 |
+
f"Camera image {(height, width)} is not divisible by patch size {patch_size}"
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
rays_bcthw = rays.permute(0, 4, 1, 2, 3).contiguous()
|
| 163 |
+
token_h = height // patch_size
|
| 164 |
+
token_w = width // patch_size
|
| 165 |
+
return (
|
| 166 |
+
rays_bcthw.reshape(
|
| 167 |
+
batch,
|
| 168 |
+
channels,
|
| 169 |
+
frames,
|
| 170 |
+
token_h,
|
| 171 |
+
patch_size,
|
| 172 |
+
token_w,
|
| 173 |
+
patch_size,
|
| 174 |
+
)
|
| 175 |
+
.permute(0, 1, 4, 6, 2, 3, 5)
|
| 176 |
+
.reshape(
|
| 177 |
+
batch,
|
| 178 |
+
channels * patch_size * patch_size,
|
| 179 |
+
frames,
|
| 180 |
+
token_h,
|
| 181 |
+
token_w,
|
| 182 |
+
)
|
| 183 |
+
.contiguous()
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def build_camera_conditioning(
|
| 188 |
+
camera_to_world: torch.Tensor,
|
| 189 |
+
intrinsics: torch.Tensor,
|
| 190 |
+
*,
|
| 191 |
+
image_height: int,
|
| 192 |
+
image_width: int,
|
| 193 |
+
frame_stride: int = 4,
|
| 194 |
+
patch_size: int = 16,
|
| 195 |
+
num_frame_per_block: int = 1,
|
| 196 |
+
expected_latent_frames: int | None = None,
|
| 197 |
+
output_dtype: torch.dtype | None = None,
|
| 198 |
+
) -> torch.Tensor:
|
| 199 |
+
"""Build block-relative origin/direction camera tokens from pixel cameras."""
|
| 200 |
+
if camera_to_world.ndim != 4 or camera_to_world.shape[-2:] != (4, 4):
|
| 201 |
+
raise ValueError("camera_to_world must have shape [B, T, 4, 4]")
|
| 202 |
+
num_pixel_frames = camera_to_world.shape[1]
|
| 203 |
+
indices = camera_frame_indices(
|
| 204 |
+
num_pixel_frames,
|
| 205 |
+
frame_stride,
|
| 206 |
+
device=camera_to_world.device,
|
| 207 |
+
)
|
| 208 |
+
if expected_latent_frames is not None and indices.numel() != expected_latent_frames:
|
| 209 |
+
raise ValueError(
|
| 210 |
+
f"Camera sequence produces {indices.numel()} latent frames; "
|
| 211 |
+
f"expected {expected_latent_frames}"
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
sampled_poses = camera_to_world.index_select(1, indices)
|
| 215 |
+
sampled_intrinsics = _per_frame_intrinsics(
|
| 216 |
+
intrinsics.to(device=camera_to_world.device),
|
| 217 |
+
indices,
|
| 218 |
+
num_pixel_frames,
|
| 219 |
+
)
|
| 220 |
+
relative_poses = frame_relative_camera_to_world(
|
| 221 |
+
sampled_poses,
|
| 222 |
+
num_frame_per_block=num_frame_per_block,
|
| 223 |
+
)
|
| 224 |
+
rays = camera_rays(
|
| 225 |
+
relative_poses,
|
| 226 |
+
sampled_intrinsics,
|
| 227 |
+
image_height,
|
| 228 |
+
image_width,
|
| 229 |
+
)
|
| 230 |
+
conditioning = patchify_camera_rays(rays, patch_size=patch_size)
|
| 231 |
+
if output_dtype is not None:
|
| 232 |
+
conditioning = conditioning.to(dtype=output_dtype)
|
| 233 |
+
return conditioning
|
cosmos/causal_model.py
ADDED
|
@@ -0,0 +1,960 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
# SPDX-FileCopyrightText: Modifications Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 17 |
+
# SPDX-License-Identifier: LicenseRef-NvidiaOneWayNoncommercial
|
| 18 |
+
|
| 19 |
+
"""Block-causal Cosmos-Predict2.5 model with streaming KV cache."""
|
| 20 |
+
|
| 21 |
+
import math
|
| 22 |
+
from typing import Optional
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
import torch.distributed as dist
|
| 26 |
+
from torch.nn.attention.flex_attention import BlockMask
|
| 27 |
+
from torch.nn.attention.flex_attention import flex_attention as torch_flex_attention
|
| 28 |
+
|
| 29 |
+
from cosmos.kv_cache import (
|
| 30 |
+
AttentionOpWithKVCache,
|
| 31 |
+
KVCacheConfig,
|
| 32 |
+
VideoSeqPos,
|
| 33 |
+
)
|
| 34 |
+
from cosmos.minimal_v1_lvg_dit import MinimalV1LVGDiT
|
| 35 |
+
from cosmos.minimal_v4_dit import VideoSize, i4_attention_op
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# FlexAttention represents the block mask sparsely, avoiding a dense
|
| 39 |
+
# [T*H*W, T*H*W] mask for full-resolution video tokens. Unlike Wan 1.3B,
|
| 40 |
+
# Cosmos has a standard 16-head/128-dim layout and does not need expensive
|
| 41 |
+
# max-autotune kernel benchmarking.
|
| 42 |
+
flex_attention = torch.compile(
|
| 43 |
+
torch_flex_attention,
|
| 44 |
+
dynamic=False,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
_SPARSE_BLOCK_SIZE = 128
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _merge_intervals(
|
| 52 |
+
intervals: list[tuple[int, int]],
|
| 53 |
+
limit: int,
|
| 54 |
+
) -> tuple[tuple[int, int], ...]:
|
| 55 |
+
"""Clip and merge half-open token intervals."""
|
| 56 |
+
clipped = sorted(
|
| 57 |
+
(max(0, start), min(limit, end))
|
| 58 |
+
for start, end in intervals
|
| 59 |
+
if max(0, start) < min(limit, end)
|
| 60 |
+
)
|
| 61 |
+
merged: list[list[int]] = []
|
| 62 |
+
for start, end in clipped:
|
| 63 |
+
if merged and start <= merged[-1][1]:
|
| 64 |
+
merged[-1][1] = max(merged[-1][1], end)
|
| 65 |
+
else:
|
| 66 |
+
merged.append([start, end])
|
| 67 |
+
return tuple((start, end) for start, end in merged)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def _full_sequence_query_ranges(
|
| 71 |
+
*,
|
| 72 |
+
total_length: int,
|
| 73 |
+
tokens_per_frame: int,
|
| 74 |
+
num_frame_per_block: int,
|
| 75 |
+
independent_first_frame: bool,
|
| 76 |
+
local_attn_size: int,
|
| 77 |
+
sink_size: int,
|
| 78 |
+
) -> list[tuple[int, int, tuple[tuple[int, int], ...]]]:
|
| 79 |
+
"""Describe the allowed K/V intervals for full causal attention."""
|
| 80 |
+
block_tokens = num_frame_per_block * tokens_per_frame
|
| 81 |
+
prefix_tokens = tokens_per_frame if independent_first_frame else 0
|
| 82 |
+
use_local_attn = local_attn_size > 0
|
| 83 |
+
sink_tokens = max(sink_size, 0) * tokens_per_frame
|
| 84 |
+
window_tokens = max(local_attn_size - sink_size, 0) * tokens_per_frame
|
| 85 |
+
|
| 86 |
+
query_ranges = []
|
| 87 |
+
if prefix_tokens:
|
| 88 |
+
prefix_intervals = (
|
| 89 |
+
[(0, prefix_tokens)]
|
| 90 |
+
if not use_local_attn
|
| 91 |
+
else [(max(prefix_tokens - window_tokens, 0), prefix_tokens)]
|
| 92 |
+
)
|
| 93 |
+
query_ranges.append(
|
| 94 |
+
(
|
| 95 |
+
0,
|
| 96 |
+
prefix_tokens,
|
| 97 |
+
_merge_intervals(prefix_intervals, total_length),
|
| 98 |
+
)
|
| 99 |
+
)
|
| 100 |
+
for query_start in range(prefix_tokens, total_length, block_tokens):
|
| 101 |
+
raw_query_end = query_start + block_tokens
|
| 102 |
+
query_end = min(raw_query_end, total_length)
|
| 103 |
+
if use_local_attn:
|
| 104 |
+
recent_start = max(raw_query_end - window_tokens, 0)
|
| 105 |
+
promoted_sink_end = min(
|
| 106 |
+
query_start,
|
| 107 |
+
sink_tokens,
|
| 108 |
+
recent_start,
|
| 109 |
+
)
|
| 110 |
+
recent_start = max(promoted_sink_end, recent_start)
|
| 111 |
+
intervals = [
|
| 112 |
+
(0, promoted_sink_end),
|
| 113 |
+
(recent_start, raw_query_end),
|
| 114 |
+
]
|
| 115 |
+
else:
|
| 116 |
+
intervals = [(0, raw_query_end)]
|
| 117 |
+
query_ranges.append(
|
| 118 |
+
(
|
| 119 |
+
query_start,
|
| 120 |
+
query_end,
|
| 121 |
+
_merge_intervals(intervals, total_length),
|
| 122 |
+
)
|
| 123 |
+
)
|
| 124 |
+
return query_ranges
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def _build_sparse_block_rows(
|
| 128 |
+
*,
|
| 129 |
+
query_ranges: list[
|
| 130 |
+
tuple[int, int, tuple[tuple[int, int], ...]]
|
| 131 |
+
],
|
| 132 |
+
q_total: int,
|
| 133 |
+
kv_total: int,
|
| 134 |
+
block_size: int = _SPARSE_BLOCK_SIZE,
|
| 135 |
+
) -> tuple[list[int], list[list[int]], list[int], list[list[int]]]:
|
| 136 |
+
"""Build exact sparse-tile metadata without a dense token mask.
|
| 137 |
+
|
| 138 |
+
Each query range has one fixed union of allowed key intervals. Query and
|
| 139 |
+
key tile boundaries need not align with frame boundaries; tiles crossing a
|
| 140 |
+
boundary are emitted as partial and evaluated by ``mask_mod`` in the
|
| 141 |
+
FlexAttention kernel.
|
| 142 |
+
"""
|
| 143 |
+
if q_total <= 0 or kv_total <= 0:
|
| 144 |
+
raise ValueError("Sparse attention lengths must be positive")
|
| 145 |
+
if q_total % block_size or kv_total % block_size:
|
| 146 |
+
raise ValueError("Sparse attention lengths must be block aligned")
|
| 147 |
+
|
| 148 |
+
previous_end = 0
|
| 149 |
+
for query_start, query_end, intervals in query_ranges:
|
| 150 |
+
if query_start != previous_end or query_end <= query_start:
|
| 151 |
+
raise ValueError("Query ranges must be contiguous and non-empty")
|
| 152 |
+
if query_end > q_total:
|
| 153 |
+
raise ValueError("Query range exceeds padded query length")
|
| 154 |
+
for key_start, key_end in intervals:
|
| 155 |
+
if not (0 <= key_start < key_end <= kv_total):
|
| 156 |
+
raise ValueError("Key interval exceeds padded key length")
|
| 157 |
+
previous_end = query_end
|
| 158 |
+
|
| 159 |
+
q_block_count = q_total // block_size
|
| 160 |
+
kv_block_count = kv_total // block_size
|
| 161 |
+
partial_counts: list[int] = []
|
| 162 |
+
partial_rows: list[list[int]] = []
|
| 163 |
+
full_counts: list[int] = []
|
| 164 |
+
full_rows: list[list[int]] = []
|
| 165 |
+
|
| 166 |
+
range_index = 0
|
| 167 |
+
for query_block in range(q_block_count):
|
| 168 |
+
query_start = query_block * block_size
|
| 169 |
+
query_end = query_start + block_size
|
| 170 |
+
while (
|
| 171 |
+
range_index < len(query_ranges)
|
| 172 |
+
and query_ranges[range_index][1] <= query_start
|
| 173 |
+
):
|
| 174 |
+
range_index += 1
|
| 175 |
+
|
| 176 |
+
query_segments = []
|
| 177 |
+
candidate_index = range_index
|
| 178 |
+
cursor = query_start
|
| 179 |
+
fully_covered_query = True
|
| 180 |
+
while (
|
| 181 |
+
candidate_index < len(query_ranges)
|
| 182 |
+
and query_ranges[candidate_index][0] < query_end
|
| 183 |
+
):
|
| 184 |
+
range_start, range_end, intervals = query_ranges[candidate_index]
|
| 185 |
+
segment_start = max(query_start, range_start)
|
| 186 |
+
segment_end = min(query_end, range_end)
|
| 187 |
+
if segment_start > cursor:
|
| 188 |
+
fully_covered_query = False
|
| 189 |
+
if segment_start < segment_end:
|
| 190 |
+
query_segments.append((segment_start, segment_end, intervals))
|
| 191 |
+
cursor = segment_end
|
| 192 |
+
candidate_index += 1
|
| 193 |
+
if cursor < query_end:
|
| 194 |
+
fully_covered_query = False
|
| 195 |
+
|
| 196 |
+
partial_indices: list[int] = []
|
| 197 |
+
full_indices: list[int] = []
|
| 198 |
+
for key_block in range(kv_block_count):
|
| 199 |
+
key_start = key_block * block_size
|
| 200 |
+
key_end = key_start + block_size
|
| 201 |
+
any_allowed = False
|
| 202 |
+
fully_allowed = fully_covered_query and bool(query_segments)
|
| 203 |
+
for _segment_start, _segment_end, intervals in query_segments:
|
| 204 |
+
segment_overlaps = any(
|
| 205 |
+
interval_start < key_end and key_start < interval_end
|
| 206 |
+
for interval_start, interval_end in intervals
|
| 207 |
+
)
|
| 208 |
+
any_allowed = any_allowed or segment_overlaps
|
| 209 |
+
segment_contains = any(
|
| 210 |
+
interval_start <= key_start and key_end <= interval_end
|
| 211 |
+
for interval_start, interval_end in intervals
|
| 212 |
+
)
|
| 213 |
+
fully_allowed = fully_allowed and segment_contains
|
| 214 |
+
|
| 215 |
+
if fully_allowed:
|
| 216 |
+
full_indices.append(key_block)
|
| 217 |
+
elif any_allowed:
|
| 218 |
+
partial_indices.append(key_block)
|
| 219 |
+
|
| 220 |
+
partial_counts.append(len(partial_indices))
|
| 221 |
+
full_counts.append(len(full_indices))
|
| 222 |
+
partial_rows.append(
|
| 223 |
+
partial_indices + [0] * (kv_block_count - len(partial_indices))
|
| 224 |
+
)
|
| 225 |
+
full_rows.append(
|
| 226 |
+
full_indices + [0] * (kv_block_count - len(full_indices))
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
return partial_counts, partial_rows, full_counts, full_rows
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def _block_mask_from_intervals(
|
| 233 |
+
*,
|
| 234 |
+
query_ranges: list[
|
| 235 |
+
tuple[int, int, tuple[tuple[int, int], ...]]
|
| 236 |
+
],
|
| 237 |
+
q_total: int,
|
| 238 |
+
kv_total: int,
|
| 239 |
+
mask_mod,
|
| 240 |
+
device: torch.device,
|
| 241 |
+
) -> BlockMask:
|
| 242 |
+
"""Create a FlexAttention BlockMask from compact interval metadata."""
|
| 243 |
+
(
|
| 244 |
+
partial_counts,
|
| 245 |
+
partial_rows,
|
| 246 |
+
full_counts,
|
| 247 |
+
full_rows,
|
| 248 |
+
) = _build_sparse_block_rows(
|
| 249 |
+
query_ranges=query_ranges,
|
| 250 |
+
q_total=q_total,
|
| 251 |
+
kv_total=kv_total,
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
def count_tensor(values: list[int]) -> torch.Tensor:
|
| 255 |
+
return torch.tensor(
|
| 256 |
+
values,
|
| 257 |
+
dtype=torch.int32,
|
| 258 |
+
device=device,
|
| 259 |
+
).view(1, 1, -1)
|
| 260 |
+
|
| 261 |
+
def row_tensor(values: list[list[int]]) -> torch.Tensor:
|
| 262 |
+
return torch.tensor(
|
| 263 |
+
values,
|
| 264 |
+
dtype=torch.int32,
|
| 265 |
+
device=device,
|
| 266 |
+
).view(1, 1, len(values), -1)
|
| 267 |
+
|
| 268 |
+
return BlockMask.from_kv_blocks(
|
| 269 |
+
kv_num_blocks=count_tensor(partial_counts),
|
| 270 |
+
kv_indices=row_tensor(partial_rows),
|
| 271 |
+
full_kv_num_blocks=count_tensor(full_counts),
|
| 272 |
+
full_kv_indices=row_tensor(full_rows),
|
| 273 |
+
BLOCK_SIZE=_SPARSE_BLOCK_SIZE,
|
| 274 |
+
mask_mod=mask_mod,
|
| 275 |
+
seq_lengths=(q_total, kv_total),
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
class CausalCosmosAttention(AttentionOpWithKVCache):
|
| 280 |
+
"""Causal full-sequence attention and past-only streaming attention."""
|
| 281 |
+
|
| 282 |
+
_block_mask_cache: dict[tuple, BlockMask] = {}
|
| 283 |
+
|
| 284 |
+
def __init__(self, local_attn_size: int = -1, sink_size: int = 0) -> None:
|
| 285 |
+
if local_attn_size == 0 or local_attn_size < -1:
|
| 286 |
+
raise ValueError("local_attn_size must be -1 or a positive frame count")
|
| 287 |
+
if sink_size < 0:
|
| 288 |
+
raise ValueError("sink_size must be non-negative")
|
| 289 |
+
super().__init__(i4_attention_op)
|
| 290 |
+
self.local_attn_size = local_attn_size
|
| 291 |
+
self.sink_size = sink_size
|
| 292 |
+
self.num_frame_per_block = 1
|
| 293 |
+
self.independent_first_frame = False
|
| 294 |
+
|
| 295 |
+
def reset_kv_cache(self, max_cache_size: Optional[int] = None) -> None:
|
| 296 |
+
self.k_cache: Optional[torch.Tensor] = None
|
| 297 |
+
self.v_cache: Optional[torch.Tensor] = None
|
| 298 |
+
self._cache_slot_by_frame: dict[int, int] = {}
|
| 299 |
+
self._cache_frame_by_slot: dict[int, int] = {}
|
| 300 |
+
self._cache_tokens_per_frame: Optional[int] = None
|
| 301 |
+
self._cache_slot_indices: dict[tuple[int, ...], torch.Tensor] = {}
|
| 302 |
+
self.max_cache_size = max_cache_size
|
| 303 |
+
|
| 304 |
+
def _cache_capacity(self, required_frames: int) -> int:
|
| 305 |
+
# Training may retain more frames than the logical attention window so
|
| 306 |
+
# activation-checkpoint recomputation sees the original cache history.
|
| 307 |
+
local_capacity = (
|
| 308 |
+
self.sink_size + self.local_attn_size
|
| 309 |
+
if self.local_attn_size > 0
|
| 310 |
+
else 1
|
| 311 |
+
)
|
| 312 |
+
return max(
|
| 313 |
+
required_frames,
|
| 314 |
+
local_capacity,
|
| 315 |
+
self.max_cache_size or 0,
|
| 316 |
+
)
|
| 317 |
+
|
| 318 |
+
def _initialize_cache_storage(
|
| 319 |
+
self,
|
| 320 |
+
value: torch.Tensor,
|
| 321 |
+
tokens_per_frame: int,
|
| 322 |
+
required_frames: int,
|
| 323 |
+
) -> None:
|
| 324 |
+
capacity = self._cache_capacity(required_frames)
|
| 325 |
+
shape = (
|
| 326 |
+
value.shape[0],
|
| 327 |
+
capacity,
|
| 328 |
+
tokens_per_frame,
|
| 329 |
+
value.shape[2],
|
| 330 |
+
value.shape[3],
|
| 331 |
+
)
|
| 332 |
+
self.k_cache = torch.empty(shape, device=value.device, dtype=value.dtype)
|
| 333 |
+
self.v_cache = torch.empty(shape, device=value.device, dtype=value.dtype)
|
| 334 |
+
self._cache_tokens_per_frame = tokens_per_frame
|
| 335 |
+
|
| 336 |
+
def _cache_slot(self, frame_index: int) -> int:
|
| 337 |
+
if self.k_cache is None:
|
| 338 |
+
raise RuntimeError("Cosmos K/V cache storage is not initialized")
|
| 339 |
+
capacity = self.k_cache.shape[1]
|
| 340 |
+
if frame_index < self.sink_size:
|
| 341 |
+
return frame_index
|
| 342 |
+
recent_capacity = capacity - self.sink_size
|
| 343 |
+
if recent_capacity <= 0:
|
| 344 |
+
raise RuntimeError("Cosmos K/V cache has no recent-history capacity")
|
| 345 |
+
return self.sink_size + (
|
| 346 |
+
(frame_index - self.sink_size) % recent_capacity
|
| 347 |
+
)
|
| 348 |
+
|
| 349 |
+
def _store_cache_frame(
|
| 350 |
+
self,
|
| 351 |
+
frame_index: int,
|
| 352 |
+
k: torch.Tensor,
|
| 353 |
+
v: torch.Tensor,
|
| 354 |
+
) -> None:
|
| 355 |
+
tokens_per_frame = k.shape[1]
|
| 356 |
+
if self.k_cache is None or self.v_cache is None:
|
| 357 |
+
self._initialize_cache_storage(
|
| 358 |
+
k,
|
| 359 |
+
tokens_per_frame,
|
| 360 |
+
frame_index + 1,
|
| 361 |
+
)
|
| 362 |
+
if self._cache_tokens_per_frame != tokens_per_frame:
|
| 363 |
+
raise ValueError("Cosmos K/V cache token geometry changed")
|
| 364 |
+
if (
|
| 365 |
+
self.k_cache.shape[0] != k.shape[0]
|
| 366 |
+
or self.k_cache.shape[3:] != k.shape[2:]
|
| 367 |
+
or self.k_cache.device != k.device
|
| 368 |
+
or self.k_cache.dtype != k.dtype
|
| 369 |
+
):
|
| 370 |
+
raise ValueError("Cosmos K/V cache tensor geometry changed")
|
| 371 |
+
|
| 372 |
+
slot = self._cache_slot(frame_index)
|
| 373 |
+
replaced_frame = self._cache_frame_by_slot.get(slot)
|
| 374 |
+
if replaced_frame is not None:
|
| 375 |
+
self._cache_slot_by_frame.pop(replaced_frame, None)
|
| 376 |
+
self.k_cache[:, slot].copy_(k.detach())
|
| 377 |
+
self.v_cache[:, slot].copy_(v.detach())
|
| 378 |
+
self._cache_slot_by_frame[frame_index] = slot
|
| 379 |
+
self._cache_frame_by_slot[slot] = frame_index
|
| 380 |
+
|
| 381 |
+
def _read_cache_frames(
|
| 382 |
+
self,
|
| 383 |
+
frame_indices: list[int],
|
| 384 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 385 |
+
if self.k_cache is None or self.v_cache is None:
|
| 386 |
+
raise RuntimeError("Cosmos K/V cache is empty")
|
| 387 |
+
missing = [
|
| 388 |
+
index for index in frame_indices
|
| 389 |
+
if index not in self._cache_slot_by_frame
|
| 390 |
+
]
|
| 391 |
+
if missing:
|
| 392 |
+
raise RuntimeError(
|
| 393 |
+
f"Cosmos KV cache is missing frames: {missing[:4]}"
|
| 394 |
+
)
|
| 395 |
+
slots = tuple(self._cache_slot_by_frame[index] for index in frame_indices)
|
| 396 |
+
slot_indices = self._cache_slot_indices.get(slots)
|
| 397 |
+
if slot_indices is None:
|
| 398 |
+
slot_indices = torch.tensor(
|
| 399 |
+
slots,
|
| 400 |
+
device=self.k_cache.device,
|
| 401 |
+
dtype=torch.long,
|
| 402 |
+
)
|
| 403 |
+
self._cache_slot_indices[slots] = slot_indices
|
| 404 |
+
cached_k = self.k_cache.index_select(1, slot_indices).flatten(1, 2)
|
| 405 |
+
cached_v = self.v_cache.index_select(1, slot_indices).flatten(1, 2)
|
| 406 |
+
return cached_k, cached_v
|
| 407 |
+
|
| 408 |
+
def _full_sequence_attention(
|
| 409 |
+
self,
|
| 410 |
+
q: torch.Tensor,
|
| 411 |
+
k: torch.Tensor,
|
| 412 |
+
v: torch.Tensor,
|
| 413 |
+
video_size: VideoSize,
|
| 414 |
+
) -> torch.Tensor:
|
| 415 |
+
if q.shape != k.shape or k.shape != v.shape:
|
| 416 |
+
raise ValueError("Full causal attention requires matching Q/K/V shapes")
|
| 417 |
+
tokens_per_frame = video_size.H * video_size.W
|
| 418 |
+
expected_tokens = video_size.T * tokens_per_frame
|
| 419 |
+
if q.shape[1] != expected_tokens:
|
| 420 |
+
raise ValueError(
|
| 421 |
+
f"Expected {expected_tokens} video tokens, received {q.shape[1]}"
|
| 422 |
+
)
|
| 423 |
+
block_mask, padded_length = self._block_causal_mask(
|
| 424 |
+
device=q.device,
|
| 425 |
+
num_frames=video_size.T,
|
| 426 |
+
tokens_per_frame=tokens_per_frame,
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
if padded_length:
|
| 430 |
+
padding = q.new_zeros(
|
| 431 |
+
q.shape[0], padded_length, q.shape[2], q.shape[3]
|
| 432 |
+
)
|
| 433 |
+
q = torch.cat([q, padding], dim=1)
|
| 434 |
+
k = torch.cat([k, padding], dim=1)
|
| 435 |
+
v = torch.cat([v, padding], dim=1)
|
| 436 |
+
|
| 437 |
+
output = flex_attention(
|
| 438 |
+
query=q.transpose(1, 2),
|
| 439 |
+
key=k.transpose(1, 2),
|
| 440 |
+
value=v.transpose(1, 2),
|
| 441 |
+
block_mask=block_mask,
|
| 442 |
+
).transpose(1, 2)
|
| 443 |
+
if padded_length:
|
| 444 |
+
output = output[:, :-padded_length]
|
| 445 |
+
return output.flatten(2)
|
| 446 |
+
|
| 447 |
+
def _block_causal_mask(
|
| 448 |
+
self,
|
| 449 |
+
*,
|
| 450 |
+
device: torch.device,
|
| 451 |
+
num_frames: int,
|
| 452 |
+
tokens_per_frame: int,
|
| 453 |
+
) -> tuple[BlockMask, int]:
|
| 454 |
+
total_length = num_frames * tokens_per_frame
|
| 455 |
+
padded_length = math.ceil(total_length / 128) * 128 - total_length
|
| 456 |
+
cache_key = (
|
| 457 |
+
str(device),
|
| 458 |
+
num_frames,
|
| 459 |
+
tokens_per_frame,
|
| 460 |
+
self.num_frame_per_block,
|
| 461 |
+
self.independent_first_frame,
|
| 462 |
+
self.local_attn_size,
|
| 463 |
+
self.sink_size,
|
| 464 |
+
)
|
| 465 |
+
if cache_key in self._block_mask_cache:
|
| 466 |
+
return self._block_mask_cache[cache_key], padded_length
|
| 467 |
+
|
| 468 |
+
padded_total = total_length + padded_length
|
| 469 |
+
block_tokens = self.num_frame_per_block * tokens_per_frame
|
| 470 |
+
prefix_tokens = tokens_per_frame if self.independent_first_frame else 0
|
| 471 |
+
use_local_attn = self.local_attn_size > 0
|
| 472 |
+
sink_tokens = max(self.sink_size, 0) * tokens_per_frame
|
| 473 |
+
window_tokens = (
|
| 474 |
+
max(self.local_attn_size - self.sink_size, 0)
|
| 475 |
+
* tokens_per_frame
|
| 476 |
+
)
|
| 477 |
+
|
| 478 |
+
query_ranges = _full_sequence_query_ranges(
|
| 479 |
+
total_length=total_length,
|
| 480 |
+
tokens_per_frame=tokens_per_frame,
|
| 481 |
+
num_frame_per_block=self.num_frame_per_block,
|
| 482 |
+
independent_first_frame=self.independent_first_frame,
|
| 483 |
+
local_attn_size=self.local_attn_size,
|
| 484 |
+
sink_size=self.sink_size,
|
| 485 |
+
)
|
| 486 |
+
|
| 487 |
+
def block_index(position):
|
| 488 |
+
if prefix_tokens == 0:
|
| 489 |
+
return position // block_tokens
|
| 490 |
+
is_prefix = position < prefix_tokens
|
| 491 |
+
generated_block = (position - prefix_tokens) // block_tokens + 1
|
| 492 |
+
return torch.where(
|
| 493 |
+
is_prefix,
|
| 494 |
+
torch.zeros_like(position),
|
| 495 |
+
generated_block,
|
| 496 |
+
)
|
| 497 |
+
|
| 498 |
+
def block_bounds(index):
|
| 499 |
+
if prefix_tokens == 0:
|
| 500 |
+
start = index * block_tokens
|
| 501 |
+
return start, start + block_tokens
|
| 502 |
+
is_prefix = index == 0
|
| 503 |
+
generated_block = index - 1
|
| 504 |
+
start = prefix_tokens + generated_block * block_tokens
|
| 505 |
+
end = start + block_tokens
|
| 506 |
+
return (
|
| 507 |
+
torch.where(is_prefix, torch.zeros_like(index), start),
|
| 508 |
+
torch.where(
|
| 509 |
+
is_prefix,
|
| 510 |
+
torch.full_like(index, prefix_tokens),
|
| 511 |
+
end,
|
| 512 |
+
),
|
| 513 |
+
)
|
| 514 |
+
|
| 515 |
+
def attention_mask(_batch, _head, query_index, key_index):
|
| 516 |
+
valid = (query_index < total_length) & (key_index < total_length)
|
| 517 |
+
query_block = block_index(query_index)
|
| 518 |
+
key_block = block_index(key_index)
|
| 519 |
+
query_start, query_end = block_bounds(query_block)
|
| 520 |
+
allowed = key_block <= query_block
|
| 521 |
+
if use_local_attn:
|
| 522 |
+
zero = query_end - query_end
|
| 523 |
+
recent_start = torch.maximum(
|
| 524 |
+
query_end - window_tokens,
|
| 525 |
+
zero,
|
| 526 |
+
)
|
| 527 |
+
promoted_sink_end = torch.minimum(
|
| 528 |
+
torch.minimum(query_start, zero + sink_tokens),
|
| 529 |
+
recent_start,
|
| 530 |
+
)
|
| 531 |
+
recent_start = torch.maximum(promoted_sink_end, recent_start)
|
| 532 |
+
allowed = allowed & (
|
| 533 |
+
(key_index < promoted_sink_end)
|
| 534 |
+
| (
|
| 535 |
+
(key_index >= recent_start)
|
| 536 |
+
& (key_index < query_end)
|
| 537 |
+
)
|
| 538 |
+
)
|
| 539 |
+
return valid & allowed
|
| 540 |
+
|
| 541 |
+
block_mask = _block_mask_from_intervals(
|
| 542 |
+
query_ranges=query_ranges,
|
| 543 |
+
q_total=padded_total,
|
| 544 |
+
kv_total=padded_total,
|
| 545 |
+
mask_mod=attention_mask,
|
| 546 |
+
device=device,
|
| 547 |
+
)
|
| 548 |
+
self._block_mask_cache[cache_key] = block_mask
|
| 549 |
+
if not dist.is_initialized() or dist.get_rank() == 0:
|
| 550 |
+
print(
|
| 551 |
+
"Cached Cosmos block-causal attention mask: "
|
| 552 |
+
f"frames={num_frames}, block_frames={self.num_frame_per_block}, "
|
| 553 |
+
f"tokens_per_frame={tokens_per_frame}",
|
| 554 |
+
flush=True,
|
| 555 |
+
)
|
| 556 |
+
return block_mask, padded_length
|
| 557 |
+
|
| 558 |
+
def _packed_score_attention(
|
| 559 |
+
self,
|
| 560 |
+
q: torch.Tensor,
|
| 561 |
+
k: torch.Tensor,
|
| 562 |
+
v: torch.Tensor,
|
| 563 |
+
teacher_forcing_layout: tuple[int, int],
|
| 564 |
+
video_size: VideoSize,
|
| 565 |
+
) -> torch.Tensor:
|
| 566 |
+
"""Run retained history and current targets through one causal mask."""
|
| 567 |
+
context_frames, noisy_start_frame = teacher_forcing_layout
|
| 568 |
+
if context_frames != noisy_start_frame:
|
| 569 |
+
raise ValueError("Packed score history must precede current targets")
|
| 570 |
+
return self._full_sequence_attention(q, k, v, video_size)
|
| 571 |
+
|
| 572 |
+
def _history_indices(
|
| 573 |
+
self,
|
| 574 |
+
current_idx: int,
|
| 575 |
+
current_frames: int = 1,
|
| 576 |
+
) -> list[int]:
|
| 577 |
+
if current_frames <= 0:
|
| 578 |
+
raise ValueError("current_frames must be positive")
|
| 579 |
+
window = self.local_attn_size
|
| 580 |
+
if window == -1:
|
| 581 |
+
recent_start = self.sink_size
|
| 582 |
+
else:
|
| 583 |
+
recent_window = max(window - self.sink_size, 0)
|
| 584 |
+
recent_history = max(recent_window - current_frames, 0)
|
| 585 |
+
recent_start = max(
|
| 586 |
+
self.sink_size,
|
| 587 |
+
current_idx - recent_history,
|
| 588 |
+
)
|
| 589 |
+
sink = range(min(self.sink_size, current_idx, recent_start))
|
| 590 |
+
recent = range(recent_start, current_idx)
|
| 591 |
+
return list(sink) + list(recent)
|
| 592 |
+
|
| 593 |
+
def forward(
|
| 594 |
+
self,
|
| 595 |
+
q: torch.Tensor,
|
| 596 |
+
k: torch.Tensor,
|
| 597 |
+
v: torch.Tensor,
|
| 598 |
+
*,
|
| 599 |
+
kv_cache_cfg: Optional[KVCacheConfig] = None,
|
| 600 |
+
video_size: Optional[VideoSize] = None,
|
| 601 |
+
teacher_forcing_layout: Optional[tuple[int, int]] = None,
|
| 602 |
+
**kwargs,
|
| 603 |
+
) -> torch.Tensor:
|
| 604 |
+
del kwargs
|
| 605 |
+
if teacher_forcing_layout is not None:
|
| 606 |
+
if kv_cache_cfg is not None and kv_cache_cfg.run_with_kv:
|
| 607 |
+
raise ValueError("Teacher forcing cannot be combined with KV caching")
|
| 608 |
+
if video_size is None:
|
| 609 |
+
raise ValueError("video_size is required for teacher forcing")
|
| 610 |
+
return self._packed_score_attention(
|
| 611 |
+
q,
|
| 612 |
+
k,
|
| 613 |
+
v,
|
| 614 |
+
teacher_forcing_layout,
|
| 615 |
+
video_size,
|
| 616 |
+
)
|
| 617 |
+
if kv_cache_cfg is None or not kv_cache_cfg.run_with_kv:
|
| 618 |
+
if video_size is None:
|
| 619 |
+
raise ValueError("video_size is required for causal Cosmos attention")
|
| 620 |
+
return self._full_sequence_attention(q, k, v, video_size)
|
| 621 |
+
|
| 622 |
+
current_idx = int(kv_cache_cfg.current_idx)
|
| 623 |
+
if kv_cache_cfg.store_kv and video_size is not None and video_size.T > 1:
|
| 624 |
+
tokens_per_frame = video_size.H * video_size.W
|
| 625 |
+
for frame_offset in range(video_size.T):
|
| 626 |
+
start = frame_offset * tokens_per_frame
|
| 627 |
+
end = start + tokens_per_frame
|
| 628 |
+
self._store_cache_frame(
|
| 629 |
+
current_idx + frame_offset,
|
| 630 |
+
k[:, start:end],
|
| 631 |
+
v[:, start:end],
|
| 632 |
+
)
|
| 633 |
+
return self._full_sequence_attention(q, k, v, video_size)
|
| 634 |
+
|
| 635 |
+
if kv_cache_cfg.store_kv:
|
| 636 |
+
self._store_cache_frame(current_idx, k, v)
|
| 637 |
+
|
| 638 |
+
current_frames = video_size.T if video_size is not None else 1
|
| 639 |
+
history_indices = self._history_indices(
|
| 640 |
+
current_idx,
|
| 641 |
+
current_frames=current_frames,
|
| 642 |
+
)
|
| 643 |
+
if history_indices:
|
| 644 |
+
history_k, history_v = self._read_cache_frames(history_indices)
|
| 645 |
+
else:
|
| 646 |
+
history_k = history_v = None
|
| 647 |
+
if history_k is not None and history_k.shape[0] != k.shape[0]:
|
| 648 |
+
if history_k.shape[0] != 1:
|
| 649 |
+
raise ValueError(
|
| 650 |
+
"Cached Cosmos batch cannot be broadcast to the current batch"
|
| 651 |
+
)
|
| 652 |
+
history_k = history_k.expand(k.shape[0], *history_k.shape[1:])
|
| 653 |
+
history_v = history_v.expand(v.shape[0], *history_v.shape[1:])
|
| 654 |
+
if history_k is not None:
|
| 655 |
+
k = torch.cat((history_k, k), dim=1)
|
| 656 |
+
v = torch.cat((history_v, v), dim=1)
|
| 657 |
+
return i4_attention_op(q, k, v)
|
| 658 |
+
|
| 659 |
+
def set_context_parallel_group(self, *args, **kwargs) -> None:
|
| 660 |
+
del args, kwargs
|
| 661 |
+
|
| 662 |
+
|
| 663 |
+
class CausalCosmosModel(MinimalV1LVGDiT):
|
| 664 |
+
"""Weight-compatible causal variant of the bidirectional Cosmos 2.5 DiT."""
|
| 665 |
+
|
| 666 |
+
def __init__(
|
| 667 |
+
self,
|
| 668 |
+
*args,
|
| 669 |
+
local_attn_size: int = -1,
|
| 670 |
+
sink_size: int = 0,
|
| 671 |
+
**kwargs,
|
| 672 |
+
) -> None:
|
| 673 |
+
super().__init__(*args, **kwargs)
|
| 674 |
+
self._num_frame_per_block = 1
|
| 675 |
+
self._independent_first_frame = False
|
| 676 |
+
self.causal_attention_ops = []
|
| 677 |
+
for block in self.blocks:
|
| 678 |
+
attention_op = CausalCosmosAttention(
|
| 679 |
+
local_attn_size=local_attn_size,
|
| 680 |
+
sink_size=sink_size,
|
| 681 |
+
)
|
| 682 |
+
block.self_attn.attn_op = attention_op
|
| 683 |
+
self.causal_attention_ops.append(attention_op)
|
| 684 |
+
|
| 685 |
+
@property
|
| 686 |
+
def num_frame_per_block(self) -> int:
|
| 687 |
+
return self._num_frame_per_block
|
| 688 |
+
|
| 689 |
+
@num_frame_per_block.setter
|
| 690 |
+
def num_frame_per_block(self, value: int) -> None:
|
| 691 |
+
if value <= 0:
|
| 692 |
+
raise ValueError("num_frame_per_block must be positive")
|
| 693 |
+
self._num_frame_per_block = value
|
| 694 |
+
for attention_op in getattr(self, "causal_attention_ops", []):
|
| 695 |
+
attention_op.num_frame_per_block = value
|
| 696 |
+
|
| 697 |
+
@property
|
| 698 |
+
def independent_first_frame(self) -> bool:
|
| 699 |
+
return self._independent_first_frame
|
| 700 |
+
|
| 701 |
+
@independent_first_frame.setter
|
| 702 |
+
def independent_first_frame(self, value: bool) -> None:
|
| 703 |
+
self._independent_first_frame = bool(value)
|
| 704 |
+
for attention_op in getattr(self, "causal_attention_ops", []):
|
| 705 |
+
attention_op.independent_first_frame = bool(value)
|
| 706 |
+
|
| 707 |
+
def forward_teacher_forcing(
|
| 708 |
+
self,
|
| 709 |
+
noisy_x_B_C_T_H_W: torch.Tensor,
|
| 710 |
+
clean_x_B_C_T_H_W: torch.Tensor,
|
| 711 |
+
noisy_timesteps_B_T: torch.Tensor,
|
| 712 |
+
clean_timesteps_B_T: torch.Tensor,
|
| 713 |
+
crossattn_emb: torch.Tensor,
|
| 714 |
+
*,
|
| 715 |
+
fps: Optional[torch.Tensor] = None,
|
| 716 |
+
padding_mask: Optional[torch.Tensor] = None,
|
| 717 |
+
condition_video_input_mask_B_C_T_H_W: Optional[torch.Tensor] = None,
|
| 718 |
+
clean_condition_video_input_mask_B_C_T_H_W: Optional[torch.Tensor] = None,
|
| 719 |
+
camera_condition_B_C_T_H_W: Optional[torch.Tensor] = None,
|
| 720 |
+
noisy_start_frame: int,
|
| 721 |
+
) -> torch.Tensor:
|
| 722 |
+
"""Score packed history and all current noisy targets in one pass."""
|
| 723 |
+
if (
|
| 724 |
+
condition_video_input_mask_B_C_T_H_W is None
|
| 725 |
+
or clean_condition_video_input_mask_B_C_T_H_W is None
|
| 726 |
+
):
|
| 727 |
+
raise ValueError("Noisy and clean condition masks are required")
|
| 728 |
+
if noisy_timesteps_B_T.ndim == 1:
|
| 729 |
+
noisy_timesteps_B_T = noisy_timesteps_B_T.unsqueeze(1)
|
| 730 |
+
if clean_timesteps_B_T.ndim == 1:
|
| 731 |
+
clean_timesteps_B_T = clean_timesteps_B_T.unsqueeze(1)
|
| 732 |
+
noisy_frames = noisy_x_B_C_T_H_W.shape[2]
|
| 733 |
+
context_frames = clean_x_B_C_T_H_W.shape[2]
|
| 734 |
+
if (
|
| 735 |
+
noisy_x_B_C_T_H_W.shape[:2] != clean_x_B_C_T_H_W.shape[:2]
|
| 736 |
+
or noisy_x_B_C_T_H_W.shape[-2:] != clean_x_B_C_T_H_W.shape[-2:]
|
| 737 |
+
or context_frames != noisy_start_frame
|
| 738 |
+
):
|
| 739 |
+
raise ValueError("Packed scoring requires history before noisy targets")
|
| 740 |
+
if not 0 < noisy_start_frame < noisy_frames:
|
| 741 |
+
raise ValueError("noisy_start_frame must select a non-empty suffix")
|
| 742 |
+
if noisy_timesteps_B_T.shape != (noisy_x_B_C_T_H_W.shape[0], noisy_frames):
|
| 743 |
+
raise ValueError("Noisy timesteps must cover every noisy frame")
|
| 744 |
+
if clean_timesteps_B_T.shape != (clean_x_B_C_T_H_W.shape[0], context_frames):
|
| 745 |
+
raise ValueError("Clean timesteps must cover every history frame")
|
| 746 |
+
|
| 747 |
+
noisy_input = torch.cat(
|
| 748 |
+
[
|
| 749 |
+
noisy_x_B_C_T_H_W,
|
| 750 |
+
condition_video_input_mask_B_C_T_H_W.type_as(
|
| 751 |
+
noisy_x_B_C_T_H_W
|
| 752 |
+
),
|
| 753 |
+
],
|
| 754 |
+
dim=1,
|
| 755 |
+
)
|
| 756 |
+
clean_input = torch.cat(
|
| 757 |
+
[
|
| 758 |
+
clean_x_B_C_T_H_W,
|
| 759 |
+
clean_condition_video_input_mask_B_C_T_H_W.type_as(
|
| 760 |
+
clean_x_B_C_T_H_W
|
| 761 |
+
),
|
| 762 |
+
],
|
| 763 |
+
dim=1,
|
| 764 |
+
)
|
| 765 |
+
noisy_hidden, noisy_rope, noisy_extra_pos = self.prepare_embedded_sequence(
|
| 766 |
+
noisy_input,
|
| 767 |
+
fps=fps,
|
| 768 |
+
padding_mask=padding_mask,
|
| 769 |
+
)
|
| 770 |
+
with torch.no_grad():
|
| 771 |
+
clean_hidden, clean_rope, clean_extra_pos = (
|
| 772 |
+
self.prepare_embedded_sequence(
|
| 773 |
+
clean_input,
|
| 774 |
+
fps=fps,
|
| 775 |
+
padding_mask=padding_mask,
|
| 776 |
+
)
|
| 777 |
+
)
|
| 778 |
+
if clean_hidden.shape[:1] + clean_hidden.shape[2:] != noisy_hidden.shape[:1] + noisy_hidden.shape[2:]:
|
| 779 |
+
raise ValueError("Embedded history and noisy video grids must match")
|
| 780 |
+
if noisy_rope is None or clean_rope is None:
|
| 781 |
+
raise ValueError("Causal Cosmos teacher forcing requires RoPE")
|
| 782 |
+
tokens_per_frame = noisy_hidden.shape[2] * noisy_hidden.shape[3]
|
| 783 |
+
target_hidden = noisy_hidden[:, noisy_start_frame:]
|
| 784 |
+
packed_hidden = torch.cat([clean_hidden, target_hidden], dim=1)
|
| 785 |
+
packed_rope = torch.cat(
|
| 786 |
+
[
|
| 787 |
+
clean_rope,
|
| 788 |
+
noisy_rope[noisy_start_frame * tokens_per_frame:],
|
| 789 |
+
],
|
| 790 |
+
dim=0,
|
| 791 |
+
)
|
| 792 |
+
|
| 793 |
+
def pack_optional(clean_value, noisy_value):
|
| 794 |
+
if clean_value is None or noisy_value is None:
|
| 795 |
+
if clean_value is not None or noisy_value is not None:
|
| 796 |
+
raise ValueError("Packed score embeddings must match")
|
| 797 |
+
return None
|
| 798 |
+
return torch.cat(
|
| 799 |
+
[clean_value, noisy_value[:, noisy_start_frame:]], dim=1
|
| 800 |
+
)
|
| 801 |
+
|
| 802 |
+
packed_extra_pos = pack_optional(clean_extra_pos, noisy_extra_pos)
|
| 803 |
+
|
| 804 |
+
packed_camera = None
|
| 805 |
+
if camera_condition_B_C_T_H_W is not None:
|
| 806 |
+
camera = camera_condition_B_C_T_H_W.permute(
|
| 807 |
+
0, 2, 3, 4, 1
|
| 808 |
+
).contiguous()
|
| 809 |
+
if camera.shape[:4] != noisy_hidden.shape[:4]:
|
| 810 |
+
raise ValueError(
|
| 811 |
+
"Camera conditioning does not match the teacher-forcing grid: "
|
| 812 |
+
f"{tuple(camera.shape)} versus {tuple(noisy_hidden.shape)}"
|
| 813 |
+
)
|
| 814 |
+
packed_camera = torch.cat(
|
| 815 |
+
[camera[:, :context_frames], camera[:, noisy_start_frame:]],
|
| 816 |
+
dim=1,
|
| 817 |
+
)
|
| 818 |
+
|
| 819 |
+
if self.use_crossattn_projection:
|
| 820 |
+
crossattn_emb = self.crossattn_proj(crossattn_emb)
|
| 821 |
+
|
| 822 |
+
noisy_timesteps_B_T = noisy_timesteps_B_T * self.timestep_scale
|
| 823 |
+
clean_timesteps_B_T = clean_timesteps_B_T * self.timestep_scale
|
| 824 |
+
noisy_time, noisy_adaln_lora = self.t_embedder(noisy_timesteps_B_T)
|
| 825 |
+
noisy_time = self.t_embedding_norm(noisy_time)
|
| 826 |
+
with torch.no_grad():
|
| 827 |
+
clean_time, clean_adaln_lora = self.t_embedder(
|
| 828 |
+
clean_timesteps_B_T
|
| 829 |
+
)
|
| 830 |
+
clean_time = self.t_embedding_norm(clean_time)
|
| 831 |
+
packed_time = torch.cat(
|
| 832 |
+
[clean_time, noisy_time[:, noisy_start_frame:]], dim=1
|
| 833 |
+
)
|
| 834 |
+
packed_adaln_lora = pack_optional(clean_adaln_lora, noisy_adaln_lora)
|
| 835 |
+
|
| 836 |
+
layout = (context_frames, noisy_start_frame)
|
| 837 |
+
for block in self.blocks:
|
| 838 |
+
packed_hidden = block(
|
| 839 |
+
packed_hidden,
|
| 840 |
+
packed_time,
|
| 841 |
+
crossattn_emb,
|
| 842 |
+
rope_emb_L_1_1_D=packed_rope,
|
| 843 |
+
adaln_lora_B_T_3D=packed_adaln_lora,
|
| 844 |
+
extra_per_block_pos_emb=packed_extra_pos,
|
| 845 |
+
camera_B_T_H_W_C=packed_camera,
|
| 846 |
+
teacher_forcing_layout=layout,
|
| 847 |
+
)
|
| 848 |
+
packed_hidden = torch.cat(
|
| 849 |
+
[
|
| 850 |
+
packed_hidden[:, :context_frames].detach(),
|
| 851 |
+
packed_hidden[:, context_frames:],
|
| 852 |
+
],
|
| 853 |
+
dim=1,
|
| 854 |
+
)
|
| 855 |
+
|
| 856 |
+
output = self.final_layer(
|
| 857 |
+
packed_hidden[:, context_frames:],
|
| 858 |
+
packed_time[:, context_frames:],
|
| 859 |
+
adaln_lora_B_T_3D=(
|
| 860 |
+
packed_adaln_lora[:, context_frames:]
|
| 861 |
+
if packed_adaln_lora is not None else None
|
| 862 |
+
),
|
| 863 |
+
)
|
| 864 |
+
scored_suffix = self.unpatchify(output)
|
| 865 |
+
prefix = scored_suffix.new_zeros(
|
| 866 |
+
scored_suffix.shape[0],
|
| 867 |
+
scored_suffix.shape[1],
|
| 868 |
+
noisy_start_frame,
|
| 869 |
+
scored_suffix.shape[3],
|
| 870 |
+
scored_suffix.shape[4],
|
| 871 |
+
)
|
| 872 |
+
return torch.cat([prefix, scored_suffix], dim=2)
|
| 873 |
+
|
| 874 |
+
def forward_seq(
|
| 875 |
+
self,
|
| 876 |
+
x_B_C_T_H_W: torch.Tensor,
|
| 877 |
+
video_pos: VideoSeqPos,
|
| 878 |
+
timesteps_B_T: torch.Tensor,
|
| 879 |
+
crossattn_emb: torch.Tensor,
|
| 880 |
+
*,
|
| 881 |
+
fps: Optional[torch.Tensor] = None,
|
| 882 |
+
padding_mask: Optional[torch.Tensor] = None,
|
| 883 |
+
condition_video_input_mask_B_C_T_H_W: Optional[torch.Tensor] = None,
|
| 884 |
+
camera_condition_B_C_T_H_W: Optional[torch.Tensor] = None,
|
| 885 |
+
full_video_size: Optional[tuple[int, int, int]] = None,
|
| 886 |
+
kv_cache_cfg: Optional[KVCacheConfig] = None,
|
| 887 |
+
) -> torch.Tensor:
|
| 888 |
+
"""Run one causal sequence chunk using the same blocks and weights."""
|
| 889 |
+
if condition_video_input_mask_B_C_T_H_W is None:
|
| 890 |
+
raise ValueError("condition_video_input_mask_B_C_T_H_W is required")
|
| 891 |
+
|
| 892 |
+
x_B_C_T_H_W = torch.cat(
|
| 893 |
+
[
|
| 894 |
+
x_B_C_T_H_W,
|
| 895 |
+
condition_video_input_mask_B_C_T_H_W.type_as(x_B_C_T_H_W),
|
| 896 |
+
],
|
| 897 |
+
dim=1,
|
| 898 |
+
)
|
| 899 |
+
x_B_T_H_W_D, _, _ = self.prepare_embedded_sequence(
|
| 900 |
+
x_B_C_T_H_W,
|
| 901 |
+
fps=fps,
|
| 902 |
+
padding_mask=padding_mask,
|
| 903 |
+
)
|
| 904 |
+
_, token_t, token_h, token_w, _ = x_B_T_H_W_D.shape
|
| 905 |
+
if token_t * token_h * token_w != video_pos.size():
|
| 906 |
+
raise ValueError("Cosmos sequence positions do not match the input tokens")
|
| 907 |
+
camera_B_T_H_W_C = None
|
| 908 |
+
if camera_condition_B_C_T_H_W is not None:
|
| 909 |
+
camera_B_T_H_W_C = camera_condition_B_C_T_H_W.permute(
|
| 910 |
+
0, 2, 3, 4, 1
|
| 911 |
+
).contiguous()
|
| 912 |
+
if camera_B_T_H_W_C.shape[:4] != x_B_T_H_W_D.shape[:4]:
|
| 913 |
+
raise ValueError(
|
| 914 |
+
"Camera conditioning does not match the causal video grid: "
|
| 915 |
+
f"{tuple(camera_B_T_H_W_C.shape)} versus {tuple(x_B_T_H_W_D.shape)}"
|
| 916 |
+
)
|
| 917 |
+
|
| 918 |
+
if self.use_crossattn_projection:
|
| 919 |
+
crossattn_emb = self.crossattn_proj(crossattn_emb)
|
| 920 |
+
|
| 921 |
+
if timesteps_B_T.ndim == 1:
|
| 922 |
+
timesteps_B_T = timesteps_B_T.unsqueeze(1)
|
| 923 |
+
timesteps_B_T = timesteps_B_T * self.timestep_scale
|
| 924 |
+
time_embedding, adaln_lora = self.t_embedder(timesteps_B_T)
|
| 925 |
+
time_embedding = self.t_embedding_norm(time_embedding)
|
| 926 |
+
|
| 927 |
+
if full_video_size is None:
|
| 928 |
+
full_t = int(video_pos.pos_t.max().item()) + 1
|
| 929 |
+
full_h = int(video_pos.pos_h.max().item()) + 1
|
| 930 |
+
full_w = int(video_pos.pos_w.max().item()) + 1
|
| 931 |
+
else:
|
| 932 |
+
full_t, full_h, full_w = full_video_size
|
| 933 |
+
rope = self.pos_embedder.generate_embeddings(
|
| 934 |
+
torch.Size([1, full_t, full_h, full_w, self.model_channels])
|
| 935 |
+
)
|
| 936 |
+
linear_index = (
|
| 937 |
+
video_pos.pos_t * (full_h * full_w)
|
| 938 |
+
+ video_pos.pos_h * full_w
|
| 939 |
+
+ video_pos.pos_w
|
| 940 |
+
)
|
| 941 |
+
rope = rope.index_select(0, linear_index.to(device=rope.device))
|
| 942 |
+
|
| 943 |
+
for block in self.blocks:
|
| 944 |
+
x_B_T_H_W_D = block(
|
| 945 |
+
x_B_T_H_W_D,
|
| 946 |
+
time_embedding,
|
| 947 |
+
crossattn_emb,
|
| 948 |
+
rope_emb_L_1_1_D=rope,
|
| 949 |
+
adaln_lora_B_T_3D=adaln_lora,
|
| 950 |
+
extra_per_block_pos_emb=None,
|
| 951 |
+
kv_cache_cfg=kv_cache_cfg,
|
| 952 |
+
camera_B_T_H_W_C=camera_B_T_H_W_C,
|
| 953 |
+
)
|
| 954 |
+
|
| 955 |
+
output = self.final_layer(
|
| 956 |
+
x_B_T_H_W_D,
|
| 957 |
+
time_embedding,
|
| 958 |
+
adaln_lora_B_T_3D=adaln_lora,
|
| 959 |
+
)
|
| 960 |
+
return self.unpatchify(output)
|
cosmos/kv_cache.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
from dataclasses import dataclass
|
| 19 |
+
from typing import Any, Optional
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
import torch.nn as nn
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class KVCacheConfig:
|
| 27 |
+
run_with_kv: bool = False
|
| 28 |
+
store_kv: bool = False
|
| 29 |
+
current_idx: int = 0
|
| 30 |
+
recompute_cross_attn_kv: bool = False
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class AttentionOpWithKVCache(nn.Module):
|
| 34 |
+
"""A thin wrapper that adds K/V caching to an existing attention op.
|
| 35 |
+
|
| 36 |
+
This wrapper expects the wrapped op to accept (q, k, v, attn_mask=None)
|
| 37 |
+
and return attention outputs with heads already flattened on the last dim.
|
| 38 |
+
|
| 39 |
+
Cache semantics:
|
| 40 |
+
- Cache entries are stored as per-chunk tensors, where each chunk corresponds
|
| 41 |
+
to one latent frame composed of HxW tokens (after patchify).
|
| 42 |
+
- The `max_cache_size` capacity therefore refers to the number of latent
|
| 43 |
+
frames (chunks), NOT the number of individual tokens.
|
| 44 |
+
- When `max_cache_size` is None, the cache grows without an automatic
|
| 45 |
+
rolling window; otherwise, it acts as a rolling window of at most
|
| 46 |
+
`max_cache_size` frames. Upon overflow, the oldest frames are dropped.
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
def __init__(self, attn_op: nn.Module | Any, max_cache_size: Optional[int] = None):
|
| 50 |
+
"""Initialize the KV cache wrapper.
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
attn_op: The underlying attention operation (q, k, v[, attn_mask]) -> out.
|
| 54 |
+
max_cache_size: Optional capacity measured in number of latent frames
|
| 55 |
+
(chunks). Each chunk is a single frame worth of HxW tokens. If None,
|
| 56 |
+
the cache does not enforce a rolling capacity.
|
| 57 |
+
"""
|
| 58 |
+
super().__init__()
|
| 59 |
+
self.attn_op = attn_op
|
| 60 |
+
self.reset_kv_cache(max_cache_size=max_cache_size)
|
| 61 |
+
self.pg: Optional[Any] = None
|
| 62 |
+
self.stream: Optional[Any] = None
|
| 63 |
+
|
| 64 |
+
def reset_kv_cache(self, max_cache_size: Optional[int] = None) -> None:
|
| 65 |
+
"""Reset/initialize the KV caches.
|
| 66 |
+
|
| 67 |
+
Args:
|
| 68 |
+
max_cache_size: Optional capacity measured in number of latent frames
|
| 69 |
+
(chunks). Each chunk is a single frame worth of HxW tokens. If None,
|
| 70 |
+
the cache does not enforce a rolling capacity.
|
| 71 |
+
"""
|
| 72 |
+
# Initialize list-based caches and optionally set capacity in chunks
|
| 73 |
+
self.start_idx = 0
|
| 74 |
+
self.k_cache: list[torch.Tensor | None] = [None] * (max_cache_size or 99999)
|
| 75 |
+
self.v_cache: list[torch.Tensor | None] = [None] * (max_cache_size or 99999)
|
| 76 |
+
self.max_cache_size = max_cache_size
|
| 77 |
+
|
| 78 |
+
def forward(
|
| 79 |
+
self,
|
| 80 |
+
q: torch.Tensor,
|
| 81 |
+
k: torch.Tensor,
|
| 82 |
+
v: torch.Tensor,
|
| 83 |
+
*,
|
| 84 |
+
kv_cache_cfg: KVCacheConfig,
|
| 85 |
+
**kwargs,
|
| 86 |
+
) -> torch.Tensor:
|
| 87 |
+
assert self.k_cache is not None and self.v_cache is not None, (
|
| 88 |
+
"KV cache is not initialized. Call reset_kv_cache() first."
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
# Store into cache at start_idx location (list-based)
|
| 92 |
+
if kv_cache_cfg.store_kv:
|
| 93 |
+
index = int(kv_cache_cfg.current_idx)
|
| 94 |
+
self.k_cache[index] = k.detach()
|
| 95 |
+
self.v_cache[index] = v.detach()
|
| 96 |
+
|
| 97 |
+
# Prepend cached prefix up to start_idx (list-based)
|
| 98 |
+
if kv_cache_cfg.run_with_kv and kv_cache_cfg.current_idx > 0:
|
| 99 |
+
history_k = self.k_cache[self.start_idx : kv_cache_cfg.current_idx]
|
| 100 |
+
history_v = self.v_cache[self.start_idx : kv_cache_cfg.current_idx]
|
| 101 |
+
assert not any(x is None for x in history_k)
|
| 102 |
+
assert not any(x is None for x in history_v)
|
| 103 |
+
k_out = torch.cat(history_k + [k], dim=1) # type: ignore
|
| 104 |
+
v_out = torch.cat(history_v + [v], dim=1) # type: ignore
|
| 105 |
+
else:
|
| 106 |
+
k_out = k
|
| 107 |
+
v_out = v
|
| 108 |
+
|
| 109 |
+
# Enforce rolling capacity in number of cached chunks (frames)
|
| 110 |
+
if kv_cache_cfg.run_with_kv and self.max_cache_size is not None:
|
| 111 |
+
# Instead of deleting, just update start_idx for rolling window
|
| 112 |
+
self.start_idx = max(0, int(kv_cache_cfg.current_idx) - self.max_cache_size)
|
| 113 |
+
|
| 114 |
+
return self.attn_op(q, k_out, v_out, **kwargs)
|
| 115 |
+
|
| 116 |
+
def set_context_parallel_group(self, process_group, ranks, stream, cp_comm_type: str = "p2p"):
|
| 117 |
+
self.attn_op.set_context_parallel_group(process_group, ranks, stream, cp_comm_type=cp_comm_type) # type: ignore
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
class VideoSeqPos:
|
| 121 |
+
"""Flattened 3D grid positions for a video clip.
|
| 122 |
+
|
| 123 |
+
Stores flattened t/h/w indices of length L = T*H*W to enable constructing
|
| 124 |
+
RoPE frequencies aligned with global positions across sequential chunks.
|
| 125 |
+
"""
|
| 126 |
+
|
| 127 |
+
def __init__(self, T: int, H: int, W: int, pos_h=None, pos_w=None, pos_t=None) -> None:
|
| 128 |
+
self.T = T
|
| 129 |
+
self.H = H
|
| 130 |
+
self.W = W
|
| 131 |
+
|
| 132 |
+
if pos_h is not None and pos_w is not None and pos_t is not None:
|
| 133 |
+
self.pos_h = pos_h.to(dtype=torch.long)
|
| 134 |
+
self.pos_w = pos_w.to(dtype=torch.long)
|
| 135 |
+
self.pos_t = pos_t.to(dtype=torch.long)
|
| 136 |
+
return
|
| 137 |
+
|
| 138 |
+
device = torch.device("cuda", torch.cuda.current_device()) if torch.cuda.is_available() else torch.device("cpu")
|
| 139 |
+
t = torch.arange(self.T, device=device, dtype=torch.long)
|
| 140 |
+
h = torch.arange(self.H, device=device, dtype=torch.long)
|
| 141 |
+
w = torch.arange(self.W, device=device, dtype=torch.long)
|
| 142 |
+
pos_t, pos_h, pos_w = torch.meshgrid(t, h, w, indexing="ij")
|
| 143 |
+
self.pos_t = pos_t.reshape(-1)
|
| 144 |
+
self.pos_h = pos_h.reshape(-1)
|
| 145 |
+
self.pos_w = pos_w.reshape(-1)
|
| 146 |
+
|
| 147 |
+
def size(self) -> int:
|
| 148 |
+
return int(self.pos_h.numel())
|
| 149 |
+
|
| 150 |
+
def frame(self, t_idx: int) -> "VideoSeqPos":
|
| 151 |
+
"""Return a `VideoSeqPos` view for a single frame at absolute index `t_idx`.
|
| 152 |
+
|
| 153 |
+
This is useful for streaming / KV-cache inference where the model is run on
|
| 154 |
+
one frame at a time but RoPE positions must reflect global video indices.
|
| 155 |
+
"""
|
| 156 |
+
t_idx = int(t_idx)
|
| 157 |
+
if t_idx < 0 or t_idx >= int(self.T):
|
| 158 |
+
raise IndexError(f"t_idx out of range: {t_idx} (valid: [0, {self.T}))")
|
| 159 |
+
tokens_per_frame = int(self.H) * int(self.W)
|
| 160 |
+
start = t_idx * tokens_per_frame
|
| 161 |
+
end = start + tokens_per_frame
|
| 162 |
+
return VideoSeqPos(
|
| 163 |
+
T=1,
|
| 164 |
+
H=int(self.H),
|
| 165 |
+
W=int(self.W),
|
| 166 |
+
pos_h=self.pos_h[start:end],
|
| 167 |
+
pos_w=self.pos_w[start:end],
|
| 168 |
+
pos_t=self.pos_t[start:end],
|
| 169 |
+
)
|
cosmos/minimal_v1_lvg_dit.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
# SPDX-FileCopyrightText: Modifications Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 17 |
+
# SPDX-License-Identifier: LicenseRef-NvidiaOneWayNoncommercial
|
| 18 |
+
|
| 19 |
+
from typing import List, Optional, Tuple
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
|
| 23 |
+
from cosmos.minimal_v4_dit import MiniTrainDIT
|
| 24 |
+
from cosmos.runtime import DataType
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class MinimalV1LVGDiT(MiniTrainDIT):
|
| 28 |
+
def __init__(self, *args, timestep_scale: float = 1.0, **kwargs):
|
| 29 |
+
assert "in_channels" in kwargs, "in_channels must be provided"
|
| 30 |
+
kwargs["in_channels"] += 1 # Add 1 for the condition mask
|
| 31 |
+
self.timestep_scale = timestep_scale
|
| 32 |
+
super().__init__(*args, **kwargs)
|
| 33 |
+
|
| 34 |
+
def forward(
|
| 35 |
+
self,
|
| 36 |
+
x_B_C_T_H_W: torch.Tensor,
|
| 37 |
+
timesteps_B_T: torch.Tensor,
|
| 38 |
+
crossattn_emb: torch.Tensor,
|
| 39 |
+
condition_video_input_mask_B_C_T_H_W: Optional[torch.Tensor] = None,
|
| 40 |
+
fps: Optional[torch.Tensor] = None,
|
| 41 |
+
padding_mask: Optional[torch.Tensor] = None,
|
| 42 |
+
data_type: Optional[DataType] = DataType.VIDEO,
|
| 43 |
+
intermediate_feature_ids: Optional[List[int]] = None,
|
| 44 |
+
img_context_emb: Optional[torch.Tensor] = None,
|
| 45 |
+
camera_condition_B_C_T_H_W: Optional[torch.Tensor] = None,
|
| 46 |
+
**kwargs,
|
| 47 |
+
) -> torch.Tensor | List[torch.Tensor] | Tuple[torch.Tensor, List[torch.Tensor]]:
|
| 48 |
+
del kwargs
|
| 49 |
+
|
| 50 |
+
if data_type == DataType.VIDEO:
|
| 51 |
+
x_B_C_T_H_W = torch.cat([x_B_C_T_H_W, condition_video_input_mask_B_C_T_H_W.type_as(x_B_C_T_H_W)], dim=1)
|
| 52 |
+
else:
|
| 53 |
+
B, _, T, H, W = x_B_C_T_H_W.shape
|
| 54 |
+
x_B_C_T_H_W = torch.cat(
|
| 55 |
+
[x_B_C_T_H_W, torch.zeros((B, 1, T, H, W), dtype=x_B_C_T_H_W.dtype, device=x_B_C_T_H_W.device)], dim=1
|
| 56 |
+
)
|
| 57 |
+
return super().forward(
|
| 58 |
+
x_B_C_T_H_W=x_B_C_T_H_W,
|
| 59 |
+
timesteps_B_T=timesteps_B_T * self.timestep_scale,
|
| 60 |
+
crossattn_emb=crossattn_emb,
|
| 61 |
+
fps=fps,
|
| 62 |
+
padding_mask=padding_mask,
|
| 63 |
+
data_type=data_type,
|
| 64 |
+
intermediate_feature_ids=intermediate_feature_ids,
|
| 65 |
+
img_context_emb=img_context_emb,
|
| 66 |
+
camera_condition_B_C_T_H_W=camera_condition_B_C_T_H_W,
|
| 67 |
+
)
|
cosmos/minimal_v4_dit.py
ADDED
|
@@ -0,0 +1,2054 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
# SPDX-FileCopyrightText: Modifications Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 17 |
+
# SPDX-License-Identifier: LicenseRef-NvidiaOneWayNoncommercial
|
| 18 |
+
|
| 19 |
+
import collections
|
| 20 |
+
import math
|
| 21 |
+
from collections import namedtuple
|
| 22 |
+
from collections.abc import Sequence
|
| 23 |
+
from dataclasses import dataclass
|
| 24 |
+
from enum import Enum
|
| 25 |
+
from typing import List, Optional, Tuple, Union
|
| 26 |
+
|
| 27 |
+
from cosmos.kv_cache import AttentionOpWithKVCache, KVCacheConfig
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
import megatron.core.parallel_state as parallel_state
|
| 31 |
+
|
| 32 |
+
USE_MEGATRON = True
|
| 33 |
+
except ImportError:
|
| 34 |
+
USE_MEGATRON = False
|
| 35 |
+
|
| 36 |
+
import numpy as np
|
| 37 |
+
import torch
|
| 38 |
+
import torch.amp as amp
|
| 39 |
+
from einops import rearrange, repeat
|
| 40 |
+
from einops.layers.torch import Rearrange
|
| 41 |
+
from torch import nn
|
| 42 |
+
from torch.distributed import ProcessGroup, get_process_group_ranks
|
| 43 |
+
from torch.distributed._composable.fsdp import fully_shard
|
| 44 |
+
from torch.distributed.algorithms._checkpoint.checkpoint_wrapper import checkpoint_wrapper as ptd_checkpoint_wrapper
|
| 45 |
+
|
| 46 |
+
try:
|
| 47 |
+
from torch.utils.checkpoint import CheckpointPolicy, create_selective_checkpoint_contexts
|
| 48 |
+
except ImportError:
|
| 49 |
+
CheckpointPolicy = None
|
| 50 |
+
|
| 51 |
+
from torchvision import transforms
|
| 52 |
+
|
| 53 |
+
from torch.nn.attention.flex_attention import BlockMask, create_block_mask, flex_attention
|
| 54 |
+
|
| 55 |
+
from cosmos.model_weights_stats import WeightTrainingStat
|
| 56 |
+
from cosmos.runtime import (
|
| 57 |
+
DataType,
|
| 58 |
+
DotProductAttention,
|
| 59 |
+
MinimalA2AAttnOp,
|
| 60 |
+
NattenA2AAttnOp,
|
| 61 |
+
NeighborhoodAttention,
|
| 62 |
+
RMSNorm,
|
| 63 |
+
apply_rotary_pos_emb,
|
| 64 |
+
attention,
|
| 65 |
+
log,
|
| 66 |
+
split_inputs_cp,
|
| 67 |
+
)
|
| 68 |
+
from cosmos.selective_activation_checkpoint import SACConfig as _SACConfig
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# selective activation checkpoint; only apply to the minimal v4 model. if there are change in the networks, some policy will not work as we expect.
|
| 72 |
+
def predict2_2B_720_context_fn():
|
| 73 |
+
op_count = collections.defaultdict(int)
|
| 74 |
+
|
| 75 |
+
def policy_fn(ctx, func, *args, **kwargs):
|
| 76 |
+
mode = "recompute" if ctx.is_recompute else "forward"
|
| 77 |
+
if func == torch.ops.aten.mm.default:
|
| 78 |
+
op_count_key = f"{mode}_mm_count"
|
| 79 |
+
# from cosmos_predict2._src.imaginaire.utils import log
|
| 80 |
+
# log.info(f"op_count_key: {op_count_key}, op_count[op_count_key]: {op_count[op_count_key]}, {args[0].shape}, {args[1].shape}")
|
| 81 |
+
# there are totally 6 + 4 + 4 + 2 = 16 block
|
| 82 |
+
op_count[op_count_key] = (op_count[op_count_key] + 1) % 16
|
| 83 |
+
if op_count[op_count_key] > 8: # recompute self attn first 3 linear layers
|
| 84 |
+
return CheckpointPolicy.MUST_SAVE
|
| 85 |
+
if "flash_attn" in str(func):
|
| 86 |
+
op_count_key = f"{mode}_flash_attn_count"
|
| 87 |
+
op_count[op_count_key] = (op_count[op_count_key] + 1) % 2
|
| 88 |
+
if op_count[op_count_key]:
|
| 89 |
+
return CheckpointPolicy.MUST_SAVE
|
| 90 |
+
return CheckpointPolicy.PREFER_RECOMPUTE
|
| 91 |
+
|
| 92 |
+
return create_selective_checkpoint_contexts(policy_fn)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def predict2_2B_720_context_fn_aggressive():
|
| 96 |
+
op_count = collections.defaultdict(int)
|
| 97 |
+
|
| 98 |
+
def policy_fn(ctx, func, *args, **kwargs):
|
| 99 |
+
# The default policy is to recompute everything. This is the most memory-efficient
|
| 100 |
+
# starting point. We then selectively choose what to save.
|
| 101 |
+
default_policy = CheckpointPolicy.PREFER_RECOMPUTE
|
| 102 |
+
|
| 103 |
+
# Save the output of Flash Attention. This is the most computationally
|
| 104 |
+
# expensive part of a transformer block. Saving its output provides a
|
| 105 |
+
# good balance between memory savings and computational overhead.
|
| 106 |
+
if "flash_attn" in str(func):
|
| 107 |
+
return CheckpointPolicy.MUST_SAVE
|
| 108 |
+
|
| 109 |
+
# All other operations (e.g., torch.ops.aten.mm.default, layer norms, additions)
|
| 110 |
+
# will fall through to the default policy and be recomputed.
|
| 111 |
+
return default_policy
|
| 112 |
+
|
| 113 |
+
return create_selective_checkpoint_contexts(policy_fn)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def predict2_2B_720_context_fn_aggressive_v2():
|
| 117 |
+
"""
|
| 118 |
+
The most memory-aggressive checkpointing policy. Recomputes ALL operations.
|
| 119 |
+
"""
|
| 120 |
+
|
| 121 |
+
def policy_fn(ctx, func, *args, **kwargs):
|
| 122 |
+
# The policy is to always recompute everything.
|
| 123 |
+
# This saves the maximum amount of memory but incurs the highest
|
| 124 |
+
# computational cost during the backward pass.
|
| 125 |
+
return CheckpointPolicy.PREFER_RECOMPUTE
|
| 126 |
+
|
| 127 |
+
return create_selective_checkpoint_contexts(policy_fn)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def predict2_14B_720_context_fn():
|
| 131 |
+
op_count = collections.defaultdict(int)
|
| 132 |
+
|
| 133 |
+
def policy_fn(ctx, func, *args, **kwargs):
|
| 134 |
+
mode = "recompute" if ctx.is_recompute else "forward"
|
| 135 |
+
if func == torch.ops.aten.mm.default:
|
| 136 |
+
op_count_key = f"{mode}_mm_count"
|
| 137 |
+
# from cosmos_predict2._src.imaginaire.utils import log
|
| 138 |
+
# log.info(f"op_count_key: {op_count_key}, op_count[op_count_key]: {op_count[op_count_key]}, {args[0].shape}, {args[1].shape}")
|
| 139 |
+
# there are totally 6 + 4 + 4 + 2 = 16 block
|
| 140 |
+
op_count[op_count_key] = (op_count[op_count_key] + 1) % 16
|
| 141 |
+
if op_count[op_count_key] > 8: # recompute self attn first 1 linear layers
|
| 142 |
+
return CheckpointPolicy.MUST_SAVE
|
| 143 |
+
if "flash_attn" in str(func):
|
| 144 |
+
op_count_key = f"{mode}_flash_attn_count"
|
| 145 |
+
op_count[op_count_key] = (op_count[op_count_key] + 1) % 2
|
| 146 |
+
if op_count[op_count_key]:
|
| 147 |
+
return CheckpointPolicy.MUST_SAVE
|
| 148 |
+
return CheckpointPolicy.PREFER_RECOMPUTE
|
| 149 |
+
|
| 150 |
+
return create_selective_checkpoint_contexts(policy_fn)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def predict2_14B_720_context_fn_aggressive():
|
| 154 |
+
op_count = collections.defaultdict(int)
|
| 155 |
+
|
| 156 |
+
def policy_fn(ctx, func, *args, **kwargs):
|
| 157 |
+
mode = "recompute" if ctx.is_recompute else "forward"
|
| 158 |
+
if func == torch.ops.aten.mm.default:
|
| 159 |
+
op_count_key = f"{mode}_mm_count"
|
| 160 |
+
op_count[op_count_key] = (op_count[op_count_key] + 1) % 16
|
| 161 |
+
if op_count[op_count_key] > 12: # recompute self attn first 1 linear layers
|
| 162 |
+
return CheckpointPolicy.MUST_SAVE
|
| 163 |
+
return CheckpointPolicy.PREFER_RECOMPUTE
|
| 164 |
+
|
| 165 |
+
return create_selective_checkpoint_contexts(policy_fn)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def linear_selfattn_context_fn():
|
| 169 |
+
op_count = collections.defaultdict(int)
|
| 170 |
+
|
| 171 |
+
def policy_fn(ctx, func, *args, **kwargs):
|
| 172 |
+
mode = "recompute" if ctx.is_recompute else "forward"
|
| 173 |
+
if func == torch.ops.aten.mm.default:
|
| 174 |
+
return CheckpointPolicy.MUST_SAVE
|
| 175 |
+
if "flash_attn" in str(func):
|
| 176 |
+
op_count_key = f"{mode}_flash_attn_count"
|
| 177 |
+
op_count[op_count_key] = (op_count[op_count_key] + 1) % 2
|
| 178 |
+
if op_count[op_count_key]:
|
| 179 |
+
return CheckpointPolicy.MUST_SAVE
|
| 180 |
+
return CheckpointPolicy.PREFER_RECOMPUTE
|
| 181 |
+
|
| 182 |
+
return create_selective_checkpoint_contexts(policy_fn)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
class CheckpointMode(str, Enum):
|
| 186 |
+
NONE = "none"
|
| 187 |
+
MM_ONLY = "mm_only"
|
| 188 |
+
BLOCK_WISE = "block_wise"
|
| 189 |
+
LINEAR_SELFATTN = "linear_selfattn"
|
| 190 |
+
PREDICT2_2B_720 = "predict2_2b_720"
|
| 191 |
+
PREDICT2_14B_720 = "predict2_14b_720"
|
| 192 |
+
PREDICT2_2B_720_AGGRESSIVE = "predict2_2b_720_aggressive"
|
| 193 |
+
PREDICT2_2B_720_AGGRESSIVE_V2 = "predict2_2b_720_aggressive_v2"
|
| 194 |
+
PREDICT2_14B_720_AGGRESSIVE = "predict2_14b_720_aggressive"
|
| 195 |
+
|
| 196 |
+
def __str__(self) -> str:
|
| 197 |
+
return self.value
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
@dataclass
|
| 201 |
+
class SACConfig(_SACConfig):
|
| 202 |
+
def get_context_fn(self):
|
| 203 |
+
if self.mode == CheckpointMode.LINEAR_SELFATTN:
|
| 204 |
+
return linear_selfattn_context_fn
|
| 205 |
+
elif self.mode == CheckpointMode.PREDICT2_2B_720:
|
| 206 |
+
return predict2_2B_720_context_fn
|
| 207 |
+
elif self.mode == CheckpointMode.PREDICT2_2B_720_AGGRESSIVE:
|
| 208 |
+
return predict2_2B_720_context_fn_aggressive
|
| 209 |
+
elif self.mode == CheckpointMode.PREDICT2_2B_720_AGGRESSIVE_V2:
|
| 210 |
+
return predict2_2B_720_context_fn_aggressive_v2
|
| 211 |
+
elif self.mode == CheckpointMode.PREDICT2_14B_720:
|
| 212 |
+
return predict2_14B_720_context_fn
|
| 213 |
+
elif self.mode == CheckpointMode.PREDICT2_14B_720_AGGRESSIVE:
|
| 214 |
+
return predict2_14B_720_context_fn_aggressive
|
| 215 |
+
else:
|
| 216 |
+
# Reuse parent class implementation for other modes
|
| 217 |
+
return super().get_context_fn()
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
VideoSize = namedtuple("VideoSize", ["T", "H", "W"])
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
# ---------------------- Feed Forward Network -----------------------
|
| 224 |
+
class GPT2FeedForward(nn.Module):
|
| 225 |
+
def __init__(self, d_model: int, d_ff: int):
|
| 226 |
+
super().__init__()
|
| 227 |
+
self.activation = nn.GELU()
|
| 228 |
+
self.layer1 = nn.Linear(d_model, d_ff, bias=False)
|
| 229 |
+
self.layer2 = nn.Linear(d_ff, d_model, bias=False)
|
| 230 |
+
|
| 231 |
+
self._layer_id = None
|
| 232 |
+
self._dim = d_model
|
| 233 |
+
self._hidden_dim = d_ff
|
| 234 |
+
self.init_weights()
|
| 235 |
+
|
| 236 |
+
def init_weights(self) -> None:
|
| 237 |
+
std = 1.0 / math.sqrt(self._dim)
|
| 238 |
+
torch.nn.init.trunc_normal_(self.layer1.weight, std=std, a=-3 * std, b=3 * std)
|
| 239 |
+
|
| 240 |
+
# scale init by depth as in https://arxiv.org/abs/1908.11365 -- worked slightly better.
|
| 241 |
+
std = 1.0 / math.sqrt(self._hidden_dim)
|
| 242 |
+
if self._layer_id is not None:
|
| 243 |
+
std = std / math.sqrt(2 * (self._layer_id + 1))
|
| 244 |
+
torch.nn.init.trunc_normal_(self.layer2.weight, std=std, a=-3 * std, b=3 * std)
|
| 245 |
+
|
| 246 |
+
def forward(self, x: torch.Tensor):
|
| 247 |
+
x = self.layer1(x)
|
| 248 |
+
|
| 249 |
+
x = self.activation(x)
|
| 250 |
+
x = self.layer2(x)
|
| 251 |
+
return x
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def torch_attention_op(
|
| 255 |
+
q_B_S_H_D: torch.Tensor,
|
| 256 |
+
k_B_S_H_D: torch.Tensor,
|
| 257 |
+
v_B_S_H_D: torch.Tensor,
|
| 258 |
+
attn_mask: Optional[torch.Tensor] = None,
|
| 259 |
+
flatten_heads: bool = True,
|
| 260 |
+
) -> torch.Tensor:
|
| 261 |
+
"""Scaled dot-product attention with optional mask.
|
| 262 |
+
|
| 263 |
+
Inputs are shaped [B, S, H, D]. If flatten_heads=True, flattens heads to return [B, S, H*D].
|
| 264 |
+
Otherwise returns [B, S, H, D].
|
| 265 |
+
"""
|
| 266 |
+
q_B_H_S_D = rearrange(q_B_S_H_D, "b s h d -> b h s d")
|
| 267 |
+
k_B_H_S_D = rearrange(k_B_S_H_D, "b s h d -> b h s d")
|
| 268 |
+
v_B_H_S_D = rearrange(v_B_S_H_D, "b s h d -> b h s d")
|
| 269 |
+
result_B_H_S_D = torch.nn.functional.scaled_dot_product_attention(
|
| 270 |
+
q_B_H_S_D, k_B_H_S_D, v_B_H_S_D, attn_mask=attn_mask
|
| 271 |
+
)
|
| 272 |
+
if flatten_heads:
|
| 273 |
+
return rearrange(result_B_H_S_D, "b h s d -> b s (h d)")
|
| 274 |
+
else:
|
| 275 |
+
return rearrange(result_B_H_S_D, "b h s d -> b s h d")
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
def flex_attention_op(
|
| 279 |
+
q_B_S_H_D: torch.Tensor,
|
| 280 |
+
k_B_S_H_D: torch.Tensor,
|
| 281 |
+
v_B_S_H_D: torch.Tensor,
|
| 282 |
+
attn_mask: Optional[BlockMask] = None,
|
| 283 |
+
flatten_heads: bool = True,
|
| 284 |
+
) -> torch.Tensor:
|
| 285 |
+
# Rearrange to [B, H, S, D]
|
| 286 |
+
q_B_H_Sq_D = rearrange(q_B_S_H_D, "b s h d -> b h s d")
|
| 287 |
+
k_B_H_Sk_D = rearrange(k_B_S_H_D, "b s h d -> b h s d")
|
| 288 |
+
v_B_H_Sk_D = rearrange(v_B_S_H_D, "b s h d -> b h s d")
|
| 289 |
+
|
| 290 |
+
S_q = q_B_H_Sq_D.shape[2]
|
| 291 |
+
S_kv = k_B_H_Sk_D.shape[2]
|
| 292 |
+
# Right-pad to multiples of 128 for optimal FlexAttention kernels
|
| 293 |
+
pad_q = ((S_q + 127) // 128) * 128 - S_q
|
| 294 |
+
pad_kv = ((S_kv + 127) // 128) * 128 - S_kv
|
| 295 |
+
|
| 296 |
+
if pad_q > 0:
|
| 297 |
+
q_pad_tensor = torch.zeros(
|
| 298 |
+
(q_B_H_Sq_D.shape[0], q_B_H_Sq_D.shape[1], pad_q, q_B_H_Sq_D.shape[3]),
|
| 299 |
+
device=q_B_H_Sq_D.device,
|
| 300 |
+
dtype=q_B_H_Sq_D.dtype,
|
| 301 |
+
)
|
| 302 |
+
q_cat = torch.cat([q_B_H_Sq_D, q_pad_tensor], dim=2)
|
| 303 |
+
else:
|
| 304 |
+
q_cat = q_B_H_Sq_D
|
| 305 |
+
|
| 306 |
+
if pad_kv > 0:
|
| 307 |
+
kv_pad_tensor = torch.zeros(
|
| 308 |
+
(k_B_H_Sk_D.shape[0], k_B_H_Sk_D.shape[1], pad_kv, k_B_H_Sk_D.shape[3]),
|
| 309 |
+
device=k_B_H_Sk_D.device,
|
| 310 |
+
dtype=k_B_H_Sk_D.dtype,
|
| 311 |
+
)
|
| 312 |
+
k_cat = torch.cat([k_B_H_Sk_D, kv_pad_tensor], dim=2)
|
| 313 |
+
v_cat = torch.cat([v_B_H_Sk_D, kv_pad_tensor], dim=2)
|
| 314 |
+
else:
|
| 315 |
+
k_cat, v_cat = k_B_H_Sk_D, v_B_H_Sk_D
|
| 316 |
+
|
| 317 |
+
block_mask = None
|
| 318 |
+
if attn_mask is not None and isinstance(attn_mask, BlockMask):
|
| 319 |
+
block_mask = attn_mask
|
| 320 |
+
else:
|
| 321 |
+
# When padding is introduced without an explicit mask, build a validity mask
|
| 322 |
+
if pad_q > 0 or pad_kv > 0:
|
| 323 |
+
|
| 324 |
+
def allow_valid(b, h, q_idx, kv_idx):
|
| 325 |
+
return (q_idx < S_q) & (kv_idx < S_kv)
|
| 326 |
+
|
| 327 |
+
block_mask = create_block_mask(
|
| 328 |
+
allow_valid,
|
| 329 |
+
B=None,
|
| 330 |
+
H=None,
|
| 331 |
+
Q_LEN=q_cat.shape[2],
|
| 332 |
+
KV_LEN=k_cat.shape[2],
|
| 333 |
+
_compile=True,
|
| 334 |
+
device=q_cat.device,
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
if block_mask is not None:
|
| 338 |
+
out_B_H_Sqp_D = torch.compile(flex_attention)(query=q_cat, key=k_cat, value=v_cat, block_mask=block_mask)
|
| 339 |
+
else:
|
| 340 |
+
out_B_H_Sqp_D = torch.compile(flex_attention)(query=q_cat, key=k_cat, value=v_cat)
|
| 341 |
+
|
| 342 |
+
out_B_H_Sq_D = out_B_H_Sqp_D[:, :, :S_q] if pad_q > 0 else out_B_H_Sqp_D
|
| 343 |
+
if flatten_heads:
|
| 344 |
+
return rearrange(out_B_H_Sq_D, "b h s d -> b s (h d)")
|
| 345 |
+
else:
|
| 346 |
+
return rearrange(out_B_H_Sq_D, "b h s d -> b s h d")
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def i4_attention_op(
|
| 350 |
+
q_B_S_H_D: torch.Tensor,
|
| 351 |
+
k_B_S_H_D: torch.Tensor,
|
| 352 |
+
v_B_S_H_D: torch.Tensor,
|
| 353 |
+
flatten_heads: bool = True,
|
| 354 |
+
**kwargs: dict,
|
| 355 |
+
) -> torch.Tensor:
|
| 356 |
+
"""
|
| 357 |
+
I4 regular (bidirectional) attention.
|
| 358 |
+
Matches torch_attention_op's signature but omits attn_mask (full attention assumed).
|
| 359 |
+
Ignores any additional kwargs (e.g., video_size).
|
| 360 |
+
"""
|
| 361 |
+
out_B_S_H_D = attention(
|
| 362 |
+
query=q_B_S_H_D,
|
| 363 |
+
key=k_B_S_H_D,
|
| 364 |
+
value=v_B_S_H_D,
|
| 365 |
+
is_causal=False,
|
| 366 |
+
)
|
| 367 |
+
if isinstance(out_B_S_H_D, tuple):
|
| 368 |
+
out_B_S_H_D = out_B_S_H_D[0]
|
| 369 |
+
if flatten_heads:
|
| 370 |
+
return rearrange(out_B_S_H_D, "b s h d -> b s (h d)")
|
| 371 |
+
else:
|
| 372 |
+
return out_B_S_H_D
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
class Attention(nn.Module):
|
| 376 |
+
"""
|
| 377 |
+
A flexible attention module supporting both self-attention and cross-attention mechanisms.
|
| 378 |
+
|
| 379 |
+
This module implements a multi-head attention layer that can operate in either self-attention
|
| 380 |
+
or cross-attention mode. The mode is determined by whether a context dimension is provided.
|
| 381 |
+
The implementation uses scaled dot-product attention and supports optional bias terms and
|
| 382 |
+
dropout regularization.
|
| 383 |
+
|
| 384 |
+
Args:
|
| 385 |
+
query_dim (int): The dimensionality of the query vectors.
|
| 386 |
+
context_dim (int, optional): The dimensionality of the context (key/value) vectors.
|
| 387 |
+
If None, the module operates in self-attention mode using query_dim. Default: None
|
| 388 |
+
n_heads (int, optional): Number of attention heads for multi-head attention. Default: 8
|
| 389 |
+
head_dim (int, optional): The dimension of each attention head. Default: 64
|
| 390 |
+
dropout (float, optional): Dropout probability applied to the output. Default: 0.0
|
| 391 |
+
qkv_format (str, optional): Format specification for QKV tensors. Default: "bshd"
|
| 392 |
+
backend (str, optional): Backend to use for the attention operation. Default: "transformer_engine"
|
| 393 |
+
|
| 394 |
+
Examples:
|
| 395 |
+
>>> # Self-attention with 512 dimensions and 8 heads
|
| 396 |
+
>>> self_attn = Attention(query_dim=512)
|
| 397 |
+
>>> x = torch.randn(32, 16, 512) # (batch_size, seq_len, dim)
|
| 398 |
+
>>> out = self_attn(x) # (32, 16, 512)
|
| 399 |
+
|
| 400 |
+
>>> # Cross-attention
|
| 401 |
+
>>> cross_attn = Attention(query_dim=512, context_dim=256)
|
| 402 |
+
>>> query = torch.randn(32, 16, 512)
|
| 403 |
+
>>> context = torch.randn(32, 8, 256)
|
| 404 |
+
>>> out = cross_attn(query, context) # (32, 16, 512)
|
| 405 |
+
"""
|
| 406 |
+
|
| 407 |
+
def __init__(
|
| 408 |
+
self,
|
| 409 |
+
query_dim: int,
|
| 410 |
+
context_dim=None,
|
| 411 |
+
n_heads=8,
|
| 412 |
+
head_dim=64,
|
| 413 |
+
dropout=0.0,
|
| 414 |
+
qkv_format: str = "bshd",
|
| 415 |
+
backend: str = "transformer_engine",
|
| 416 |
+
use_wan_fp32_strategy: bool = False,
|
| 417 |
+
) -> None:
|
| 418 |
+
super().__init__()
|
| 419 |
+
log.debug(
|
| 420 |
+
f"Setting up {self.__class__.__name__}. Query dim is {query_dim}, context_dim is {context_dim} and using "
|
| 421 |
+
f"{n_heads} heads with a dimension of {head_dim}."
|
| 422 |
+
)
|
| 423 |
+
self.is_selfattn = context_dim is None # self attention
|
| 424 |
+
|
| 425 |
+
assert backend in ["transformer_engine", "torch", "torch-flex", "minimal_a2a", "i4"], (
|
| 426 |
+
f"Invalid backend: {backend}"
|
| 427 |
+
)
|
| 428 |
+
self.backend = backend
|
| 429 |
+
|
| 430 |
+
context_dim = query_dim if context_dim is None else context_dim
|
| 431 |
+
inner_dim = head_dim * n_heads
|
| 432 |
+
|
| 433 |
+
self.n_heads = n_heads
|
| 434 |
+
self.head_dim = head_dim
|
| 435 |
+
self.qkv_format = qkv_format
|
| 436 |
+
self.query_dim = query_dim
|
| 437 |
+
self.context_dim = context_dim
|
| 438 |
+
self.use_wan_fp32_strategy = use_wan_fp32_strategy
|
| 439 |
+
|
| 440 |
+
self.q_proj = nn.Linear(query_dim, inner_dim, bias=False)
|
| 441 |
+
self.q_norm = RMSNorm(self.head_dim, eps=1e-6)
|
| 442 |
+
|
| 443 |
+
self.k_proj = nn.Linear(context_dim, inner_dim, bias=False)
|
| 444 |
+
self.k_norm = RMSNorm(self.head_dim, eps=1e-6)
|
| 445 |
+
|
| 446 |
+
self.v_proj = nn.Linear(context_dim, inner_dim, bias=False)
|
| 447 |
+
self.v_norm = nn.Identity()
|
| 448 |
+
|
| 449 |
+
self.output_proj = nn.Linear(inner_dim, query_dim, bias=False)
|
| 450 |
+
self.output_dropout = nn.Dropout(dropout) if dropout > 1e-4 else nn.Identity()
|
| 451 |
+
# Camera-control checkpoints store the projection under each
|
| 452 |
+
# self-attention module as `self_attn.cam_encoder`.
|
| 453 |
+
self.cam_encoder = None
|
| 454 |
+
|
| 455 |
+
if self.backend == "transformer_engine":
|
| 456 |
+
self.attn_op = DotProductAttention(
|
| 457 |
+
self.n_heads,
|
| 458 |
+
self.head_dim,
|
| 459 |
+
num_gqa_groups=self.n_heads,
|
| 460 |
+
attention_dropout=0,
|
| 461 |
+
qkv_format=qkv_format,
|
| 462 |
+
attn_mask_type="no_mask",
|
| 463 |
+
)
|
| 464 |
+
elif self.backend == "minimal_a2a":
|
| 465 |
+
self.attn_op = MinimalA2AAttnOp()
|
| 466 |
+
elif self.backend == "torch":
|
| 467 |
+
self.attn_op = torch_attention_op
|
| 468 |
+
elif self.backend == "torch-flex":
|
| 469 |
+
# FlexAttention backend; returns [B, S, H*D]
|
| 470 |
+
self.attn_op = flex_attention_op
|
| 471 |
+
elif self.backend == "i4":
|
| 472 |
+
# I4 spatio-temporal attention; returns [B, S, H*D]
|
| 473 |
+
self.attn_op = i4_attention_op
|
| 474 |
+
|
| 475 |
+
if not hasattr(self.attn_op, "set_context_parallel_group"):
|
| 476 |
+
|
| 477 |
+
def set_context_parallel_group(*args, **kwargs) -> None:
|
| 478 |
+
return None
|
| 479 |
+
|
| 480 |
+
self.attn_op.set_context_parallel_group = set_context_parallel_group
|
| 481 |
+
|
| 482 |
+
self._query_dim = query_dim
|
| 483 |
+
self._context_dim = context_dim
|
| 484 |
+
self._inner_dim = inner_dim
|
| 485 |
+
|
| 486 |
+
def init_weights(self) -> None:
|
| 487 |
+
std = 1.0 / math.sqrt(self._query_dim)
|
| 488 |
+
torch.nn.init.trunc_normal_(self.q_proj.weight, std=std, a=-3 * std, b=3 * std)
|
| 489 |
+
std = 1.0 / math.sqrt(self._context_dim)
|
| 490 |
+
torch.nn.init.trunc_normal_(self.k_proj.weight, std=std, a=-3 * std, b=3 * std)
|
| 491 |
+
torch.nn.init.trunc_normal_(self.v_proj.weight, std=std, a=-3 * std, b=3 * std)
|
| 492 |
+
|
| 493 |
+
std = 1.0 / math.sqrt(self._inner_dim)
|
| 494 |
+
torch.nn.init.trunc_normal_(self.output_proj.weight, std=std, a=-3 * std, b=3 * std)
|
| 495 |
+
|
| 496 |
+
for layer in self.q_norm, self.k_norm, self.v_norm:
|
| 497 |
+
if hasattr(layer, "reset_parameters"):
|
| 498 |
+
layer.reset_parameters()
|
| 499 |
+
|
| 500 |
+
def compute_qkv(self, x, context=None, rope_emb=None) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 501 |
+
q = self.q_proj(x)
|
| 502 |
+
context = x if context is None else context
|
| 503 |
+
k = self.k_proj(context)
|
| 504 |
+
v = self.v_proj(context)
|
| 505 |
+
q, k, v = map(
|
| 506 |
+
lambda t: rearrange(t, "b ... (h d) -> b ... h d", h=self.n_heads, d=self.head_dim),
|
| 507 |
+
(q, k, v),
|
| 508 |
+
)
|
| 509 |
+
|
| 510 |
+
def apply_norm_and_rotary_pos_emb(q, k, v, rope_emb):
|
| 511 |
+
q = self.q_norm(q)
|
| 512 |
+
k = self.k_norm(k)
|
| 513 |
+
v = self.v_norm(v)
|
| 514 |
+
original_dtype = q.dtype
|
| 515 |
+
if self.is_selfattn and rope_emb is not None: # only apply to self-attention!
|
| 516 |
+
if self.use_wan_fp32_strategy: # wan will force q and k to fp32 before rotary pos emb
|
| 517 |
+
q = q.to(torch.float32)
|
| 518 |
+
k = k.to(torch.float32)
|
| 519 |
+
q = apply_rotary_pos_emb(q, rope_emb, tensor_format=self.qkv_format, fused=True)
|
| 520 |
+
k = apply_rotary_pos_emb(k, rope_emb, tensor_format=self.qkv_format, fused=True)
|
| 521 |
+
if self.use_wan_fp32_strategy:
|
| 522 |
+
q = q.to(original_dtype)
|
| 523 |
+
k = k.to(original_dtype)
|
| 524 |
+
return q, k, v
|
| 525 |
+
|
| 526 |
+
q, k, v = apply_norm_and_rotary_pos_emb(q, k, v, rope_emb)
|
| 527 |
+
|
| 528 |
+
return q, k, v
|
| 529 |
+
|
| 530 |
+
def compute_attention(
|
| 531 |
+
self,
|
| 532 |
+
q,
|
| 533 |
+
k,
|
| 534 |
+
v,
|
| 535 |
+
video_size: Optional[VideoSize] = None,
|
| 536 |
+
kv_cache_cfg: Optional[KVCacheConfig] = None,
|
| 537 |
+
teacher_forcing_layout: Optional[tuple[int, int]] = None,
|
| 538 |
+
):
|
| 539 |
+
additional_args = {}
|
| 540 |
+
if isinstance(self.attn_op, (NattenA2AAttnOp, NeighborhoodAttention)) or self.backend == "i4":
|
| 541 |
+
additional_args["video_size"] = video_size
|
| 542 |
+
if isinstance(self.attn_op, AttentionOpWithKVCache):
|
| 543 |
+
additional_args["kv_cache_cfg"] = kv_cache_cfg
|
| 544 |
+
additional_args["teacher_forcing_layout"] = teacher_forcing_layout
|
| 545 |
+
|
| 546 |
+
result = self.attn_op(q, k, v, **additional_args) # [B, S, H, D]
|
| 547 |
+
return self.output_dropout(self.output_proj(result))
|
| 548 |
+
|
| 549 |
+
def forward(
|
| 550 |
+
self,
|
| 551 |
+
x,
|
| 552 |
+
context: Optional[torch.Tensor] = None,
|
| 553 |
+
rope_emb: Optional[torch.Tensor] = None,
|
| 554 |
+
video_size: Optional[VideoSize] = None,
|
| 555 |
+
kv_cache_cfg: Optional[KVCacheConfig] = None,
|
| 556 |
+
teacher_forcing_layout: Optional[tuple[int, int]] = None,
|
| 557 |
+
):
|
| 558 |
+
"""
|
| 559 |
+
Args:
|
| 560 |
+
x (Tensor): The query tensor of shape [B, Mq, K]
|
| 561 |
+
context (Optional[Tensor]): The key tensor of shape [B, Mk, K] or use x as context [self attention] if None
|
| 562 |
+
rope_emb (Optional[Tensor]): RoPE embedding tensor, or no RoPE embeddings (i.e. in cross attention)
|
| 563 |
+
video_size(VideoSize): Shape [T, H, W]
|
| 564 |
+
"""
|
| 565 |
+
q, k, v = self.compute_qkv(x, context, rope_emb=rope_emb)
|
| 566 |
+
if teacher_forcing_layout is not None:
|
| 567 |
+
if video_size is None:
|
| 568 |
+
raise ValueError("video_size is required for packed teacher forcing")
|
| 569 |
+
context_frames, _ = teacher_forcing_layout
|
| 570 |
+
context_tokens = context_frames * video_size.H * video_size.W
|
| 571 |
+
k = torch.cat([k[:, :context_tokens].detach(), k[:, context_tokens:]], dim=1)
|
| 572 |
+
v = torch.cat([v[:, :context_tokens].detach(), v[:, context_tokens:]], dim=1)
|
| 573 |
+
result = self.compute_attention(
|
| 574 |
+
q,
|
| 575 |
+
k,
|
| 576 |
+
v,
|
| 577 |
+
video_size=video_size,
|
| 578 |
+
kv_cache_cfg=kv_cache_cfg,
|
| 579 |
+
teacher_forcing_layout=teacher_forcing_layout,
|
| 580 |
+
)
|
| 581 |
+
return result
|
| 582 |
+
|
| 583 |
+
def set_context_parallel_group(self, process_group, ranks, stream, cp_comm_type: str = "p2p"):
|
| 584 |
+
# self.attn_op.set_context_parallel_group(process_group, ranks, stream, cp_comm_type="a2a")
|
| 585 |
+
self.attn_op.set_context_parallel_group(process_group, ranks, stream, cp_comm_type=cp_comm_type)
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
class I2VCrossAttention(Attention):
|
| 589 |
+
def __init__(self, *args, img_latent_dim: int = 1024, **kwargs):
|
| 590 |
+
super().__init__(*args, **kwargs)
|
| 591 |
+
inner_dim = self.head_dim * self.n_heads
|
| 592 |
+
self.k_img = nn.Linear(img_latent_dim, inner_dim, bias=False)
|
| 593 |
+
self.v_img = nn.Linear(img_latent_dim, inner_dim, bias=False)
|
| 594 |
+
self.k_img_norm = RMSNorm(self.head_dim, eps=1e-6)
|
| 595 |
+
|
| 596 |
+
def init_weights(self) -> None:
|
| 597 |
+
super().init_weights()
|
| 598 |
+
torch.nn.init.trunc_normal_(self.k_img.weight, std=1.0 / math.sqrt(self._inner_dim))
|
| 599 |
+
torch.nn.init.trunc_normal_(self.v_img.weight, std=1.0 / math.sqrt(self._inner_dim))
|
| 600 |
+
self.k_img_norm.reset_parameters()
|
| 601 |
+
|
| 602 |
+
def compute_qkv(
|
| 603 |
+
self, x, context, rope_emb=None
|
| 604 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 605 |
+
text_context, img_context = context
|
| 606 |
+
q, k, v = super().compute_qkv(x, text_context, rope_emb)
|
| 607 |
+
k_img = self.k_img(img_context)
|
| 608 |
+
v_img = self.v_img(img_context)
|
| 609 |
+
# Rearrange k_img, v_img
|
| 610 |
+
k_img, v_img = map(
|
| 611 |
+
lambda t: rearrange(t, "b ... (h d) -> b ... h d", h=self.n_heads, d=self.head_dim),
|
| 612 |
+
(k_img, v_img),
|
| 613 |
+
)
|
| 614 |
+
|
| 615 |
+
return q, k, v, self.k_img_norm(k_img), v_img
|
| 616 |
+
|
| 617 |
+
def compute_attention(self, q, k, v, k_img, v_img):
|
| 618 |
+
result = self.attn_op(q, k, v) # [B, S, H, D]
|
| 619 |
+
result_img = self.attn_op(q, k_img, v_img)
|
| 620 |
+
return self.output_dropout(self.output_proj(result + result_img))
|
| 621 |
+
|
| 622 |
+
def forward(
|
| 623 |
+
self,
|
| 624 |
+
x,
|
| 625 |
+
context=None,
|
| 626 |
+
rope_emb=None,
|
| 627 |
+
):
|
| 628 |
+
q, k, v, k_img, v_img = self.compute_qkv(x, context, rope_emb)
|
| 629 |
+
return self.compute_attention(q, k, v, k_img, v_img)
|
| 630 |
+
|
| 631 |
+
|
| 632 |
+
class VideoPositionEmb(nn.Module):
|
| 633 |
+
def __init__(self):
|
| 634 |
+
super().__init__()
|
| 635 |
+
self._cp_group = None
|
| 636 |
+
|
| 637 |
+
def enable_context_parallel(self, process_group: ProcessGroup):
|
| 638 |
+
self._cp_group = process_group
|
| 639 |
+
|
| 640 |
+
def disable_context_parallel(self):
|
| 641 |
+
self._cp_group = None
|
| 642 |
+
|
| 643 |
+
@property
|
| 644 |
+
def seq_dim(self):
|
| 645 |
+
return 1
|
| 646 |
+
|
| 647 |
+
def forward(self, x_B_T_H_W_C: torch.Tensor, fps=Optional[torch.Tensor]) -> torch.Tensor:
|
| 648 |
+
"""
|
| 649 |
+
With CP, the function assume that the input tensor is already split.
|
| 650 |
+
It delegates the embedding generation to generate_embeddings function.
|
| 651 |
+
"""
|
| 652 |
+
B_T_H_W_C = x_B_T_H_W_C.shape
|
| 653 |
+
if self._cp_group is not None:
|
| 654 |
+
cp_ranks = get_process_group_ranks(self._cp_group)
|
| 655 |
+
cp_size = len(cp_ranks)
|
| 656 |
+
cp_size_t = cp_size
|
| 657 |
+
if USE_MEGATRON and hasattr(parallel_state, "cp_size_t"):
|
| 658 |
+
# We saved cp_size_t in find_split function for combined temporal and spatial splitting.
|
| 659 |
+
# We need cp_size_t to find out the split values for T and H dimensions for correct embedding calculations.
|
| 660 |
+
cp_size_t = parallel_state.cp_size_t
|
| 661 |
+
cp_size_h = max(1, cp_size // cp_size_t)
|
| 662 |
+
B, T, H, W, C = B_T_H_W_C
|
| 663 |
+
B_T_H_W_C = (B, T * cp_size_t, H * cp_size_h, W, C)
|
| 664 |
+
embeddings = self.generate_embeddings(B_T_H_W_C, fps=fps)
|
| 665 |
+
|
| 666 |
+
return self._split_for_context_parallel(embeddings)
|
| 667 |
+
|
| 668 |
+
def generate_embeddings(self, B_T_H_W_C: torch.Size, fps=Optional[torch.Tensor]):
|
| 669 |
+
raise NotImplementedError
|
| 670 |
+
|
| 671 |
+
def _split_for_context_parallel(self, embeddings):
|
| 672 |
+
if self._cp_group is not None:
|
| 673 |
+
embeddings = split_inputs_cp(x=embeddings, seq_dim=self.seq_dim, cp_group=self._cp_group)
|
| 674 |
+
return embeddings
|
| 675 |
+
|
| 676 |
+
|
| 677 |
+
class VideoRopePosition3DEmb(VideoPositionEmb):
|
| 678 |
+
def __init__(
|
| 679 |
+
self,
|
| 680 |
+
*, # enforce keyword arguments
|
| 681 |
+
head_dim: int,
|
| 682 |
+
len_h: int,
|
| 683 |
+
len_w: int,
|
| 684 |
+
len_t: int,
|
| 685 |
+
base_fps: int = 24,
|
| 686 |
+
h_extrapolation_ratio: float = 1.0,
|
| 687 |
+
w_extrapolation_ratio: float = 1.0,
|
| 688 |
+
t_extrapolation_ratio: float = 1.0,
|
| 689 |
+
enable_fps_modulation: bool = True,
|
| 690 |
+
**kwargs, # used for compatibility with other positional embeddings; unused in this class
|
| 691 |
+
):
|
| 692 |
+
del kwargs
|
| 693 |
+
super().__init__()
|
| 694 |
+
self.register_buffer("seq", torch.arange(max(len_h, len_w, len_t), dtype=torch.float))
|
| 695 |
+
self.base_fps = base_fps
|
| 696 |
+
self.max_h = len_h
|
| 697 |
+
self.max_w = len_w
|
| 698 |
+
self.max_t = len_t
|
| 699 |
+
self.enable_fps_modulation = enable_fps_modulation
|
| 700 |
+
dim = head_dim
|
| 701 |
+
dim_h = dim // 6 * 2
|
| 702 |
+
dim_w = dim_h
|
| 703 |
+
dim_t = dim - 2 * dim_h
|
| 704 |
+
assert dim == dim_h + dim_w + dim_t, f"bad dim: {dim} != {dim_h} + {dim_w} + {dim_t}"
|
| 705 |
+
|
| 706 |
+
self.register_buffer(
|
| 707 |
+
"dim_spatial_range",
|
| 708 |
+
torch.arange(0, dim_h, 2)[: (dim_h // 2)].float() / dim_h,
|
| 709 |
+
persistent=True,
|
| 710 |
+
)
|
| 711 |
+
self.register_buffer(
|
| 712 |
+
"dim_temporal_range",
|
| 713 |
+
torch.arange(0, dim_t, 2)[: (dim_t // 2)].float() / dim_t,
|
| 714 |
+
persistent=True,
|
| 715 |
+
)
|
| 716 |
+
self._dim_h = dim_h
|
| 717 |
+
self._dim_t = dim_t
|
| 718 |
+
|
| 719 |
+
self.h_ntk_factor = h_extrapolation_ratio ** (dim_h / (dim_h - 2))
|
| 720 |
+
self.w_ntk_factor = w_extrapolation_ratio ** (dim_w / (dim_w - 2))
|
| 721 |
+
self.t_ntk_factor = t_extrapolation_ratio ** (dim_t / (dim_t - 2))
|
| 722 |
+
self.reset_parameters()
|
| 723 |
+
|
| 724 |
+
def reset_parameters(self) -> None:
|
| 725 |
+
dim_h = self._dim_h
|
| 726 |
+
dim_t = self._dim_t
|
| 727 |
+
|
| 728 |
+
self.seq = torch.arange(max(self.max_h, self.max_w, self.max_t)).float().to(self.dim_spatial_range.device)
|
| 729 |
+
self.dim_spatial_range = (
|
| 730 |
+
torch.arange(0, dim_h, 2)[: (dim_h // 2)].float().to(self.dim_spatial_range.device) / dim_h
|
| 731 |
+
)
|
| 732 |
+
self.dim_temporal_range = (
|
| 733 |
+
torch.arange(0, dim_t, 2)[: (dim_t // 2)].float().to(self.dim_spatial_range.device) / dim_t
|
| 734 |
+
)
|
| 735 |
+
|
| 736 |
+
def generate_embeddings(
|
| 737 |
+
self,
|
| 738 |
+
B_T_H_W_C: torch.Size,
|
| 739 |
+
fps: Optional[torch.Tensor] = None,
|
| 740 |
+
h_ntk_factor: Optional[float] = None,
|
| 741 |
+
w_ntk_factor: Optional[float] = None,
|
| 742 |
+
t_ntk_factor: Optional[float] = None,
|
| 743 |
+
):
|
| 744 |
+
"""
|
| 745 |
+
Generate embeddings for the given input size.
|
| 746 |
+
|
| 747 |
+
Args:
|
| 748 |
+
B_T_H_W_C (torch.Size): Input tensor size (Batch, Time, Height, Width, Channels).
|
| 749 |
+
fps (Optional[torch.Tensor], optional): Frames per second. Defaults to None.
|
| 750 |
+
h_ntk_factor (Optional[float], optional): Height NTK factor. If None, uses self.h_ntk_factor.
|
| 751 |
+
w_ntk_factor (Optional[float], optional): Width NTK factor. If None, uses self.w_ntk_factor.
|
| 752 |
+
t_ntk_factor (Optional[float], optional): Time NTK factor. If None, uses self.t_ntk_factor.
|
| 753 |
+
|
| 754 |
+
Returns:
|
| 755 |
+
Not specified in the original code snippet.
|
| 756 |
+
"""
|
| 757 |
+
h_ntk_factor = h_ntk_factor if h_ntk_factor is not None else self.h_ntk_factor
|
| 758 |
+
w_ntk_factor = w_ntk_factor if w_ntk_factor is not None else self.w_ntk_factor
|
| 759 |
+
t_ntk_factor = t_ntk_factor if t_ntk_factor is not None else self.t_ntk_factor
|
| 760 |
+
|
| 761 |
+
h_theta = 10000.0 * h_ntk_factor
|
| 762 |
+
w_theta = 10000.0 * w_ntk_factor
|
| 763 |
+
t_theta = 10000.0 * t_ntk_factor
|
| 764 |
+
|
| 765 |
+
h_spatial_freqs = 1.0 / (h_theta ** self.dim_spatial_range.float())
|
| 766 |
+
w_spatial_freqs = 1.0 / (w_theta ** self.dim_spatial_range.float())
|
| 767 |
+
temporal_freqs = 1.0 / (t_theta ** self.dim_temporal_range.float())
|
| 768 |
+
|
| 769 |
+
B, T, H, W, _ = B_T_H_W_C
|
| 770 |
+
assert H <= self.max_h and W <= self.max_w, (
|
| 771 |
+
f"Input dimensions (H={H}, W={W}) exceed the maximum dimensions (max_h={self.max_h}, max_w={self.max_w})"
|
| 772 |
+
)
|
| 773 |
+
half_emb_h = torch.outer(self.seq[:H], h_spatial_freqs)
|
| 774 |
+
half_emb_w = torch.outer(self.seq[:W], w_spatial_freqs)
|
| 775 |
+
|
| 776 |
+
if self.enable_fps_modulation:
|
| 777 |
+
uniform_fps = (fps is None) or (fps.min() == fps.max())
|
| 778 |
+
assert uniform_fps or B == 1 or T == 1, (
|
| 779 |
+
"For video batch, batch size should be 1 for non-uniform fps. For image batch, T should be 1"
|
| 780 |
+
)
|
| 781 |
+
|
| 782 |
+
# apply sequence scaling in temporal dimension
|
| 783 |
+
if fps is None: # image case
|
| 784 |
+
assert T == 1, "T should be 1 for image batch."
|
| 785 |
+
half_emb_t = torch.outer(self.seq[:T], temporal_freqs)
|
| 786 |
+
else:
|
| 787 |
+
half_emb_t = torch.outer(self.seq[:T] / fps[:1] * self.base_fps, temporal_freqs)
|
| 788 |
+
else:
|
| 789 |
+
half_emb_t = torch.outer(self.seq[:T], temporal_freqs)
|
| 790 |
+
|
| 791 |
+
em_T_H_W_D = torch.cat(
|
| 792 |
+
[
|
| 793 |
+
repeat(half_emb_t, "t d -> t h w d", h=H, w=W),
|
| 794 |
+
repeat(half_emb_h, "h d -> t h w d", t=T, w=W),
|
| 795 |
+
repeat(half_emb_w, "w d -> t h w d", t=T, h=H),
|
| 796 |
+
]
|
| 797 |
+
* 2,
|
| 798 |
+
dim=-1,
|
| 799 |
+
)
|
| 800 |
+
|
| 801 |
+
return rearrange(em_T_H_W_D, "t h w d -> (t h w) 1 1 d").float()
|
| 802 |
+
|
| 803 |
+
@property
|
| 804 |
+
def seq_dim(self):
|
| 805 |
+
return 0
|
| 806 |
+
|
| 807 |
+
|
| 808 |
+
class LearnablePosEmbAxis(VideoPositionEmb):
|
| 809 |
+
def __init__(
|
| 810 |
+
self,
|
| 811 |
+
*, # enforce keyword arguments
|
| 812 |
+
interpolation: str,
|
| 813 |
+
model_channels: int,
|
| 814 |
+
len_h: int,
|
| 815 |
+
len_w: int,
|
| 816 |
+
len_t: int,
|
| 817 |
+
**kwargs,
|
| 818 |
+
):
|
| 819 |
+
"""
|
| 820 |
+
Args:
|
| 821 |
+
interpolation (str): we curretly only support "crop", ideally when we need extrapolation capacity, we should adjust frequency or other more advanced methods. they are not implemented yet.
|
| 822 |
+
"""
|
| 823 |
+
del kwargs # unused
|
| 824 |
+
super().__init__()
|
| 825 |
+
self.interpolation = interpolation
|
| 826 |
+
assert self.interpolation in ["crop"], f"Unknown interpolation method {self.interpolation}"
|
| 827 |
+
self.model_channels = model_channels
|
| 828 |
+
|
| 829 |
+
self.pos_emb_h = nn.Parameter(torch.zeros(len_h, model_channels))
|
| 830 |
+
self.pos_emb_w = nn.Parameter(torch.zeros(len_w, model_channels))
|
| 831 |
+
self.pos_emb_t = nn.Parameter(torch.zeros(len_t, model_channels))
|
| 832 |
+
|
| 833 |
+
self.reset_parameters()
|
| 834 |
+
|
| 835 |
+
def reset_parameters(self):
|
| 836 |
+
std = 1.0 / math.sqrt(self.model_channels)
|
| 837 |
+
torch.nn.init.trunc_normal_(self.pos_emb_h, std=std, a=-3 * std, b=3 * std)
|
| 838 |
+
torch.nn.init.trunc_normal_(self.pos_emb_w, std=std, a=-3 * std, b=3 * std)
|
| 839 |
+
torch.nn.init.trunc_normal_(self.pos_emb_t, std=std, a=-3 * std, b=3 * std)
|
| 840 |
+
|
| 841 |
+
def generate_embeddings(self, B_T_H_W_C: torch.Size, fps=Optional[torch.Tensor]) -> torch.Tensor:
|
| 842 |
+
B, T, H, W, _ = B_T_H_W_C
|
| 843 |
+
if self.interpolation == "crop":
|
| 844 |
+
emb_h_H = self.pos_emb_h[:H]
|
| 845 |
+
emb_w_W = self.pos_emb_w[:W]
|
| 846 |
+
emb_t_T = self.pos_emb_t[:T]
|
| 847 |
+
emb = (
|
| 848 |
+
repeat(emb_t_T, "t d-> b t h w d", b=B, h=H, w=W)
|
| 849 |
+
+ repeat(emb_h_H, "h d-> b t h w d", b=B, t=T, w=W)
|
| 850 |
+
+ repeat(emb_w_W, "w d-> b t h w d", b=B, t=T, h=H)
|
| 851 |
+
)
|
| 852 |
+
assert list(emb.shape)[:4] == [B, T, H, W], f"bad shape: {list(emb.shape)[:4]} != {B, T, H, W}"
|
| 853 |
+
else:
|
| 854 |
+
raise ValueError(f"Unknown interpolation method {self.interpolation}")
|
| 855 |
+
|
| 856 |
+
norm = torch.linalg.vector_norm(emb, dim=-1, keepdim=True, dtype=torch.float32)
|
| 857 |
+
norm = torch.add(1e-6, norm, alpha=np.sqrt(norm.numel() / emb.numel()))
|
| 858 |
+
return emb / norm.to(emb.dtype)
|
| 859 |
+
|
| 860 |
+
|
| 861 |
+
def modulate(x, shift, scale):
|
| 862 |
+
return x * (1 + scale) + shift
|
| 863 |
+
|
| 864 |
+
|
| 865 |
+
class Timesteps(nn.Module):
|
| 866 |
+
def __init__(self, num_channels):
|
| 867 |
+
super().__init__()
|
| 868 |
+
self.num_channels = num_channels
|
| 869 |
+
|
| 870 |
+
def forward(self, timesteps_B_T):
|
| 871 |
+
assert timesteps_B_T.ndim == 2, f"Expected 2D input, got {timesteps_B_T.ndim}"
|
| 872 |
+
# wan need emb to be in fp32
|
| 873 |
+
in_dtype = timesteps_B_T.dtype
|
| 874 |
+
timesteps = timesteps_B_T.flatten().float()
|
| 875 |
+
half_dim = self.num_channels // 2
|
| 876 |
+
exponent = -math.log(10000) * torch.arange(half_dim, dtype=torch.float32, device=timesteps.device)
|
| 877 |
+
exponent = exponent / (half_dim - 0.0)
|
| 878 |
+
|
| 879 |
+
emb = torch.exp(exponent)
|
| 880 |
+
emb = timesteps[:, None].float() * emb[None, :]
|
| 881 |
+
|
| 882 |
+
sin_emb = torch.sin(emb)
|
| 883 |
+
cos_emb = torch.cos(emb)
|
| 884 |
+
emb = torch.cat([cos_emb, sin_emb], dim=-1)
|
| 885 |
+
|
| 886 |
+
return rearrange(emb.to(dtype=in_dtype), "(b t) d -> b t d", b=timesteps_B_T.shape[0], t=timesteps_B_T.shape[1])
|
| 887 |
+
|
| 888 |
+
|
| 889 |
+
class TimestepEmbedding(nn.Module):
|
| 890 |
+
def __init__(self, in_features: int, out_features: int, use_adaln_lora: bool = False):
|
| 891 |
+
super().__init__()
|
| 892 |
+
log.debug(
|
| 893 |
+
f"Using AdaLN LoRA Flag: {use_adaln_lora}. We enable bias if no AdaLN LoRA for backward compatibility."
|
| 894 |
+
)
|
| 895 |
+
self.in_dim = in_features
|
| 896 |
+
self.out_dim = out_features
|
| 897 |
+
self.linear_1 = nn.Linear(in_features, out_features, bias=not use_adaln_lora)
|
| 898 |
+
self.activation = nn.SiLU()
|
| 899 |
+
self.use_adaln_lora = use_adaln_lora
|
| 900 |
+
if use_adaln_lora:
|
| 901 |
+
self.linear_2 = nn.Linear(out_features, 3 * out_features, bias=False)
|
| 902 |
+
else:
|
| 903 |
+
self.linear_2 = nn.Linear(out_features, out_features, bias=False)
|
| 904 |
+
|
| 905 |
+
self.init_weights()
|
| 906 |
+
|
| 907 |
+
def init_weights(self) -> None:
|
| 908 |
+
std = 1.0 / math.sqrt(self.in_dim)
|
| 909 |
+
torch.nn.init.trunc_normal_(self.linear_1.weight, std=std, a=-3 * std, b=3 * std)
|
| 910 |
+
|
| 911 |
+
std = 1.0 / math.sqrt(self.out_dim)
|
| 912 |
+
torch.nn.init.trunc_normal_(self.linear_2.weight, std=std, a=-3 * std, b=3 * std)
|
| 913 |
+
|
| 914 |
+
def forward(self, sample: torch.Tensor) -> torch.Tensor:
|
| 915 |
+
# Inference schedulers produce fp32 timesteps even when the released
|
| 916 |
+
# Cosmos checkpoint is bf16. Standard nn.Linear requires matching
|
| 917 |
+
# dtypes (Transformer Engine handled this cast in NVIDIA's runtime).
|
| 918 |
+
sample = sample.to(dtype=self.linear_1.weight.dtype)
|
| 919 |
+
emb = self.linear_1(sample)
|
| 920 |
+
emb = self.activation(emb)
|
| 921 |
+
emb = self.linear_2(emb)
|
| 922 |
+
|
| 923 |
+
if self.use_adaln_lora:
|
| 924 |
+
adaln_lora_B_T_3D = emb
|
| 925 |
+
emb_B_T_D = sample
|
| 926 |
+
else:
|
| 927 |
+
emb_B_T_D = emb
|
| 928 |
+
adaln_lora_B_T_3D = None
|
| 929 |
+
|
| 930 |
+
return emb_B_T_D, adaln_lora_B_T_3D
|
| 931 |
+
|
| 932 |
+
|
| 933 |
+
class FourierFeatures(nn.Module):
|
| 934 |
+
"""
|
| 935 |
+
Implements a layer that generates Fourier features from input tensors, based on randomly sampled
|
| 936 |
+
frequencies and phases. This can help in learning high-frequency functions in low-dimensional problems.
|
| 937 |
+
|
| 938 |
+
[B] -> [B, D]
|
| 939 |
+
|
| 940 |
+
Parameters:
|
| 941 |
+
num_channels (int): The number of Fourier features to generate.
|
| 942 |
+
bandwidth (float, optional): The scaling factor for the frequency of the Fourier features. Defaults to 1.
|
| 943 |
+
normalize (bool, optional): If set to True, the outputs are scaled by sqrt(2), usually to normalize
|
| 944 |
+
the variance of the features. Defaults to False.
|
| 945 |
+
|
| 946 |
+
Example:
|
| 947 |
+
>>> layer = FourierFeatures(num_channels=256, bandwidth=0.5, normalize=True)
|
| 948 |
+
>>> x = torch.randn(10, 256) # Example input tensor
|
| 949 |
+
>>> output = layer(x)
|
| 950 |
+
>>> print(output.shape) # Expected shape: (10, 256)
|
| 951 |
+
"""
|
| 952 |
+
|
| 953 |
+
def __init__(self, num_channels, bandwidth=1, normalize=False):
|
| 954 |
+
super().__init__()
|
| 955 |
+
self.register_buffer("freqs", 2 * np.pi * bandwidth * torch.randn(num_channels), persistent=True)
|
| 956 |
+
self.register_buffer("phases", 2 * np.pi * torch.rand(num_channels), persistent=True)
|
| 957 |
+
self.gain = np.sqrt(2) if normalize else 1
|
| 958 |
+
self.bandwidth = bandwidth
|
| 959 |
+
self.num_channels = num_channels
|
| 960 |
+
|
| 961 |
+
self.reset_parameters()
|
| 962 |
+
|
| 963 |
+
def reset_parameters(self) -> None:
|
| 964 |
+
generator = torch.Generator()
|
| 965 |
+
generator.manual_seed(0)
|
| 966 |
+
self.freqs = (
|
| 967 |
+
2 * np.pi * self.bandwidth * torch.randn(self.num_channels, generator=generator).to(self.freqs.device)
|
| 968 |
+
)
|
| 969 |
+
self.phases = 2 * np.pi * torch.rand(self.num_channels, generator=generator).to(self.freqs.device)
|
| 970 |
+
|
| 971 |
+
def forward(self, x, gain: float = 1.0):
|
| 972 |
+
"""
|
| 973 |
+
Apply the Fourier feature transformation to the input tensor.
|
| 974 |
+
|
| 975 |
+
Args:
|
| 976 |
+
x (torch.Tensor): The input tensor.
|
| 977 |
+
gain (float, optional): An additional gain factor applied during the forward pass. Defaults to 1.
|
| 978 |
+
|
| 979 |
+
Returns:
|
| 980 |
+
torch.Tensor: The transformed tensor, with Fourier features applied.
|
| 981 |
+
"""
|
| 982 |
+
in_dtype = x.dtype
|
| 983 |
+
x = x.to(torch.float32).ger(self.freqs.to(torch.float32)).add(self.phases.to(torch.float32))
|
| 984 |
+
x = x.cos().mul(self.gain * gain).to(in_dtype)
|
| 985 |
+
return x
|
| 986 |
+
|
| 987 |
+
|
| 988 |
+
class PatchEmbed(nn.Module):
|
| 989 |
+
"""
|
| 990 |
+
PatchEmbed is a module for embedding patches from an input tensor by applying either 3D or 2D convolutional layers,
|
| 991 |
+
depending on the . This module can process inputs with temporal (video) and spatial (image) dimensions,
|
| 992 |
+
making it suitable for video and image processing tasks. It supports dividing the input into patches
|
| 993 |
+
and embedding each patch into a vector of size `out_channels`.
|
| 994 |
+
|
| 995 |
+
Parameters:
|
| 996 |
+
- spatial_patch_size (int): The size of each spatial patch.
|
| 997 |
+
- temporal_patch_size (int): The size of each temporal patch.
|
| 998 |
+
- in_channels (int): Number of input channels. Default: 3.
|
| 999 |
+
- out_channels (int): The dimension of the embedding vector for each patch. Default: 768.
|
| 1000 |
+
- bias (bool): If True, adds a learnable bias to the output of the convolutional layers. Default: True.
|
| 1001 |
+
"""
|
| 1002 |
+
|
| 1003 |
+
def __init__(
|
| 1004 |
+
self,
|
| 1005 |
+
spatial_patch_size,
|
| 1006 |
+
temporal_patch_size,
|
| 1007 |
+
in_channels=3,
|
| 1008 |
+
out_channels=768,
|
| 1009 |
+
):
|
| 1010 |
+
super().__init__()
|
| 1011 |
+
self.spatial_patch_size = spatial_patch_size
|
| 1012 |
+
self.temporal_patch_size = temporal_patch_size
|
| 1013 |
+
|
| 1014 |
+
self.proj = nn.Sequential(
|
| 1015 |
+
Rearrange(
|
| 1016 |
+
"b c (t r) (h m) (w n) -> b t h w (c r m n)",
|
| 1017 |
+
r=temporal_patch_size,
|
| 1018 |
+
m=spatial_patch_size,
|
| 1019 |
+
n=spatial_patch_size,
|
| 1020 |
+
),
|
| 1021 |
+
nn.Linear(
|
| 1022 |
+
in_channels * spatial_patch_size * spatial_patch_size * temporal_patch_size, out_channels, bias=False
|
| 1023 |
+
),
|
| 1024 |
+
)
|
| 1025 |
+
self.dim = in_channels * spatial_patch_size * spatial_patch_size * temporal_patch_size
|
| 1026 |
+
|
| 1027 |
+
self.init_weights()
|
| 1028 |
+
|
| 1029 |
+
def init_weights(self) -> None:
|
| 1030 |
+
std = 1.0 / math.sqrt(self.dim)
|
| 1031 |
+
torch.nn.init.trunc_normal_(self.proj[1].weight, std=std, a=-3 * std, b=3 * std)
|
| 1032 |
+
|
| 1033 |
+
def forward(self, x):
|
| 1034 |
+
"""
|
| 1035 |
+
Forward pass of the PatchEmbed module.
|
| 1036 |
+
|
| 1037 |
+
Parameters:
|
| 1038 |
+
- x (torch.Tensor): The input tensor of shape (B, C, T, H, W) where
|
| 1039 |
+
B is the batch size,
|
| 1040 |
+
C is the number of channels,
|
| 1041 |
+
T is the temporal dimension,
|
| 1042 |
+
H is the height, and
|
| 1043 |
+
W is the width of the input.
|
| 1044 |
+
|
| 1045 |
+
Returns:
|
| 1046 |
+
- torch.Tensor: The embedded patches as a tensor, with shape b t h w c.
|
| 1047 |
+
"""
|
| 1048 |
+
assert x.dim() == 5
|
| 1049 |
+
_, _, T, H, W = x.shape
|
| 1050 |
+
assert H % self.spatial_patch_size == 0 and W % self.spatial_patch_size == 0, (
|
| 1051 |
+
f"H,W {(H, W)} should be divisible by spatial_patch_size {self.spatial_patch_size}"
|
| 1052 |
+
)
|
| 1053 |
+
assert T % self.temporal_patch_size == 0
|
| 1054 |
+
x = self.proj(x)
|
| 1055 |
+
return x
|
| 1056 |
+
|
| 1057 |
+
|
| 1058 |
+
class FinalLayer(nn.Module):
|
| 1059 |
+
"""
|
| 1060 |
+
The final layer of video DiT.
|
| 1061 |
+
"""
|
| 1062 |
+
|
| 1063 |
+
def __init__(
|
| 1064 |
+
self,
|
| 1065 |
+
hidden_size,
|
| 1066 |
+
spatial_patch_size,
|
| 1067 |
+
temporal_patch_size,
|
| 1068 |
+
out_channels,
|
| 1069 |
+
use_adaln_lora: bool = False,
|
| 1070 |
+
adaln_lora_dim: int = 256,
|
| 1071 |
+
use_wan_fp32_strategy: bool = False,
|
| 1072 |
+
):
|
| 1073 |
+
super().__init__()
|
| 1074 |
+
self.use_wan_fp32_strategy = use_wan_fp32_strategy
|
| 1075 |
+
self.layer_norm = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 1076 |
+
self.linear = nn.Linear(
|
| 1077 |
+
hidden_size, spatial_patch_size * spatial_patch_size * temporal_patch_size * out_channels, bias=False
|
| 1078 |
+
)
|
| 1079 |
+
self.hidden_size = hidden_size
|
| 1080 |
+
self.n_adaln_chunks = 2
|
| 1081 |
+
self.use_adaln_lora = use_adaln_lora
|
| 1082 |
+
self.adaln_lora_dim = adaln_lora_dim
|
| 1083 |
+
if use_adaln_lora:
|
| 1084 |
+
self.adaln_modulation = nn.Sequential(
|
| 1085 |
+
nn.SiLU(),
|
| 1086 |
+
nn.Linear(hidden_size, adaln_lora_dim, bias=False),
|
| 1087 |
+
nn.Linear(adaln_lora_dim, self.n_adaln_chunks * hidden_size, bias=False),
|
| 1088 |
+
)
|
| 1089 |
+
else:
|
| 1090 |
+
self.adaln_modulation = nn.Sequential(
|
| 1091 |
+
nn.SiLU(), nn.Linear(hidden_size, self.n_adaln_chunks * hidden_size, bias=False)
|
| 1092 |
+
)
|
| 1093 |
+
|
| 1094 |
+
self.init_weights()
|
| 1095 |
+
|
| 1096 |
+
def init_weights(self) -> None:
|
| 1097 |
+
std = 1.0 / math.sqrt(self.hidden_size)
|
| 1098 |
+
torch.nn.init.trunc_normal_(self.linear.weight, std=std, a=-3 * std, b=3 * std)
|
| 1099 |
+
if self.use_adaln_lora:
|
| 1100 |
+
torch.nn.init.trunc_normal_(self.adaln_modulation[1].weight, std=std, a=-3 * std, b=3 * std)
|
| 1101 |
+
torch.nn.init.zeros_(self.adaln_modulation[2].weight)
|
| 1102 |
+
else:
|
| 1103 |
+
torch.nn.init.zeros_(self.adaln_modulation[1].weight)
|
| 1104 |
+
|
| 1105 |
+
self.layer_norm.reset_parameters()
|
| 1106 |
+
|
| 1107 |
+
def forward(
|
| 1108 |
+
self,
|
| 1109 |
+
# x_BT_HW_D,
|
| 1110 |
+
x_B_T_H_W_D,
|
| 1111 |
+
emb_B_T_D,
|
| 1112 |
+
adaln_lora_B_T_3D: Optional[torch.Tensor] = None,
|
| 1113 |
+
):
|
| 1114 |
+
with amp.autocast("cuda", enabled=self.use_wan_fp32_strategy, dtype=torch.float32):
|
| 1115 |
+
if self.use_adaln_lora:
|
| 1116 |
+
assert adaln_lora_B_T_3D is not None
|
| 1117 |
+
shift_B_T_D, scale_B_T_D = (
|
| 1118 |
+
self.adaln_modulation(emb_B_T_D) + adaln_lora_B_T_3D[:, :, : 2 * self.hidden_size]
|
| 1119 |
+
).chunk(2, dim=-1)
|
| 1120 |
+
else:
|
| 1121 |
+
shift_B_T_D, scale_B_T_D = self.adaln_modulation(emb_B_T_D).chunk(2, dim=-1)
|
| 1122 |
+
|
| 1123 |
+
shift_B_T_1_1_D, scale_B_T_1_1_D = (
|
| 1124 |
+
rearrange(shift_B_T_D, "b t d -> b t 1 1 d"),
|
| 1125 |
+
rearrange(scale_B_T_D, "b t d -> b t 1 1 d"),
|
| 1126 |
+
)
|
| 1127 |
+
|
| 1128 |
+
def _fn(_x_B_T_H_W_D, _norm_layer, _scale_B_T_1_1_D, _shift_B_T_1_1_D):
|
| 1129 |
+
return _norm_layer(_x_B_T_H_W_D) * (1 + _scale_B_T_1_1_D) + _shift_B_T_1_1_D
|
| 1130 |
+
|
| 1131 |
+
x_B_T_H_W_D = _fn(x_B_T_H_W_D, self.layer_norm, scale_B_T_1_1_D, shift_B_T_1_1_D)
|
| 1132 |
+
x_B_T_H_W_O = self.linear(
|
| 1133 |
+
x_B_T_H_W_D
|
| 1134 |
+
) # O = spatial_patch_size * spatial_patch_size * temporal_patch_size * out_channels
|
| 1135 |
+
return x_B_T_H_W_O
|
| 1136 |
+
|
| 1137 |
+
|
| 1138 |
+
class Block(nn.Module):
|
| 1139 |
+
"""
|
| 1140 |
+
A transformer block that combines self-attention, cross-attention and MLP layers with AdaLN modulation.
|
| 1141 |
+
Each component (self-attention, cross-attention, MLP) has its own layer normalization and AdaLN modulation.
|
| 1142 |
+
|
| 1143 |
+
Parameters:
|
| 1144 |
+
x_dim (int): Dimension of input features
|
| 1145 |
+
context_dim (int): Dimension of context features for cross-attention
|
| 1146 |
+
num_heads (int): Number of attention heads
|
| 1147 |
+
mlp_ratio (float): Multiplier for MLP hidden dimension. Default: 4.0
|
| 1148 |
+
use_adaln_lora (bool): Whether to use AdaLN-LoRA modulation. Default: False
|
| 1149 |
+
adaln_lora_dim (int): Hidden dimension for AdaLN-LoRA layers. Default: 256
|
| 1150 |
+
use_wan_fp32_strategy (bool): Whether to use Wan's FP32 strategy. Default: False
|
| 1151 |
+
If True, in Attention layer, if do self-attention, q and k will be forced to fp32 before rotary pos emb
|
| 1152 |
+
also, in modulation computation, force entire computation in fp32
|
| 1153 |
+
|
| 1154 |
+
The block applies the following sequence:
|
| 1155 |
+
1. Self-attention with AdaLN modulation
|
| 1156 |
+
2. Cross-attention with AdaLN modulation
|
| 1157 |
+
3. MLP with AdaLN modulation
|
| 1158 |
+
|
| 1159 |
+
Each component uses skip connections and layer normalization.
|
| 1160 |
+
"""
|
| 1161 |
+
|
| 1162 |
+
def __init__(
|
| 1163 |
+
self,
|
| 1164 |
+
x_dim: int,
|
| 1165 |
+
context_dim: int,
|
| 1166 |
+
num_heads: int,
|
| 1167 |
+
mlp_ratio: float = 4.0,
|
| 1168 |
+
use_adaln_lora: bool = False,
|
| 1169 |
+
adaln_lora_dim: int = 256,
|
| 1170 |
+
backend: str = "transformer_engine",
|
| 1171 |
+
image_context_dim: Optional[int] = None,
|
| 1172 |
+
use_wan_fp32_strategy: bool = False,
|
| 1173 |
+
):
|
| 1174 |
+
super().__init__()
|
| 1175 |
+
self.x_dim = x_dim
|
| 1176 |
+
self.layer_norm_self_attn = nn.LayerNorm(x_dim, elementwise_affine=False, eps=1e-6)
|
| 1177 |
+
self.self_attn = Attention(
|
| 1178 |
+
x_dim,
|
| 1179 |
+
None,
|
| 1180 |
+
num_heads,
|
| 1181 |
+
x_dim // num_heads,
|
| 1182 |
+
qkv_format="bshd",
|
| 1183 |
+
backend=backend,
|
| 1184 |
+
use_wan_fp32_strategy=use_wan_fp32_strategy,
|
| 1185 |
+
)
|
| 1186 |
+
self.layer_norm_cross_attn = nn.LayerNorm(x_dim, elementwise_affine=False, eps=1e-6)
|
| 1187 |
+
|
| 1188 |
+
if image_context_dim is None:
|
| 1189 |
+
self.cross_attn = Attention(
|
| 1190 |
+
x_dim, context_dim, num_heads, x_dim // num_heads, qkv_format="bshd", backend=backend
|
| 1191 |
+
)
|
| 1192 |
+
else:
|
| 1193 |
+
self.cross_attn = I2VCrossAttention(
|
| 1194 |
+
x_dim,
|
| 1195 |
+
context_dim,
|
| 1196 |
+
num_heads,
|
| 1197 |
+
x_dim // num_heads,
|
| 1198 |
+
img_latent_dim=image_context_dim,
|
| 1199 |
+
qkv_format="bshd",
|
| 1200 |
+
backend=backend,
|
| 1201 |
+
)
|
| 1202 |
+
|
| 1203 |
+
self.layer_norm_mlp = nn.LayerNorm(x_dim, elementwise_affine=False, eps=1e-6)
|
| 1204 |
+
self.mlp = GPT2FeedForward(x_dim, int(x_dim * mlp_ratio))
|
| 1205 |
+
|
| 1206 |
+
self.use_adaln_lora = use_adaln_lora
|
| 1207 |
+
if self.use_adaln_lora:
|
| 1208 |
+
self.adaln_modulation_self_attn = nn.Sequential(
|
| 1209 |
+
nn.SiLU(),
|
| 1210 |
+
nn.Linear(x_dim, adaln_lora_dim, bias=False),
|
| 1211 |
+
nn.Linear(adaln_lora_dim, 3 * x_dim, bias=False),
|
| 1212 |
+
)
|
| 1213 |
+
self.adaln_modulation_cross_attn = nn.Sequential(
|
| 1214 |
+
nn.SiLU(),
|
| 1215 |
+
nn.Linear(x_dim, adaln_lora_dim, bias=False),
|
| 1216 |
+
nn.Linear(adaln_lora_dim, 3 * x_dim, bias=False),
|
| 1217 |
+
)
|
| 1218 |
+
self.adaln_modulation_mlp = nn.Sequential(
|
| 1219 |
+
nn.SiLU(),
|
| 1220 |
+
nn.Linear(x_dim, adaln_lora_dim, bias=False),
|
| 1221 |
+
nn.Linear(adaln_lora_dim, 3 * x_dim, bias=False),
|
| 1222 |
+
)
|
| 1223 |
+
else:
|
| 1224 |
+
self.adaln_modulation_self_attn = nn.Sequential(nn.SiLU(), nn.Linear(x_dim, 3 * x_dim, bias=False))
|
| 1225 |
+
self.adaln_modulation_cross_attn = nn.Sequential(nn.SiLU(), nn.Linear(x_dim, 3 * x_dim, bias=False))
|
| 1226 |
+
self.adaln_modulation_mlp = nn.Sequential(nn.SiLU(), nn.Linear(x_dim, 3 * x_dim, bias=False))
|
| 1227 |
+
|
| 1228 |
+
self.cp_size = None
|
| 1229 |
+
self.use_wan_fp32_strategy = use_wan_fp32_strategy
|
| 1230 |
+
|
| 1231 |
+
def set_context_parallel_group(self, process_group, ranks, stream, cp_comm_type: str = "p2p"):
|
| 1232 |
+
self.cp_size = None if ranks is None else len(ranks)
|
| 1233 |
+
self.self_attn.set_context_parallel_group(
|
| 1234 |
+
process_group=process_group,
|
| 1235 |
+
ranks=ranks,
|
| 1236 |
+
stream=stream,
|
| 1237 |
+
cp_comm_type=cp_comm_type,
|
| 1238 |
+
)
|
| 1239 |
+
|
| 1240 |
+
def reset_parameters(self) -> None:
|
| 1241 |
+
self.layer_norm_self_attn.reset_parameters()
|
| 1242 |
+
self.layer_norm_cross_attn.reset_parameters()
|
| 1243 |
+
self.layer_norm_mlp.reset_parameters()
|
| 1244 |
+
|
| 1245 |
+
if self.use_adaln_lora:
|
| 1246 |
+
std = 1.0 / math.sqrt(self.x_dim)
|
| 1247 |
+
torch.nn.init.trunc_normal_(self.adaln_modulation_self_attn[1].weight, std=std, a=-3 * std, b=3 * std)
|
| 1248 |
+
torch.nn.init.trunc_normal_(self.adaln_modulation_cross_attn[1].weight, std=std, a=-3 * std, b=3 * std)
|
| 1249 |
+
torch.nn.init.trunc_normal_(self.adaln_modulation_mlp[1].weight, std=std, a=-3 * std, b=3 * std)
|
| 1250 |
+
torch.nn.init.zeros_(self.adaln_modulation_self_attn[2].weight)
|
| 1251 |
+
torch.nn.init.zeros_(self.adaln_modulation_cross_attn[2].weight)
|
| 1252 |
+
torch.nn.init.zeros_(self.adaln_modulation_mlp[2].weight)
|
| 1253 |
+
else:
|
| 1254 |
+
torch.nn.init.zeros_(self.adaln_modulation_self_attn[1].weight)
|
| 1255 |
+
torch.nn.init.zeros_(self.adaln_modulation_cross_attn[1].weight)
|
| 1256 |
+
torch.nn.init.zeros_(self.adaln_modulation_mlp[1].weight)
|
| 1257 |
+
|
| 1258 |
+
def init_weights(self) -> None:
|
| 1259 |
+
self.reset_parameters()
|
| 1260 |
+
self.self_attn.init_weights()
|
| 1261 |
+
self.cross_attn.init_weights()
|
| 1262 |
+
self.mlp.init_weights()
|
| 1263 |
+
|
| 1264 |
+
def forward(
|
| 1265 |
+
self,
|
| 1266 |
+
x_B_T_H_W_D: torch.Tensor,
|
| 1267 |
+
emb_B_T_D: torch.Tensor,
|
| 1268 |
+
crossattn_emb: torch.Tensor,
|
| 1269 |
+
rope_emb_L_1_1_D: Optional[torch.Tensor] = None,
|
| 1270 |
+
adaln_lora_B_T_3D: Optional[torch.Tensor] = None,
|
| 1271 |
+
extra_per_block_pos_emb: Optional[torch.Tensor] = None,
|
| 1272 |
+
kv_cache_cfg: Optional[KVCacheConfig] = None,
|
| 1273 |
+
camera_B_T_H_W_C: Optional[torch.Tensor] = None,
|
| 1274 |
+
teacher_forcing_layout: Optional[tuple[int, int]] = None,
|
| 1275 |
+
) -> torch.Tensor:
|
| 1276 |
+
return self._forward_single(
|
| 1277 |
+
x_B_T_H_W_D,
|
| 1278 |
+
emb_B_T_D,
|
| 1279 |
+
crossattn_emb,
|
| 1280 |
+
rope_emb_L_1_1_D=rope_emb_L_1_1_D,
|
| 1281 |
+
adaln_lora_B_T_3D=adaln_lora_B_T_3D,
|
| 1282 |
+
extra_per_block_pos_emb=extra_per_block_pos_emb,
|
| 1283 |
+
kv_cache_cfg=kv_cache_cfg,
|
| 1284 |
+
camera_B_T_H_W_C=camera_B_T_H_W_C,
|
| 1285 |
+
teacher_forcing_layout=teacher_forcing_layout,
|
| 1286 |
+
)
|
| 1287 |
+
|
| 1288 |
+
def _forward_single(
|
| 1289 |
+
self,
|
| 1290 |
+
x_B_T_H_W_D: torch.Tensor,
|
| 1291 |
+
emb_B_T_D: torch.Tensor,
|
| 1292 |
+
crossattn_emb: torch.Tensor,
|
| 1293 |
+
rope_emb_L_1_1_D: Optional[torch.Tensor] = None,
|
| 1294 |
+
adaln_lora_B_T_3D: Optional[torch.Tensor] = None,
|
| 1295 |
+
extra_per_block_pos_emb: Optional[torch.Tensor] = None,
|
| 1296 |
+
kv_cache_cfg: Optional[KVCacheConfig] = None,
|
| 1297 |
+
camera_B_T_H_W_C: Optional[torch.Tensor] = None,
|
| 1298 |
+
teacher_forcing_layout: Optional[tuple[int, int]] = None,
|
| 1299 |
+
) -> torch.Tensor:
|
| 1300 |
+
if extra_per_block_pos_emb is not None:
|
| 1301 |
+
x_B_T_H_W_D = x_B_T_H_W_D + extra_per_block_pos_emb
|
| 1302 |
+
|
| 1303 |
+
with amp.autocast("cuda", enabled=self.use_wan_fp32_strategy, dtype=torch.float32):
|
| 1304 |
+
if self.use_adaln_lora:
|
| 1305 |
+
shift_self_attn_B_T_D, scale_self_attn_B_T_D, gate_self_attn_B_T_D = (
|
| 1306 |
+
self.adaln_modulation_self_attn(emb_B_T_D) + adaln_lora_B_T_3D
|
| 1307 |
+
).chunk(3, dim=-1)
|
| 1308 |
+
shift_cross_attn_B_T_D, scale_cross_attn_B_T_D, gate_cross_attn_B_T_D = (
|
| 1309 |
+
self.adaln_modulation_cross_attn(emb_B_T_D) + adaln_lora_B_T_3D
|
| 1310 |
+
).chunk(3, dim=-1)
|
| 1311 |
+
shift_mlp_B_T_D, scale_mlp_B_T_D, gate_mlp_B_T_D = (
|
| 1312 |
+
self.adaln_modulation_mlp(emb_B_T_D) + adaln_lora_B_T_3D
|
| 1313 |
+
).chunk(3, dim=-1)
|
| 1314 |
+
else:
|
| 1315 |
+
shift_self_attn_B_T_D, scale_self_attn_B_T_D, gate_self_attn_B_T_D = self.adaln_modulation_self_attn(
|
| 1316 |
+
emb_B_T_D
|
| 1317 |
+
).chunk(3, dim=-1)
|
| 1318 |
+
shift_cross_attn_B_T_D, scale_cross_attn_B_T_D, gate_cross_attn_B_T_D = (
|
| 1319 |
+
self.adaln_modulation_cross_attn(emb_B_T_D).chunk(3, dim=-1)
|
| 1320 |
+
)
|
| 1321 |
+
shift_mlp_B_T_D, scale_mlp_B_T_D, gate_mlp_B_T_D = self.adaln_modulation_mlp(emb_B_T_D).chunk(3, dim=-1)
|
| 1322 |
+
|
| 1323 |
+
# Reshape tensors from (B, T, D) to (B, T, 1, 1, D) for broadcasting
|
| 1324 |
+
shift_self_attn_B_T_1_1_D = rearrange(shift_self_attn_B_T_D, "b t d -> b t 1 1 d").type_as(x_B_T_H_W_D)
|
| 1325 |
+
scale_self_attn_B_T_1_1_D = rearrange(scale_self_attn_B_T_D, "b t d -> b t 1 1 d").type_as(x_B_T_H_W_D)
|
| 1326 |
+
gate_self_attn_B_T_1_1_D = rearrange(gate_self_attn_B_T_D, "b t d -> b t 1 1 d").type_as(x_B_T_H_W_D)
|
| 1327 |
+
|
| 1328 |
+
shift_cross_attn_B_T_1_1_D = rearrange(shift_cross_attn_B_T_D, "b t d -> b t 1 1 d").type_as(x_B_T_H_W_D)
|
| 1329 |
+
scale_cross_attn_B_T_1_1_D = rearrange(scale_cross_attn_B_T_D, "b t d -> b t 1 1 d").type_as(x_B_T_H_W_D)
|
| 1330 |
+
gate_cross_attn_B_T_1_1_D = rearrange(gate_cross_attn_B_T_D, "b t d -> b t 1 1 d").type_as(x_B_T_H_W_D)
|
| 1331 |
+
|
| 1332 |
+
shift_mlp_B_T_1_1_D = rearrange(shift_mlp_B_T_D, "b t d -> b t 1 1 d").type_as(x_B_T_H_W_D)
|
| 1333 |
+
scale_mlp_B_T_1_1_D = rearrange(scale_mlp_B_T_D, "b t d -> b t 1 1 d").type_as(x_B_T_H_W_D)
|
| 1334 |
+
gate_mlp_B_T_1_1_D = rearrange(gate_mlp_B_T_D, "b t d -> b t 1 1 d").type_as(x_B_T_H_W_D)
|
| 1335 |
+
|
| 1336 |
+
B, T, H, W, D = x_B_T_H_W_D.shape
|
| 1337 |
+
|
| 1338 |
+
def _fn(_x_B_T_H_W_D, _norm_layer, _scale_B_T_1_1_D, _shift_B_T_1_1_D):
|
| 1339 |
+
return _norm_layer(_x_B_T_H_W_D) * (1 + _scale_B_T_1_1_D) + _shift_B_T_1_1_D
|
| 1340 |
+
|
| 1341 |
+
normalized_x_B_T_H_W_D = _fn(
|
| 1342 |
+
x_B_T_H_W_D,
|
| 1343 |
+
self.layer_norm_self_attn,
|
| 1344 |
+
scale_self_attn_B_T_1_1_D,
|
| 1345 |
+
shift_self_attn_B_T_1_1_D,
|
| 1346 |
+
)
|
| 1347 |
+
if camera_B_T_H_W_C is not None:
|
| 1348 |
+
if self.self_attn.cam_encoder is None:
|
| 1349 |
+
raise RuntimeError(
|
| 1350 |
+
"Camera conditioning was provided to a block without a camera encoder"
|
| 1351 |
+
)
|
| 1352 |
+
if camera_B_T_H_W_C.shape[:4] != normalized_x_B_T_H_W_D.shape[:4]:
|
| 1353 |
+
raise ValueError(
|
| 1354 |
+
"Camera and video token grids do not match: "
|
| 1355 |
+
f"{tuple(camera_B_T_H_W_C.shape[:4])} and "
|
| 1356 |
+
f"{tuple(normalized_x_B_T_H_W_D.shape[:4])}"
|
| 1357 |
+
)
|
| 1358 |
+
normalized_x_B_T_H_W_D = normalized_x_B_T_H_W_D + self.self_attn.cam_encoder(
|
| 1359 |
+
camera_B_T_H_W_C.to(dtype=normalized_x_B_T_H_W_D.dtype)
|
| 1360 |
+
)
|
| 1361 |
+
|
| 1362 |
+
video_size = VideoSize(T=T, H=H, W=W)
|
| 1363 |
+
|
| 1364 |
+
# (ahassani): Hack to correct `video_size` when CP is enabled.
|
| 1365 |
+
# I really don't like this, but there doesn't seem to be any central
|
| 1366 |
+
# piece of code that's responsible for handling CP/TP that also defines the
|
| 1367 |
+
# layout of shardings. Other parts of the code (i.e. RoPE) seem to make this
|
| 1368 |
+
# assumption that CP sharding is always done along T.
|
| 1369 |
+
if self.cp_size is not None and self.cp_size > 1:
|
| 1370 |
+
video_size = VideoSize(T=T * self.cp_size, H=H, W=W)
|
| 1371 |
+
|
| 1372 |
+
self_attn_result = self.self_attn(
|
| 1373 |
+
rearrange(normalized_x_B_T_H_W_D, "b t h w d -> b (t h w) d"),
|
| 1374 |
+
None,
|
| 1375 |
+
rope_emb=rope_emb_L_1_1_D,
|
| 1376 |
+
video_size=video_size,
|
| 1377 |
+
kv_cache_cfg=kv_cache_cfg,
|
| 1378 |
+
teacher_forcing_layout=teacher_forcing_layout,
|
| 1379 |
+
)
|
| 1380 |
+
result_B_T_H_W_D = rearrange(
|
| 1381 |
+
self_attn_result,
|
| 1382 |
+
"b (t h w) d -> b t h w d",
|
| 1383 |
+
t=T,
|
| 1384 |
+
h=H,
|
| 1385 |
+
w=W,
|
| 1386 |
+
)
|
| 1387 |
+
x_B_T_H_W_D = x_B_T_H_W_D + gate_self_attn_B_T_1_1_D * result_B_T_H_W_D
|
| 1388 |
+
|
| 1389 |
+
def _x_fn(
|
| 1390 |
+
_x_B_T_H_W_D,
|
| 1391 |
+
layer_norm_cross_attn,
|
| 1392 |
+
_scale_cross_attn_B_T_1_1_D,
|
| 1393 |
+
_shift_cross_attn_B_T_1_1_D,
|
| 1394 |
+
_gate_cross_attn_B_T_1_1_D,
|
| 1395 |
+
):
|
| 1396 |
+
_normalized_x_B_T_H_W_D = _fn(
|
| 1397 |
+
_x_B_T_H_W_D, layer_norm_cross_attn, _scale_cross_attn_B_T_1_1_D, _shift_cross_attn_B_T_1_1_D
|
| 1398 |
+
)
|
| 1399 |
+
_result_B_T_H_W_D = rearrange(
|
| 1400 |
+
self.cross_attn(
|
| 1401 |
+
rearrange(_normalized_x_B_T_H_W_D, "b t h w d -> b (t h w) d"),
|
| 1402 |
+
crossattn_emb,
|
| 1403 |
+
rope_emb=rope_emb_L_1_1_D,
|
| 1404 |
+
),
|
| 1405 |
+
"b (t h w) d -> b t h w d",
|
| 1406 |
+
t=T,
|
| 1407 |
+
h=H,
|
| 1408 |
+
w=W,
|
| 1409 |
+
)
|
| 1410 |
+
# _x_B_T_H_W_D = _x_B_T_H_W_D + _gate_cross_attn_B_T_1_1_D * _result_B_T_H_W_D
|
| 1411 |
+
return _result_B_T_H_W_D
|
| 1412 |
+
|
| 1413 |
+
result_B_T_H_W_D = _x_fn(
|
| 1414 |
+
x_B_T_H_W_D,
|
| 1415 |
+
self.layer_norm_cross_attn,
|
| 1416 |
+
scale_cross_attn_B_T_1_1_D,
|
| 1417 |
+
shift_cross_attn_B_T_1_1_D,
|
| 1418 |
+
gate_cross_attn_B_T_1_1_D,
|
| 1419 |
+
)
|
| 1420 |
+
x_B_T_H_W_D = result_B_T_H_W_D * gate_cross_attn_B_T_1_1_D + x_B_T_H_W_D
|
| 1421 |
+
|
| 1422 |
+
normalized_x_B_T_H_W_D = _fn(
|
| 1423 |
+
x_B_T_H_W_D,
|
| 1424 |
+
self.layer_norm_mlp,
|
| 1425 |
+
scale_mlp_B_T_1_1_D,
|
| 1426 |
+
shift_mlp_B_T_1_1_D,
|
| 1427 |
+
)
|
| 1428 |
+
result_B_T_H_W_D = self.mlp(normalized_x_B_T_H_W_D)
|
| 1429 |
+
x_B_T_H_W_D = x_B_T_H_W_D + gate_mlp_B_T_1_1_D * result_B_T_H_W_D
|
| 1430 |
+
return x_B_T_H_W_D
|
| 1431 |
+
|
| 1432 |
+
|
| 1433 |
+
class MiniTrainDIT(WeightTrainingStat):
|
| 1434 |
+
"""
|
| 1435 |
+
A clean impl of DIT that can load and reproduce the training results of the original DIT model in edify_video/v4~(cosmos 1)
|
| 1436 |
+
A general implementation of adaln-modulated VIT-like~(DiT) transformer for video processing.
|
| 1437 |
+
|
| 1438 |
+
Args:
|
| 1439 |
+
max_img_h (int): Maximum height of the input images.
|
| 1440 |
+
max_img_w (int): Maximum width of the input images.
|
| 1441 |
+
max_frames (int): Maximum number of frames in the video sequence.
|
| 1442 |
+
in_channels (int): Number of input channels (e.g., RGB channels for color images).
|
| 1443 |
+
out_channels (int): Number of output channels.
|
| 1444 |
+
patch_spatial (int): Spatial resolution of patches for input processing.
|
| 1445 |
+
patch_temporal (int): Temporal resolution of patches for input processing.
|
| 1446 |
+
concat_padding_mask (bool): If True, includes a mask channel in the input to handle padding.
|
| 1447 |
+
model_channels (int): Base number of channels used throughout the model.
|
| 1448 |
+
num_blocks (int): Number of transformer blocks.
|
| 1449 |
+
num_heads (int): Number of heads in the multi-head attention layers.
|
| 1450 |
+
mlp_ratio (float): Expansion ratio for MLP blocks.
|
| 1451 |
+
crossattn_emb_channels (int): Number of embedding channels for cross-attention.
|
| 1452 |
+
extra_image_context_dim (int): Number of embedding channels for extra image context.
|
| 1453 |
+
pos_emb_cls (str): Type of positional embeddings.
|
| 1454 |
+
pos_emb_learnable (bool): Whether positional embeddings are learnable.
|
| 1455 |
+
pos_emb_interpolation (str): Method for interpolating positional embeddings.
|
| 1456 |
+
min_fps (int): Minimum frames per second.
|
| 1457 |
+
max_fps (int): Maximum frames per second.
|
| 1458 |
+
use_adaln_lora (bool): Whether to use AdaLN-LoRA.
|
| 1459 |
+
adaln_lora_dim (int): Dimension for AdaLN-LoRA.
|
| 1460 |
+
rope_h_extrapolation_ratio (float): Height extrapolation ratio for RoPE.
|
| 1461 |
+
rope_w_extrapolation_ratio (float): Width extrapolation ratio for RoPE.
|
| 1462 |
+
rope_t_extrapolation_ratio (float): Temporal extrapolation ratio for RoPE.
|
| 1463 |
+
extra_per_block_abs_pos_emb (bool): Whether to use extra per-block absolute positional embeddings.
|
| 1464 |
+
extra_h_extrapolation_ratio (float): Height extrapolation ratio for extra embeddings.
|
| 1465 |
+
extra_w_extrapolation_ratio (float): Width extrapolation ratio for extra embeddings.
|
| 1466 |
+
extra_t_extrapolation_ratio (float): Temporal extrapolation ratio for extra embeddings.
|
| 1467 |
+
n_dense_blocks (`int`, *optional*, defaults to -1):
|
| 1468 |
+
Number of blocks that will remain dense (not replaced with sparse attention)
|
| 1469 |
+
If -1, no blocks are replaced with sparse attention
|
| 1470 |
+
If 0, all blocks use sparse attention
|
| 1471 |
+
Otherwise, n_dense_blocks blocks will remain dense, distributed evenly across the network
|
| 1472 |
+
natten_parameters (`dict`, *optional*, defaults to None):
|
| 1473 |
+
NATTEN (Sparse attention) parameter list.
|
| 1474 |
+
The list length must be the same as the number of layers, with each list element
|
| 1475 |
+
indicating NATTEN parameters for that layer. If None, NATTEN will not be used in that
|
| 1476 |
+
layer and it would remain a full dense self attention. If not None, it must be a
|
| 1477 |
+
dictionary/mapping with at least the following key:
|
| 1478 |
+
- window_size: `tuple` of size 3 indicating neighborhood attention window size.
|
| 1479 |
+
window size of -1 along any dimension means self attention.
|
| 1480 |
+
Other optional parameters and their keys:
|
| 1481 |
+
- stride: `tuple` of size 3 indicating neighborhood attention stride value.
|
| 1482 |
+
stride = 1 is standard neighborhood attention, stride = window size means
|
| 1483 |
+
blocked/window self attention (WSA) along that dimension. Any other values are
|
| 1484 |
+
strided neighborhood attention. Refer to the GNA paper for more information.
|
| 1485 |
+
|
| 1486 |
+
- dilation: `tuple` of size 3 indicating neighborhood attention dilation value.
|
| 1487 |
+
dilation = 1 is standard neighborhood attention. Refer to the DiNAT paper for more
|
| 1488 |
+
information.
|
| 1489 |
+
|
| 1490 |
+
- is_causal: `tuple` of 3 booleans indicating whether causal masking is enabled for
|
| 1491 |
+
any of the T, H, W dimensions.
|
| 1492 |
+
"""
|
| 1493 |
+
|
| 1494 |
+
def __init__(
|
| 1495 |
+
self,
|
| 1496 |
+
max_img_h: int,
|
| 1497 |
+
max_img_w: int,
|
| 1498 |
+
max_frames: int,
|
| 1499 |
+
in_channels: int,
|
| 1500 |
+
out_channels: int,
|
| 1501 |
+
patch_spatial: int,
|
| 1502 |
+
patch_temporal: int,
|
| 1503 |
+
concat_padding_mask: bool = True,
|
| 1504 |
+
# attention settings
|
| 1505 |
+
model_channels: int = 768,
|
| 1506 |
+
num_blocks: int = 10,
|
| 1507 |
+
num_heads: int = 16,
|
| 1508 |
+
mlp_ratio: float = 4.0,
|
| 1509 |
+
atten_backend: str = "transformer_engine",
|
| 1510 |
+
# cross attention settings
|
| 1511 |
+
crossattn_emb_channels: int = 1024,
|
| 1512 |
+
use_crossattn_projection: bool = False,
|
| 1513 |
+
crossattn_proj_in_channels: int = 1024,
|
| 1514 |
+
extra_image_context_dim: Optional[int] = None,
|
| 1515 |
+
# positional embedding settings
|
| 1516 |
+
pos_emb_cls: str = "sincos",
|
| 1517 |
+
pos_emb_learnable: bool = False,
|
| 1518 |
+
pos_emb_interpolation: str = "crop",
|
| 1519 |
+
min_fps: int = 1,
|
| 1520 |
+
max_fps: int = 30,
|
| 1521 |
+
use_adaln_lora: bool = False,
|
| 1522 |
+
adaln_lora_dim: int = 256,
|
| 1523 |
+
rope_h_extrapolation_ratio: float = 1.0,
|
| 1524 |
+
rope_w_extrapolation_ratio: float = 1.0,
|
| 1525 |
+
rope_t_extrapolation_ratio: float = 1.0,
|
| 1526 |
+
extra_per_block_abs_pos_emb: bool = False,
|
| 1527 |
+
extra_h_extrapolation_ratio: float = 1.0,
|
| 1528 |
+
extra_w_extrapolation_ratio: float = 1.0,
|
| 1529 |
+
extra_t_extrapolation_ratio: float = 1.0,
|
| 1530 |
+
rope_enable_fps_modulation: bool = True,
|
| 1531 |
+
sac_config: SACConfig = SACConfig(),
|
| 1532 |
+
n_dense_blocks: int = -1,
|
| 1533 |
+
natten_parameters: Union[dict, list] = None,
|
| 1534 |
+
# if True, will closely match wan's strategy to use fp32 in certain layers/operations
|
| 1535 |
+
use_wan_fp32_strategy: bool = False,
|
| 1536 |
+
) -> None:
|
| 1537 |
+
super().__init__()
|
| 1538 |
+
self.max_img_h = max_img_h
|
| 1539 |
+
self.max_img_w = max_img_w
|
| 1540 |
+
self.max_frames = max_frames
|
| 1541 |
+
self.in_channels = in_channels
|
| 1542 |
+
self.out_channels = out_channels
|
| 1543 |
+
self.patch_spatial = patch_spatial
|
| 1544 |
+
self.patch_temporal = patch_temporal
|
| 1545 |
+
self.num_heads = num_heads
|
| 1546 |
+
self.num_blocks = num_blocks
|
| 1547 |
+
self.model_channels = model_channels
|
| 1548 |
+
self.concat_padding_mask = concat_padding_mask
|
| 1549 |
+
self.atten_backend = atten_backend
|
| 1550 |
+
# positional embedding settings
|
| 1551 |
+
self.pos_emb_cls = pos_emb_cls
|
| 1552 |
+
self.pos_emb_learnable = pos_emb_learnable
|
| 1553 |
+
self.pos_emb_interpolation = pos_emb_interpolation
|
| 1554 |
+
self.min_fps = min_fps
|
| 1555 |
+
self.max_fps = max_fps
|
| 1556 |
+
self.rope_h_extrapolation_ratio = rope_h_extrapolation_ratio
|
| 1557 |
+
self.rope_w_extrapolation_ratio = rope_w_extrapolation_ratio
|
| 1558 |
+
self.rope_t_extrapolation_ratio = rope_t_extrapolation_ratio
|
| 1559 |
+
self.extra_per_block_abs_pos_emb = extra_per_block_abs_pos_emb
|
| 1560 |
+
self.extra_h_extrapolation_ratio = extra_h_extrapolation_ratio
|
| 1561 |
+
self.extra_w_extrapolation_ratio = extra_w_extrapolation_ratio
|
| 1562 |
+
self.extra_t_extrapolation_ratio = extra_t_extrapolation_ratio
|
| 1563 |
+
self.rope_enable_fps_modulation = rope_enable_fps_modulation
|
| 1564 |
+
self.extra_image_context_dim = extra_image_context_dim
|
| 1565 |
+
self.build_patch_embed()
|
| 1566 |
+
self.build_pos_embed()
|
| 1567 |
+
self.use_adaln_lora = use_adaln_lora
|
| 1568 |
+
self.adaln_lora_dim = adaln_lora_dim
|
| 1569 |
+
self.t_embedder = nn.Sequential(
|
| 1570 |
+
Timesteps(model_channels),
|
| 1571 |
+
TimestepEmbedding(model_channels, model_channels, use_adaln_lora=use_adaln_lora),
|
| 1572 |
+
)
|
| 1573 |
+
self.use_crossattn_projection = use_crossattn_projection
|
| 1574 |
+
self.crossattn_proj_in_channels = crossattn_proj_in_channels
|
| 1575 |
+
self.use_wan_fp32_strategy = use_wan_fp32_strategy
|
| 1576 |
+
|
| 1577 |
+
self.blocks = nn.ModuleList(
|
| 1578 |
+
[
|
| 1579 |
+
Block(
|
| 1580 |
+
x_dim=model_channels,
|
| 1581 |
+
context_dim=crossattn_emb_channels,
|
| 1582 |
+
num_heads=num_heads,
|
| 1583 |
+
mlp_ratio=mlp_ratio,
|
| 1584 |
+
use_adaln_lora=use_adaln_lora,
|
| 1585 |
+
adaln_lora_dim=adaln_lora_dim,
|
| 1586 |
+
backend=atten_backend,
|
| 1587 |
+
image_context_dim=None if extra_image_context_dim is None else model_channels,
|
| 1588 |
+
use_wan_fp32_strategy=use_wan_fp32_strategy,
|
| 1589 |
+
)
|
| 1590 |
+
for _ in range(num_blocks)
|
| 1591 |
+
]
|
| 1592 |
+
)
|
| 1593 |
+
|
| 1594 |
+
self.final_layer = FinalLayer(
|
| 1595 |
+
hidden_size=self.model_channels,
|
| 1596 |
+
spatial_patch_size=self.patch_spatial,
|
| 1597 |
+
temporal_patch_size=self.patch_temporal,
|
| 1598 |
+
out_channels=self.out_channels,
|
| 1599 |
+
use_adaln_lora=self.use_adaln_lora,
|
| 1600 |
+
adaln_lora_dim=self.adaln_lora_dim,
|
| 1601 |
+
use_wan_fp32_strategy=self.use_wan_fp32_strategy,
|
| 1602 |
+
)
|
| 1603 |
+
|
| 1604 |
+
self.t_embedding_norm = RMSNorm(model_channels, eps=1e-6)
|
| 1605 |
+
if extra_image_context_dim is not None:
|
| 1606 |
+
self.img_context_proj = nn.Sequential(
|
| 1607 |
+
nn.Linear(
|
| 1608 |
+
extra_image_context_dim, model_channels, bias=True
|
| 1609 |
+
), # help distinguish between image and video context
|
| 1610 |
+
nn.GELU(),
|
| 1611 |
+
)
|
| 1612 |
+
|
| 1613 |
+
if use_crossattn_projection:
|
| 1614 |
+
self.crossattn_proj = nn.Sequential(
|
| 1615 |
+
nn.Linear(crossattn_proj_in_channels, crossattn_emb_channels, bias=True),
|
| 1616 |
+
nn.GELU(),
|
| 1617 |
+
)
|
| 1618 |
+
|
| 1619 |
+
self.init_weights()
|
| 1620 |
+
self.enable_selective_checkpoint(sac_config, self.blocks)
|
| 1621 |
+
|
| 1622 |
+
# Replace self-attention with sparse attention if specified
|
| 1623 |
+
if n_dense_blocks != -1:
|
| 1624 |
+
self = replace_selfattn_op_with_sparse_attn_op(self, n_dense_blocks, natten_parameters=natten_parameters)
|
| 1625 |
+
|
| 1626 |
+
self._is_context_parallel_enabled = False
|
| 1627 |
+
|
| 1628 |
+
def init_weights(self):
|
| 1629 |
+
self.x_embedder.init_weights()
|
| 1630 |
+
self.pos_embedder.reset_parameters()
|
| 1631 |
+
if self.extra_per_block_abs_pos_emb:
|
| 1632 |
+
self.extra_pos_embedder.reset_parameters()
|
| 1633 |
+
|
| 1634 |
+
self.t_embedder[1].init_weights()
|
| 1635 |
+
for block in self.blocks:
|
| 1636 |
+
block.init_weights()
|
| 1637 |
+
|
| 1638 |
+
self.final_layer.init_weights()
|
| 1639 |
+
self.t_embedding_norm.reset_parameters()
|
| 1640 |
+
|
| 1641 |
+
if self.extra_image_context_dim is not None:
|
| 1642 |
+
self.img_context_proj[0].reset_parameters()
|
| 1643 |
+
|
| 1644 |
+
def enable_camera_conditioning(
|
| 1645 |
+
self,
|
| 1646 |
+
camera_dim: int,
|
| 1647 |
+
init_seed: int = 0,
|
| 1648 |
+
) -> None:
|
| 1649 |
+
"""Attach the checkpoint-compatible camera projection to each self-attention."""
|
| 1650 |
+
if camera_dim <= 0:
|
| 1651 |
+
raise ValueError("camera_dim must be positive")
|
| 1652 |
+
first_weight = self.blocks[0].self_attn.q_proj.weight
|
| 1653 |
+
if first_weight.is_meta:
|
| 1654 |
+
raise RuntimeError("Load the base Cosmos weights before adding camera encoders")
|
| 1655 |
+
generator = torch.Generator(device=first_weight.device)
|
| 1656 |
+
generator.manual_seed(init_seed)
|
| 1657 |
+
std = 1.0 / math.sqrt(self.model_channels)
|
| 1658 |
+
|
| 1659 |
+
for block in self.blocks:
|
| 1660 |
+
if block.self_attn.cam_encoder is not None:
|
| 1661 |
+
if block.self_attn.cam_encoder.in_features != camera_dim:
|
| 1662 |
+
raise ValueError(
|
| 1663 |
+
"Camera conditioning is already enabled with a different dimension"
|
| 1664 |
+
)
|
| 1665 |
+
continue
|
| 1666 |
+
reference = block.self_attn.q_proj.weight
|
| 1667 |
+
camera_encoder = nn.Linear(
|
| 1668 |
+
camera_dim,
|
| 1669 |
+
self.model_channels,
|
| 1670 |
+
bias=False,
|
| 1671 |
+
device=reference.device,
|
| 1672 |
+
dtype=reference.dtype,
|
| 1673 |
+
)
|
| 1674 |
+
initialized = torch.empty(
|
| 1675 |
+
camera_encoder.weight.shape,
|
| 1676 |
+
device=reference.device,
|
| 1677 |
+
dtype=torch.float32,
|
| 1678 |
+
)
|
| 1679 |
+
torch.nn.init.trunc_normal_(
|
| 1680 |
+
initialized,
|
| 1681 |
+
std=std,
|
| 1682 |
+
a=-3 * std,
|
| 1683 |
+
b=3 * std,
|
| 1684 |
+
generator=generator,
|
| 1685 |
+
)
|
| 1686 |
+
with torch.no_grad():
|
| 1687 |
+
camera_encoder.weight.copy_(initialized)
|
| 1688 |
+
block.self_attn.cam_encoder = camera_encoder
|
| 1689 |
+
self.camera_condition_dim = camera_dim
|
| 1690 |
+
|
| 1691 |
+
def build_patch_embed(self):
|
| 1692 |
+
(
|
| 1693 |
+
concat_padding_mask,
|
| 1694 |
+
in_channels,
|
| 1695 |
+
patch_spatial,
|
| 1696 |
+
patch_temporal,
|
| 1697 |
+
model_channels,
|
| 1698 |
+
) = (
|
| 1699 |
+
self.concat_padding_mask,
|
| 1700 |
+
self.in_channels,
|
| 1701 |
+
self.patch_spatial,
|
| 1702 |
+
self.patch_temporal,
|
| 1703 |
+
self.model_channels,
|
| 1704 |
+
)
|
| 1705 |
+
in_channels = in_channels + 1 if concat_padding_mask else in_channels
|
| 1706 |
+
self.x_embedder = PatchEmbed(
|
| 1707 |
+
spatial_patch_size=patch_spatial,
|
| 1708 |
+
temporal_patch_size=patch_temporal,
|
| 1709 |
+
in_channels=in_channels,
|
| 1710 |
+
out_channels=model_channels,
|
| 1711 |
+
)
|
| 1712 |
+
|
| 1713 |
+
def build_pos_embed(self):
|
| 1714 |
+
if self.pos_emb_cls == "rope3d":
|
| 1715 |
+
cls_type = VideoRopePosition3DEmb
|
| 1716 |
+
else:
|
| 1717 |
+
raise ValueError(f"Unknown pos_emb_cls {self.pos_emb_cls}")
|
| 1718 |
+
|
| 1719 |
+
log.debug(f"Building positional embedding with {self.pos_emb_cls} class, impl {cls_type}")
|
| 1720 |
+
kwargs = dict(
|
| 1721 |
+
model_channels=self.model_channels,
|
| 1722 |
+
len_h=self.max_img_h // self.patch_spatial,
|
| 1723 |
+
len_w=self.max_img_w // self.patch_spatial,
|
| 1724 |
+
len_t=self.max_frames // self.patch_temporal,
|
| 1725 |
+
max_fps=self.max_fps,
|
| 1726 |
+
min_fps=self.min_fps,
|
| 1727 |
+
is_learnable=self.pos_emb_learnable,
|
| 1728 |
+
interpolation=self.pos_emb_interpolation,
|
| 1729 |
+
head_dim=self.model_channels // self.num_heads,
|
| 1730 |
+
h_extrapolation_ratio=self.rope_h_extrapolation_ratio,
|
| 1731 |
+
w_extrapolation_ratio=self.rope_w_extrapolation_ratio,
|
| 1732 |
+
t_extrapolation_ratio=self.rope_t_extrapolation_ratio,
|
| 1733 |
+
enable_fps_modulation=self.rope_enable_fps_modulation,
|
| 1734 |
+
)
|
| 1735 |
+
self.pos_embedder = cls_type(
|
| 1736 |
+
**kwargs,
|
| 1737 |
+
)
|
| 1738 |
+
|
| 1739 |
+
if self.extra_per_block_abs_pos_emb:
|
| 1740 |
+
kwargs["h_extrapolation_ratio"] = self.extra_h_extrapolation_ratio
|
| 1741 |
+
kwargs["w_extrapolation_ratio"] = self.extra_w_extrapolation_ratio
|
| 1742 |
+
kwargs["t_extrapolation_ratio"] = self.extra_t_extrapolation_ratio
|
| 1743 |
+
self.extra_pos_embedder = LearnablePosEmbAxis(
|
| 1744 |
+
**kwargs,
|
| 1745 |
+
)
|
| 1746 |
+
|
| 1747 |
+
def prepare_embedded_sequence(
|
| 1748 |
+
self,
|
| 1749 |
+
x_B_C_T_H_W: torch.Tensor,
|
| 1750 |
+
fps: Optional[torch.Tensor] = None,
|
| 1751 |
+
padding_mask: Optional[torch.Tensor] = None,
|
| 1752 |
+
) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[torch.Tensor]]:
|
| 1753 |
+
"""
|
| 1754 |
+
Prepares an embedded sequence tensor by applying positional embeddings and handling padding masks.
|
| 1755 |
+
|
| 1756 |
+
Args:
|
| 1757 |
+
x_B_C_T_H_W (torch.Tensor): video
|
| 1758 |
+
fps (Optional[torch.Tensor]): Frames per second tensor to be used for positional embedding when required.
|
| 1759 |
+
If None, a default value (`self.base_fps`) will be used.
|
| 1760 |
+
padding_mask (Optional[torch.Tensor]): current it is not used
|
| 1761 |
+
|
| 1762 |
+
Returns:
|
| 1763 |
+
Tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 1764 |
+
- A tensor of shape (B, T, H, W, D) with the embedded sequence.
|
| 1765 |
+
- An optional positional embedding tensor, returned only if the positional embedding class
|
| 1766 |
+
(`self.pos_emb_cls`) includes 'rope'. Otherwise, None.
|
| 1767 |
+
|
| 1768 |
+
Notes:
|
| 1769 |
+
- If `self.concat_padding_mask` is True, a padding mask channel is concatenated to the input tensor.
|
| 1770 |
+
- The method of applying positional embeddings depends on the value of `self.pos_emb_cls`.
|
| 1771 |
+
- If 'rope' is in `self.pos_emb_cls` (case insensitive), the positional embeddings are generated using
|
| 1772 |
+
the `self.pos_embedder` with the shape [T, H, W].
|
| 1773 |
+
- If "fps_aware" is in `self.pos_emb_cls`, the positional embeddings are generated using the
|
| 1774 |
+
`self.pos_embedder` with the fps tensor.
|
| 1775 |
+
- Otherwise, the positional embeddings are generated without considering fps.
|
| 1776 |
+
"""
|
| 1777 |
+
if self.concat_padding_mask:
|
| 1778 |
+
padding_mask = transforms.functional.resize(
|
| 1779 |
+
padding_mask, list(x_B_C_T_H_W.shape[-2:]), interpolation=transforms.InterpolationMode.NEAREST
|
| 1780 |
+
)
|
| 1781 |
+
x_B_C_T_H_W = torch.cat(
|
| 1782 |
+
[x_B_C_T_H_W, padding_mask.unsqueeze(1).repeat(1, 1, x_B_C_T_H_W.shape[2], 1, 1)], dim=1
|
| 1783 |
+
)
|
| 1784 |
+
x_B_T_H_W_D = self.x_embedder(x_B_C_T_H_W)
|
| 1785 |
+
|
| 1786 |
+
if self.extra_per_block_abs_pos_emb:
|
| 1787 |
+
extra_pos_emb = self.extra_pos_embedder(x_B_T_H_W_D, fps=fps)
|
| 1788 |
+
else:
|
| 1789 |
+
extra_pos_emb = None
|
| 1790 |
+
|
| 1791 |
+
if "rope" in self.pos_emb_cls.lower():
|
| 1792 |
+
return x_B_T_H_W_D, self.pos_embedder(x_B_T_H_W_D, fps=fps), extra_pos_emb
|
| 1793 |
+
x_B_T_H_W_D = x_B_T_H_W_D + self.pos_embedder(x_B_T_H_W_D) # [B, T, H, W, D]
|
| 1794 |
+
|
| 1795 |
+
return x_B_T_H_W_D, None, extra_pos_emb
|
| 1796 |
+
|
| 1797 |
+
def unpatchify(self, x_B_T_H_W_M):
|
| 1798 |
+
x_B_C_Tt_Hp_Wp = rearrange(
|
| 1799 |
+
x_B_T_H_W_M,
|
| 1800 |
+
"B T H W (p1 p2 t C) -> B C (T t) (H p1) (W p2)",
|
| 1801 |
+
p1=self.patch_spatial,
|
| 1802 |
+
p2=self.patch_spatial,
|
| 1803 |
+
t=self.patch_temporal,
|
| 1804 |
+
)
|
| 1805 |
+
return x_B_C_Tt_Hp_Wp
|
| 1806 |
+
|
| 1807 |
+
def forward(
|
| 1808 |
+
self,
|
| 1809 |
+
x_B_C_T_H_W: torch.Tensor,
|
| 1810 |
+
timesteps_B_T: torch.Tensor,
|
| 1811 |
+
crossattn_emb: torch.Tensor,
|
| 1812 |
+
fps: Optional[torch.Tensor] = None,
|
| 1813 |
+
padding_mask: Optional[torch.Tensor] = None,
|
| 1814 |
+
data_type: Optional[DataType] = DataType.VIDEO,
|
| 1815 |
+
intermediate_feature_ids: Optional[List[int]] = None,
|
| 1816 |
+
img_context_emb: Optional[torch.Tensor] = None,
|
| 1817 |
+
camera_condition_B_C_T_H_W: Optional[torch.Tensor] = None,
|
| 1818 |
+
) -> torch.Tensor | List[torch.Tensor] | Tuple[torch.Tensor, List[torch.Tensor]]:
|
| 1819 |
+
"""
|
| 1820 |
+
Args:
|
| 1821 |
+
x: (B, C, T, H, W) tensor of spatial-temp inputs
|
| 1822 |
+
timesteps: (B, ) tensor of timesteps
|
| 1823 |
+
crossattn_emb: (B, N, D) tensor of cross-attention embeddings
|
| 1824 |
+
"""
|
| 1825 |
+
assert isinstance(data_type, DataType), (
|
| 1826 |
+
f"Expected DataType, got {type(data_type)}. We need discuss this flag later."
|
| 1827 |
+
)
|
| 1828 |
+
x_B_T_H_W_D, rope_emb_L_1_1_D, extra_pos_emb_B_T_H_W_D_or_T_H_W_B_D = self.prepare_embedded_sequence(
|
| 1829 |
+
x_B_C_T_H_W,
|
| 1830 |
+
fps=fps,
|
| 1831 |
+
padding_mask=padding_mask,
|
| 1832 |
+
)
|
| 1833 |
+
camera_B_T_H_W_C = None
|
| 1834 |
+
if camera_condition_B_C_T_H_W is not None:
|
| 1835 |
+
camera_B_T_H_W_C = camera_condition_B_C_T_H_W.permute(
|
| 1836 |
+
0, 2, 3, 4, 1
|
| 1837 |
+
).contiguous()
|
| 1838 |
+
if camera_B_T_H_W_C.shape[:4] != x_B_T_H_W_D.shape[:4]:
|
| 1839 |
+
raise ValueError(
|
| 1840 |
+
"Camera conditioning does not match the embedded video grid: "
|
| 1841 |
+
f"{tuple(camera_B_T_H_W_C.shape)} versus {tuple(x_B_T_H_W_D.shape)}"
|
| 1842 |
+
)
|
| 1843 |
+
|
| 1844 |
+
if self.use_crossattn_projection:
|
| 1845 |
+
crossattn_emb = self.crossattn_proj(crossattn_emb)
|
| 1846 |
+
|
| 1847 |
+
if img_context_emb is not None:
|
| 1848 |
+
assert self.extra_image_context_dim is not None, (
|
| 1849 |
+
"extra_image_context_dim must be set if img_context_emb is provided"
|
| 1850 |
+
)
|
| 1851 |
+
img_context_emb = self.img_context_proj(img_context_emb)
|
| 1852 |
+
context_input = (crossattn_emb, img_context_emb)
|
| 1853 |
+
else:
|
| 1854 |
+
context_input = crossattn_emb
|
| 1855 |
+
|
| 1856 |
+
with amp.autocast("cuda", enabled=self.use_wan_fp32_strategy, dtype=torch.float32):
|
| 1857 |
+
if timesteps_B_T.ndim == 1:
|
| 1858 |
+
timesteps_B_T = timesteps_B_T.unsqueeze(1)
|
| 1859 |
+
t_embedding_B_T_D, adaln_lora_B_T_3D = self.t_embedder(timesteps_B_T)
|
| 1860 |
+
t_embedding_B_T_D = self.t_embedding_norm(t_embedding_B_T_D)
|
| 1861 |
+
|
| 1862 |
+
# for logging purpose
|
| 1863 |
+
affline_scale_log_info = {}
|
| 1864 |
+
affline_scale_log_info["t_embedding_B_T_D"] = t_embedding_B_T_D.detach()
|
| 1865 |
+
self.affline_scale_log_info = affline_scale_log_info
|
| 1866 |
+
self.affline_emb = t_embedding_B_T_D
|
| 1867 |
+
self.crossattn_emb = crossattn_emb
|
| 1868 |
+
|
| 1869 |
+
if extra_pos_emb_B_T_H_W_D_or_T_H_W_B_D is not None:
|
| 1870 |
+
assert x_B_T_H_W_D.shape == extra_pos_emb_B_T_H_W_D_or_T_H_W_B_D.shape, (
|
| 1871 |
+
f"{x_B_T_H_W_D.shape} != {extra_pos_emb_B_T_H_W_D_or_T_H_W_B_D.shape}"
|
| 1872 |
+
)
|
| 1873 |
+
|
| 1874 |
+
B, T, H, W, D = x_B_T_H_W_D.shape
|
| 1875 |
+
# x_B_THW_D = rearrange(x_B_T_H_W_D, "b t h w d -> b (t h w) d")
|
| 1876 |
+
|
| 1877 |
+
intermediate_features_outputs = []
|
| 1878 |
+
for i, block in enumerate(self.blocks):
|
| 1879 |
+
x_B_T_H_W_D = block(
|
| 1880 |
+
x_B_T_H_W_D,
|
| 1881 |
+
t_embedding_B_T_D,
|
| 1882 |
+
context_input,
|
| 1883 |
+
rope_emb_L_1_1_D=rope_emb_L_1_1_D,
|
| 1884 |
+
adaln_lora_B_T_3D=adaln_lora_B_T_3D,
|
| 1885 |
+
extra_per_block_pos_emb=extra_pos_emb_B_T_H_W_D_or_T_H_W_B_D,
|
| 1886 |
+
camera_B_T_H_W_C=camera_B_T_H_W_C,
|
| 1887 |
+
)
|
| 1888 |
+
if intermediate_feature_ids and i in intermediate_feature_ids:
|
| 1889 |
+
x_reshaped_for_disc = rearrange(x_B_T_H_W_D, "b tp hp wp d -> b (tp hp wp) d")
|
| 1890 |
+
intermediate_features_outputs.append(x_reshaped_for_disc)
|
| 1891 |
+
|
| 1892 |
+
# x_B_T_H_W_D = rearrange(x_B_THW_D, "b (t h w) d -> b t h w d", t=T, h=H, w=W)
|
| 1893 |
+
# O = out_channels * spatial_patch_size * spatial_patch_size * temporal_patch_size
|
| 1894 |
+
x_B_T_H_W_O = self.final_layer(x_B_T_H_W_D, t_embedding_B_T_D, adaln_lora_B_T_3D=adaln_lora_B_T_3D)
|
| 1895 |
+
x_B_C_Tt_Hp_Wp = self.unpatchify(x_B_T_H_W_O)
|
| 1896 |
+
if intermediate_feature_ids:
|
| 1897 |
+
if len(intermediate_features_outputs) != len(intermediate_feature_ids):
|
| 1898 |
+
log.warning(
|
| 1899 |
+
f"Collected {len(intermediate_features_outputs)} intermediate features, "
|
| 1900 |
+
f"but expected {len(intermediate_feature_ids)}. "
|
| 1901 |
+
f"Requested IDs: {intermediate_feature_ids}"
|
| 1902 |
+
)
|
| 1903 |
+
return x_B_C_Tt_Hp_Wp, intermediate_features_outputs
|
| 1904 |
+
|
| 1905 |
+
return x_B_C_Tt_Hp_Wp
|
| 1906 |
+
|
| 1907 |
+
def enable_selective_checkpoint(self, sac_config: SACConfig, blocks: nn.ModuleList):
|
| 1908 |
+
if sac_config.mode == CheckpointMode.NONE:
|
| 1909 |
+
return self
|
| 1910 |
+
|
| 1911 |
+
log.info(
|
| 1912 |
+
f"Enable selective checkpoint with {sac_config.mode}, for every {sac_config.every_n_blocks} blocks. Total blocks: {len(blocks)}"
|
| 1913 |
+
)
|
| 1914 |
+
_context_fn = sac_config.get_context_fn()
|
| 1915 |
+
for block_id, block in blocks.named_children():
|
| 1916 |
+
if int(block_id) % sac_config.every_n_blocks == 0:
|
| 1917 |
+
log.info(f"Enable selective checkpoint for block {block_id}")
|
| 1918 |
+
block = ptd_checkpoint_wrapper(
|
| 1919 |
+
block,
|
| 1920 |
+
context_fn=_context_fn,
|
| 1921 |
+
preserve_rng_state=False,
|
| 1922 |
+
)
|
| 1923 |
+
blocks.register_module(block_id, block)
|
| 1924 |
+
self.register_module(
|
| 1925 |
+
"final_layer",
|
| 1926 |
+
ptd_checkpoint_wrapper(
|
| 1927 |
+
self.final_layer,
|
| 1928 |
+
context_fn=_context_fn,
|
| 1929 |
+
preserve_rng_state=False,
|
| 1930 |
+
),
|
| 1931 |
+
)
|
| 1932 |
+
return self
|
| 1933 |
+
|
| 1934 |
+
def fully_shard(self, mesh, **fsdp_kwargs):
|
| 1935 |
+
for i, block in enumerate(self.blocks):
|
| 1936 |
+
reshard_after_forward = i < len(self.blocks) - 1
|
| 1937 |
+
fully_shard(block, mesh=mesh, reshard_after_forward=reshard_after_forward, **fsdp_kwargs)
|
| 1938 |
+
|
| 1939 |
+
fully_shard(self.final_layer, mesh=mesh, reshard_after_forward=True, **fsdp_kwargs)
|
| 1940 |
+
if self.extra_per_block_abs_pos_emb:
|
| 1941 |
+
fully_shard(self.extra_pos_embedder, mesh=mesh, reshard_after_forward=True, **fsdp_kwargs)
|
| 1942 |
+
fully_shard(self.t_embedder, mesh=mesh, reshard_after_forward=False, **fsdp_kwargs)
|
| 1943 |
+
if self.extra_image_context_dim is not None:
|
| 1944 |
+
fully_shard(self.img_context_proj, mesh=mesh, reshard_after_forward=False, **fsdp_kwargs)
|
| 1945 |
+
|
| 1946 |
+
def disable_context_parallel(self):
|
| 1947 |
+
# pos_embedder
|
| 1948 |
+
self.pos_embedder.disable_context_parallel()
|
| 1949 |
+
if self.extra_per_block_abs_pos_emb:
|
| 1950 |
+
self.extra_pos_embedder.disable_context_parallel()
|
| 1951 |
+
|
| 1952 |
+
# attention
|
| 1953 |
+
for block in self.blocks:
|
| 1954 |
+
block.set_context_parallel_group(
|
| 1955 |
+
process_group=None,
|
| 1956 |
+
ranks=None,
|
| 1957 |
+
stream=torch.cuda.Stream(),
|
| 1958 |
+
)
|
| 1959 |
+
|
| 1960 |
+
self._is_context_parallel_enabled = False
|
| 1961 |
+
|
| 1962 |
+
def enable_context_parallel(self, process_group: Optional[ProcessGroup] = None):
|
| 1963 |
+
# pos_embedder
|
| 1964 |
+
self.pos_embedder.enable_context_parallel(process_group=process_group)
|
| 1965 |
+
if self.extra_per_block_abs_pos_emb:
|
| 1966 |
+
self.extra_pos_embedder.enable_context_parallel(process_group=process_group)
|
| 1967 |
+
|
| 1968 |
+
# attention
|
| 1969 |
+
cp_ranks = get_process_group_ranks(process_group)
|
| 1970 |
+
for block in self.blocks:
|
| 1971 |
+
block.set_context_parallel_group(
|
| 1972 |
+
process_group=process_group,
|
| 1973 |
+
ranks=cp_ranks,
|
| 1974 |
+
stream=torch.cuda.Stream(),
|
| 1975 |
+
)
|
| 1976 |
+
|
| 1977 |
+
self._is_context_parallel_enabled = True
|
| 1978 |
+
|
| 1979 |
+
@property
|
| 1980 |
+
def is_context_parallel_enabled(self):
|
| 1981 |
+
return self._is_context_parallel_enabled
|
| 1982 |
+
|
| 1983 |
+
|
| 1984 |
+
def replace_selfattn_op_with_sparse_attn_op(
|
| 1985 |
+
model: MiniTrainDIT, n_dense_blocks: int = 0, natten_parameters: Union[dict, list] = None
|
| 1986 |
+
) -> MiniTrainDIT:
|
| 1987 |
+
"""
|
| 1988 |
+
Replace the self-attention operator with a sparse self-attention operator.
|
| 1989 |
+
|
| 1990 |
+
Args:
|
| 1991 |
+
model: MiniTrainDIT instance
|
| 1992 |
+
n_dense_blocks: Number of blocks that will remain dense (not replaced with NeighborhoodAttention)
|
| 1993 |
+
If 0, all blocks use NeighborhoodAttention.
|
| 1994 |
+
If -1, return model directly without any modifications.
|
| 1995 |
+
Otherwise, n_dense_blocks blocks will remain dense, distributed evenly across the network.
|
| 1996 |
+
|
| 1997 |
+
Returns:
|
| 1998 |
+
Modified instance
|
| 1999 |
+
"""
|
| 2000 |
+
# Special case: return model directly without modifications
|
| 2001 |
+
if n_dense_blocks == -1:
|
| 2002 |
+
return model
|
| 2003 |
+
|
| 2004 |
+
num_blocks = len(model.blocks)
|
| 2005 |
+
|
| 2006 |
+
if natten_parameters is None:
|
| 2007 |
+
raise ValueError("Please specify natten_parameters when n_dense_blocks > -1.")
|
| 2008 |
+
|
| 2009 |
+
if isinstance(natten_parameters, Sequence) and len(natten_parameters) != num_blocks:
|
| 2010 |
+
raise ValueError(
|
| 2011 |
+
"List of NATTEN parameters must be the same length as the number of blocks, "
|
| 2012 |
+
f"got {len(natten_parameters)=} != {num_blocks=}."
|
| 2013 |
+
)
|
| 2014 |
+
|
| 2015 |
+
if isinstance(natten_parameters, Sequence) and n_dense_blocks > 0:
|
| 2016 |
+
log.warning(f"NATTEN parameters was a list; ignoring {n_dense_blocks=}.")
|
| 2017 |
+
|
| 2018 |
+
if isinstance(natten_parameters, Sequence):
|
| 2019 |
+
natten_parameters_list = natten_parameters
|
| 2020 |
+
else:
|
| 2021 |
+
if n_dense_blocks >= num_blocks:
|
| 2022 |
+
raise ValueError(f"n_dense_blocks ({n_dense_blocks}) must be less than the number of blocks ({num_blocks})")
|
| 2023 |
+
|
| 2024 |
+
# Determine which blocks should remain dense
|
| 2025 |
+
dense_indices = set()
|
| 2026 |
+
|
| 2027 |
+
if n_dense_blocks > 0:
|
| 2028 |
+
# General rule: distribute n_dense_blocks blocks evenly across the network
|
| 2029 |
+
if n_dense_blocks == 1:
|
| 2030 |
+
# Special case: just the middle block
|
| 2031 |
+
dense_indices.add(num_blocks // 2)
|
| 2032 |
+
else:
|
| 2033 |
+
# For multiple blocks, distribute them evenly from start to end
|
| 2034 |
+
indices = np.linspace(0, num_blocks - 1, n_dense_blocks, dtype=int)
|
| 2035 |
+
dense_indices.update(indices.tolist())
|
| 2036 |
+
|
| 2037 |
+
natten_parameters_list = [None if i in dense_indices else natten_parameters for i in range(num_blocks)]
|
| 2038 |
+
|
| 2039 |
+
# Replace self-attention with NeighborhoodAttention for non-dense blocks
|
| 2040 |
+
for i, block in enumerate(model.blocks):
|
| 2041 |
+
natten_params = natten_parameters_list[i]
|
| 2042 |
+
if natten_params is not None:
|
| 2043 |
+
natten_parameters_layer = {k: v for k, v in natten_params.items()}
|
| 2044 |
+
natten_parameters_layer["layer_id"] = i
|
| 2045 |
+
if block.self_attn.backend == "minimal_a2a":
|
| 2046 |
+
sparse_attn_op = NattenA2AAttnOp(natten_parameters=natten_parameters_layer)
|
| 2047 |
+
else:
|
| 2048 |
+
raise NotImplementedError(
|
| 2049 |
+
f"Using sparsity with attention backend {block.self_attn.backend} is not supported."
|
| 2050 |
+
)
|
| 2051 |
+
|
| 2052 |
+
block.self_attn.register_module("attn_op", sparse_attn_op)
|
| 2053 |
+
|
| 2054 |
+
return model
|
cosmos/model_weights_stats.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
from abc import ABC, abstractmethod
|
| 17 |
+
from dataclasses import dataclass
|
| 18 |
+
from typing import Any
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
from torch import nn
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@dataclass
|
| 25 |
+
class TrainingStats:
|
| 26 |
+
"""Data class to hold training statistics."""
|
| 27 |
+
|
| 28 |
+
video_samples: int = 0
|
| 29 |
+
image_samples: int = 0
|
| 30 |
+
iterations: int = 0
|
| 31 |
+
training_hours: float = 0.0
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class WeightTrainingStat(nn.Module, ABC):
|
| 35 |
+
"""Abstract base class for tracking training statistics."""
|
| 36 |
+
|
| 37 |
+
def __init__(self) -> None:
|
| 38 |
+
super().__init__()
|
| 39 |
+
self._initialize_tracking_buffers()
|
| 40 |
+
|
| 41 |
+
def _initialize_tracking_buffers(self) -> None:
|
| 42 |
+
"""Initialize tracking buffers with default values."""
|
| 43 |
+
tracking_buffers = {
|
| 44 |
+
"accum_video_sample_counter": torch.tensor(0, dtype=torch.int64),
|
| 45 |
+
"accum_image_sample_counter": torch.tensor(0, dtype=torch.int64),
|
| 46 |
+
"accum_iteration": torch.tensor(0, dtype=torch.int64),
|
| 47 |
+
"accum_train_in_hours": torch.tensor(0.0, dtype=torch.float32),
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
for name, tensor in tracking_buffers.items():
|
| 51 |
+
self.register_buffer(name, tensor)
|
| 52 |
+
|
| 53 |
+
def get_training_stats(self) -> TrainingStats:
|
| 54 |
+
"""Return current training statistics."""
|
| 55 |
+
return TrainingStats(
|
| 56 |
+
video_samples=self.accum_video_sample_counter.item(),
|
| 57 |
+
image_samples=self.accum_image_sample_counter.item(),
|
| 58 |
+
iterations=self.accum_iteration.item(),
|
| 59 |
+
training_hours=self.accum_train_in_hours.item(),
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
@abstractmethod
|
| 63 |
+
def forward(self, *args, **kwargs) -> Any:
|
| 64 |
+
pass
|
cosmos/runtime.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
# SPDX-FileCopyrightText: Modifications Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 17 |
+
# SPDX-License-Identifier: LicenseRef-NvidiaOneWayNoncommercial
|
| 18 |
+
|
| 19 |
+
"""Small runtime helpers needed by the vendored Cosmos-Predict2.5 DiT.
|
| 20 |
+
|
| 21 |
+
The training integration does not use NVIDIA's context-parallel or neighborhood
|
| 22 |
+
attention paths. Keeping these helpers local avoids importing the full
|
| 23 |
+
``cosmos_predict2`` package (whose top-level import requires a CUDA extra).
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
import logging
|
| 27 |
+
from functools import lru_cache
|
| 28 |
+
from enum import Enum
|
| 29 |
+
from typing import Optional
|
| 30 |
+
|
| 31 |
+
import torch
|
| 32 |
+
import torch.distributed as dist
|
| 33 |
+
import torch.nn.functional as F
|
| 34 |
+
from torch import nn
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
from transformer_engine.pytorch.attention import (
|
| 38 |
+
DotProductAttention as _TransformerEngineAttention,
|
| 39 |
+
)
|
| 40 |
+
try:
|
| 41 |
+
from transformer_engine.pytorch.attention.rope import (
|
| 42 |
+
apply_rotary_pos_emb as _transformer_engine_rope,
|
| 43 |
+
)
|
| 44 |
+
except ImportError:
|
| 45 |
+
from transformer_engine.pytorch.attention import (
|
| 46 |
+
apply_rotary_pos_emb as _transformer_engine_rope,
|
| 47 |
+
)
|
| 48 |
+
except ImportError:
|
| 49 |
+
_TransformerEngineAttention = None
|
| 50 |
+
_transformer_engine_rope = None
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
log = logging.getLogger(__name__)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class DataType(str, Enum):
|
| 57 |
+
IMAGE = "image"
|
| 58 |
+
VIDEO = "video"
|
| 59 |
+
MIX = "mix"
|
| 60 |
+
|
| 61 |
+
def __str__(self) -> str:
|
| 62 |
+
return self.value
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class RMSNorm(nn.Module):
|
| 66 |
+
"""Checkpoint-compatible RMSNorm backed by PyTorch's fused operator."""
|
| 67 |
+
|
| 68 |
+
def __init__(self, dim: int, eps: float = 1e-6) -> None:
|
| 69 |
+
super().__init__()
|
| 70 |
+
self.eps = eps
|
| 71 |
+
self.weight = nn.Parameter(torch.ones(dim))
|
| 72 |
+
|
| 73 |
+
def reset_parameters(self) -> None:
|
| 74 |
+
nn.init.ones_(self.weight)
|
| 75 |
+
|
| 76 |
+
def forward(self, value: torch.Tensor) -> torch.Tensor:
|
| 77 |
+
return F.rms_norm(
|
| 78 |
+
value,
|
| 79 |
+
(value.shape[-1],),
|
| 80 |
+
self.weight,
|
| 81 |
+
self.eps,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def apply_rotary_pos_emb(
|
| 86 |
+
value: torch.Tensor,
|
| 87 |
+
freqs: torch.Tensor,
|
| 88 |
+
*,
|
| 89 |
+
tensor_format: str = "bshd",
|
| 90 |
+
fused: bool = True,
|
| 91 |
+
) -> torch.Tensor:
|
| 92 |
+
"""Apply rotary embeddings with the fused CUDA implementation when present."""
|
| 93 |
+
if tensor_format != "bshd":
|
| 94 |
+
raise ValueError(f"Unsupported rotary tensor format: {tensor_format}")
|
| 95 |
+
if _transformer_engine_rope is not None and value.is_cuda:
|
| 96 |
+
return _transformer_engine_rope(
|
| 97 |
+
value.contiguous(),
|
| 98 |
+
freqs.contiguous(),
|
| 99 |
+
tensor_format=tensor_format,
|
| 100 |
+
fused=fused,
|
| 101 |
+
)
|
| 102 |
+
if freqs.ndim == 4 and freqs.shape[0] == value.shape[1]:
|
| 103 |
+
freqs = freqs.permute(1, 0, 2, 3)
|
| 104 |
+
first, second = value.chunk(2, dim=-1)
|
| 105 |
+
rotated = torch.cat((-second, first), dim=-1)
|
| 106 |
+
return value * freqs.cos() + rotated * freqs.sin()
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
@lru_cache(maxsize=None)
|
| 110 |
+
def _get_transformer_engine_attention(
|
| 111 |
+
num_heads: int,
|
| 112 |
+
head_dim: int,
|
| 113 |
+
) -> nn.Module:
|
| 114 |
+
if _TransformerEngineAttention is None:
|
| 115 |
+
raise RuntimeError("Transformer Engine attention is unavailable")
|
| 116 |
+
module = _TransformerEngineAttention(
|
| 117 |
+
num_heads,
|
| 118 |
+
head_dim,
|
| 119 |
+
num_gqa_groups=num_heads,
|
| 120 |
+
attention_dropout=0.0,
|
| 121 |
+
qkv_format="bshd",
|
| 122 |
+
attn_mask_type="no_mask",
|
| 123 |
+
)
|
| 124 |
+
module.eval()
|
| 125 |
+
return module
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def attention(
|
| 129 |
+
query: torch.Tensor,
|
| 130 |
+
key: torch.Tensor,
|
| 131 |
+
value: torch.Tensor,
|
| 132 |
+
*,
|
| 133 |
+
is_causal: bool = False,
|
| 134 |
+
**_kwargs,
|
| 135 |
+
) -> torch.Tensor:
|
| 136 |
+
"""Fused attention for Cosmos tensors shaped ``[B, S, H, D]``."""
|
| 137 |
+
if (
|
| 138 |
+
_TransformerEngineAttention is not None
|
| 139 |
+
and query.is_cuda
|
| 140 |
+
and not is_causal
|
| 141 |
+
):
|
| 142 |
+
query = query.contiguous().clone()
|
| 143 |
+
key = key.contiguous().clone()
|
| 144 |
+
value = value.contiguous().clone()
|
| 145 |
+
fused_attention = _get_transformer_engine_attention(
|
| 146 |
+
int(query.shape[2]),
|
| 147 |
+
int(query.shape[3]),
|
| 148 |
+
)
|
| 149 |
+
output = fused_attention(
|
| 150 |
+
query,
|
| 151 |
+
key,
|
| 152 |
+
value,
|
| 153 |
+
)
|
| 154 |
+
if isinstance(output, tuple):
|
| 155 |
+
output = output[0]
|
| 156 |
+
if output.ndim == 3:
|
| 157 |
+
output = output.unflatten(-1, (query.shape[2], query.shape[3]))
|
| 158 |
+
return output
|
| 159 |
+
|
| 160 |
+
output = torch.nn.functional.scaled_dot_product_attention(
|
| 161 |
+
query.transpose(1, 2),
|
| 162 |
+
key.transpose(1, 2),
|
| 163 |
+
value.transpose(1, 2),
|
| 164 |
+
is_causal=is_causal,
|
| 165 |
+
)
|
| 166 |
+
return output.transpose(1, 2)
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
class DotProductAttention(nn.Module):
|
| 170 |
+
"""Parameter-free attention module with the expected Cosmos interface."""
|
| 171 |
+
|
| 172 |
+
def __init__(self, num_heads: int, head_dim: int, **kwargs) -> None:
|
| 173 |
+
del kwargs
|
| 174 |
+
super().__init__()
|
| 175 |
+
self.num_heads = int(num_heads)
|
| 176 |
+
self.head_dim = int(head_dim)
|
| 177 |
+
|
| 178 |
+
def forward(
|
| 179 |
+
self,
|
| 180 |
+
query: torch.Tensor,
|
| 181 |
+
key: torch.Tensor,
|
| 182 |
+
value: torch.Tensor,
|
| 183 |
+
**kwargs,
|
| 184 |
+
) -> torch.Tensor:
|
| 185 |
+
del kwargs
|
| 186 |
+
if query.shape[2:] != (self.num_heads, self.head_dim):
|
| 187 |
+
raise ValueError(
|
| 188 |
+
"Attention input shape does not match the configured heads: "
|
| 189 |
+
f"{tuple(query.shape)}"
|
| 190 |
+
)
|
| 191 |
+
return attention(query, key, value).flatten(-2)
|
| 192 |
+
|
| 193 |
+
def set_context_parallel_group(self, *args, **kwargs) -> None:
|
| 194 |
+
del args, kwargs
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def split_inputs_cp(
|
| 198 |
+
x: torch.Tensor,
|
| 199 |
+
seq_dim: int,
|
| 200 |
+
cp_group: Optional[dist.ProcessGroup],
|
| 201 |
+
) -> torch.Tensor:
|
| 202 |
+
"""Split a tensor for the optional context-parallel model path."""
|
| 203 |
+
if cp_group is None or dist.get_world_size(cp_group) == 1:
|
| 204 |
+
return x
|
| 205 |
+
world_size = dist.get_world_size(cp_group)
|
| 206 |
+
if x.shape[seq_dim] % world_size:
|
| 207 |
+
raise ValueError("Context-parallel sequence length must divide world size")
|
| 208 |
+
rank = dist.get_rank(cp_group)
|
| 209 |
+
return x.chunk(world_size, dim=seq_dim)[rank].contiguous()
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
class MinimalA2AAttnOp(nn.Module):
|
| 213 |
+
"""Non-context-parallel equivalent of Predict2.5's minimal A2A op."""
|
| 214 |
+
|
| 215 |
+
def forward(
|
| 216 |
+
self,
|
| 217 |
+
query: torch.Tensor,
|
| 218 |
+
key: torch.Tensor,
|
| 219 |
+
value: torch.Tensor,
|
| 220 |
+
**kwargs,
|
| 221 |
+
) -> torch.Tensor:
|
| 222 |
+
return attention(query, key, value, **kwargs).flatten(-2)
|
| 223 |
+
|
| 224 |
+
def set_context_parallel_group(self, process_group, *args, **kwargs) -> None:
|
| 225 |
+
del args, kwargs
|
| 226 |
+
if process_group is not None and dist.get_world_size(process_group) > 1:
|
| 227 |
+
raise NotImplementedError(
|
| 228 |
+
"The local Cosmos integration does not enable context parallelism"
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
class NeighborhoodAttention(nn.Module):
|
| 233 |
+
"""Marker for the unsupported sparse-attention option."""
|
| 234 |
+
|
| 235 |
+
def __init__(self, *args, **kwargs) -> None:
|
| 236 |
+
del args, kwargs
|
| 237 |
+
super().__init__()
|
| 238 |
+
raise NotImplementedError(
|
| 239 |
+
"Neighborhood attention is not used by the Cosmos 2B configuration"
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
class NattenA2AAttnOp(NeighborhoodAttention):
|
| 244 |
+
pass
|
cosmos/selective_activation_checkpoint.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
from dataclasses import dataclass
|
| 17 |
+
from enum import Enum
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
|
| 21 |
+
try:
|
| 22 |
+
from torch.utils.checkpoint import CheckpointPolicy, create_selective_checkpoint_contexts, noop_context_fn
|
| 23 |
+
except ImportError:
|
| 24 |
+
CheckpointPolicy = None
|
| 25 |
+
|
| 26 |
+
mm_only_save_list = {
|
| 27 |
+
torch.ops.aten.mm.default,
|
| 28 |
+
torch.ops.aten._scaled_dot_product_efficient_attention.default,
|
| 29 |
+
torch.ops.aten._scaled_dot_product_flash_attention.default,
|
| 30 |
+
torch.ops.aten.addmm.default,
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class CheckpointMode(str, Enum):
|
| 35 |
+
"""
|
| 36 |
+
Enum for the different checkpoint modes.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
NONE = "none"
|
| 40 |
+
MM_ONLY = "mm_only"
|
| 41 |
+
BLOCK_WISE = "block_wise"
|
| 42 |
+
|
| 43 |
+
def __str__(self) -> str:
|
| 44 |
+
# Optional: makes print() show just the value
|
| 45 |
+
return self.value
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def mm_only_policy(ctx, func, *args, **kwargs):
|
| 49 |
+
"""
|
| 50 |
+
In newer flash-attn and TE versions, FA2 shows up in the list of ops with the name of 'flash_attn._flash_attn_forward'.
|
| 51 |
+
However, FA2 is much slower (2-3x) than FA3 or cuDNN kernel. Registering cuDNN kernel would require heavy changes in TE code.
|
| 52 |
+
That's why the best option is to use FA3 with small modifications to flash_attn_interface.py to register FA3 as PyTorch op.
|
| 53 |
+
"""
|
| 54 |
+
to_save = func in mm_only_save_list or "flash_attn" in str(func)
|
| 55 |
+
return CheckpointPolicy.MUST_SAVE if to_save else CheckpointPolicy.PREFER_RECOMPUTE
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def mm_only_context_fn():
|
| 59 |
+
return create_selective_checkpoint_contexts(mm_only_policy)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
@dataclass
|
| 63 |
+
class SACConfig:
|
| 64 |
+
mode: str = "mm_only"
|
| 65 |
+
every_n_blocks: int = 1
|
| 66 |
+
|
| 67 |
+
def get_context_fn(self):
|
| 68 |
+
if self.mode == CheckpointMode.MM_ONLY:
|
| 69 |
+
return mm_only_context_fn
|
| 70 |
+
elif self.mode == CheckpointMode.BLOCK_WISE:
|
| 71 |
+
return noop_context_fn
|
| 72 |
+
else:
|
| 73 |
+
raise ValueError(f"Invalid mode: {self.mode}")
|
cosmos/wrapper.py
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: LicenseRef-NvidiaOneWayNoncommercial
|
| 3 |
+
|
| 4 |
+
"""Self-Forcing adapters for the official Cosmos-Predict2.5 implementation."""
|
| 5 |
+
|
| 6 |
+
import types
|
| 7 |
+
from typing import List, Optional
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
from cosmos.camera_conditioning import CAMERA_FEATURE_DIM
|
| 12 |
+
from huggingface_hub import hf_hub_download
|
| 13 |
+
|
| 14 |
+
from utils.scheduler import FlowMatchScheduler, SchedulerInterface
|
| 15 |
+
from wan.modules.vae import _video_vae
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
DEFAULT_MODEL_ID = "nvidia/Cosmos-Predict2.5-2B"
|
| 19 |
+
DEFAULT_CHECKPOINT = (
|
| 20 |
+
"base/pre-trained/"
|
| 21 |
+
"d20b7120-df3e-4911-919d-db6e08bad31c_ema_bf16.pt"
|
| 22 |
+
)
|
| 23 |
+
DEFAULT_TEXT_ENCODER_ID = "nvidia/Cosmos-Reason1-7B"
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _mean_normalize(tensor: torch.Tensor) -> torch.Tensor:
|
| 27 |
+
return (tensor - tensor.mean(dim=-1, keepdim=True)) / (
|
| 28 |
+
tensor.std(dim=-1, keepdim=True) + 1e-8
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class CosmosTextEncoder(torch.nn.Module):
|
| 33 |
+
"""Cosmos-Reason1 text-only adapter producing Predict2.5 embeddings."""
|
| 34 |
+
|
| 35 |
+
def __init__(
|
| 36 |
+
self,
|
| 37 |
+
model_name: str = DEFAULT_TEXT_ENCODER_ID,
|
| 38 |
+
max_length: int = 512,
|
| 39 |
+
) -> None:
|
| 40 |
+
super().__init__()
|
| 41 |
+
from transformers import AutoProcessor, Qwen2_5_VLForConditionalGeneration
|
| 42 |
+
|
| 43 |
+
self.max_length = max_length
|
| 44 |
+
self.processor = AutoProcessor.from_pretrained(model_name)
|
| 45 |
+
self.text_encoder = Qwen2_5_VLForConditionalGeneration.from_pretrained(
|
| 46 |
+
model_name,
|
| 47 |
+
torch_dtype=torch.bfloat16,
|
| 48 |
+
low_cpu_mem_usage=True,
|
| 49 |
+
).eval()
|
| 50 |
+
|
| 51 |
+
@property
|
| 52 |
+
def device(self) -> torch.device:
|
| 53 |
+
return next(self.text_encoder.parameters()).device
|
| 54 |
+
|
| 55 |
+
def forward(self, text_prompts: List[str]) -> dict:
|
| 56 |
+
conversations = [
|
| 57 |
+
[
|
| 58 |
+
{
|
| 59 |
+
"role": "system",
|
| 60 |
+
"content": [
|
| 61 |
+
{
|
| 62 |
+
"type": "text",
|
| 63 |
+
"text": (
|
| 64 |
+
"You are a helpful assistant who will provide "
|
| 65 |
+
"prompts to an image generator."
|
| 66 |
+
),
|
| 67 |
+
}
|
| 68 |
+
],
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"role": "user",
|
| 72 |
+
"content": [{"type": "text", "text": prompt}],
|
| 73 |
+
},
|
| 74 |
+
]
|
| 75 |
+
for prompt in text_prompts
|
| 76 |
+
]
|
| 77 |
+
texts = [
|
| 78 |
+
self.processor.apply_chat_template(
|
| 79 |
+
conversation,
|
| 80 |
+
tokenize=False,
|
| 81 |
+
add_generation_prompt=False,
|
| 82 |
+
add_vision_id=False,
|
| 83 |
+
)
|
| 84 |
+
for conversation in conversations
|
| 85 |
+
]
|
| 86 |
+
inputs = self.processor.tokenizer(
|
| 87 |
+
texts,
|
| 88 |
+
padding="max_length",
|
| 89 |
+
truncation=True,
|
| 90 |
+
max_length=self.max_length,
|
| 91 |
+
return_tensors="pt",
|
| 92 |
+
).to(self.device)
|
| 93 |
+
outputs = self.text_encoder(
|
| 94 |
+
input_ids=inputs.input_ids,
|
| 95 |
+
output_hidden_states=True,
|
| 96 |
+
use_cache=False,
|
| 97 |
+
return_dict=True,
|
| 98 |
+
)
|
| 99 |
+
# Predict2.5 concatenates the normalized outputs of all 28 language
|
| 100 |
+
# layers: 28 * 3584 = 100352 channels.
|
| 101 |
+
prompt_embeds = torch.cat(
|
| 102 |
+
[_mean_normalize(state) for state in outputs.hidden_states[1:]],
|
| 103 |
+
dim=-1,
|
| 104 |
+
)
|
| 105 |
+
return {"prompt_embeds": prompt_embeds}
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class CosmosVAEWrapper(torch.nn.Module):
|
| 109 |
+
"""Wan2.1 VAE packaged with Cosmos-Predict2.5."""
|
| 110 |
+
|
| 111 |
+
def __init__(
|
| 112 |
+
self,
|
| 113 |
+
model_name: str = DEFAULT_MODEL_ID,
|
| 114 |
+
checkpoint_filename: str = "tokenizer.pth",
|
| 115 |
+
) -> None:
|
| 116 |
+
super().__init__()
|
| 117 |
+
mean = [
|
| 118 |
+
-0.7571, -0.7089, -0.9113, 0.1075, -0.1745, 0.9653,
|
| 119 |
+
-0.1517, 1.5508, 0.4134, -0.0715, 0.5517, -0.3632,
|
| 120 |
+
-0.1922, -0.9497, 0.2503, -0.2921,
|
| 121 |
+
]
|
| 122 |
+
std = [
|
| 123 |
+
2.8184, 1.4541, 2.3275, 2.6558, 1.2196, 1.7708,
|
| 124 |
+
2.6052, 2.0743, 3.2687, 2.1526, 2.8652, 1.5579,
|
| 125 |
+
1.6382, 1.1253, 2.8251, 1.9160,
|
| 126 |
+
]
|
| 127 |
+
self.mean = torch.tensor(mean, dtype=torch.float32)
|
| 128 |
+
self.std = torch.tensor(std, dtype=torch.float32)
|
| 129 |
+
checkpoint_path = hf_hub_download(
|
| 130 |
+
repo_id=model_name,
|
| 131 |
+
filename=checkpoint_filename,
|
| 132 |
+
)
|
| 133 |
+
self.model = _video_vae(
|
| 134 |
+
pretrained_path=checkpoint_path,
|
| 135 |
+
z_dim=16,
|
| 136 |
+
).eval().requires_grad_(False)
|
| 137 |
+
|
| 138 |
+
def encode_to_latent(self, pixel: torch.Tensor) -> torch.Tensor:
|
| 139 |
+
device, dtype = pixel.device, pixel.dtype
|
| 140 |
+
scale = [
|
| 141 |
+
self.mean.to(device=device, dtype=dtype),
|
| 142 |
+
1.0 / self.std.to(device=device, dtype=dtype),
|
| 143 |
+
]
|
| 144 |
+
output = [
|
| 145 |
+
self.model.encode(sample.unsqueeze(0), scale).float().squeeze(0)
|
| 146 |
+
for sample in pixel
|
| 147 |
+
]
|
| 148 |
+
return torch.stack(output, dim=0).permute(0, 2, 1, 3, 4)
|
| 149 |
+
|
| 150 |
+
def decode_to_pixel(
|
| 151 |
+
self,
|
| 152 |
+
latent: torch.Tensor,
|
| 153 |
+
use_cache: bool = False,
|
| 154 |
+
) -> torch.Tensor:
|
| 155 |
+
zs = latent.permute(0, 2, 1, 3, 4)
|
| 156 |
+
if use_cache:
|
| 157 |
+
assert latent.shape[0] == 1, "Cached VAE decode requires batch size 1"
|
| 158 |
+
device, dtype = latent.device, latent.dtype
|
| 159 |
+
scale = [
|
| 160 |
+
self.mean.to(device=device, dtype=dtype),
|
| 161 |
+
1.0 / self.std.to(device=device, dtype=dtype),
|
| 162 |
+
]
|
| 163 |
+
decode = self.model.cached_decode if use_cache else self.model.decode
|
| 164 |
+
output = [
|
| 165 |
+
decode(sample.unsqueeze(0), scale).float().clamp_(-1, 1).squeeze(0)
|
| 166 |
+
for sample in zs
|
| 167 |
+
]
|
| 168 |
+
return torch.stack(output, dim=0).permute(0, 2, 1, 3, 4)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
class CosmosDiffusionWrapper(torch.nn.Module):
|
| 172 |
+
"""Match Cosmos-Predict2.5 to Self-Forcing's Wan wrapper contract."""
|
| 173 |
+
|
| 174 |
+
num_transformer_blocks = 28
|
| 175 |
+
frame_seq_length = 1 # Cosmos cache positions are latent-frame indices.
|
| 176 |
+
|
| 177 |
+
def __init__(
|
| 178 |
+
self,
|
| 179 |
+
model_name: str = DEFAULT_MODEL_ID,
|
| 180 |
+
checkpoint_filename: str = DEFAULT_CHECKPOINT,
|
| 181 |
+
timestep_shift: float = 5.0,
|
| 182 |
+
is_causal: bool = False,
|
| 183 |
+
local_attn_size: int = -1,
|
| 184 |
+
sink_size: int = 0,
|
| 185 |
+
i2v: bool = True,
|
| 186 |
+
camera_conditioning: bool = False,
|
| 187 |
+
camera_patch_size: int = 16,
|
| 188 |
+
camera_init_seed: int = 0,
|
| 189 |
+
) -> None:
|
| 190 |
+
super().__init__()
|
| 191 |
+
self.is_causal = is_causal
|
| 192 |
+
self.i2v = i2v
|
| 193 |
+
self.local_attn_size = local_attn_size
|
| 194 |
+
self.camera_conditioning = bool(camera_conditioning)
|
| 195 |
+
self.camera_patch_size = int(camera_patch_size)
|
| 196 |
+
if self.camera_patch_size <= 0:
|
| 197 |
+
raise ValueError("camera_patch_size must be positive")
|
| 198 |
+
self.uniform_timestep = not is_causal
|
| 199 |
+
self._cache_max_frames = 128
|
| 200 |
+
self._gradient_checkpointing = False
|
| 201 |
+
|
| 202 |
+
self.model = self._load_model(
|
| 203 |
+
model_name=model_name,
|
| 204 |
+
checkpoint_filename=checkpoint_filename,
|
| 205 |
+
is_causal=is_causal,
|
| 206 |
+
local_attn_size=local_attn_size,
|
| 207 |
+
sink_size=sink_size,
|
| 208 |
+
).eval()
|
| 209 |
+
if self.camera_conditioning:
|
| 210 |
+
self.model.enable_camera_conditioning(
|
| 211 |
+
camera_dim=CAMERA_FEATURE_DIM * self.camera_patch_size**2,
|
| 212 |
+
init_seed=int(camera_init_seed),
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
if is_causal:
|
| 216 |
+
self._kv_attention_ops = [
|
| 217 |
+
block.self_attn.attn_op for block in self.model.blocks
|
| 218 |
+
]
|
| 219 |
+
for attention_op in self._kv_attention_ops:
|
| 220 |
+
attention_op.reset_kv_cache(
|
| 221 |
+
max_cache_size=self._cache_max_frames,
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
self.scheduler = FlowMatchScheduler(
|
| 225 |
+
shift=timestep_shift,
|
| 226 |
+
sigma_min=0.0,
|
| 227 |
+
extra_one_step=True,
|
| 228 |
+
)
|
| 229 |
+
self.scheduler.set_timesteps(1000, training=True)
|
| 230 |
+
self.register_buffer(
|
| 231 |
+
"_flow_sigmas",
|
| 232 |
+
self.scheduler.sigmas.float(),
|
| 233 |
+
persistent=False,
|
| 234 |
+
)
|
| 235 |
+
self.register_buffer(
|
| 236 |
+
"_flow_timesteps",
|
| 237 |
+
self.scheduler.timesteps.float(),
|
| 238 |
+
persistent=False,
|
| 239 |
+
)
|
| 240 |
+
self.post_init()
|
| 241 |
+
|
| 242 |
+
@staticmethod
|
| 243 |
+
def _model_kwargs(is_causal: bool) -> dict:
|
| 244 |
+
from cosmos.minimal_v4_dit import SACConfig
|
| 245 |
+
|
| 246 |
+
return {
|
| 247 |
+
"max_img_h": 240,
|
| 248 |
+
"max_img_w": 240,
|
| 249 |
+
"max_frames": 128,
|
| 250 |
+
"in_channels": 16,
|
| 251 |
+
"out_channels": 16,
|
| 252 |
+
"patch_spatial": 2,
|
| 253 |
+
"patch_temporal": 1,
|
| 254 |
+
"model_channels": 2048,
|
| 255 |
+
"num_blocks": 28,
|
| 256 |
+
"num_heads": 16,
|
| 257 |
+
"concat_padding_mask": True,
|
| 258 |
+
"pos_emb_cls": "rope3d",
|
| 259 |
+
"pos_emb_learnable": True,
|
| 260 |
+
"pos_emb_interpolation": "crop",
|
| 261 |
+
"use_adaln_lora": True,
|
| 262 |
+
"adaln_lora_dim": 256,
|
| 263 |
+
"extra_per_block_abs_pos_emb": False,
|
| 264 |
+
"rope_enable_fps_modulation": False,
|
| 265 |
+
"rope_h_extrapolation_ratio": 3.0,
|
| 266 |
+
"rope_w_extrapolation_ratio": 3.0,
|
| 267 |
+
"rope_t_extrapolation_ratio": 1.0,
|
| 268 |
+
"use_crossattn_projection": True,
|
| 269 |
+
"crossattn_proj_in_channels": 100352,
|
| 270 |
+
"crossattn_emb_channels": 1024,
|
| 271 |
+
"timestep_scale": 0.001,
|
| 272 |
+
"use_wan_fp32_strategy": True,
|
| 273 |
+
"atten_backend": "i4" if is_causal else "minimal_a2a",
|
| 274 |
+
"sac_config": SACConfig(mode="none"),
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
@classmethod
|
| 278 |
+
def _load_model(
|
| 279 |
+
cls,
|
| 280 |
+
model_name: str,
|
| 281 |
+
checkpoint_filename: str,
|
| 282 |
+
is_causal: bool,
|
| 283 |
+
local_attn_size: int,
|
| 284 |
+
sink_size: int,
|
| 285 |
+
) -> torch.nn.Module:
|
| 286 |
+
if is_causal:
|
| 287 |
+
from cosmos.causal_model import CausalCosmosModel as Model
|
| 288 |
+
else:
|
| 289 |
+
from cosmos.minimal_v1_lvg_dit import (
|
| 290 |
+
MinimalV1LVGDiT as Model,
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
with torch.device("meta"):
|
| 294 |
+
model_kwargs = cls._model_kwargs(is_causal)
|
| 295 |
+
if is_causal:
|
| 296 |
+
model_kwargs.update(
|
| 297 |
+
local_attn_size=local_attn_size,
|
| 298 |
+
sink_size=sink_size,
|
| 299 |
+
)
|
| 300 |
+
model = Model(**model_kwargs)
|
| 301 |
+
|
| 302 |
+
checkpoint_path = hf_hub_download(
|
| 303 |
+
repo_id=model_name,
|
| 304 |
+
filename=checkpoint_filename,
|
| 305 |
+
)
|
| 306 |
+
state_dict = torch.load(
|
| 307 |
+
checkpoint_path,
|
| 308 |
+
map_location="cpu",
|
| 309 |
+
weights_only=True,
|
| 310 |
+
mmap=True,
|
| 311 |
+
)
|
| 312 |
+
if "model" in state_dict:
|
| 313 |
+
state_dict = state_dict["model"]
|
| 314 |
+
elif "state_dict" in state_dict:
|
| 315 |
+
state_dict = state_dict["state_dict"]
|
| 316 |
+
|
| 317 |
+
available_prefixes = (
|
| 318 |
+
"net_ema." if any(key.startswith("net_ema.") for key in state_dict)
|
| 319 |
+
else "net."
|
| 320 |
+
)
|
| 321 |
+
net_state_dict = {}
|
| 322 |
+
for key, value in state_dict.items():
|
| 323 |
+
if not key.startswith(available_prefixes):
|
| 324 |
+
continue
|
| 325 |
+
key = key.removeprefix(available_prefixes)
|
| 326 |
+
if not key.endswith("_extra_state"):
|
| 327 |
+
net_state_dict[key] = value
|
| 328 |
+
|
| 329 |
+
if not net_state_dict:
|
| 330 |
+
raise RuntimeError("Cosmos checkpoint contains no diffusion weights")
|
| 331 |
+
|
| 332 |
+
model.load_state_dict(net_state_dict, strict=False, assign=True)
|
| 333 |
+
unloaded = [
|
| 334 |
+
name
|
| 335 |
+
for name, tensor in (
|
| 336 |
+
list(model.named_parameters()) + list(model.named_buffers())
|
| 337 |
+
)
|
| 338 |
+
if tensor.is_meta
|
| 339 |
+
]
|
| 340 |
+
if unloaded:
|
| 341 |
+
raise RuntimeError(
|
| 342 |
+
"Cosmos checkpoint did not initialize parameters: "
|
| 343 |
+
+ ", ".join(unloaded[:10])
|
| 344 |
+
)
|
| 345 |
+
return model
|
| 346 |
+
|
| 347 |
+
def enable_gradient_checkpointing(self) -> None:
|
| 348 |
+
if self._gradient_checkpointing:
|
| 349 |
+
return
|
| 350 |
+
from cosmos.minimal_v4_dit import SACConfig
|
| 351 |
+
|
| 352 |
+
# FlexAttention is a higher-order op and PyTorch does not implement it
|
| 353 |
+
# for selective checkpointing's _CachingTorchDispatchMode. Ordinary
|
| 354 |
+
# block checkpointing preserves the memory saving without that mode.
|
| 355 |
+
self.model.enable_selective_checkpoint(
|
| 356 |
+
SACConfig(mode="block_wise"),
|
| 357 |
+
self.model.blocks,
|
| 358 |
+
)
|
| 359 |
+
self._gradient_checkpointing = True
|
| 360 |
+
|
| 361 |
+
def initialize_kv_cache(
|
| 362 |
+
self,
|
| 363 |
+
max_frames: int,
|
| 364 |
+
batch_size: int,
|
| 365 |
+
dtype: torch.dtype,
|
| 366 |
+
device: torch.device,
|
| 367 |
+
) -> object:
|
| 368 |
+
del batch_size, dtype, device
|
| 369 |
+
if not self.is_causal:
|
| 370 |
+
raise RuntimeError("KV cache is only available on the causal Cosmos model")
|
| 371 |
+
self._cache_max_frames = max_frames
|
| 372 |
+
for attention_op in self._kv_attention_ops:
|
| 373 |
+
attention_op.reset_kv_cache(max_cache_size=max_frames)
|
| 374 |
+
return self # The actual cache is owned by each attention block.
|
| 375 |
+
|
| 376 |
+
def initialize_crossattn_cache(self, **kwargs) -> None:
|
| 377 |
+
del kwargs
|
| 378 |
+
return None
|
| 379 |
+
|
| 380 |
+
@staticmethod
|
| 381 |
+
def cache_position(frame_index: int) -> int:
|
| 382 |
+
return frame_index
|
| 383 |
+
|
| 384 |
+
def _condition_inputs(
|
| 385 |
+
self,
|
| 386 |
+
noisy_video: torch.Tensor,
|
| 387 |
+
conditional_dict: dict,
|
| 388 |
+
timestep: torch.Tensor,
|
| 389 |
+
apply_initial_condition: bool = True,
|
| 390 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 391 |
+
batch, frames, _, height, width = noisy_video.shape
|
| 392 |
+
mask = torch.zeros(
|
| 393 |
+
batch,
|
| 394 |
+
1,
|
| 395 |
+
frames,
|
| 396 |
+
height,
|
| 397 |
+
width,
|
| 398 |
+
device=noisy_video.device,
|
| 399 |
+
dtype=noisy_video.dtype,
|
| 400 |
+
)
|
| 401 |
+
initial_latent = conditional_dict.get("initial_latent")
|
| 402 |
+
if self.i2v and initial_latent is not None and apply_initial_condition:
|
| 403 |
+
cond_frames = min(initial_latent.shape[1], frames)
|
| 404 |
+
noisy_video = noisy_video.clone()
|
| 405 |
+
noisy_video[:, :cond_frames] = initial_latent[:, :cond_frames]
|
| 406 |
+
mask[:, :, :cond_frames] = 1
|
| 407 |
+
timestep = timestep.clone()
|
| 408 |
+
timestep[:, :cond_frames] = 0
|
| 409 |
+
return noisy_video, timestep, mask
|
| 410 |
+
|
| 411 |
+
def _camera_condition(
|
| 412 |
+
self,
|
| 413 |
+
conditional_dict: dict,
|
| 414 |
+
model_input: torch.Tensor,
|
| 415 |
+
*,
|
| 416 |
+
current_start: Optional[int],
|
| 417 |
+
streaming: bool,
|
| 418 |
+
) -> Optional[torch.Tensor]:
|
| 419 |
+
camera = conditional_dict.get("camera_condition")
|
| 420 |
+
if not self.camera_conditioning:
|
| 421 |
+
if camera is not None:
|
| 422 |
+
raise ValueError(
|
| 423 |
+
"camera_condition was provided, but camera_conditioning is disabled"
|
| 424 |
+
)
|
| 425 |
+
return None
|
| 426 |
+
if camera is None:
|
| 427 |
+
raise ValueError(
|
| 428 |
+
"camera_conditioning is enabled, but conditional_dict has no camera_condition"
|
| 429 |
+
)
|
| 430 |
+
if camera.ndim != 5:
|
| 431 |
+
raise ValueError(
|
| 432 |
+
"camera_condition must have shape [B, C, T, H, W]; got "
|
| 433 |
+
f"{tuple(camera.shape)}"
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
target_frames = model_input.shape[1]
|
| 437 |
+
start = int(current_start or 0) if streaming else 0
|
| 438 |
+
if camera.shape[2] >= start + target_frames:
|
| 439 |
+
camera = camera[:, :, start : start + target_frames]
|
| 440 |
+
elif camera.shape[2] != target_frames:
|
| 441 |
+
raise ValueError(
|
| 442 |
+
"Camera conditioning is too short for the requested video frames: "
|
| 443 |
+
f"start={start}, frames={target_frames}, camera_frames={camera.shape[2]}"
|
| 444 |
+
)
|
| 445 |
+
|
| 446 |
+
expected_spatial_grid = (
|
| 447 |
+
model_input.shape[-2] // self.model.patch_spatial,
|
| 448 |
+
model_input.shape[-1] // self.model.patch_spatial,
|
| 449 |
+
)
|
| 450 |
+
if camera.shape[-2:] != expected_spatial_grid:
|
| 451 |
+
raise ValueError(
|
| 452 |
+
"Camera and video spatial token grids do not match: camera="
|
| 453 |
+
f"{tuple(camera.shape[-2:])}, video="
|
| 454 |
+
f"{expected_spatial_grid}"
|
| 455 |
+
)
|
| 456 |
+
if camera.shape[0] != model_input.shape[0]:
|
| 457 |
+
if model_input.shape[0] % camera.shape[0]:
|
| 458 |
+
raise ValueError(
|
| 459 |
+
"Camera batch cannot be expanded to the model batch: "
|
| 460 |
+
f"{camera.shape[0]} and {model_input.shape[0]}"
|
| 461 |
+
)
|
| 462 |
+
camera = camera.repeat(
|
| 463 |
+
model_input.shape[0] // camera.shape[0], 1, 1, 1, 1
|
| 464 |
+
)
|
| 465 |
+
return camera
|
| 466 |
+
|
| 467 |
+
def forward(
|
| 468 |
+
self,
|
| 469 |
+
noisy_image_or_video: torch.Tensor,
|
| 470 |
+
conditional_dict: dict,
|
| 471 |
+
timestep: torch.Tensor,
|
| 472 |
+
kv_cache: Optional[object] = None,
|
| 473 |
+
crossattn_cache: Optional[object] = None,
|
| 474 |
+
current_start: Optional[int] = None,
|
| 475 |
+
classify_mode: bool = False,
|
| 476 |
+
concat_time_embeddings: bool = False,
|
| 477 |
+
clean_x: Optional[torch.Tensor] = None,
|
| 478 |
+
aug_t: Optional[torch.Tensor] = None,
|
| 479 |
+
teacher_forcing_start: Optional[int] = None,
|
| 480 |
+
cache_start: Optional[int] = None,
|
| 481 |
+
store_kv: bool = False,
|
| 482 |
+
) -> torch.Tensor:
|
| 483 |
+
del crossattn_cache, concat_time_embeddings, cache_start
|
| 484 |
+
if classify_mode:
|
| 485 |
+
raise NotImplementedError("Cosmos GAN classifier branch is not integrated")
|
| 486 |
+
teacher_forcing = clean_x is not None or aug_t is not None
|
| 487 |
+
if teacher_forcing and (clean_x is None or aug_t is None):
|
| 488 |
+
raise ValueError("Teacher forcing requires both clean_x and aug_t")
|
| 489 |
+
if teacher_forcing and teacher_forcing_start is None:
|
| 490 |
+
raise ValueError("Teacher forcing requires teacher_forcing_start")
|
| 491 |
+
if not teacher_forcing and teacher_forcing_start is not None:
|
| 492 |
+
raise ValueError("teacher_forcing_start requires teacher forcing")
|
| 493 |
+
if teacher_forcing and (not self.is_causal or kv_cache is not None):
|
| 494 |
+
raise ValueError(
|
| 495 |
+
"Teacher forcing requires a causal model without a streaming KV cache"
|
| 496 |
+
)
|
| 497 |
+
|
| 498 |
+
prompt_embeds = conditional_dict["prompt_embeds"]
|
| 499 |
+
if self.uniform_timestep:
|
| 500 |
+
timestep = timestep[:, :1].expand(-1, noisy_image_or_video.shape[1])
|
| 501 |
+
model_input, input_timestep, condition_mask = self._condition_inputs(
|
| 502 |
+
noisy_image_or_video,
|
| 503 |
+
conditional_dict,
|
| 504 |
+
timestep,
|
| 505 |
+
# Full-sequence training conditions frame zero. Streaming inference
|
| 506 |
+
# has already cached that frame, so later chunks must remain noisy.
|
| 507 |
+
apply_initial_condition=(
|
| 508 |
+
kv_cache is None or int(current_start or 0) == 0
|
| 509 |
+
),
|
| 510 |
+
)
|
| 511 |
+
clean_model_input = None
|
| 512 |
+
clean_input_timestep = None
|
| 513 |
+
if teacher_forcing:
|
| 514 |
+
clean_model_input, clean_input_timestep, clean_condition_mask = (
|
| 515 |
+
self._condition_inputs(
|
| 516 |
+
clean_x,
|
| 517 |
+
conditional_dict,
|
| 518 |
+
aug_t,
|
| 519 |
+
apply_initial_condition=True,
|
| 520 |
+
)
|
| 521 |
+
)
|
| 522 |
+
camera_condition = self._camera_condition(
|
| 523 |
+
conditional_dict,
|
| 524 |
+
model_input,
|
| 525 |
+
current_start=current_start,
|
| 526 |
+
streaming=kv_cache is not None,
|
| 527 |
+
)
|
| 528 |
+
# Keep flow/noise/target construction in FP32, then cast only at the
|
| 529 |
+
# DiT boundary just like a mixed-precision root module would.
|
| 530 |
+
compute_dtype = self.model.x_embedder.proj[1].weight.dtype
|
| 531 |
+
model_input_bcthw = model_input.to(dtype=compute_dtype).permute(0, 2, 1, 3, 4)
|
| 532 |
+
clean_model_input_bcthw = (
|
| 533 |
+
clean_model_input.to(dtype=compute_dtype).permute(0, 2, 1, 3, 4)
|
| 534 |
+
if clean_model_input is not None
|
| 535 |
+
else None
|
| 536 |
+
)
|
| 537 |
+
prompt_embeds = prompt_embeds.to(dtype=compute_dtype)
|
| 538 |
+
condition_mask = condition_mask.to(dtype=compute_dtype)
|
| 539 |
+
if camera_condition is not None:
|
| 540 |
+
camera_condition = camera_condition.to(
|
| 541 |
+
device=model_input.device,
|
| 542 |
+
dtype=compute_dtype,
|
| 543 |
+
)
|
| 544 |
+
padding_mask = torch.zeros(
|
| 545 |
+
model_input.shape[0],
|
| 546 |
+
1,
|
| 547 |
+
model_input.shape[-2],
|
| 548 |
+
model_input.shape[-1],
|
| 549 |
+
device=model_input.device,
|
| 550 |
+
dtype=compute_dtype,
|
| 551 |
+
)
|
| 552 |
+
|
| 553 |
+
if kv_cache is not None:
|
| 554 |
+
from cosmos.kv_cache import (
|
| 555 |
+
KVCacheConfig,
|
| 556 |
+
VideoSeqPos,
|
| 557 |
+
)
|
| 558 |
+
|
| 559 |
+
frame_index = int(current_start or 0)
|
| 560 |
+
frame_count = model_input.shape[1]
|
| 561 |
+
token_h = model_input.shape[-2] // self.model.patch_spatial
|
| 562 |
+
token_w = model_input.shape[-1] // self.model.patch_spatial
|
| 563 |
+
video_pos = VideoSeqPos(
|
| 564 |
+
T=frame_count,
|
| 565 |
+
H=token_h,
|
| 566 |
+
W=token_w,
|
| 567 |
+
)
|
| 568 |
+
video_pos.pos_t = video_pos.pos_t + frame_index
|
| 569 |
+
should_store_kv = bool(store_kv)
|
| 570 |
+
block_frames = int(getattr(self.model, "num_frame_per_block", 1))
|
| 571 |
+
if (
|
| 572 |
+
frame_count > 1
|
| 573 |
+
and not should_store_kv
|
| 574 |
+
and frame_count != block_frames
|
| 575 |
+
):
|
| 576 |
+
raise ValueError(
|
| 577 |
+
"Multi-frame causal input must match num_frame_per_block: "
|
| 578 |
+
f"frames={frame_count}, block={block_frames}"
|
| 579 |
+
)
|
| 580 |
+
flow_pred = self.model.forward_seq(
|
| 581 |
+
x_B_C_T_H_W=model_input_bcthw,
|
| 582 |
+
video_pos=video_pos,
|
| 583 |
+
timesteps_B_T=input_timestep,
|
| 584 |
+
crossattn_emb=prompt_embeds,
|
| 585 |
+
padding_mask=padding_mask,
|
| 586 |
+
condition_video_input_mask_B_C_T_H_W=condition_mask,
|
| 587 |
+
camera_condition_B_C_T_H_W=camera_condition,
|
| 588 |
+
full_video_size=(frame_index + frame_count, token_h, token_w),
|
| 589 |
+
kv_cache_cfg=KVCacheConfig(
|
| 590 |
+
run_with_kv=True,
|
| 591 |
+
store_kv=should_store_kv,
|
| 592 |
+
current_idx=frame_index,
|
| 593 |
+
),
|
| 594 |
+
).permute(0, 2, 1, 3, 4)
|
| 595 |
+
elif teacher_forcing:
|
| 596 |
+
flow_pred = self.model.forward_teacher_forcing(
|
| 597 |
+
noisy_x_B_C_T_H_W=model_input_bcthw,
|
| 598 |
+
clean_x_B_C_T_H_W=clean_model_input_bcthw,
|
| 599 |
+
noisy_timesteps_B_T=input_timestep,
|
| 600 |
+
clean_timesteps_B_T=clean_input_timestep,
|
| 601 |
+
crossattn_emb=prompt_embeds,
|
| 602 |
+
padding_mask=padding_mask,
|
| 603 |
+
condition_video_input_mask_B_C_T_H_W=condition_mask,
|
| 604 |
+
clean_condition_video_input_mask_B_C_T_H_W=clean_condition_mask,
|
| 605 |
+
camera_condition_B_C_T_H_W=camera_condition,
|
| 606 |
+
noisy_start_frame=int(teacher_forcing_start),
|
| 607 |
+
).permute(0, 2, 1, 3, 4)
|
| 608 |
+
else:
|
| 609 |
+
flow_pred = self.model(
|
| 610 |
+
x_B_C_T_H_W=model_input_bcthw,
|
| 611 |
+
timesteps_B_T=input_timestep,
|
| 612 |
+
crossattn_emb=prompt_embeds,
|
| 613 |
+
padding_mask=padding_mask,
|
| 614 |
+
condition_video_input_mask_B_C_T_H_W=condition_mask,
|
| 615 |
+
camera_condition_B_C_T_H_W=camera_condition,
|
| 616 |
+
).permute(0, 2, 1, 3, 4)
|
| 617 |
+
|
| 618 |
+
pred_x0 = self._convert_flow_pred_to_x0(
|
| 619 |
+
flow_pred=flow_pred.flatten(0, 1),
|
| 620 |
+
xt=model_input.flatten(0, 1),
|
| 621 |
+
timestep=input_timestep.flatten(0, 1),
|
| 622 |
+
).unflatten(0, flow_pred.shape[:2])
|
| 623 |
+
return flow_pred, pred_x0
|
| 624 |
+
|
| 625 |
+
def _convert_flow_pred_to_x0(
|
| 626 |
+
self,
|
| 627 |
+
flow_pred: torch.Tensor,
|
| 628 |
+
xt: torch.Tensor,
|
| 629 |
+
timestep: torch.Tensor,
|
| 630 |
+
) -> torch.Tensor:
|
| 631 |
+
original_dtype = flow_pred.dtype
|
| 632 |
+
flow_pred = flow_pred.float()
|
| 633 |
+
xt = xt.float()
|
| 634 |
+
timestep_id = torch.argmin(
|
| 635 |
+
(
|
| 636 |
+
self._flow_timesteps.unsqueeze(0)
|
| 637 |
+
- timestep.float().unsqueeze(1)
|
| 638 |
+
).abs(),
|
| 639 |
+
dim=1,
|
| 640 |
+
)
|
| 641 |
+
sigma_t = self._flow_sigmas[timestep_id].reshape(-1, 1, 1, 1)
|
| 642 |
+
return (xt - sigma_t * flow_pred).to(original_dtype)
|
| 643 |
+
|
| 644 |
+
@staticmethod
|
| 645 |
+
def _convert_x0_to_flow_pred(
|
| 646 |
+
scheduler,
|
| 647 |
+
x0_pred: torch.Tensor,
|
| 648 |
+
xt: torch.Tensor,
|
| 649 |
+
timestep: torch.Tensor,
|
| 650 |
+
) -> torch.Tensor:
|
| 651 |
+
original_dtype = x0_pred.dtype
|
| 652 |
+
x0_pred, xt, sigmas, timesteps = map(
|
| 653 |
+
lambda value: value.float().to(x0_pred.device),
|
| 654 |
+
[x0_pred, xt, scheduler.sigmas, scheduler.timesteps],
|
| 655 |
+
)
|
| 656 |
+
timestep_id = torch.argmin(
|
| 657 |
+
(timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(),
|
| 658 |
+
dim=1,
|
| 659 |
+
)
|
| 660 |
+
sigma_t = sigmas[timestep_id].reshape(-1, 1, 1, 1)
|
| 661 |
+
return ((xt - x0_pred) / sigma_t).to(original_dtype)
|
| 662 |
+
|
| 663 |
+
def get_scheduler(self) -> SchedulerInterface:
|
| 664 |
+
scheduler = self.scheduler
|
| 665 |
+
scheduler.convert_x0_to_noise = types.MethodType(
|
| 666 |
+
SchedulerInterface.convert_x0_to_noise,
|
| 667 |
+
scheduler,
|
| 668 |
+
)
|
| 669 |
+
scheduler.convert_noise_to_x0 = types.MethodType(
|
| 670 |
+
SchedulerInterface.convert_noise_to_x0,
|
| 671 |
+
scheduler,
|
| 672 |
+
)
|
| 673 |
+
scheduler.convert_velocity_to_x0 = types.MethodType(
|
| 674 |
+
SchedulerInterface.convert_velocity_to_x0,
|
| 675 |
+
scheduler,
|
| 676 |
+
)
|
| 677 |
+
return scheduler
|
| 678 |
+
|
| 679 |
+
def post_init(self) -> None:
|
| 680 |
+
self.get_scheduler()
|
examples/image.png
ADDED
|
Git LFS Details
|
examples/prompt.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
A grounded first-person game camera, showing occasional leather-gloved hands at the bottom edge, surfs smoothly along a slow, glassy water current flowing through a desert caravanserai moments before sunrise. In the near foreground, small submerged stones and surface wear on the aqueduct’s rim create rapid near-field parallax as the viewpoint glides along the shallow S-curve toward a towering central courtyard. High thin clouds diffuse the very first rays of dawn, casting broken sunlight that moves steadily across the sandstone paving. The surfing motion remains fluid and continuous, requiring only slight leans to navigate the gentle bends of the ancient water channel. As the camera rounds the initial curve, the surrounding masonry reveals fragments of a much older civilization, with colossal, weathered statues seamlessly incorporated into the newer brickwork. The subtly stylized realism highlights the believable material response of wet stone and dry terracotta under the shifting morning light. Passing a row of silent merchant stalls, the channel straightens out, and the gloved hands adjust their grip subtly to maintain balance. The destination courtyard grows larger, its grand archway framing the brightening sky. The gentle current carries the viewpoint perfectly through the final stretch of the corridor, easing to a quiet halt as the shallow water spills out into the expansive, sunlit central basin.
|
pipeline/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Trimmed package initializer for the CMD Hugging Face Space.
|
| 2 |
+
# The upstream nv-tlabs/cmd package initializers eagerly import training-only
|
| 3 |
+
# and multi-GPU modules that this inference-only demo does not need. See NOTICE
|
| 4 |
+
# and LICENSE for the upstream licenses that continue to apply.
|
pipeline/causal_inference.py
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2025 The Self-Forcing Authors. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
# SPDX-FileCopyrightText: Modifications Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 17 |
+
# SPDX-License-Identifier: LicenseRef-NvidiaOneWayNoncommercial
|
| 18 |
+
|
| 19 |
+
from typing import List, Optional
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
from wan.utils.fm_solvers_unipc import FlowUniPCMultistepScheduler
|
| 23 |
+
from utils.model_factory import (
|
| 24 |
+
build_diffusion_wrapper,
|
| 25 |
+
build_text_encoder,
|
| 26 |
+
build_vae,
|
| 27 |
+
)
|
| 28 |
+
from cosmos.camera_conditioning import build_camera_conditioning
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class CausalInferencePipeline(torch.nn.Module):
|
| 32 |
+
def __init__(
|
| 33 |
+
self,
|
| 34 |
+
args,
|
| 35 |
+
device,
|
| 36 |
+
generator=None,
|
| 37 |
+
text_encoder=None,
|
| 38 |
+
vae=None
|
| 39 |
+
):
|
| 40 |
+
super().__init__()
|
| 41 |
+
# Step 1: Initialize all models
|
| 42 |
+
self.generator = (
|
| 43 |
+
build_diffusion_wrapper(args, is_causal=True)
|
| 44 |
+
if generator is None else generator
|
| 45 |
+
)
|
| 46 |
+
self.text_encoder = build_text_encoder(args) if text_encoder is None else text_encoder
|
| 47 |
+
self.vae = build_vae(args) if vae is None else vae
|
| 48 |
+
|
| 49 |
+
# Step 2: Initialize all causal hyperparmeters
|
| 50 |
+
self.scheduler = self.generator.get_scheduler()
|
| 51 |
+
self.num_inference_steps = int(getattr(args, "num_inference_steps", 0))
|
| 52 |
+
self.denoising_step_list = None
|
| 53 |
+
if self.num_inference_steps <= 0:
|
| 54 |
+
self.denoising_step_list = torch.tensor(
|
| 55 |
+
args.denoising_step_list, dtype=torch.long)
|
| 56 |
+
if args.warp_denoising_step:
|
| 57 |
+
timesteps = torch.cat((self.scheduler.timesteps.cpu(), torch.tensor([0], dtype=torch.float32)))
|
| 58 |
+
self.denoising_step_list = timesteps[1000 - self.denoising_step_list]
|
| 59 |
+
|
| 60 |
+
self.num_transformer_blocks = getattr(self.generator, "num_transformer_blocks", 30)
|
| 61 |
+
self.frame_seq_length = getattr(self.generator, "frame_seq_length", 1560)
|
| 62 |
+
|
| 63 |
+
self.kv_cache1 = None
|
| 64 |
+
self.args = args
|
| 65 |
+
self.context_noise = int(getattr(args, "context_noise", 0))
|
| 66 |
+
if self.context_noise < 0:
|
| 67 |
+
raise ValueError("context_noise must be non-negative")
|
| 68 |
+
self.num_frame_per_block = getattr(args, "num_frame_per_block", 1)
|
| 69 |
+
self.independent_first_frame = args.independent_first_frame
|
| 70 |
+
self.local_attn_size = getattr(self.generator.model, "local_attn_size", -1)
|
| 71 |
+
|
| 72 |
+
print(f"KV inference with {self.num_frame_per_block} frames per block")
|
| 73 |
+
|
| 74 |
+
if self.num_frame_per_block > 1 and hasattr(self.generator.model, "num_frame_per_block"):
|
| 75 |
+
self.generator.model.num_frame_per_block = self.num_frame_per_block
|
| 76 |
+
|
| 77 |
+
def inference(
|
| 78 |
+
self,
|
| 79 |
+
noise: torch.Tensor,
|
| 80 |
+
text_prompts: List[str],
|
| 81 |
+
initial_latent: Optional[torch.Tensor] = None,
|
| 82 |
+
camera_poses: Optional[torch.Tensor] = None,
|
| 83 |
+
camera_intrinsics: Optional[torch.Tensor] = None,
|
| 84 |
+
camera_condition: Optional[torch.Tensor] = None,
|
| 85 |
+
return_latents: bool = False,
|
| 86 |
+
decode: bool = True,
|
| 87 |
+
profile: bool = False,
|
| 88 |
+
) -> torch.Tensor:
|
| 89 |
+
"""
|
| 90 |
+
Perform inference on the given noise and text prompts.
|
| 91 |
+
Inputs:
|
| 92 |
+
noise (torch.Tensor): The input noise tensor of shape
|
| 93 |
+
(batch_size, num_output_frames, num_channels, height, width).
|
| 94 |
+
text_prompts (List[str]): The list of text prompts.
|
| 95 |
+
initial_latent (torch.Tensor): The initial latent tensor of shape
|
| 96 |
+
(batch_size, num_input_frames, num_channels, height, width).
|
| 97 |
+
If num_input_frames is 1, perform image to video.
|
| 98 |
+
If num_input_frames is greater than 1, perform video extension.
|
| 99 |
+
return_latents (bool): Whether to return the latents.
|
| 100 |
+
Outputs:
|
| 101 |
+
video (torch.Tensor): The generated video tensor of shape
|
| 102 |
+
(batch_size, num_output_frames, num_channels, height, width).
|
| 103 |
+
It is normalized to be in the range [0, 1].
|
| 104 |
+
"""
|
| 105 |
+
batch_size, num_frames, num_channels, height, width = noise.shape
|
| 106 |
+
if not self.independent_first_frame or (self.independent_first_frame and initial_latent is not None):
|
| 107 |
+
# If the first frame is independent and the first frame is provided, then the number of frames in the
|
| 108 |
+
# noise should still be a multiple of num_frame_per_block
|
| 109 |
+
assert num_frames % self.num_frame_per_block == 0
|
| 110 |
+
num_blocks = num_frames // self.num_frame_per_block
|
| 111 |
+
else:
|
| 112 |
+
# Using a [1, 4, 4, 4, 4, 4, ...] model to generate a video without image conditioning
|
| 113 |
+
assert (num_frames - 1) % self.num_frame_per_block == 0
|
| 114 |
+
num_blocks = (num_frames - 1) // self.num_frame_per_block
|
| 115 |
+
num_input_frames = initial_latent.shape[1] if initial_latent is not None else 0
|
| 116 |
+
num_output_frames = num_frames + num_input_frames # add the initial latent frames
|
| 117 |
+
conditional_dict = self.text_encoder(
|
| 118 |
+
text_prompts=text_prompts
|
| 119 |
+
)
|
| 120 |
+
unconditional_dict = None
|
| 121 |
+
if self.num_inference_steps > 0 and self.args.guidance_scale > 1.0:
|
| 122 |
+
unconditional_dict = self.text_encoder(
|
| 123 |
+
text_prompts=[self.args.negative_prompt] * len(text_prompts)
|
| 124 |
+
)
|
| 125 |
+
if initial_latent is not None:
|
| 126 |
+
conditional_dict["initial_latent"] = initial_latent
|
| 127 |
+
if unconditional_dict is not None:
|
| 128 |
+
unconditional_dict["initial_latent"] = initial_latent
|
| 129 |
+
if getattr(self.args, "camera_conditioning", False):
|
| 130 |
+
if camera_condition is None:
|
| 131 |
+
if camera_poses is None or camera_intrinsics is None:
|
| 132 |
+
raise ValueError(
|
| 133 |
+
"Camera-conditioned inference requires camera_poses and "
|
| 134 |
+
"camera_intrinsics, or a precomputed camera_condition"
|
| 135 |
+
)
|
| 136 |
+
camera_condition = build_camera_conditioning(
|
| 137 |
+
camera_poses.to(device=noise.device, dtype=torch.float32),
|
| 138 |
+
camera_intrinsics.to(device=noise.device, dtype=torch.float32),
|
| 139 |
+
image_height=int(self.args.height),
|
| 140 |
+
image_width=int(self.args.width),
|
| 141 |
+
frame_stride=int(getattr(self.args, "camera_frame_stride", 4)),
|
| 142 |
+
patch_size=int(getattr(self.args, "camera_patch_size", 16)),
|
| 143 |
+
num_frame_per_block=int(self.num_frame_per_block),
|
| 144 |
+
expected_latent_frames=num_output_frames,
|
| 145 |
+
output_dtype=noise.dtype,
|
| 146 |
+
)
|
| 147 |
+
conditional_dict["camera_condition"] = camera_condition
|
| 148 |
+
if unconditional_dict is not None:
|
| 149 |
+
unconditional_dict["camera_condition"] = camera_condition
|
| 150 |
+
|
| 151 |
+
output = torch.zeros(
|
| 152 |
+
[batch_size, num_output_frames, num_channels, height, width],
|
| 153 |
+
device=noise.device,
|
| 154 |
+
dtype=noise.dtype
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
# Set up profiling if requested
|
| 158 |
+
if profile:
|
| 159 |
+
init_start = torch.cuda.Event(enable_timing=True)
|
| 160 |
+
init_end = torch.cuda.Event(enable_timing=True)
|
| 161 |
+
diffusion_start = torch.cuda.Event(enable_timing=True)
|
| 162 |
+
diffusion_end = torch.cuda.Event(enable_timing=True)
|
| 163 |
+
vae_start = torch.cuda.Event(enable_timing=True)
|
| 164 |
+
vae_end = torch.cuda.Event(enable_timing=True)
|
| 165 |
+
block_times = []
|
| 166 |
+
block_start = torch.cuda.Event(enable_timing=True)
|
| 167 |
+
block_end = torch.cuda.Event(enable_timing=True)
|
| 168 |
+
init_start.record()
|
| 169 |
+
|
| 170 |
+
# Step 1: Initialize KV cache to all zeros
|
| 171 |
+
if hasattr(self.generator, "initialize_kv_cache"):
|
| 172 |
+
self._initialize_kv_cache(
|
| 173 |
+
batch_size=batch_size,
|
| 174 |
+
dtype=noise.dtype,
|
| 175 |
+
device=noise.device,
|
| 176 |
+
max_frames=num_output_frames,
|
| 177 |
+
)
|
| 178 |
+
self._initialize_crossattn_cache(
|
| 179 |
+
batch_size=batch_size,
|
| 180 |
+
dtype=noise.dtype,
|
| 181 |
+
device=noise.device,
|
| 182 |
+
)
|
| 183 |
+
elif self.kv_cache1 is None:
|
| 184 |
+
self._initialize_kv_cache(
|
| 185 |
+
batch_size=batch_size,
|
| 186 |
+
dtype=noise.dtype,
|
| 187 |
+
device=noise.device
|
| 188 |
+
)
|
| 189 |
+
self._initialize_crossattn_cache(
|
| 190 |
+
batch_size=batch_size,
|
| 191 |
+
dtype=noise.dtype,
|
| 192 |
+
device=noise.device
|
| 193 |
+
)
|
| 194 |
+
else:
|
| 195 |
+
# reset cross attn cache
|
| 196 |
+
for block_index in range(self.num_transformer_blocks):
|
| 197 |
+
self.crossattn_cache[block_index]["is_init"] = False
|
| 198 |
+
# reset kv cache
|
| 199 |
+
for block_index in range(len(self.kv_cache1)):
|
| 200 |
+
self.kv_cache1[block_index]["global_end_index"] = torch.tensor(
|
| 201 |
+
[0], dtype=torch.long, device=noise.device)
|
| 202 |
+
self.kv_cache1[block_index]["local_end_index"] = torch.tensor(
|
| 203 |
+
[0], dtype=torch.long, device=noise.device)
|
| 204 |
+
|
| 205 |
+
# Step 2: Cache context feature
|
| 206 |
+
current_start_frame = 0
|
| 207 |
+
if initial_latent is not None:
|
| 208 |
+
timestep = torch.ones([batch_size, 1], device=noise.device, dtype=torch.int64) * 0
|
| 209 |
+
if self.independent_first_frame:
|
| 210 |
+
# Assume num_input_frames is 1 + self.num_frame_per_block * num_input_blocks
|
| 211 |
+
assert (num_input_frames - 1) % self.num_frame_per_block == 0
|
| 212 |
+
num_input_blocks = (num_input_frames - 1) // self.num_frame_per_block
|
| 213 |
+
output[:, :1] = initial_latent[:, :1]
|
| 214 |
+
self.generator(
|
| 215 |
+
noisy_image_or_video=initial_latent[:, :1],
|
| 216 |
+
conditional_dict=conditional_dict,
|
| 217 |
+
timestep=timestep * 0,
|
| 218 |
+
kv_cache=self.kv_cache1,
|
| 219 |
+
crossattn_cache=self.crossattn_cache,
|
| 220 |
+
current_start=self._cache_position(current_start_frame),
|
| 221 |
+
store_kv=True,
|
| 222 |
+
)
|
| 223 |
+
current_start_frame += 1
|
| 224 |
+
else:
|
| 225 |
+
# Assume num_input_frames is self.num_frame_per_block * num_input_blocks
|
| 226 |
+
assert num_input_frames % self.num_frame_per_block == 0
|
| 227 |
+
num_input_blocks = num_input_frames // self.num_frame_per_block
|
| 228 |
+
|
| 229 |
+
for _ in range(num_input_blocks):
|
| 230 |
+
current_ref_latents = \
|
| 231 |
+
initial_latent[:, current_start_frame:current_start_frame + self.num_frame_per_block]
|
| 232 |
+
output[:, current_start_frame:current_start_frame + self.num_frame_per_block] = current_ref_latents
|
| 233 |
+
self.generator(
|
| 234 |
+
noisy_image_or_video=current_ref_latents,
|
| 235 |
+
conditional_dict=conditional_dict,
|
| 236 |
+
timestep=timestep * 0,
|
| 237 |
+
kv_cache=self.kv_cache1,
|
| 238 |
+
crossattn_cache=self.crossattn_cache,
|
| 239 |
+
current_start=self._cache_position(current_start_frame),
|
| 240 |
+
store_kv=True,
|
| 241 |
+
)
|
| 242 |
+
current_start_frame += self.num_frame_per_block
|
| 243 |
+
|
| 244 |
+
if profile:
|
| 245 |
+
init_end.record()
|
| 246 |
+
torch.cuda.synchronize()
|
| 247 |
+
diffusion_start.record()
|
| 248 |
+
|
| 249 |
+
# Step 3: Temporal denoising loop
|
| 250 |
+
all_num_frames = [self.num_frame_per_block] * num_blocks
|
| 251 |
+
if self.independent_first_frame and initial_latent is None:
|
| 252 |
+
all_num_frames = [1] + all_num_frames
|
| 253 |
+
for current_num_frames in all_num_frames:
|
| 254 |
+
if profile:
|
| 255 |
+
block_start.record()
|
| 256 |
+
|
| 257 |
+
noisy_input = noise[
|
| 258 |
+
:, current_start_frame - num_input_frames:current_start_frame + current_num_frames - num_input_frames]
|
| 259 |
+
|
| 260 |
+
# Step 3.1: Integrate the flow trajectory for a regular FM model.
|
| 261 |
+
# The legacy x0/re-noise path below is retained only for distilled
|
| 262 |
+
# few-step configs that explicitly provide denoising_step_list.
|
| 263 |
+
if self.num_inference_steps > 0:
|
| 264 |
+
sample_scheduler = FlowUniPCMultistepScheduler(
|
| 265 |
+
num_train_timesteps=self.args.num_train_timestep,
|
| 266 |
+
shift=1,
|
| 267 |
+
use_dynamic_shifting=False,
|
| 268 |
+
)
|
| 269 |
+
sample_scheduler.set_timesteps(
|
| 270 |
+
self.num_inference_steps,
|
| 271 |
+
device=noise.device,
|
| 272 |
+
shift=self.args.timestep_shift,
|
| 273 |
+
)
|
| 274 |
+
denoised_pred = noisy_input
|
| 275 |
+
for current_timestep in sample_scheduler.timesteps:
|
| 276 |
+
if profile:
|
| 277 |
+
print(f"current_timestep: {current_timestep}")
|
| 278 |
+
timestep = torch.ones(
|
| 279 |
+
[batch_size, current_num_frames],
|
| 280 |
+
device=noise.device,
|
| 281 |
+
dtype=current_timestep.dtype,
|
| 282 |
+
) * current_timestep
|
| 283 |
+
if unconditional_dict is not None:
|
| 284 |
+
cfg_dict = dict(conditional_dict)
|
| 285 |
+
cfg_dict["prompt_embeds"] = torch.cat(
|
| 286 |
+
[
|
| 287 |
+
conditional_dict["prompt_embeds"],
|
| 288 |
+
unconditional_dict["prompt_embeds"],
|
| 289 |
+
],
|
| 290 |
+
dim=0,
|
| 291 |
+
)
|
| 292 |
+
flow_pred_cfg, _ = self.generator(
|
| 293 |
+
noisy_image_or_video=torch.cat(
|
| 294 |
+
[denoised_pred, denoised_pred], dim=0
|
| 295 |
+
),
|
| 296 |
+
conditional_dict=cfg_dict,
|
| 297 |
+
timestep=torch.cat([timestep, timestep], dim=0),
|
| 298 |
+
kv_cache=self.kv_cache1,
|
| 299 |
+
crossattn_cache=self.crossattn_cache,
|
| 300 |
+
current_start=self._cache_position(current_start_frame),
|
| 301 |
+
)
|
| 302 |
+
flow_pred_cond, flow_pred_uncond = flow_pred_cfg.chunk(2)
|
| 303 |
+
# Cosmos Predict2.5 V2W guidance is applied in velocity
|
| 304 |
+
# space around the conditional prediction.
|
| 305 |
+
flow_pred = flow_pred_cond + self.args.guidance_scale * (
|
| 306 |
+
flow_pred_cond - flow_pred_uncond
|
| 307 |
+
)
|
| 308 |
+
else:
|
| 309 |
+
flow_pred, _ = self.generator(
|
| 310 |
+
noisy_image_or_video=denoised_pred,
|
| 311 |
+
conditional_dict=conditional_dict,
|
| 312 |
+
timestep=timestep,
|
| 313 |
+
kv_cache=self.kv_cache1,
|
| 314 |
+
crossattn_cache=self.crossattn_cache,
|
| 315 |
+
current_start=self._cache_position(current_start_frame),
|
| 316 |
+
)
|
| 317 |
+
denoised_pred = sample_scheduler.step(
|
| 318 |
+
flow_pred,
|
| 319 |
+
current_timestep,
|
| 320 |
+
denoised_pred,
|
| 321 |
+
return_dict=False,
|
| 322 |
+
)[0]
|
| 323 |
+
else:
|
| 324 |
+
for index, current_timestep in enumerate(self.denoising_step_list):
|
| 325 |
+
if profile:
|
| 326 |
+
print(f"current_timestep: {current_timestep}")
|
| 327 |
+
timestep = torch.ones(
|
| 328 |
+
[batch_size, current_num_frames],
|
| 329 |
+
device=noise.device,
|
| 330 |
+
dtype=torch.int64) * current_timestep
|
| 331 |
+
|
| 332 |
+
if index < len(self.denoising_step_list) - 1:
|
| 333 |
+
_, denoised_pred = self.generator(
|
| 334 |
+
noisy_image_or_video=noisy_input,
|
| 335 |
+
conditional_dict=conditional_dict,
|
| 336 |
+
timestep=timestep,
|
| 337 |
+
kv_cache=self.kv_cache1,
|
| 338 |
+
crossattn_cache=self.crossattn_cache,
|
| 339 |
+
current_start=self._cache_position(current_start_frame)
|
| 340 |
+
)
|
| 341 |
+
next_timestep = self.denoising_step_list[index + 1]
|
| 342 |
+
noisy_input = self.scheduler.add_noise(
|
| 343 |
+
denoised_pred.flatten(0, 1),
|
| 344 |
+
torch.randn_like(denoised_pred.flatten(0, 1)),
|
| 345 |
+
next_timestep * torch.ones(
|
| 346 |
+
[batch_size * current_num_frames], device=noise.device, dtype=torch.long)
|
| 347 |
+
).unflatten(0, denoised_pred.shape[:2])
|
| 348 |
+
else:
|
| 349 |
+
_, denoised_pred = self.generator(
|
| 350 |
+
noisy_image_or_video=noisy_input,
|
| 351 |
+
conditional_dict=conditional_dict,
|
| 352 |
+
timestep=timestep,
|
| 353 |
+
kv_cache=self.kv_cache1,
|
| 354 |
+
crossattn_cache=self.crossattn_cache,
|
| 355 |
+
current_start=self._cache_position(current_start_frame)
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
# Step 3.2: record the model's output
|
| 359 |
+
output[:, current_start_frame:current_start_frame + current_num_frames] = denoised_pred
|
| 360 |
+
|
| 361 |
+
# Step 3.3: commit the generated frame to K/V at the context-noise
|
| 362 |
+
# level used by the causal training/inference recipe. The original
|
| 363 |
+
# I2V prefix remains clean at timestep zero.
|
| 364 |
+
context_timestep = torch.ones_like(timestep) * self.context_noise
|
| 365 |
+
cache_input = denoised_pred.detach()
|
| 366 |
+
if self.context_noise > 0:
|
| 367 |
+
cache_input = self.scheduler.add_noise(
|
| 368 |
+
cache_input.flatten(0, 1),
|
| 369 |
+
torch.randn_like(cache_input.flatten(0, 1)),
|
| 370 |
+
context_timestep.flatten(0, 1),
|
| 371 |
+
).unflatten(0, cache_input.shape[:2])
|
| 372 |
+
self.generator(
|
| 373 |
+
noisy_image_or_video=cache_input,
|
| 374 |
+
conditional_dict=conditional_dict,
|
| 375 |
+
timestep=context_timestep,
|
| 376 |
+
kv_cache=self.kv_cache1,
|
| 377 |
+
crossattn_cache=self.crossattn_cache,
|
| 378 |
+
current_start=self._cache_position(current_start_frame),
|
| 379 |
+
store_kv=True,
|
| 380 |
+
)
|
| 381 |
+
|
| 382 |
+
if profile:
|
| 383 |
+
block_end.record()
|
| 384 |
+
torch.cuda.synchronize()
|
| 385 |
+
block_time = block_start.elapsed_time(block_end)
|
| 386 |
+
block_times.append(block_time)
|
| 387 |
+
|
| 388 |
+
# Step 3.4: update the start and end frame indices
|
| 389 |
+
current_start_frame += current_num_frames
|
| 390 |
+
|
| 391 |
+
if profile:
|
| 392 |
+
# End diffusion timing and synchronize CUDA
|
| 393 |
+
diffusion_end.record()
|
| 394 |
+
torch.cuda.synchronize()
|
| 395 |
+
diffusion_time = diffusion_start.elapsed_time(diffusion_end)
|
| 396 |
+
init_time = init_start.elapsed_time(init_end)
|
| 397 |
+
vae_start.record()
|
| 398 |
+
|
| 399 |
+
# Step 4: Decode the output when pixels are requested.
|
| 400 |
+
video = None
|
| 401 |
+
if decode:
|
| 402 |
+
video = self.vae.decode_to_pixel(output, use_cache=False)
|
| 403 |
+
video = (video * 0.5 + 0.5).clamp(0, 1)
|
| 404 |
+
|
| 405 |
+
if profile:
|
| 406 |
+
# End VAE timing and synchronize CUDA
|
| 407 |
+
vae_end.record()
|
| 408 |
+
torch.cuda.synchronize()
|
| 409 |
+
vae_time = vae_start.elapsed_time(vae_end) if decode else 0.0
|
| 410 |
+
total_time = init_time + diffusion_time + vae_time
|
| 411 |
+
|
| 412 |
+
print("Profiling results:")
|
| 413 |
+
print(f" - Initialization/caching time: {init_time:.2f} ms ({100 * init_time / total_time:.2f}%)")
|
| 414 |
+
print(f" - Diffusion generation time: {diffusion_time:.2f} ms ({100 * diffusion_time / total_time:.2f}%)")
|
| 415 |
+
for i, block_time in enumerate(block_times):
|
| 416 |
+
print(f" - Block {i} generation time: {block_time:.2f} ms ({100 * block_time / diffusion_time:.2f}% of diffusion)")
|
| 417 |
+
print(f" - VAE decoding time: {vae_time:.2f} ms ({100 * vae_time / total_time:.2f}%)")
|
| 418 |
+
print(f" - Total time: {total_time:.2f} ms")
|
| 419 |
+
|
| 420 |
+
if return_latents:
|
| 421 |
+
return video, output
|
| 422 |
+
if not decode:
|
| 423 |
+
return output
|
| 424 |
+
else:
|
| 425 |
+
return video
|
| 426 |
+
|
| 427 |
+
def _initialize_kv_cache(self, batch_size, dtype, device, max_frames=None):
|
| 428 |
+
"""
|
| 429 |
+
Initialize the backend's causal KV cache.
|
| 430 |
+
"""
|
| 431 |
+
if hasattr(self.generator, "initialize_kv_cache"):
|
| 432 |
+
self.kv_cache1 = self.generator.initialize_kv_cache(
|
| 433 |
+
max_frames=max_frames or getattr(self.args, "num_training_frames", 21),
|
| 434 |
+
batch_size=batch_size,
|
| 435 |
+
dtype=dtype,
|
| 436 |
+
device=device,
|
| 437 |
+
)
|
| 438 |
+
return
|
| 439 |
+
|
| 440 |
+
kv_cache1 = []
|
| 441 |
+
if self.local_attn_size != -1:
|
| 442 |
+
# Use the local attention size to compute the KV cache size
|
| 443 |
+
kv_cache_size = self.local_attn_size * self.frame_seq_length
|
| 444 |
+
else:
|
| 445 |
+
# Use the default KV cache size
|
| 446 |
+
kv_cache_size = 32760
|
| 447 |
+
|
| 448 |
+
for _ in range(self.num_transformer_blocks):
|
| 449 |
+
kv_cache1.append({
|
| 450 |
+
"k": torch.zeros([batch_size, kv_cache_size, 12, 128], dtype=dtype, device=device),
|
| 451 |
+
"v": torch.zeros([batch_size, kv_cache_size, 12, 128], dtype=dtype, device=device),
|
| 452 |
+
"global_end_index": torch.tensor([0], dtype=torch.long, device=device),
|
| 453 |
+
"local_end_index": torch.tensor([0], dtype=torch.long, device=device)
|
| 454 |
+
})
|
| 455 |
+
|
| 456 |
+
self.kv_cache1 = kv_cache1 # always store the clean cache
|
| 457 |
+
|
| 458 |
+
def _initialize_crossattn_cache(self, batch_size, dtype, device):
|
| 459 |
+
"""
|
| 460 |
+
Initialize the backend's cross-attention cache when it has one.
|
| 461 |
+
"""
|
| 462 |
+
if hasattr(self.generator, "initialize_crossattn_cache"):
|
| 463 |
+
self.crossattn_cache = self.generator.initialize_crossattn_cache(
|
| 464 |
+
batch_size=batch_size,
|
| 465 |
+
dtype=dtype,
|
| 466 |
+
device=device,
|
| 467 |
+
)
|
| 468 |
+
return
|
| 469 |
+
|
| 470 |
+
crossattn_cache = []
|
| 471 |
+
|
| 472 |
+
for _ in range(self.num_transformer_blocks):
|
| 473 |
+
crossattn_cache.append({
|
| 474 |
+
"k": torch.zeros([batch_size, 512, 12, 128], dtype=dtype, device=device),
|
| 475 |
+
"v": torch.zeros([batch_size, 512, 12, 128], dtype=dtype, device=device),
|
| 476 |
+
"is_init": False
|
| 477 |
+
})
|
| 478 |
+
self.crossattn_cache = crossattn_cache
|
| 479 |
+
|
| 480 |
+
def _cache_position(self, frame_index: int) -> int:
|
| 481 |
+
if hasattr(self.generator, "cache_position"):
|
| 482 |
+
return self.generator.cache_position(frame_index)
|
| 483 |
+
return frame_index * self.frame_seq_length
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torchvision
|
| 2 |
+
transformers>=4.57,<5
|
| 3 |
+
accelerate
|
| 4 |
+
diffusers>=0.35.2
|
| 5 |
+
safetensors
|
| 6 |
+
sentencepiece
|
| 7 |
+
omegaconf
|
| 8 |
+
einops
|
| 9 |
+
numpy
|
| 10 |
+
pillow
|
| 11 |
+
imageio
|
| 12 |
+
imageio-ffmpeg
|
utils/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Trimmed package initializer for the CMD Hugging Face Space.
|
| 2 |
+
# The upstream nv-tlabs/cmd package initializers eagerly import training-only
|
| 3 |
+
# and multi-GPU modules that this inference-only demo does not need. See NOTICE
|
| 4 |
+
# and LICENSE for the upstream licenses that continue to apply.
|
utils/misc.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2025 The Self-Forcing Authors. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
import numpy as np
|
| 17 |
+
import random
|
| 18 |
+
import torch
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def set_seed(seed: int, deterministic: bool = False):
|
| 22 |
+
"""
|
| 23 |
+
Helper function for reproducible behavior to set the seed in `random`, `numpy`, `torch`.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
seed (`int`):
|
| 27 |
+
The seed to set.
|
| 28 |
+
deterministic (`bool`, *optional*, defaults to `False`):
|
| 29 |
+
Whether to use deterministic algorithms where available. Can slow down training.
|
| 30 |
+
"""
|
| 31 |
+
random.seed(seed)
|
| 32 |
+
np.random.seed(seed)
|
| 33 |
+
torch.manual_seed(seed)
|
| 34 |
+
torch.cuda.manual_seed_all(seed)
|
| 35 |
+
|
| 36 |
+
if deterministic:
|
| 37 |
+
torch.use_deterministic_algorithms(True)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def merge_dict_list(dict_list):
|
| 41 |
+
if len(dict_list) == 1:
|
| 42 |
+
return dict_list[0]
|
| 43 |
+
|
| 44 |
+
merged_dict = {}
|
| 45 |
+
for k, v in dict_list[0].items():
|
| 46 |
+
if isinstance(v, torch.Tensor):
|
| 47 |
+
if v.ndim == 0:
|
| 48 |
+
merged_dict[k] = torch.stack([d[k] for d in dict_list], dim=0)
|
| 49 |
+
else:
|
| 50 |
+
merged_dict[k] = torch.cat([d[k] for d in dict_list], dim=0)
|
| 51 |
+
else:
|
| 52 |
+
# for non-tensor values, we just copy the value from the first item
|
| 53 |
+
merged_dict[k] = v
|
| 54 |
+
return merged_dict
|
utils/scheduler.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-FileCopyrightText: Copyright (c) 2025 The Self-Forcing Authors. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
from abc import abstractmethod, ABC
|
| 17 |
+
import torch
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class SchedulerInterface(ABC):
|
| 21 |
+
"""
|
| 22 |
+
Base class for diffusion noise schedule.
|
| 23 |
+
"""
|
| 24 |
+
alphas_cumprod: torch.Tensor # [T], alphas for defining the noise schedule
|
| 25 |
+
|
| 26 |
+
@abstractmethod
|
| 27 |
+
def add_noise(
|
| 28 |
+
self, clean_latent: torch.Tensor,
|
| 29 |
+
noise: torch.Tensor, timestep: torch.Tensor
|
| 30 |
+
):
|
| 31 |
+
"""
|
| 32 |
+
Diffusion forward corruption process.
|
| 33 |
+
Input:
|
| 34 |
+
- clean_latent: the clean latent with shape [B, C, H, W]
|
| 35 |
+
- noise: the noise with shape [B, C, H, W]
|
| 36 |
+
- timestep: the timestep with shape [B]
|
| 37 |
+
Output: the corrupted latent with shape [B, C, H, W]
|
| 38 |
+
"""
|
| 39 |
+
pass
|
| 40 |
+
|
| 41 |
+
def convert_x0_to_noise(
|
| 42 |
+
self, x0: torch.Tensor, xt: torch.Tensor,
|
| 43 |
+
timestep: torch.Tensor
|
| 44 |
+
) -> torch.Tensor:
|
| 45 |
+
"""
|
| 46 |
+
Convert the diffusion network's x0 prediction to noise predidction.
|
| 47 |
+
x0: the predicted clean data with shape [B, C, H, W]
|
| 48 |
+
xt: the input noisy data with shape [B, C, H, W]
|
| 49 |
+
timestep: the timestep with shape [B]
|
| 50 |
+
|
| 51 |
+
noise = (xt-sqrt(alpha_t)*x0) / sqrt(beta_t) (eq 11 in https://arxiv.org/abs/2311.18828)
|
| 52 |
+
"""
|
| 53 |
+
# use higher precision for calculations
|
| 54 |
+
original_dtype = x0.dtype
|
| 55 |
+
x0, xt, alphas_cumprod = map(
|
| 56 |
+
lambda x: x.double().to(x0.device), [x0, xt,
|
| 57 |
+
self.alphas_cumprod]
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
alpha_prod_t = alphas_cumprod[timestep].reshape(-1, 1, 1, 1)
|
| 61 |
+
beta_prod_t = 1 - alpha_prod_t
|
| 62 |
+
|
| 63 |
+
noise_pred = (xt - alpha_prod_t **
|
| 64 |
+
(0.5) * x0) / beta_prod_t ** (0.5)
|
| 65 |
+
return noise_pred.to(original_dtype)
|
| 66 |
+
|
| 67 |
+
def convert_noise_to_x0(
|
| 68 |
+
self, noise: torch.Tensor, xt: torch.Tensor,
|
| 69 |
+
timestep: torch.Tensor
|
| 70 |
+
) -> torch.Tensor:
|
| 71 |
+
"""
|
| 72 |
+
Convert the diffusion network's noise prediction to x0 predidction.
|
| 73 |
+
noise: the predicted noise with shape [B, C, H, W]
|
| 74 |
+
xt: the input noisy data with shape [B, C, H, W]
|
| 75 |
+
timestep: the timestep with shape [B]
|
| 76 |
+
|
| 77 |
+
x0 = (x_t - sqrt(beta_t) * noise) / sqrt(alpha_t) (eq 11 in https://arxiv.org/abs/2311.18828)
|
| 78 |
+
"""
|
| 79 |
+
# use higher precision for calculations
|
| 80 |
+
original_dtype = noise.dtype
|
| 81 |
+
noise, xt, alphas_cumprod = map(
|
| 82 |
+
lambda x: x.double().to(noise.device), [noise, xt,
|
| 83 |
+
self.alphas_cumprod]
|
| 84 |
+
)
|
| 85 |
+
alpha_prod_t = alphas_cumprod[timestep].reshape(-1, 1, 1, 1)
|
| 86 |
+
beta_prod_t = 1 - alpha_prod_t
|
| 87 |
+
|
| 88 |
+
x0_pred = (xt - beta_prod_t **
|
| 89 |
+
(0.5) * noise) / alpha_prod_t ** (0.5)
|
| 90 |
+
return x0_pred.to(original_dtype)
|
| 91 |
+
|
| 92 |
+
def convert_velocity_to_x0(
|
| 93 |
+
self, velocity: torch.Tensor, xt: torch.Tensor,
|
| 94 |
+
timestep: torch.Tensor
|
| 95 |
+
) -> torch.Tensor:
|
| 96 |
+
"""
|
| 97 |
+
Convert the diffusion network's velocity prediction to x0 predidction.
|
| 98 |
+
velocity: the predicted noise with shape [B, C, H, W]
|
| 99 |
+
xt: the input noisy data with shape [B, C, H, W]
|
| 100 |
+
timestep: the timestep with shape [B]
|
| 101 |
+
|
| 102 |
+
v = sqrt(alpha_t) * noise - sqrt(beta_t) x0
|
| 103 |
+
noise = (xt-sqrt(alpha_t)*x0) / sqrt(beta_t)
|
| 104 |
+
given v, x_t, we have
|
| 105 |
+
x0 = sqrt(alpha_t) * x_t - sqrt(beta_t) * v
|
| 106 |
+
see derivations https://chatgpt.com/share/679fb6c8-3a30-8008-9b0e-d1ae892dac56
|
| 107 |
+
"""
|
| 108 |
+
# use higher precision for calculations
|
| 109 |
+
original_dtype = velocity.dtype
|
| 110 |
+
velocity, xt, alphas_cumprod = map(
|
| 111 |
+
lambda x: x.double().to(velocity.device), [velocity, xt,
|
| 112 |
+
self.alphas_cumprod]
|
| 113 |
+
)
|
| 114 |
+
alpha_prod_t = alphas_cumprod[timestep].reshape(-1, 1, 1, 1)
|
| 115 |
+
beta_prod_t = 1 - alpha_prod_t
|
| 116 |
+
|
| 117 |
+
x0_pred = (alpha_prod_t ** 0.5) * xt - (beta_prod_t ** 0.5) * velocity
|
| 118 |
+
return x0_pred.to(original_dtype)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
class FlowMatchScheduler():
|
| 122 |
+
|
| 123 |
+
def __init__(self, num_inference_steps=100, num_train_timesteps=1000, shift=3.0, sigma_max=1.0, sigma_min=0.003 / 1.002, inverse_timesteps=False, extra_one_step=False, reverse_sigmas=False):
|
| 124 |
+
self.num_train_timesteps = num_train_timesteps
|
| 125 |
+
self.shift = shift
|
| 126 |
+
self.sigma_max = sigma_max
|
| 127 |
+
self.sigma_min = sigma_min
|
| 128 |
+
self.inverse_timesteps = inverse_timesteps
|
| 129 |
+
self.extra_one_step = extra_one_step
|
| 130 |
+
self.reverse_sigmas = reverse_sigmas
|
| 131 |
+
self.set_timesteps(num_inference_steps)
|
| 132 |
+
|
| 133 |
+
def set_timesteps(self, num_inference_steps=100, denoising_strength=1.0, training=False):
|
| 134 |
+
sigma_start = self.sigma_min + \
|
| 135 |
+
(self.sigma_max - self.sigma_min) * denoising_strength
|
| 136 |
+
if self.extra_one_step:
|
| 137 |
+
self.sigmas = torch.linspace(
|
| 138 |
+
sigma_start, self.sigma_min, num_inference_steps + 1)[:-1]
|
| 139 |
+
else:
|
| 140 |
+
self.sigmas = torch.linspace(
|
| 141 |
+
sigma_start, self.sigma_min, num_inference_steps)
|
| 142 |
+
if self.inverse_timesteps:
|
| 143 |
+
self.sigmas = torch.flip(self.sigmas, dims=[0])
|
| 144 |
+
self.sigmas = self.shift * self.sigmas / \
|
| 145 |
+
(1 + (self.shift - 1) * self.sigmas)
|
| 146 |
+
if self.reverse_sigmas:
|
| 147 |
+
self.sigmas = 1 - self.sigmas
|
| 148 |
+
self.timesteps = self.sigmas * self.num_train_timesteps
|
| 149 |
+
if training:
|
| 150 |
+
x = self.timesteps
|
| 151 |
+
y = torch.exp(-2 * ((x - num_inference_steps / 2) /
|
| 152 |
+
num_inference_steps) ** 2)
|
| 153 |
+
y_shifted = y - y.min()
|
| 154 |
+
bsmntw_weighing = y_shifted * \
|
| 155 |
+
(num_inference_steps / y_shifted.sum())
|
| 156 |
+
self.linear_timesteps_weights = bsmntw_weighing
|
| 157 |
+
|
| 158 |
+
def step(self, model_output, timestep, sample, to_final=False):
|
| 159 |
+
if timestep.ndim == 2:
|
| 160 |
+
timestep = timestep.flatten(0, 1)
|
| 161 |
+
self.sigmas = self.sigmas.to(model_output.device)
|
| 162 |
+
self.timesteps = self.timesteps.to(model_output.device)
|
| 163 |
+
timestep_id = torch.argmin(
|
| 164 |
+
(self.timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(), dim=1)
|
| 165 |
+
sigma = self.sigmas[timestep_id].reshape(-1, 1, 1, 1)
|
| 166 |
+
if to_final or (timestep_id + 1 >= len(self.timesteps)).any():
|
| 167 |
+
sigma_ = 1 if (
|
| 168 |
+
self.inverse_timesteps or self.reverse_sigmas) else 0
|
| 169 |
+
else:
|
| 170 |
+
sigma_ = self.sigmas[timestep_id + 1].reshape(-1, 1, 1, 1)
|
| 171 |
+
prev_sample = sample + model_output * (sigma_ - sigma)
|
| 172 |
+
return prev_sample
|
| 173 |
+
|
| 174 |
+
def add_noise(self, original_samples, noise, timestep):
|
| 175 |
+
"""
|
| 176 |
+
Diffusion forward corruption process.
|
| 177 |
+
Input:
|
| 178 |
+
- clean_latent: the clean latent with shape [B*T, C, H, W]
|
| 179 |
+
- noise: the noise with shape [B*T, C, H, W]
|
| 180 |
+
- timestep: the timestep with shape [B*T]
|
| 181 |
+
Output: the corrupted latent with shape [B*T, C, H, W]
|
| 182 |
+
"""
|
| 183 |
+
if timestep.ndim == 2:
|
| 184 |
+
timestep = timestep.flatten(0, 1)
|
| 185 |
+
self.sigmas = self.sigmas.to(noise.device)
|
| 186 |
+
self.timesteps = self.timesteps.to(noise.device)
|
| 187 |
+
timestep_id = torch.argmin(
|
| 188 |
+
(self.timesteps.unsqueeze(0) - timestep.unsqueeze(1)).abs(), dim=1)
|
| 189 |
+
sigma = self.sigmas[timestep_id].reshape(-1, 1, 1, 1)
|
| 190 |
+
sample = (1 - sigma) * original_samples + sigma * noise
|
| 191 |
+
return sample.type_as(noise)
|
| 192 |
+
|
| 193 |
+
def training_target(self, sample, noise, timestep):
|
| 194 |
+
target = noise - sample
|
| 195 |
+
return target
|
| 196 |
+
|
| 197 |
+
def training_weight(self, timestep):
|
| 198 |
+
"""
|
| 199 |
+
Input:
|
| 200 |
+
- timestep: the timestep with shape [B*T]
|
| 201 |
+
Output: the corresponding weighting [B*T]
|
| 202 |
+
"""
|
| 203 |
+
if timestep.ndim == 2:
|
| 204 |
+
timestep = timestep.flatten(0, 1)
|
| 205 |
+
self.linear_timesteps_weights = self.linear_timesteps_weights.to(timestep.device)
|
| 206 |
+
timestep_id = torch.argmin(
|
| 207 |
+
(self.timesteps.unsqueeze(1) - timestep.unsqueeze(0)).abs(), dim=0)
|
| 208 |
+
weights = self.linear_timesteps_weights[timestep_id]
|
| 209 |
+
return weights
|
wan/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Trimmed package initializer for the CMD Hugging Face Space.
|
| 2 |
+
# The upstream nv-tlabs/cmd package initializers eagerly import training-only
|
| 3 |
+
# and multi-GPU modules that this inference-only demo does not need. See NOTICE
|
| 4 |
+
# and LICENSE for the upstream licenses that continue to apply.
|
wan/modules/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Trimmed package initializer for the CMD Hugging Face Space.
|
| 2 |
+
# The upstream nv-tlabs/cmd package initializers eagerly import training-only
|
| 3 |
+
# and multi-GPU modules that this inference-only demo does not need. See NOTICE
|
| 4 |
+
# and LICENSE for the upstream licenses that continue to apply.
|
wan/modules/vae.py
ADDED
|
@@ -0,0 +1,697 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
import logging
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
import torch.cuda.amp as amp
|
| 20 |
+
import torch.nn as nn
|
| 21 |
+
import torch.nn.functional as F
|
| 22 |
+
from einops import rearrange
|
| 23 |
+
|
| 24 |
+
__all__ = [
|
| 25 |
+
'WanVAE',
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
CACHE_T = 2
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class CausalConv3d(nn.Conv3d):
|
| 32 |
+
"""
|
| 33 |
+
Causal 3d convolusion.
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
def __init__(self, *args, **kwargs):
|
| 37 |
+
super().__init__(*args, **kwargs)
|
| 38 |
+
self._padding = (self.padding[2], self.padding[2], self.padding[1],
|
| 39 |
+
self.padding[1], 2 * self.padding[0], 0)
|
| 40 |
+
self.padding = (0, 0, 0)
|
| 41 |
+
|
| 42 |
+
def forward(self, x, cache_x=None):
|
| 43 |
+
padding = list(self._padding)
|
| 44 |
+
if cache_x is not None and self._padding[4] > 0:
|
| 45 |
+
cache_x = cache_x.to(x.device)
|
| 46 |
+
x = torch.cat([cache_x, x], dim=2)
|
| 47 |
+
padding[4] -= cache_x.shape[2]
|
| 48 |
+
x = F.pad(x, padding)
|
| 49 |
+
|
| 50 |
+
return super().forward(x)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class RMS_norm(nn.Module):
|
| 54 |
+
|
| 55 |
+
def __init__(self, dim, channel_first=True, images=True, bias=False):
|
| 56 |
+
super().__init__()
|
| 57 |
+
broadcastable_dims = (1, 1, 1) if not images else (1, 1)
|
| 58 |
+
shape = (dim, *broadcastable_dims) if channel_first else (dim,)
|
| 59 |
+
|
| 60 |
+
self.channel_first = channel_first
|
| 61 |
+
self.scale = dim**0.5
|
| 62 |
+
self.gamma = nn.Parameter(torch.ones(shape))
|
| 63 |
+
self.bias = nn.Parameter(torch.zeros(shape)) if bias else 0.
|
| 64 |
+
|
| 65 |
+
def forward(self, x):
|
| 66 |
+
return F.normalize(
|
| 67 |
+
x, dim=(1 if self.channel_first else
|
| 68 |
+
-1)) * self.scale * self.gamma + self.bias
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class Upsample(nn.Upsample):
|
| 72 |
+
|
| 73 |
+
def forward(self, x):
|
| 74 |
+
"""
|
| 75 |
+
Fix bfloat16 support for nearest neighbor interpolation.
|
| 76 |
+
"""
|
| 77 |
+
return super().forward(x.float()).type_as(x)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class Resample(nn.Module):
|
| 81 |
+
|
| 82 |
+
def __init__(self, dim, mode):
|
| 83 |
+
assert mode in ('none', 'upsample2d', 'upsample3d', 'downsample2d',
|
| 84 |
+
'downsample3d')
|
| 85 |
+
super().__init__()
|
| 86 |
+
self.dim = dim
|
| 87 |
+
self.mode = mode
|
| 88 |
+
|
| 89 |
+
# layers
|
| 90 |
+
if mode == 'upsample2d':
|
| 91 |
+
self.resample = nn.Sequential(
|
| 92 |
+
Upsample(scale_factor=(2., 2.), mode='nearest'),
|
| 93 |
+
nn.Conv2d(dim, dim // 2, 3, padding=1))
|
| 94 |
+
elif mode == 'upsample3d':
|
| 95 |
+
self.resample = nn.Sequential(
|
| 96 |
+
Upsample(scale_factor=(2., 2.), mode='nearest'),
|
| 97 |
+
nn.Conv2d(dim, dim // 2, 3, padding=1))
|
| 98 |
+
self.time_conv = CausalConv3d(
|
| 99 |
+
dim, dim * 2, (3, 1, 1), padding=(1, 0, 0))
|
| 100 |
+
|
| 101 |
+
elif mode == 'downsample2d':
|
| 102 |
+
self.resample = nn.Sequential(
|
| 103 |
+
nn.ZeroPad2d((0, 1, 0, 1)),
|
| 104 |
+
nn.Conv2d(dim, dim, 3, stride=(2, 2)))
|
| 105 |
+
elif mode == 'downsample3d':
|
| 106 |
+
self.resample = nn.Sequential(
|
| 107 |
+
nn.ZeroPad2d((0, 1, 0, 1)),
|
| 108 |
+
nn.Conv2d(dim, dim, 3, stride=(2, 2)))
|
| 109 |
+
self.time_conv = CausalConv3d(
|
| 110 |
+
dim, dim, (3, 1, 1), stride=(2, 1, 1), padding=(0, 0, 0))
|
| 111 |
+
|
| 112 |
+
else:
|
| 113 |
+
self.resample = nn.Identity()
|
| 114 |
+
|
| 115 |
+
def forward(self, x, feat_cache=None, feat_idx=[0]):
|
| 116 |
+
b, c, t, h, w = x.size()
|
| 117 |
+
if self.mode == 'upsample3d':
|
| 118 |
+
if feat_cache is not None:
|
| 119 |
+
idx = feat_idx[0]
|
| 120 |
+
if feat_cache[idx] is None:
|
| 121 |
+
feat_cache[idx] = 'Rep'
|
| 122 |
+
feat_idx[0] += 1
|
| 123 |
+
else:
|
| 124 |
+
|
| 125 |
+
cache_x = x[:, :, -CACHE_T:, :, :].clone()
|
| 126 |
+
if cache_x.shape[2] < 2 and feat_cache[
|
| 127 |
+
idx] is not None and feat_cache[idx] != 'Rep':
|
| 128 |
+
# cache last frame of last two chunk
|
| 129 |
+
cache_x = torch.cat([
|
| 130 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
|
| 131 |
+
cache_x.device), cache_x
|
| 132 |
+
],
|
| 133 |
+
dim=2)
|
| 134 |
+
if cache_x.shape[2] < 2 and feat_cache[
|
| 135 |
+
idx] is not None and feat_cache[idx] == 'Rep':
|
| 136 |
+
cache_x = torch.cat([
|
| 137 |
+
torch.zeros_like(cache_x).to(cache_x.device),
|
| 138 |
+
cache_x
|
| 139 |
+
],
|
| 140 |
+
dim=2)
|
| 141 |
+
if feat_cache[idx] == 'Rep':
|
| 142 |
+
x = self.time_conv(x)
|
| 143 |
+
else:
|
| 144 |
+
x = self.time_conv(x, feat_cache[idx])
|
| 145 |
+
feat_cache[idx] = cache_x
|
| 146 |
+
feat_idx[0] += 1
|
| 147 |
+
|
| 148 |
+
x = x.reshape(b, 2, c, t, h, w)
|
| 149 |
+
x = torch.stack((x[:, 0, :, :, :, :], x[:, 1, :, :, :, :]),
|
| 150 |
+
3)
|
| 151 |
+
x = x.reshape(b, c, t * 2, h, w)
|
| 152 |
+
t = x.shape[2]
|
| 153 |
+
x = rearrange(x, 'b c t h w -> (b t) c h w')
|
| 154 |
+
x = self.resample(x)
|
| 155 |
+
x = rearrange(x, '(b t) c h w -> b c t h w', t=t)
|
| 156 |
+
|
| 157 |
+
if self.mode == 'downsample3d':
|
| 158 |
+
if feat_cache is not None:
|
| 159 |
+
idx = feat_idx[0]
|
| 160 |
+
if feat_cache[idx] is None:
|
| 161 |
+
feat_cache[idx] = x.clone()
|
| 162 |
+
feat_idx[0] += 1
|
| 163 |
+
else:
|
| 164 |
+
|
| 165 |
+
cache_x = x[:, :, -1:, :, :].clone()
|
| 166 |
+
# if cache_x.shape[2] < 2 and feat_cache[idx] is not None and feat_cache[idx]!='Rep':
|
| 167 |
+
# # cache last frame of last two chunk
|
| 168 |
+
# cache_x = torch.cat([feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device), cache_x], dim=2)
|
| 169 |
+
|
| 170 |
+
x = self.time_conv(
|
| 171 |
+
torch.cat([feat_cache[idx][:, :, -1:, :, :], x], 2))
|
| 172 |
+
feat_cache[idx] = cache_x
|
| 173 |
+
feat_idx[0] += 1
|
| 174 |
+
return x
|
| 175 |
+
|
| 176 |
+
def init_weight(self, conv):
|
| 177 |
+
conv_weight = conv.weight
|
| 178 |
+
nn.init.zeros_(conv_weight)
|
| 179 |
+
c1, c2, t, h, w = conv_weight.size()
|
| 180 |
+
one_matrix = torch.eye(c1, c2)
|
| 181 |
+
init_matrix = one_matrix
|
| 182 |
+
nn.init.zeros_(conv_weight)
|
| 183 |
+
# conv_weight.data[:,:,-1,1,1] = init_matrix * 0.5
|
| 184 |
+
conv_weight.data[:, :, 1, 0, 0] = init_matrix # * 0.5
|
| 185 |
+
conv.weight.data.copy_(conv_weight)
|
| 186 |
+
nn.init.zeros_(conv.bias.data)
|
| 187 |
+
|
| 188 |
+
def init_weight2(self, conv):
|
| 189 |
+
conv_weight = conv.weight.data
|
| 190 |
+
nn.init.zeros_(conv_weight)
|
| 191 |
+
c1, c2, t, h, w = conv_weight.size()
|
| 192 |
+
init_matrix = torch.eye(c1 // 2, c2)
|
| 193 |
+
# init_matrix = repeat(init_matrix, 'o ... -> (o 2) ...').permute(1,0,2).contiguous().reshape(c1,c2)
|
| 194 |
+
conv_weight[:c1 // 2, :, -1, 0, 0] = init_matrix
|
| 195 |
+
conv_weight[c1 // 2:, :, -1, 0, 0] = init_matrix
|
| 196 |
+
conv.weight.data.copy_(conv_weight)
|
| 197 |
+
nn.init.zeros_(conv.bias.data)
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
class ResidualBlock(nn.Module):
|
| 201 |
+
|
| 202 |
+
def __init__(self, in_dim, out_dim, dropout=0.0):
|
| 203 |
+
super().__init__()
|
| 204 |
+
self.in_dim = in_dim
|
| 205 |
+
self.out_dim = out_dim
|
| 206 |
+
|
| 207 |
+
# layers
|
| 208 |
+
self.residual = nn.Sequential(
|
| 209 |
+
RMS_norm(in_dim, images=False), nn.SiLU(),
|
| 210 |
+
CausalConv3d(in_dim, out_dim, 3, padding=1),
|
| 211 |
+
RMS_norm(out_dim, images=False), nn.SiLU(), nn.Dropout(dropout),
|
| 212 |
+
CausalConv3d(out_dim, out_dim, 3, padding=1))
|
| 213 |
+
self.shortcut = CausalConv3d(in_dim, out_dim, 1) \
|
| 214 |
+
if in_dim != out_dim else nn.Identity()
|
| 215 |
+
|
| 216 |
+
def forward(self, x, feat_cache=None, feat_idx=[0]):
|
| 217 |
+
h = self.shortcut(x)
|
| 218 |
+
for layer in self.residual:
|
| 219 |
+
if isinstance(layer, CausalConv3d) and feat_cache is not None:
|
| 220 |
+
idx = feat_idx[0]
|
| 221 |
+
cache_x = x[:, :, -CACHE_T:, :, :].clone()
|
| 222 |
+
if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
|
| 223 |
+
# cache last frame of last two chunk
|
| 224 |
+
cache_x = torch.cat([
|
| 225 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
|
| 226 |
+
cache_x.device), cache_x
|
| 227 |
+
],
|
| 228 |
+
dim=2)
|
| 229 |
+
x = layer(x, feat_cache[idx])
|
| 230 |
+
feat_cache[idx] = cache_x
|
| 231 |
+
feat_idx[0] += 1
|
| 232 |
+
else:
|
| 233 |
+
x = layer(x)
|
| 234 |
+
return x + h
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
class AttentionBlock(nn.Module):
|
| 238 |
+
"""
|
| 239 |
+
Causal self-attention with a single head.
|
| 240 |
+
"""
|
| 241 |
+
|
| 242 |
+
def __init__(self, dim):
|
| 243 |
+
super().__init__()
|
| 244 |
+
self.dim = dim
|
| 245 |
+
|
| 246 |
+
# layers
|
| 247 |
+
self.norm = RMS_norm(dim)
|
| 248 |
+
self.to_qkv = nn.Conv2d(dim, dim * 3, 1)
|
| 249 |
+
self.proj = nn.Conv2d(dim, dim, 1)
|
| 250 |
+
|
| 251 |
+
# zero out the last layer params
|
| 252 |
+
nn.init.zeros_(self.proj.weight)
|
| 253 |
+
|
| 254 |
+
def forward(self, x):
|
| 255 |
+
identity = x
|
| 256 |
+
b, c, t, h, w = x.size()
|
| 257 |
+
x = rearrange(x, 'b c t h w -> (b t) c h w')
|
| 258 |
+
x = self.norm(x)
|
| 259 |
+
# compute query, key, value
|
| 260 |
+
q, k, v = self.to_qkv(x).reshape(b * t, 1, c * 3,
|
| 261 |
+
-1).permute(0, 1, 3,
|
| 262 |
+
2).contiguous().chunk(
|
| 263 |
+
3, dim=-1)
|
| 264 |
+
|
| 265 |
+
# apply attention
|
| 266 |
+
x = F.scaled_dot_product_attention(
|
| 267 |
+
q,
|
| 268 |
+
k,
|
| 269 |
+
v,
|
| 270 |
+
)
|
| 271 |
+
x = x.squeeze(1).permute(0, 2, 1).reshape(b * t, c, h, w)
|
| 272 |
+
|
| 273 |
+
# output
|
| 274 |
+
x = self.proj(x)
|
| 275 |
+
x = rearrange(x, '(b t) c h w-> b c t h w', t=t)
|
| 276 |
+
return x + identity
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
class Encoder3d(nn.Module):
|
| 280 |
+
|
| 281 |
+
def __init__(self,
|
| 282 |
+
dim=128,
|
| 283 |
+
z_dim=4,
|
| 284 |
+
dim_mult=[1, 2, 4, 4],
|
| 285 |
+
num_res_blocks=2,
|
| 286 |
+
attn_scales=[],
|
| 287 |
+
temperal_downsample=[True, True, False],
|
| 288 |
+
dropout=0.0):
|
| 289 |
+
super().__init__()
|
| 290 |
+
self.dim = dim
|
| 291 |
+
self.z_dim = z_dim
|
| 292 |
+
self.dim_mult = dim_mult
|
| 293 |
+
self.num_res_blocks = num_res_blocks
|
| 294 |
+
self.attn_scales = attn_scales
|
| 295 |
+
self.temperal_downsample = temperal_downsample
|
| 296 |
+
|
| 297 |
+
# dimensions
|
| 298 |
+
dims = [dim * u for u in [1] + dim_mult]
|
| 299 |
+
scale = 1.0
|
| 300 |
+
|
| 301 |
+
# init block
|
| 302 |
+
self.conv1 = CausalConv3d(3, dims[0], 3, padding=1)
|
| 303 |
+
|
| 304 |
+
# downsample blocks
|
| 305 |
+
downsamples = []
|
| 306 |
+
for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])):
|
| 307 |
+
# residual (+attention) blocks
|
| 308 |
+
for _ in range(num_res_blocks):
|
| 309 |
+
downsamples.append(ResidualBlock(in_dim, out_dim, dropout))
|
| 310 |
+
if scale in attn_scales:
|
| 311 |
+
downsamples.append(AttentionBlock(out_dim))
|
| 312 |
+
in_dim = out_dim
|
| 313 |
+
|
| 314 |
+
# downsample block
|
| 315 |
+
if i != len(dim_mult) - 1:
|
| 316 |
+
mode = 'downsample3d' if temperal_downsample[
|
| 317 |
+
i] else 'downsample2d'
|
| 318 |
+
downsamples.append(Resample(out_dim, mode=mode))
|
| 319 |
+
scale /= 2.0
|
| 320 |
+
self.downsamples = nn.Sequential(*downsamples)
|
| 321 |
+
|
| 322 |
+
# middle blocks
|
| 323 |
+
self.middle = nn.Sequential(
|
| 324 |
+
ResidualBlock(out_dim, out_dim, dropout), AttentionBlock(out_dim),
|
| 325 |
+
ResidualBlock(out_dim, out_dim, dropout))
|
| 326 |
+
|
| 327 |
+
# output blocks
|
| 328 |
+
self.head = nn.Sequential(
|
| 329 |
+
RMS_norm(out_dim, images=False), nn.SiLU(),
|
| 330 |
+
CausalConv3d(out_dim, z_dim, 3, padding=1))
|
| 331 |
+
|
| 332 |
+
def forward(self, x, feat_cache=None, feat_idx=[0]):
|
| 333 |
+
if feat_cache is not None:
|
| 334 |
+
idx = feat_idx[0]
|
| 335 |
+
cache_x = x[:, :, -CACHE_T:, :, :].clone()
|
| 336 |
+
if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
|
| 337 |
+
# cache last frame of last two chunk
|
| 338 |
+
cache_x = torch.cat([
|
| 339 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
|
| 340 |
+
cache_x.device), cache_x
|
| 341 |
+
],
|
| 342 |
+
dim=2)
|
| 343 |
+
x = self.conv1(x, feat_cache[idx])
|
| 344 |
+
feat_cache[idx] = cache_x
|
| 345 |
+
feat_idx[0] += 1
|
| 346 |
+
else:
|
| 347 |
+
x = self.conv1(x)
|
| 348 |
+
|
| 349 |
+
# downsamples
|
| 350 |
+
for layer in self.downsamples:
|
| 351 |
+
if feat_cache is not None:
|
| 352 |
+
x = layer(x, feat_cache, feat_idx)
|
| 353 |
+
else:
|
| 354 |
+
x = layer(x)
|
| 355 |
+
|
| 356 |
+
# middle
|
| 357 |
+
for layer in self.middle:
|
| 358 |
+
if isinstance(layer, ResidualBlock) and feat_cache is not None:
|
| 359 |
+
x = layer(x, feat_cache, feat_idx)
|
| 360 |
+
else:
|
| 361 |
+
x = layer(x)
|
| 362 |
+
|
| 363 |
+
# head
|
| 364 |
+
for layer in self.head:
|
| 365 |
+
if isinstance(layer, CausalConv3d) and feat_cache is not None:
|
| 366 |
+
idx = feat_idx[0]
|
| 367 |
+
cache_x = x[:, :, -CACHE_T:, :, :].clone()
|
| 368 |
+
if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
|
| 369 |
+
# cache last frame of last two chunk
|
| 370 |
+
cache_x = torch.cat([
|
| 371 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
|
| 372 |
+
cache_x.device), cache_x
|
| 373 |
+
],
|
| 374 |
+
dim=2)
|
| 375 |
+
x = layer(x, feat_cache[idx])
|
| 376 |
+
feat_cache[idx] = cache_x
|
| 377 |
+
feat_idx[0] += 1
|
| 378 |
+
else:
|
| 379 |
+
x = layer(x)
|
| 380 |
+
return x
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
class Decoder3d(nn.Module):
|
| 384 |
+
|
| 385 |
+
def __init__(self,
|
| 386 |
+
dim=128,
|
| 387 |
+
z_dim=4,
|
| 388 |
+
dim_mult=[1, 2, 4, 4],
|
| 389 |
+
num_res_blocks=2,
|
| 390 |
+
attn_scales=[],
|
| 391 |
+
temperal_upsample=[False, True, True],
|
| 392 |
+
dropout=0.0):
|
| 393 |
+
super().__init__()
|
| 394 |
+
self.dim = dim
|
| 395 |
+
self.z_dim = z_dim
|
| 396 |
+
self.dim_mult = dim_mult
|
| 397 |
+
self.num_res_blocks = num_res_blocks
|
| 398 |
+
self.attn_scales = attn_scales
|
| 399 |
+
self.temperal_upsample = temperal_upsample
|
| 400 |
+
|
| 401 |
+
# dimensions
|
| 402 |
+
dims = [dim * u for u in [dim_mult[-1]] + dim_mult[::-1]]
|
| 403 |
+
scale = 1.0 / 2**(len(dim_mult) - 2)
|
| 404 |
+
|
| 405 |
+
# init block
|
| 406 |
+
self.conv1 = CausalConv3d(z_dim, dims[0], 3, padding=1)
|
| 407 |
+
|
| 408 |
+
# middle blocks
|
| 409 |
+
self.middle = nn.Sequential(
|
| 410 |
+
ResidualBlock(dims[0], dims[0], dropout), AttentionBlock(dims[0]),
|
| 411 |
+
ResidualBlock(dims[0], dims[0], dropout))
|
| 412 |
+
|
| 413 |
+
# upsample blocks
|
| 414 |
+
upsamples = []
|
| 415 |
+
for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])):
|
| 416 |
+
# residual (+attention) blocks
|
| 417 |
+
if i == 1 or i == 2 or i == 3:
|
| 418 |
+
in_dim = in_dim // 2
|
| 419 |
+
for _ in range(num_res_blocks + 1):
|
| 420 |
+
upsamples.append(ResidualBlock(in_dim, out_dim, dropout))
|
| 421 |
+
if scale in attn_scales:
|
| 422 |
+
upsamples.append(AttentionBlock(out_dim))
|
| 423 |
+
in_dim = out_dim
|
| 424 |
+
|
| 425 |
+
# upsample block
|
| 426 |
+
if i != len(dim_mult) - 1:
|
| 427 |
+
mode = 'upsample3d' if temperal_upsample[i] else 'upsample2d'
|
| 428 |
+
upsamples.append(Resample(out_dim, mode=mode))
|
| 429 |
+
scale *= 2.0
|
| 430 |
+
self.upsamples = nn.Sequential(*upsamples)
|
| 431 |
+
|
| 432 |
+
# output blocks
|
| 433 |
+
self.head = nn.Sequential(
|
| 434 |
+
RMS_norm(out_dim, images=False), nn.SiLU(),
|
| 435 |
+
CausalConv3d(out_dim, 3, 3, padding=1))
|
| 436 |
+
|
| 437 |
+
def forward(self, x, feat_cache=None, feat_idx=[0]):
|
| 438 |
+
# conv1
|
| 439 |
+
if feat_cache is not None:
|
| 440 |
+
idx = feat_idx[0]
|
| 441 |
+
cache_x = x[:, :, -CACHE_T:, :, :].clone()
|
| 442 |
+
if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
|
| 443 |
+
# cache last frame of last two chunk
|
| 444 |
+
cache_x = torch.cat([
|
| 445 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
|
| 446 |
+
cache_x.device), cache_x
|
| 447 |
+
],
|
| 448 |
+
dim=2)
|
| 449 |
+
x = self.conv1(x, feat_cache[idx])
|
| 450 |
+
feat_cache[idx] = cache_x
|
| 451 |
+
feat_idx[0] += 1
|
| 452 |
+
else:
|
| 453 |
+
x = self.conv1(x)
|
| 454 |
+
|
| 455 |
+
# middle
|
| 456 |
+
for layer in self.middle:
|
| 457 |
+
if isinstance(layer, ResidualBlock) and feat_cache is not None:
|
| 458 |
+
x = layer(x, feat_cache, feat_idx)
|
| 459 |
+
else:
|
| 460 |
+
x = layer(x)
|
| 461 |
+
|
| 462 |
+
# upsamples
|
| 463 |
+
for layer in self.upsamples:
|
| 464 |
+
if feat_cache is not None:
|
| 465 |
+
x = layer(x, feat_cache, feat_idx)
|
| 466 |
+
else:
|
| 467 |
+
x = layer(x)
|
| 468 |
+
|
| 469 |
+
# head
|
| 470 |
+
for layer in self.head:
|
| 471 |
+
if isinstance(layer, CausalConv3d) and feat_cache is not None:
|
| 472 |
+
idx = feat_idx[0]
|
| 473 |
+
cache_x = x[:, :, -CACHE_T:, :, :].clone()
|
| 474 |
+
if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
|
| 475 |
+
# cache last frame of last two chunk
|
| 476 |
+
cache_x = torch.cat([
|
| 477 |
+
feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
|
| 478 |
+
cache_x.device), cache_x
|
| 479 |
+
],
|
| 480 |
+
dim=2)
|
| 481 |
+
x = layer(x, feat_cache[idx])
|
| 482 |
+
feat_cache[idx] = cache_x
|
| 483 |
+
feat_idx[0] += 1
|
| 484 |
+
else:
|
| 485 |
+
x = layer(x)
|
| 486 |
+
return x
|
| 487 |
+
|
| 488 |
+
|
| 489 |
+
def count_conv3d(model):
|
| 490 |
+
count = 0
|
| 491 |
+
for m in model.modules():
|
| 492 |
+
if isinstance(m, CausalConv3d):
|
| 493 |
+
count += 1
|
| 494 |
+
return count
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
class WanVAE_(nn.Module):
|
| 498 |
+
|
| 499 |
+
def __init__(self,
|
| 500 |
+
dim=128,
|
| 501 |
+
z_dim=4,
|
| 502 |
+
dim_mult=[1, 2, 4, 4],
|
| 503 |
+
num_res_blocks=2,
|
| 504 |
+
attn_scales=[],
|
| 505 |
+
temperal_downsample=[True, True, False],
|
| 506 |
+
dropout=0.0):
|
| 507 |
+
super().__init__()
|
| 508 |
+
self.dim = dim
|
| 509 |
+
self.z_dim = z_dim
|
| 510 |
+
self.dim_mult = dim_mult
|
| 511 |
+
self.num_res_blocks = num_res_blocks
|
| 512 |
+
self.attn_scales = attn_scales
|
| 513 |
+
self.temperal_downsample = temperal_downsample
|
| 514 |
+
self.temperal_upsample = temperal_downsample[::-1]
|
| 515 |
+
|
| 516 |
+
# modules
|
| 517 |
+
self.encoder = Encoder3d(dim, z_dim * 2, dim_mult, num_res_blocks,
|
| 518 |
+
attn_scales, self.temperal_downsample, dropout)
|
| 519 |
+
self.conv1 = CausalConv3d(z_dim * 2, z_dim * 2, 1)
|
| 520 |
+
self.conv2 = CausalConv3d(z_dim, z_dim, 1)
|
| 521 |
+
self.decoder = Decoder3d(dim, z_dim, dim_mult, num_res_blocks,
|
| 522 |
+
attn_scales, self.temperal_upsample, dropout)
|
| 523 |
+
self.clear_cache()
|
| 524 |
+
|
| 525 |
+
def forward(self, x):
|
| 526 |
+
mu, log_var = self.encode(x)
|
| 527 |
+
z = self.reparameterize(mu, log_var)
|
| 528 |
+
x_recon = self.decode(z)
|
| 529 |
+
return x_recon, mu, log_var
|
| 530 |
+
|
| 531 |
+
def encode(self, x, scale):
|
| 532 |
+
self.clear_cache()
|
| 533 |
+
# cache
|
| 534 |
+
t = x.shape[2]
|
| 535 |
+
iter_ = 1 + (t - 1) // 4
|
| 536 |
+
# 对encode输入的x,按时间拆分为1、4、4、4....
|
| 537 |
+
for i in range(iter_):
|
| 538 |
+
self._enc_conv_idx = [0]
|
| 539 |
+
if i == 0:
|
| 540 |
+
out = self.encoder(
|
| 541 |
+
x[:, :, :1, :, :],
|
| 542 |
+
feat_cache=self._enc_feat_map,
|
| 543 |
+
feat_idx=self._enc_conv_idx)
|
| 544 |
+
else:
|
| 545 |
+
out_ = self.encoder(
|
| 546 |
+
x[:, :, 1 + 4 * (i - 1):1 + 4 * i, :, :],
|
| 547 |
+
feat_cache=self._enc_feat_map,
|
| 548 |
+
feat_idx=self._enc_conv_idx)
|
| 549 |
+
out = torch.cat([out, out_], 2)
|
| 550 |
+
mu, log_var = self.conv1(out).chunk(2, dim=1)
|
| 551 |
+
if isinstance(scale[0], torch.Tensor):
|
| 552 |
+
mu = (mu - scale[0].view(1, self.z_dim, 1, 1, 1)) * scale[1].view(
|
| 553 |
+
1, self.z_dim, 1, 1, 1)
|
| 554 |
+
else:
|
| 555 |
+
mu = (mu - scale[0]) * scale[1]
|
| 556 |
+
self.clear_cache()
|
| 557 |
+
return mu
|
| 558 |
+
|
| 559 |
+
def decode(self, z, scale):
|
| 560 |
+
self.clear_cache()
|
| 561 |
+
# z: [b,c,t,h,w]
|
| 562 |
+
if isinstance(scale[0], torch.Tensor):
|
| 563 |
+
z = z / scale[1].view(1, self.z_dim, 1, 1, 1) + scale[0].view(
|
| 564 |
+
1, self.z_dim, 1, 1, 1)
|
| 565 |
+
else:
|
| 566 |
+
z = z / scale[1] + scale[0]
|
| 567 |
+
iter_ = z.shape[2]
|
| 568 |
+
x = self.conv2(z)
|
| 569 |
+
for i in range(iter_):
|
| 570 |
+
self._conv_idx = [0]
|
| 571 |
+
if i == 0:
|
| 572 |
+
out = self.decoder(
|
| 573 |
+
x[:, :, i:i + 1, :, :],
|
| 574 |
+
feat_cache=self._feat_map,
|
| 575 |
+
feat_idx=self._conv_idx)
|
| 576 |
+
else:
|
| 577 |
+
out_ = self.decoder(
|
| 578 |
+
x[:, :, i:i + 1, :, :],
|
| 579 |
+
feat_cache=self._feat_map,
|
| 580 |
+
feat_idx=self._conv_idx)
|
| 581 |
+
out = torch.cat([out, out_], 2)
|
| 582 |
+
self.clear_cache()
|
| 583 |
+
return out
|
| 584 |
+
|
| 585 |
+
def cached_decode(self, z, scale):
|
| 586 |
+
# z: [b,c,t,h,w]
|
| 587 |
+
if isinstance(scale[0], torch.Tensor):
|
| 588 |
+
z = z / scale[1].view(1, self.z_dim, 1, 1, 1) + scale[0].view(
|
| 589 |
+
1, self.z_dim, 1, 1, 1)
|
| 590 |
+
else:
|
| 591 |
+
z = z / scale[1] + scale[0]
|
| 592 |
+
iter_ = z.shape[2]
|
| 593 |
+
x = self.conv2(z)
|
| 594 |
+
for i in range(iter_):
|
| 595 |
+
self._conv_idx = [0]
|
| 596 |
+
if i == 0:
|
| 597 |
+
out = self.decoder(
|
| 598 |
+
x[:, :, i:i + 1, :, :],
|
| 599 |
+
feat_cache=self._feat_map,
|
| 600 |
+
feat_idx=self._conv_idx)
|
| 601 |
+
else:
|
| 602 |
+
out_ = self.decoder(
|
| 603 |
+
x[:, :, i:i + 1, :, :],
|
| 604 |
+
feat_cache=self._feat_map,
|
| 605 |
+
feat_idx=self._conv_idx)
|
| 606 |
+
out = torch.cat([out, out_], 2)
|
| 607 |
+
return out
|
| 608 |
+
|
| 609 |
+
def sample(self, imgs, deterministic=False):
|
| 610 |
+
mu, log_var = self.encode(imgs)
|
| 611 |
+
if deterministic:
|
| 612 |
+
return mu
|
| 613 |
+
std = torch.exp(0.5 * log_var.clamp(-30.0, 20.0))
|
| 614 |
+
return mu + std * torch.randn_like(std)
|
| 615 |
+
|
| 616 |
+
def clear_cache(self):
|
| 617 |
+
self._conv_num = count_conv3d(self.decoder)
|
| 618 |
+
self._conv_idx = [0]
|
| 619 |
+
self._feat_map = [None] * self._conv_num
|
| 620 |
+
# cache encode
|
| 621 |
+
self._enc_conv_num = count_conv3d(self.encoder)
|
| 622 |
+
self._enc_conv_idx = [0]
|
| 623 |
+
self._enc_feat_map = [None] * self._enc_conv_num
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
def _video_vae(pretrained_path=None, z_dim=None, device='cpu', **kwargs):
|
| 627 |
+
"""
|
| 628 |
+
Autoencoder3d adapted from Stable Diffusion 1.x, 2.x and XL.
|
| 629 |
+
"""
|
| 630 |
+
# params
|
| 631 |
+
cfg = dict(
|
| 632 |
+
dim=96,
|
| 633 |
+
z_dim=z_dim,
|
| 634 |
+
dim_mult=[1, 2, 4, 4],
|
| 635 |
+
num_res_blocks=2,
|
| 636 |
+
attn_scales=[],
|
| 637 |
+
temperal_downsample=[False, True, True],
|
| 638 |
+
dropout=0.0)
|
| 639 |
+
cfg.update(**kwargs)
|
| 640 |
+
|
| 641 |
+
# init model
|
| 642 |
+
with torch.device('meta'):
|
| 643 |
+
model = WanVAE_(**cfg)
|
| 644 |
+
|
| 645 |
+
# load checkpoint
|
| 646 |
+
logging.info(f'loading {pretrained_path}')
|
| 647 |
+
model.load_state_dict(
|
| 648 |
+
torch.load(pretrained_path, map_location=device), assign=True)
|
| 649 |
+
|
| 650 |
+
return model
|
| 651 |
+
|
| 652 |
+
|
| 653 |
+
class WanVAE:
|
| 654 |
+
|
| 655 |
+
def __init__(self,
|
| 656 |
+
z_dim=16,
|
| 657 |
+
vae_pth='cache/vae_step_411000.pth',
|
| 658 |
+
dtype=torch.float,
|
| 659 |
+
device="cuda"):
|
| 660 |
+
self.dtype = dtype
|
| 661 |
+
self.device = device
|
| 662 |
+
|
| 663 |
+
mean = [
|
| 664 |
+
-0.7571, -0.7089, -0.9113, 0.1075, -0.1745, 0.9653, -0.1517, 1.5508,
|
| 665 |
+
0.4134, -0.0715, 0.5517, -0.3632, -0.1922, -0.9497, 0.2503, -0.2921
|
| 666 |
+
]
|
| 667 |
+
std = [
|
| 668 |
+
2.8184, 1.4541, 2.3275, 2.6558, 1.2196, 1.7708, 2.6052, 2.0743,
|
| 669 |
+
3.2687, 2.1526, 2.8652, 1.5579, 1.6382, 1.1253, 2.8251, 1.9160
|
| 670 |
+
]
|
| 671 |
+
self.mean = torch.tensor(mean, dtype=dtype, device=device)
|
| 672 |
+
self.std = torch.tensor(std, dtype=dtype, device=device)
|
| 673 |
+
self.scale = [self.mean, 1.0 / self.std]
|
| 674 |
+
|
| 675 |
+
# init model
|
| 676 |
+
self.model = _video_vae(
|
| 677 |
+
pretrained_path=vae_pth,
|
| 678 |
+
z_dim=z_dim,
|
| 679 |
+
).eval().requires_grad_(False).to(device)
|
| 680 |
+
|
| 681 |
+
def encode(self, videos):
|
| 682 |
+
"""
|
| 683 |
+
videos: A list of videos each with shape [C, T, H, W].
|
| 684 |
+
"""
|
| 685 |
+
with amp.autocast(dtype=self.dtype):
|
| 686 |
+
return [
|
| 687 |
+
self.model.encode(u.unsqueeze(0), self.scale).float().squeeze(0)
|
| 688 |
+
for u in videos
|
| 689 |
+
]
|
| 690 |
+
|
| 691 |
+
def decode(self, zs):
|
| 692 |
+
with amp.autocast(dtype=self.dtype):
|
| 693 |
+
return [
|
| 694 |
+
self.model.decode(u.unsqueeze(0),
|
| 695 |
+
self.scale).float().clamp_(-1, 1).squeeze(0)
|
| 696 |
+
for u in zs
|
| 697 |
+
]
|
wan/utils/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Trimmed package initializer for the CMD Hugging Face Space.
|
| 2 |
+
# The upstream nv-tlabs/cmd package initializers eagerly import training-only
|
| 3 |
+
# and multi-GPU modules that this inference-only demo does not need. See NOTICE
|
| 4 |
+
# and LICENSE for the upstream licenses that continue to apply.
|
wan/utils/fm_solvers_unipc.py
ADDED
|
@@ -0,0 +1,814 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copied from https://github.com/huggingface/diffusers/blob/v0.31.0/src/diffusers/schedulers/scheduling_unipc_multistep.py
|
| 2 |
+
# Convert unipc for flow matching
|
| 3 |
+
# Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
|
| 4 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 5 |
+
#
|
| 6 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
# you may not use this file except in compliance with the License.
|
| 8 |
+
# You may obtain a copy of the License at
|
| 9 |
+
#
|
| 10 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
#
|
| 12 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
# See the License for the specific language governing permissions and
|
| 16 |
+
# limitations under the License.
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
import math
|
| 20 |
+
from typing import List, Optional, Tuple, Union
|
| 21 |
+
|
| 22 |
+
import numpy as np
|
| 23 |
+
import torch
|
| 24 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 25 |
+
from diffusers.schedulers.scheduling_utils import (KarrasDiffusionSchedulers,
|
| 26 |
+
SchedulerMixin,
|
| 27 |
+
SchedulerOutput)
|
| 28 |
+
from diffusers.utils import deprecate, is_scipy_available
|
| 29 |
+
|
| 30 |
+
if is_scipy_available():
|
| 31 |
+
import scipy.stats
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class FlowUniPCMultistepScheduler(SchedulerMixin, ConfigMixin):
|
| 35 |
+
"""
|
| 36 |
+
`UniPCMultistepScheduler` is a training-free framework designed for the fast sampling of diffusion models.
|
| 37 |
+
|
| 38 |
+
This model inherits from [`SchedulerMixin`] and [`ConfigMixin`]. Check the superclass documentation for the generic
|
| 39 |
+
methods the library implements for all schedulers such as loading and saving.
|
| 40 |
+
|
| 41 |
+
Args:
|
| 42 |
+
num_train_timesteps (`int`, defaults to 1000):
|
| 43 |
+
The number of diffusion steps to train the model.
|
| 44 |
+
solver_order (`int`, default `2`):
|
| 45 |
+
The UniPC order which can be any positive integer. The effective order of accuracy is `solver_order + 1`
|
| 46 |
+
due to the UniC. It is recommended to use `solver_order=2` for guided sampling, and `solver_order=3` for
|
| 47 |
+
unconditional sampling.
|
| 48 |
+
prediction_type (`str`, defaults to "flow_prediction"):
|
| 49 |
+
Prediction type of the scheduler function; must be `flow_prediction` for this scheduler, which predicts
|
| 50 |
+
the flow of the diffusion process.
|
| 51 |
+
thresholding (`bool`, defaults to `False`):
|
| 52 |
+
Whether to use the "dynamic thresholding" method. This is unsuitable for latent-space diffusion models such
|
| 53 |
+
as Stable Diffusion.
|
| 54 |
+
dynamic_thresholding_ratio (`float`, defaults to 0.995):
|
| 55 |
+
The ratio for the dynamic thresholding method. Valid only when `thresholding=True`.
|
| 56 |
+
sample_max_value (`float`, defaults to 1.0):
|
| 57 |
+
The threshold value for dynamic thresholding. Valid only when `thresholding=True` and `predict_x0=True`.
|
| 58 |
+
predict_x0 (`bool`, defaults to `True`):
|
| 59 |
+
Whether to use the updating algorithm on the predicted x0.
|
| 60 |
+
solver_type (`str`, default `bh2`):
|
| 61 |
+
Solver type for UniPC. It is recommended to use `bh1` for unconditional sampling when steps < 10, and `bh2`
|
| 62 |
+
otherwise.
|
| 63 |
+
lower_order_final (`bool`, default `True`):
|
| 64 |
+
Whether to use lower-order solvers in the final steps. Only valid for < 15 inference steps. This can
|
| 65 |
+
stabilize the sampling of DPMSolver for steps < 15, especially for steps <= 10.
|
| 66 |
+
disable_corrector (`list`, default `[]`):
|
| 67 |
+
Decides which step to disable the corrector to mitigate the misalignment between `epsilon_theta(x_t, c)`
|
| 68 |
+
and `epsilon_theta(x_t^c, c)` which can influence convergence for a large guidance scale. Corrector is
|
| 69 |
+
usually disabled during the first few steps.
|
| 70 |
+
solver_p (`SchedulerMixin`, default `None`):
|
| 71 |
+
Any other scheduler that if specified, the algorithm becomes `solver_p + UniC`.
|
| 72 |
+
use_karras_sigmas (`bool`, *optional*, defaults to `False`):
|
| 73 |
+
Whether to use Karras sigmas for step sizes in the noise schedule during the sampling process. If `True`,
|
| 74 |
+
the sigmas are determined according to a sequence of noise levels {σi}.
|
| 75 |
+
use_exponential_sigmas (`bool`, *optional*, defaults to `False`):
|
| 76 |
+
Whether to use exponential sigmas for step sizes in the noise schedule during the sampling process.
|
| 77 |
+
timestep_spacing (`str`, defaults to `"linspace"`):
|
| 78 |
+
The way the timesteps should be scaled. Refer to Table 2 of the [Common Diffusion Noise Schedules and
|
| 79 |
+
Sample Steps are Flawed](https://huggingface.co/papers/2305.08891) for more information.
|
| 80 |
+
steps_offset (`int`, defaults to 0):
|
| 81 |
+
An offset added to the inference steps, as required by some model families.
|
| 82 |
+
final_sigmas_type (`str`, defaults to `"zero"`):
|
| 83 |
+
The final `sigma` value for the noise schedule during the sampling process. If `"sigma_min"`, the final
|
| 84 |
+
sigma is the same as the last sigma in the training schedule. If `zero`, the final sigma is set to 0.
|
| 85 |
+
"""
|
| 86 |
+
|
| 87 |
+
_compatibles = [e.name for e in KarrasDiffusionSchedulers]
|
| 88 |
+
order = 1
|
| 89 |
+
|
| 90 |
+
@register_to_config
|
| 91 |
+
def __init__(
|
| 92 |
+
self,
|
| 93 |
+
num_train_timesteps: int = 1000,
|
| 94 |
+
solver_order: int = 2,
|
| 95 |
+
prediction_type: str = "flow_prediction",
|
| 96 |
+
shift: Optional[float] = 1.0,
|
| 97 |
+
use_dynamic_shifting=False,
|
| 98 |
+
thresholding: bool = False,
|
| 99 |
+
dynamic_thresholding_ratio: float = 0.995,
|
| 100 |
+
sample_max_value: float = 1.0,
|
| 101 |
+
predict_x0: bool = True,
|
| 102 |
+
solver_type: str = "bh2",
|
| 103 |
+
lower_order_final: bool = True,
|
| 104 |
+
disable_corrector: List[int] = [],
|
| 105 |
+
solver_p: SchedulerMixin = None,
|
| 106 |
+
timestep_spacing: str = "linspace",
|
| 107 |
+
steps_offset: int = 0,
|
| 108 |
+
final_sigmas_type: Optional[str] = "zero", # "zero", "sigma_min"
|
| 109 |
+
):
|
| 110 |
+
|
| 111 |
+
if solver_type not in ["bh1", "bh2"]:
|
| 112 |
+
if solver_type in ["midpoint", "heun", "logrho"]:
|
| 113 |
+
self.register_to_config(solver_type="bh2")
|
| 114 |
+
else:
|
| 115 |
+
raise NotImplementedError(
|
| 116 |
+
f"{solver_type} is not implemented for {self.__class__}")
|
| 117 |
+
|
| 118 |
+
self.predict_x0 = predict_x0
|
| 119 |
+
# setable values
|
| 120 |
+
self.num_inference_steps = None
|
| 121 |
+
alphas = np.linspace(1, 1 / num_train_timesteps,
|
| 122 |
+
num_train_timesteps)[::-1].copy()
|
| 123 |
+
sigmas = 1.0 - alphas
|
| 124 |
+
sigmas = torch.from_numpy(sigmas).to(dtype=torch.float32)
|
| 125 |
+
|
| 126 |
+
if not use_dynamic_shifting:
|
| 127 |
+
# when use_dynamic_shifting is True, we apply the timestep shifting on the fly based on the image resolution
|
| 128 |
+
sigmas = shift * sigmas / (1 +
|
| 129 |
+
(shift - 1) * sigmas) # pyright: ignore
|
| 130 |
+
|
| 131 |
+
self.sigmas = sigmas
|
| 132 |
+
self.timesteps = sigmas * num_train_timesteps
|
| 133 |
+
|
| 134 |
+
self.model_outputs = [None] * solver_order
|
| 135 |
+
self.timestep_list = [None] * solver_order
|
| 136 |
+
self.lower_order_nums = 0
|
| 137 |
+
self.disable_corrector = disable_corrector
|
| 138 |
+
self.solver_p = solver_p
|
| 139 |
+
self.last_sample = None
|
| 140 |
+
self._step_index = None
|
| 141 |
+
self._begin_index = None
|
| 142 |
+
|
| 143 |
+
self.sigmas = self.sigmas.to(
|
| 144 |
+
"cpu") # to avoid too much CPU/GPU communication
|
| 145 |
+
self.sigma_min = self.sigmas[-1].item()
|
| 146 |
+
self.sigma_max = self.sigmas[0].item()
|
| 147 |
+
|
| 148 |
+
@property
|
| 149 |
+
def step_index(self):
|
| 150 |
+
"""
|
| 151 |
+
The index counter for current timestep. It will increase 1 after each scheduler step.
|
| 152 |
+
"""
|
| 153 |
+
return self._step_index
|
| 154 |
+
|
| 155 |
+
@property
|
| 156 |
+
def begin_index(self):
|
| 157 |
+
"""
|
| 158 |
+
The index for the first timestep. It should be set from pipeline with `set_begin_index` method.
|
| 159 |
+
"""
|
| 160 |
+
return self._begin_index
|
| 161 |
+
|
| 162 |
+
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.set_begin_index
|
| 163 |
+
def set_begin_index(self, begin_index: int = 0):
|
| 164 |
+
"""
|
| 165 |
+
Sets the begin index for the scheduler. This function should be run from pipeline before the inference.
|
| 166 |
+
|
| 167 |
+
Args:
|
| 168 |
+
begin_index (`int`):
|
| 169 |
+
The begin index for the scheduler.
|
| 170 |
+
"""
|
| 171 |
+
self._begin_index = begin_index
|
| 172 |
+
|
| 173 |
+
# Modified from diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler.set_timesteps
|
| 174 |
+
def set_timesteps(
|
| 175 |
+
self,
|
| 176 |
+
num_inference_steps: Union[int, None] = None,
|
| 177 |
+
device: Union[str, torch.device] = None,
|
| 178 |
+
sigmas: Optional[List[float]] = None,
|
| 179 |
+
mu: Optional[Union[float, None]] = None,
|
| 180 |
+
shift: Optional[Union[float, None]] = None,
|
| 181 |
+
):
|
| 182 |
+
"""
|
| 183 |
+
Sets the discrete timesteps used for the diffusion chain (to be run before inference).
|
| 184 |
+
Args:
|
| 185 |
+
num_inference_steps (`int`):
|
| 186 |
+
Total number of the spacing of the time steps.
|
| 187 |
+
device (`str` or `torch.device`, *optional*):
|
| 188 |
+
The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
|
| 189 |
+
"""
|
| 190 |
+
|
| 191 |
+
if self.config.use_dynamic_shifting and mu is None:
|
| 192 |
+
raise ValueError(
|
| 193 |
+
" you have to pass a value for `mu` when `use_dynamic_shifting` is set to be `True`"
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
if sigmas is None:
|
| 197 |
+
sigmas = np.linspace(self.sigma_max, self.sigma_min,
|
| 198 |
+
num_inference_steps +
|
| 199 |
+
1).copy()[:-1] # pyright: ignore
|
| 200 |
+
|
| 201 |
+
if self.config.use_dynamic_shifting:
|
| 202 |
+
sigmas = self.time_shift(mu, 1.0, sigmas) # pyright: ignore
|
| 203 |
+
else:
|
| 204 |
+
if shift is None:
|
| 205 |
+
shift = self.config.shift
|
| 206 |
+
sigmas = shift * sigmas / (1 +
|
| 207 |
+
(shift - 1) * sigmas) # pyright: ignore
|
| 208 |
+
|
| 209 |
+
if self.config.final_sigmas_type == "sigma_min":
|
| 210 |
+
sigma_last = ((1 - self.alphas_cumprod[0]) /
|
| 211 |
+
self.alphas_cumprod[0])**0.5
|
| 212 |
+
elif self.config.final_sigmas_type == "zero":
|
| 213 |
+
sigma_last = 0
|
| 214 |
+
else:
|
| 215 |
+
raise ValueError(
|
| 216 |
+
f"`final_sigmas_type` must be one of 'zero', or 'sigma_min', but got {self.config.final_sigmas_type}"
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
timesteps = sigmas * self.config.num_train_timesteps
|
| 220 |
+
sigmas = np.concatenate([sigmas, [sigma_last]
|
| 221 |
+
]).astype(np.float32) # pyright: ignore
|
| 222 |
+
|
| 223 |
+
self.sigmas = torch.from_numpy(sigmas)
|
| 224 |
+
self.timesteps = torch.from_numpy(timesteps).to(
|
| 225 |
+
device=device, dtype=torch.int64)
|
| 226 |
+
|
| 227 |
+
self.num_inference_steps = len(timesteps)
|
| 228 |
+
|
| 229 |
+
self.model_outputs = [
|
| 230 |
+
None,
|
| 231 |
+
] * self.config.solver_order
|
| 232 |
+
self.lower_order_nums = 0
|
| 233 |
+
self.last_sample = None
|
| 234 |
+
if self.solver_p:
|
| 235 |
+
self.solver_p.set_timesteps(self.num_inference_steps, device=device)
|
| 236 |
+
|
| 237 |
+
# add an index counter for schedulers that allow duplicated timesteps
|
| 238 |
+
self._step_index = None
|
| 239 |
+
self._begin_index = None
|
| 240 |
+
self.sigmas = self.sigmas.to(
|
| 241 |
+
"cpu") # to avoid too much CPU/GPU communication
|
| 242 |
+
|
| 243 |
+
# Copied from diffusers.schedulers.scheduling_ddpm.DDPMScheduler._threshold_sample
|
| 244 |
+
def _threshold_sample(self, sample: torch.Tensor) -> torch.Tensor:
|
| 245 |
+
"""
|
| 246 |
+
"Dynamic thresholding: At each sampling step we set s to a certain percentile absolute pixel value in xt0 (the
|
| 247 |
+
prediction of x_0 at timestep t), and if s > 1, then we threshold xt0 to the range [-s, s] and then divide by
|
| 248 |
+
s. Dynamic thresholding pushes saturated pixels (those near -1 and 1) inwards, thereby actively preventing
|
| 249 |
+
pixels from saturation at each step. We find that dynamic thresholding results in significantly better
|
| 250 |
+
photorealism as well as better image-text alignment, especially when using very large guidance weights."
|
| 251 |
+
|
| 252 |
+
https://arxiv.org/abs/2205.11487
|
| 253 |
+
"""
|
| 254 |
+
dtype = sample.dtype
|
| 255 |
+
batch_size, channels, *remaining_dims = sample.shape
|
| 256 |
+
|
| 257 |
+
if dtype not in (torch.float32, torch.float64):
|
| 258 |
+
sample = sample.float(
|
| 259 |
+
) # upcast for quantile calculation, and clamp not implemented for cpu half
|
| 260 |
+
|
| 261 |
+
# Flatten sample for doing quantile calculation along each image
|
| 262 |
+
sample = sample.reshape(batch_size, channels * np.prod(remaining_dims))
|
| 263 |
+
|
| 264 |
+
abs_sample = sample.abs() # "a certain percentile absolute pixel value"
|
| 265 |
+
|
| 266 |
+
s = torch.quantile(
|
| 267 |
+
abs_sample, self.config.dynamic_thresholding_ratio, dim=1)
|
| 268 |
+
s = torch.clamp(
|
| 269 |
+
s, min=1, max=self.config.sample_max_value
|
| 270 |
+
) # When clamped to min=1, equivalent to standard clipping to [-1, 1]
|
| 271 |
+
s = s.unsqueeze(
|
| 272 |
+
1) # (batch_size, 1) because clamp will broadcast along dim=0
|
| 273 |
+
sample = torch.clamp(
|
| 274 |
+
sample, -s, s
|
| 275 |
+
) / s # "we threshold xt0 to the range [-s, s] and then divide by s"
|
| 276 |
+
|
| 277 |
+
sample = sample.reshape(batch_size, channels, *remaining_dims)
|
| 278 |
+
sample = sample.to(dtype)
|
| 279 |
+
|
| 280 |
+
return sample
|
| 281 |
+
|
| 282 |
+
# Copied from diffusers.schedulers.scheduling_flow_match_euler_discrete.FlowMatchEulerDiscreteScheduler._sigma_to_t
|
| 283 |
+
def _sigma_to_t(self, sigma):
|
| 284 |
+
return sigma * self.config.num_train_timesteps
|
| 285 |
+
|
| 286 |
+
def _sigma_to_alpha_sigma_t(self, sigma):
|
| 287 |
+
return 1 - sigma, sigma
|
| 288 |
+
|
| 289 |
+
# Copied from diffusers.schedulers.scheduling_flow_match_euler_discrete.set_timesteps
|
| 290 |
+
def time_shift(self, mu: float, sigma: float, t: torch.Tensor):
|
| 291 |
+
return math.exp(mu) / (math.exp(mu) + (1 / t - 1)**sigma)
|
| 292 |
+
|
| 293 |
+
def convert_model_output(
|
| 294 |
+
self,
|
| 295 |
+
model_output: torch.Tensor,
|
| 296 |
+
*args,
|
| 297 |
+
sample: torch.Tensor = None,
|
| 298 |
+
**kwargs,
|
| 299 |
+
) -> torch.Tensor:
|
| 300 |
+
r"""
|
| 301 |
+
Convert the model output to the corresponding type the UniPC algorithm needs.
|
| 302 |
+
|
| 303 |
+
Args:
|
| 304 |
+
model_output (`torch.Tensor`):
|
| 305 |
+
The direct output from the learned diffusion model.
|
| 306 |
+
timestep (`int`):
|
| 307 |
+
The current discrete timestep in the diffusion chain.
|
| 308 |
+
sample (`torch.Tensor`):
|
| 309 |
+
A current instance of a sample created by the diffusion process.
|
| 310 |
+
|
| 311 |
+
Returns:
|
| 312 |
+
`torch.Tensor`:
|
| 313 |
+
The converted model output.
|
| 314 |
+
"""
|
| 315 |
+
timestep = args[0] if len(args) > 0 else kwargs.pop("timestep", None)
|
| 316 |
+
if sample is None:
|
| 317 |
+
if len(args) > 1:
|
| 318 |
+
sample = args[1]
|
| 319 |
+
else:
|
| 320 |
+
raise ValueError(
|
| 321 |
+
"missing `sample` as a required keyward argument")
|
| 322 |
+
if timestep is not None:
|
| 323 |
+
deprecate(
|
| 324 |
+
"timesteps",
|
| 325 |
+
"1.0.0",
|
| 326 |
+
"Passing `timesteps` is deprecated and has no effect as model output conversion is now handled via an internal counter `self.step_index`",
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
sigma = self.sigmas[self.step_index]
|
| 330 |
+
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma)
|
| 331 |
+
|
| 332 |
+
if self.predict_x0:
|
| 333 |
+
if self.config.prediction_type == "flow_prediction":
|
| 334 |
+
sigma_t = self.sigmas[self.step_index]
|
| 335 |
+
x0_pred = sample - sigma_t * model_output
|
| 336 |
+
else:
|
| 337 |
+
raise ValueError(
|
| 338 |
+
f"prediction_type given as {self.config.prediction_type} must be one of `epsilon`, `sample`,"
|
| 339 |
+
" `v_prediction` or `flow_prediction` for the UniPCMultistepScheduler."
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
if self.config.thresholding:
|
| 343 |
+
x0_pred = self._threshold_sample(x0_pred)
|
| 344 |
+
|
| 345 |
+
return x0_pred
|
| 346 |
+
else:
|
| 347 |
+
if self.config.prediction_type == "flow_prediction":
|
| 348 |
+
sigma_t = self.sigmas[self.step_index]
|
| 349 |
+
epsilon = sample - (1 - sigma_t) * model_output
|
| 350 |
+
else:
|
| 351 |
+
raise ValueError(
|
| 352 |
+
f"prediction_type given as {self.config.prediction_type} must be one of `epsilon`, `sample`,"
|
| 353 |
+
" `v_prediction` or `flow_prediction` for the UniPCMultistepScheduler."
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
if self.config.thresholding:
|
| 357 |
+
sigma_t = self.sigmas[self.step_index]
|
| 358 |
+
x0_pred = sample - sigma_t * model_output
|
| 359 |
+
x0_pred = self._threshold_sample(x0_pred)
|
| 360 |
+
epsilon = model_output + x0_pred
|
| 361 |
+
|
| 362 |
+
return epsilon
|
| 363 |
+
|
| 364 |
+
def multistep_uni_p_bh_update(
|
| 365 |
+
self,
|
| 366 |
+
model_output: torch.Tensor,
|
| 367 |
+
*args,
|
| 368 |
+
sample: torch.Tensor = None,
|
| 369 |
+
order: int = None, # pyright: ignore
|
| 370 |
+
**kwargs,
|
| 371 |
+
) -> torch.Tensor:
|
| 372 |
+
"""
|
| 373 |
+
One step for the UniP (B(h) version). Alternatively, `self.solver_p` is used if is specified.
|
| 374 |
+
|
| 375 |
+
Args:
|
| 376 |
+
model_output (`torch.Tensor`):
|
| 377 |
+
The direct output from the learned diffusion model at the current timestep.
|
| 378 |
+
prev_timestep (`int`):
|
| 379 |
+
The previous discrete timestep in the diffusion chain.
|
| 380 |
+
sample (`torch.Tensor`):
|
| 381 |
+
A current instance of a sample created by the diffusion process.
|
| 382 |
+
order (`int`):
|
| 383 |
+
The order of UniP at this timestep (corresponds to the *p* in UniPC-p).
|
| 384 |
+
|
| 385 |
+
Returns:
|
| 386 |
+
`torch.Tensor`:
|
| 387 |
+
The sample tensor at the previous timestep.
|
| 388 |
+
"""
|
| 389 |
+
prev_timestep = args[0] if len(args) > 0 else kwargs.pop(
|
| 390 |
+
"prev_timestep", None)
|
| 391 |
+
if sample is None:
|
| 392 |
+
if len(args) > 1:
|
| 393 |
+
sample = args[1]
|
| 394 |
+
else:
|
| 395 |
+
raise ValueError(
|
| 396 |
+
" missing `sample` as a required keyward argument")
|
| 397 |
+
if order is None:
|
| 398 |
+
if len(args) > 2:
|
| 399 |
+
order = args[2]
|
| 400 |
+
else:
|
| 401 |
+
raise ValueError(
|
| 402 |
+
" missing `order` as a required keyward argument")
|
| 403 |
+
if prev_timestep is not None:
|
| 404 |
+
deprecate(
|
| 405 |
+
"prev_timestep",
|
| 406 |
+
"1.0.0",
|
| 407 |
+
"Passing `prev_timestep` is deprecated and has no effect as model output conversion is now handled via an internal counter `self.step_index`",
|
| 408 |
+
)
|
| 409 |
+
model_output_list = self.model_outputs
|
| 410 |
+
|
| 411 |
+
s0 = self.timestep_list[-1]
|
| 412 |
+
m0 = model_output_list[-1]
|
| 413 |
+
x = sample
|
| 414 |
+
|
| 415 |
+
if self.solver_p:
|
| 416 |
+
x_t = self.solver_p.step(model_output, s0, x).prev_sample
|
| 417 |
+
return x_t
|
| 418 |
+
|
| 419 |
+
sigma_t, sigma_s0 = self.sigmas[self.step_index + 1], self.sigmas[
|
| 420 |
+
self.step_index] # pyright: ignore
|
| 421 |
+
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma_t)
|
| 422 |
+
alpha_s0, sigma_s0 = self._sigma_to_alpha_sigma_t(sigma_s0)
|
| 423 |
+
|
| 424 |
+
lambda_t = torch.log(alpha_t) - torch.log(sigma_t)
|
| 425 |
+
lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0)
|
| 426 |
+
|
| 427 |
+
h = lambda_t - lambda_s0
|
| 428 |
+
device = sample.device
|
| 429 |
+
|
| 430 |
+
rks = []
|
| 431 |
+
D1s = []
|
| 432 |
+
for i in range(1, order):
|
| 433 |
+
si = self.step_index - i # pyright: ignore
|
| 434 |
+
mi = model_output_list[-(i + 1)]
|
| 435 |
+
alpha_si, sigma_si = self._sigma_to_alpha_sigma_t(self.sigmas[si])
|
| 436 |
+
lambda_si = torch.log(alpha_si) - torch.log(sigma_si)
|
| 437 |
+
rk = (lambda_si - lambda_s0) / h
|
| 438 |
+
rks.append(rk)
|
| 439 |
+
D1s.append((mi - m0) / rk) # pyright: ignore
|
| 440 |
+
|
| 441 |
+
rks.append(1.0)
|
| 442 |
+
rks = torch.tensor(rks, device=device)
|
| 443 |
+
|
| 444 |
+
R = []
|
| 445 |
+
b = []
|
| 446 |
+
|
| 447 |
+
hh = -h if self.predict_x0 else h
|
| 448 |
+
h_phi_1 = torch.expm1(hh) # h\phi_1(h) = e^h - 1
|
| 449 |
+
h_phi_k = h_phi_1 / hh - 1
|
| 450 |
+
|
| 451 |
+
factorial_i = 1
|
| 452 |
+
|
| 453 |
+
if self.config.solver_type == "bh1":
|
| 454 |
+
B_h = hh
|
| 455 |
+
elif self.config.solver_type == "bh2":
|
| 456 |
+
B_h = torch.expm1(hh)
|
| 457 |
+
else:
|
| 458 |
+
raise NotImplementedError()
|
| 459 |
+
|
| 460 |
+
for i in range(1, order + 1):
|
| 461 |
+
R.append(torch.pow(rks, i - 1))
|
| 462 |
+
b.append(h_phi_k * factorial_i / B_h)
|
| 463 |
+
factorial_i *= i + 1
|
| 464 |
+
h_phi_k = h_phi_k / hh - 1 / factorial_i
|
| 465 |
+
|
| 466 |
+
R = torch.stack(R)
|
| 467 |
+
b = torch.tensor(b, device=device)
|
| 468 |
+
|
| 469 |
+
if len(D1s) > 0:
|
| 470 |
+
D1s = torch.stack(D1s, dim=1) # (B, K)
|
| 471 |
+
# for order 2, we use a simplified version
|
| 472 |
+
if order == 2:
|
| 473 |
+
rhos_p = torch.tensor([0.5], dtype=x.dtype, device=device)
|
| 474 |
+
else:
|
| 475 |
+
rhos_p = torch.linalg.solve(R[:-1, :-1],
|
| 476 |
+
b[:-1]).to(device).to(x.dtype)
|
| 477 |
+
else:
|
| 478 |
+
D1s = None
|
| 479 |
+
|
| 480 |
+
if self.predict_x0:
|
| 481 |
+
x_t_ = sigma_t / sigma_s0 * x - alpha_t * h_phi_1 * m0
|
| 482 |
+
if D1s is not None:
|
| 483 |
+
pred_res = torch.einsum("k,bkc...->bc...", rhos_p,
|
| 484 |
+
D1s) # pyright: ignore
|
| 485 |
+
else:
|
| 486 |
+
pred_res = 0
|
| 487 |
+
x_t = x_t_ - alpha_t * B_h * pred_res
|
| 488 |
+
else:
|
| 489 |
+
x_t_ = alpha_t / alpha_s0 * x - sigma_t * h_phi_1 * m0
|
| 490 |
+
if D1s is not None:
|
| 491 |
+
pred_res = torch.einsum("k,bkc...->bc...", rhos_p,
|
| 492 |
+
D1s) # pyright: ignore
|
| 493 |
+
else:
|
| 494 |
+
pred_res = 0
|
| 495 |
+
x_t = x_t_ - sigma_t * B_h * pred_res
|
| 496 |
+
|
| 497 |
+
x_t = x_t.to(x.dtype)
|
| 498 |
+
return x_t
|
| 499 |
+
|
| 500 |
+
def multistep_uni_c_bh_update(
|
| 501 |
+
self,
|
| 502 |
+
this_model_output: torch.Tensor,
|
| 503 |
+
*args,
|
| 504 |
+
last_sample: torch.Tensor = None,
|
| 505 |
+
this_sample: torch.Tensor = None,
|
| 506 |
+
order: int = None, # pyright: ignore
|
| 507 |
+
**kwargs,
|
| 508 |
+
) -> torch.Tensor:
|
| 509 |
+
"""
|
| 510 |
+
One step for the UniC (B(h) version).
|
| 511 |
+
|
| 512 |
+
Args:
|
| 513 |
+
this_model_output (`torch.Tensor`):
|
| 514 |
+
The model outputs at `x_t`.
|
| 515 |
+
this_timestep (`int`):
|
| 516 |
+
The current timestep `t`.
|
| 517 |
+
last_sample (`torch.Tensor`):
|
| 518 |
+
The generated sample before the last predictor `x_{t-1}`.
|
| 519 |
+
this_sample (`torch.Tensor`):
|
| 520 |
+
The generated sample after the last predictor `x_{t}`.
|
| 521 |
+
order (`int`):
|
| 522 |
+
The `p` of UniC-p at this step. The effective order of accuracy should be `order + 1`.
|
| 523 |
+
|
| 524 |
+
Returns:
|
| 525 |
+
`torch.Tensor`:
|
| 526 |
+
The corrected sample tensor at the current timestep.
|
| 527 |
+
"""
|
| 528 |
+
this_timestep = args[0] if len(args) > 0 else kwargs.pop(
|
| 529 |
+
"this_timestep", None)
|
| 530 |
+
if last_sample is None:
|
| 531 |
+
if len(args) > 1:
|
| 532 |
+
last_sample = args[1]
|
| 533 |
+
else:
|
| 534 |
+
raise ValueError(
|
| 535 |
+
" missing`last_sample` as a required keyward argument")
|
| 536 |
+
if this_sample is None:
|
| 537 |
+
if len(args) > 2:
|
| 538 |
+
this_sample = args[2]
|
| 539 |
+
else:
|
| 540 |
+
raise ValueError(
|
| 541 |
+
" missing`this_sample` as a required keyward argument")
|
| 542 |
+
if order is None:
|
| 543 |
+
if len(args) > 3:
|
| 544 |
+
order = args[3]
|
| 545 |
+
else:
|
| 546 |
+
raise ValueError(
|
| 547 |
+
" missing`order` as a required keyward argument")
|
| 548 |
+
if this_timestep is not None:
|
| 549 |
+
deprecate(
|
| 550 |
+
"this_timestep",
|
| 551 |
+
"1.0.0",
|
| 552 |
+
"Passing `this_timestep` is deprecated and has no effect as model output conversion is now handled via an internal counter `self.step_index`",
|
| 553 |
+
)
|
| 554 |
+
|
| 555 |
+
model_output_list = self.model_outputs
|
| 556 |
+
|
| 557 |
+
m0 = model_output_list[-1]
|
| 558 |
+
x = last_sample
|
| 559 |
+
x_t = this_sample
|
| 560 |
+
model_t = this_model_output
|
| 561 |
+
|
| 562 |
+
sigma_t, sigma_s0 = self.sigmas[self.step_index], self.sigmas[
|
| 563 |
+
self.step_index - 1] # pyright: ignore
|
| 564 |
+
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma_t)
|
| 565 |
+
alpha_s0, sigma_s0 = self._sigma_to_alpha_sigma_t(sigma_s0)
|
| 566 |
+
|
| 567 |
+
lambda_t = torch.log(alpha_t) - torch.log(sigma_t)
|
| 568 |
+
lambda_s0 = torch.log(alpha_s0) - torch.log(sigma_s0)
|
| 569 |
+
|
| 570 |
+
h = lambda_t - lambda_s0
|
| 571 |
+
device = this_sample.device
|
| 572 |
+
|
| 573 |
+
rks = []
|
| 574 |
+
D1s = []
|
| 575 |
+
for i in range(1, order):
|
| 576 |
+
si = self.step_index - (i + 1) # pyright: ignore
|
| 577 |
+
mi = model_output_list[-(i + 1)]
|
| 578 |
+
alpha_si, sigma_si = self._sigma_to_alpha_sigma_t(self.sigmas[si])
|
| 579 |
+
lambda_si = torch.log(alpha_si) - torch.log(sigma_si)
|
| 580 |
+
rk = (lambda_si - lambda_s0) / h
|
| 581 |
+
rks.append(rk)
|
| 582 |
+
D1s.append((mi - m0) / rk) # pyright: ignore
|
| 583 |
+
|
| 584 |
+
rks.append(1.0)
|
| 585 |
+
rks = torch.tensor(rks, device=device)
|
| 586 |
+
|
| 587 |
+
R = []
|
| 588 |
+
b = []
|
| 589 |
+
|
| 590 |
+
hh = -h if self.predict_x0 else h
|
| 591 |
+
h_phi_1 = torch.expm1(hh) # h\phi_1(h) = e^h - 1
|
| 592 |
+
h_phi_k = h_phi_1 / hh - 1
|
| 593 |
+
|
| 594 |
+
factorial_i = 1
|
| 595 |
+
|
| 596 |
+
if self.config.solver_type == "bh1":
|
| 597 |
+
B_h = hh
|
| 598 |
+
elif self.config.solver_type == "bh2":
|
| 599 |
+
B_h = torch.expm1(hh)
|
| 600 |
+
else:
|
| 601 |
+
raise NotImplementedError()
|
| 602 |
+
|
| 603 |
+
for i in range(1, order + 1):
|
| 604 |
+
R.append(torch.pow(rks, i - 1))
|
| 605 |
+
b.append(h_phi_k * factorial_i / B_h)
|
| 606 |
+
factorial_i *= i + 1
|
| 607 |
+
h_phi_k = h_phi_k / hh - 1 / factorial_i
|
| 608 |
+
|
| 609 |
+
R = torch.stack(R)
|
| 610 |
+
b = torch.tensor(b, device=device)
|
| 611 |
+
|
| 612 |
+
if len(D1s) > 0:
|
| 613 |
+
D1s = torch.stack(D1s, dim=1)
|
| 614 |
+
else:
|
| 615 |
+
D1s = None
|
| 616 |
+
|
| 617 |
+
# for order 1, we use a simplified version
|
| 618 |
+
if order == 1:
|
| 619 |
+
rhos_c = torch.tensor([0.5], dtype=x.dtype, device=device)
|
| 620 |
+
else:
|
| 621 |
+
rhos_c = torch.linalg.solve(R, b).to(device).to(x.dtype)
|
| 622 |
+
|
| 623 |
+
if self.predict_x0:
|
| 624 |
+
x_t_ = sigma_t / sigma_s0 * x - alpha_t * h_phi_1 * m0
|
| 625 |
+
if D1s is not None:
|
| 626 |
+
corr_res = torch.einsum("k,bkc...->bc...", rhos_c[:-1], D1s)
|
| 627 |
+
else:
|
| 628 |
+
corr_res = 0
|
| 629 |
+
D1_t = model_t - m0
|
| 630 |
+
x_t = x_t_ - alpha_t * B_h * (corr_res + rhos_c[-1] * D1_t)
|
| 631 |
+
else:
|
| 632 |
+
x_t_ = alpha_t / alpha_s0 * x - sigma_t * h_phi_1 * m0
|
| 633 |
+
if D1s is not None:
|
| 634 |
+
corr_res = torch.einsum("k,bkc...->bc...", rhos_c[:-1], D1s)
|
| 635 |
+
else:
|
| 636 |
+
corr_res = 0
|
| 637 |
+
D1_t = model_t - m0
|
| 638 |
+
x_t = x_t_ - sigma_t * B_h * (corr_res + rhos_c[-1] * D1_t)
|
| 639 |
+
x_t = x_t.to(x.dtype)
|
| 640 |
+
return x_t
|
| 641 |
+
|
| 642 |
+
def index_for_timestep(self, timestep, schedule_timesteps=None):
|
| 643 |
+
if schedule_timesteps is None:
|
| 644 |
+
schedule_timesteps = self.timesteps
|
| 645 |
+
|
| 646 |
+
indices = (schedule_timesteps == timestep).nonzero()
|
| 647 |
+
|
| 648 |
+
# The sigma index that is taken for the **very** first `step`
|
| 649 |
+
# is always the second index (or the last index if there is only 1)
|
| 650 |
+
# This way we can ensure we don't accidentally skip a sigma in
|
| 651 |
+
# case we start in the middle of the denoising schedule (e.g. for image-to-image)
|
| 652 |
+
pos = 1 if len(indices) > 1 else 0
|
| 653 |
+
|
| 654 |
+
return indices[pos].item()
|
| 655 |
+
|
| 656 |
+
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler._init_step_index
|
| 657 |
+
def _init_step_index(self, timestep):
|
| 658 |
+
"""
|
| 659 |
+
Initialize the step_index counter for the scheduler.
|
| 660 |
+
"""
|
| 661 |
+
|
| 662 |
+
if self.begin_index is None:
|
| 663 |
+
if isinstance(timestep, torch.Tensor):
|
| 664 |
+
timestep = timestep.to(self.timesteps.device)
|
| 665 |
+
self._step_index = self.index_for_timestep(timestep)
|
| 666 |
+
else:
|
| 667 |
+
self._step_index = self._begin_index
|
| 668 |
+
|
| 669 |
+
def step(self,
|
| 670 |
+
model_output: torch.Tensor,
|
| 671 |
+
timestep: Union[int, torch.Tensor],
|
| 672 |
+
sample: torch.Tensor,
|
| 673 |
+
return_dict: bool = True,
|
| 674 |
+
generator=None) -> Union[SchedulerOutput, Tuple]:
|
| 675 |
+
"""
|
| 676 |
+
Predict the sample from the previous timestep by reversing the SDE. This function propagates the sample with
|
| 677 |
+
the multistep UniPC.
|
| 678 |
+
|
| 679 |
+
Args:
|
| 680 |
+
model_output (`torch.Tensor`):
|
| 681 |
+
The direct output from learned diffusion model.
|
| 682 |
+
timestep (`int`):
|
| 683 |
+
The current discrete timestep in the diffusion chain.
|
| 684 |
+
sample (`torch.Tensor`):
|
| 685 |
+
A current instance of a sample created by the diffusion process.
|
| 686 |
+
return_dict (`bool`):
|
| 687 |
+
Whether or not to return a [`~schedulers.scheduling_utils.SchedulerOutput`] or `tuple`.
|
| 688 |
+
|
| 689 |
+
Returns:
|
| 690 |
+
[`~schedulers.scheduling_utils.SchedulerOutput`] or `tuple`:
|
| 691 |
+
If return_dict is `True`, [`~schedulers.scheduling_utils.SchedulerOutput`] is returned, otherwise a
|
| 692 |
+
tuple is returned where the first element is the sample tensor.
|
| 693 |
+
|
| 694 |
+
"""
|
| 695 |
+
if self.num_inference_steps is None:
|
| 696 |
+
raise ValueError(
|
| 697 |
+
"Number of inference steps is 'None', you need to run 'set_timesteps' after creating the scheduler"
|
| 698 |
+
)
|
| 699 |
+
|
| 700 |
+
if self.step_index is None:
|
| 701 |
+
self._init_step_index(timestep)
|
| 702 |
+
|
| 703 |
+
use_corrector = (
|
| 704 |
+
self.step_index > 0 and
|
| 705 |
+
self.step_index - 1 not in self.disable_corrector and
|
| 706 |
+
self.last_sample is not None # pyright: ignore
|
| 707 |
+
)
|
| 708 |
+
|
| 709 |
+
model_output_convert = self.convert_model_output(
|
| 710 |
+
model_output, sample=sample)
|
| 711 |
+
if use_corrector:
|
| 712 |
+
sample = self.multistep_uni_c_bh_update(
|
| 713 |
+
this_model_output=model_output_convert,
|
| 714 |
+
last_sample=self.last_sample,
|
| 715 |
+
this_sample=sample,
|
| 716 |
+
order=self.this_order,
|
| 717 |
+
)
|
| 718 |
+
|
| 719 |
+
for i in range(self.config.solver_order - 1):
|
| 720 |
+
self.model_outputs[i] = self.model_outputs[i + 1]
|
| 721 |
+
self.timestep_list[i] = self.timestep_list[i + 1]
|
| 722 |
+
|
| 723 |
+
self.model_outputs[-1] = model_output_convert
|
| 724 |
+
self.timestep_list[-1] = timestep # pyright: ignore
|
| 725 |
+
|
| 726 |
+
if self.config.lower_order_final:
|
| 727 |
+
this_order = min(self.config.solver_order,
|
| 728 |
+
len(self.timesteps) -
|
| 729 |
+
self.step_index) # pyright: ignore
|
| 730 |
+
else:
|
| 731 |
+
this_order = self.config.solver_order
|
| 732 |
+
|
| 733 |
+
self.this_order = min(this_order,
|
| 734 |
+
self.lower_order_nums + 1) # warmup for multistep
|
| 735 |
+
assert self.this_order > 0
|
| 736 |
+
|
| 737 |
+
self.last_sample = sample
|
| 738 |
+
prev_sample = self.multistep_uni_p_bh_update(
|
| 739 |
+
model_output=model_output, # pass the original non-converted model output, in case solver-p is used
|
| 740 |
+
sample=sample,
|
| 741 |
+
order=self.this_order,
|
| 742 |
+
)
|
| 743 |
+
|
| 744 |
+
if self.lower_order_nums < self.config.solver_order:
|
| 745 |
+
self.lower_order_nums += 1
|
| 746 |
+
|
| 747 |
+
# upon completion increase step index by one
|
| 748 |
+
self._step_index += 1 # pyright: ignore
|
| 749 |
+
|
| 750 |
+
if not return_dict:
|
| 751 |
+
return (prev_sample,)
|
| 752 |
+
|
| 753 |
+
return SchedulerOutput(prev_sample=prev_sample)
|
| 754 |
+
|
| 755 |
+
def scale_model_input(self, sample: torch.Tensor, *args,
|
| 756 |
+
**kwargs) -> torch.Tensor:
|
| 757 |
+
"""
|
| 758 |
+
Ensures interchangeability with schedulers that need to scale the denoising model input depending on the
|
| 759 |
+
current timestep.
|
| 760 |
+
|
| 761 |
+
Args:
|
| 762 |
+
sample (`torch.Tensor`):
|
| 763 |
+
The input sample.
|
| 764 |
+
|
| 765 |
+
Returns:
|
| 766 |
+
`torch.Tensor`:
|
| 767 |
+
A scaled input sample.
|
| 768 |
+
"""
|
| 769 |
+
return sample
|
| 770 |
+
|
| 771 |
+
# Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.add_noise
|
| 772 |
+
def add_noise(
|
| 773 |
+
self,
|
| 774 |
+
original_samples: torch.Tensor,
|
| 775 |
+
noise: torch.Tensor,
|
| 776 |
+
timesteps: torch.IntTensor,
|
| 777 |
+
) -> torch.Tensor:
|
| 778 |
+
# Make sure sigmas and timesteps have the same device and dtype as original_samples
|
| 779 |
+
sigmas = self.sigmas.to(
|
| 780 |
+
device=original_samples.device, dtype=original_samples.dtype)
|
| 781 |
+
if original_samples.device.type == "mps" and torch.is_floating_point(
|
| 782 |
+
timesteps):
|
| 783 |
+
# mps does not support float64
|
| 784 |
+
schedule_timesteps = self.timesteps.to(
|
| 785 |
+
original_samples.device, dtype=torch.float32)
|
| 786 |
+
timesteps = timesteps.to(
|
| 787 |
+
original_samples.device, dtype=torch.float32)
|
| 788 |
+
else:
|
| 789 |
+
schedule_timesteps = self.timesteps.to(original_samples.device)
|
| 790 |
+
timesteps = timesteps.to(original_samples.device)
|
| 791 |
+
|
| 792 |
+
# begin_index is None when the scheduler is used for training or pipeline does not implement set_begin_index
|
| 793 |
+
if self.begin_index is None:
|
| 794 |
+
step_indices = [
|
| 795 |
+
self.index_for_timestep(t, schedule_timesteps)
|
| 796 |
+
for t in timesteps
|
| 797 |
+
]
|
| 798 |
+
elif self.step_index is not None:
|
| 799 |
+
# add_noise is called after first denoising step (for inpainting)
|
| 800 |
+
step_indices = [self.step_index] * timesteps.shape[0]
|
| 801 |
+
else:
|
| 802 |
+
# add noise is called before first denoising step to create initial latent(img2img)
|
| 803 |
+
step_indices = [self.begin_index] * timesteps.shape[0]
|
| 804 |
+
|
| 805 |
+
sigma = sigmas[step_indices].flatten()
|
| 806 |
+
while len(sigma.shape) < len(original_samples.shape):
|
| 807 |
+
sigma = sigma.unsqueeze(-1)
|
| 808 |
+
|
| 809 |
+
alpha_t, sigma_t = self._sigma_to_alpha_sigma_t(sigma)
|
| 810 |
+
noisy_samples = alpha_t * original_samples + sigma_t * noise
|
| 811 |
+
return noisy_samples
|
| 812 |
+
|
| 813 |
+
def __len__(self):
|
| 814 |
+
return self.config.num_train_timesteps
|