multimodalart HF Staff commited on
Commit
1b2d73a
·
verified ·
1 Parent(s): eb4403a

CMD chunk1-short image-to-video demo

Browse files
.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: Cmd I2v Demo
3
- emoji: 📚
4
- colorFrom: red
5
- colorTo: gray
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
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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

  • SHA256: b2ac9e6a453cf885300442fc080aceed16045753969e1edd56ba9e51ec3211f8
  • Pointer size: 132 Bytes
  • Size of remote file: 1.51 MB
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