meepmoo commited on
Commit
0dcccdd
1 Parent(s): fc65ce3

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +1 -1
  2. LICENSE +201 -0
  3. __init__.py +3 -0
  4. app.py +49 -0
  5. asset/2.png +0 -0
  6. asset/3.png +0 -0
  7. asset/4.png +0 -0
  8. asset/5.png +0 -0
  9. cogvideox/__init__.py +0 -0
  10. cogvideox/api/api.py +173 -0
  11. cogvideox/api/post_infer.py +89 -0
  12. cogvideox/data/bucket_sampler.py +379 -0
  13. cogvideox/data/dataset_image.py +76 -0
  14. cogvideox/data/dataset_image_video.py +550 -0
  15. cogvideox/data/dataset_video.py +262 -0
  16. cogvideox/models/autoencoder_magvit.py +1296 -0
  17. cogvideox/models/transformer3d.py +609 -0
  18. cogvideox/pipeline/pipeline_cogvideox.py +751 -0
  19. cogvideox/pipeline/pipeline_cogvideox_control.py +843 -0
  20. cogvideox/pipeline/pipeline_cogvideox_inpaint.py +1020 -0
  21. cogvideox/ui/ui.py +1614 -0
  22. cogvideox/utils/__init__.py +0 -0
  23. cogvideox/utils/lora_utils.py +477 -0
  24. cogvideox/utils/utils.py +208 -0
  25. cogvideox/video_caption/README.md +174 -0
  26. cogvideox/video_caption/README_zh-CN.md +159 -0
  27. cogvideox/video_caption/beautiful_prompt.py +103 -0
  28. cogvideox/video_caption/caption_rewrite.py +224 -0
  29. cogvideox/video_caption/compute_motion_score.py +186 -0
  30. cogvideox/video_caption/compute_text_score.py +214 -0
  31. cogvideox/video_caption/compute_video_quality.py +201 -0
  32. cogvideox/video_caption/cutscene_detect.py +97 -0
  33. cogvideox/video_caption/filter_meta_train.py +88 -0
  34. cogvideox/video_caption/package_patches/easyocr_detection_patched.py +114 -0
  35. cogvideox/video_caption/package_patches/vila_siglip_encoder_patched.py +42 -0
  36. cogvideox/video_caption/prompt/beautiful_prompt.txt +9 -0
  37. cogvideox/video_caption/prompt/rewrite.txt +9 -0
  38. cogvideox/video_caption/requirements.txt +9 -0
  39. cogvideox/video_caption/scripts/stage_1_video_splitting.sh +39 -0
  40. cogvideox/video_caption/scripts/stage_2_video_filtering.sh +41 -0
  41. cogvideox/video_caption/scripts/stage_3_video_recaptioning.sh +52 -0
  42. cogvideox/video_caption/utils/filter.py +162 -0
  43. cogvideox/video_caption/utils/gather_jsonl.py +55 -0
  44. cogvideox/video_caption/utils/get_meta_file.py +74 -0
  45. cogvideox/video_caption/utils/image_evaluator.py +248 -0
  46. cogvideox/video_caption/utils/logger.py +36 -0
  47. cogvideox/video_caption/utils/longclip/README.md +19 -0
  48. cogvideox/video_caption/utils/longclip/__init__.py +1 -0
  49. cogvideox/video_caption/utils/longclip/bpe_simple_vocab_16e6.txt.gz +3 -0
  50. cogvideox/video_caption/utils/longclip/longclip.py +353 -0
Dockerfile CHANGED
@@ -42,4 +42,4 @@ COPY ./datasets /content/datasets
42
  COPY ./reports /content/reports
43
  COPY ./requirements.txt /content/requirements.txt
44
  RUN pip install -r /content/requirements.txt
45
- WORKDIR /content
 
42
  COPY ./reports /content/reports
43
  COPY ./requirements.txt /content/requirements.txt
44
  RUN pip install -r /content/requirements.txt
45
+ WORKDIR /content
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .comfyui.comfyui_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
2
+
3
+ __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
app.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import torch
3
+
4
+ from cogvideox.api.api import infer_forward_api, update_diffusion_transformer_api, update_edition_api
5
+ from cogvideox.ui.ui import ui_modelscope, ui_eas, ui
6
+
7
+ if __name__ == "__main__":
8
+ # Choose the ui mode
9
+ ui_mode = "normal"
10
+
11
+ # Low gpu memory mode, this is used when the GPU memory is under 16GB
12
+ low_gpu_memory_mode = False
13
+ # Use torch.float16 if GPU does not support torch.bfloat16
14
+ # ome graphics cards, such as v100, 2080ti, do not support torch.bfloat16
15
+ weight_dtype = torch.bfloat16
16
+
17
+ # Server ip
18
+ server_name = "0.0.0.0"
19
+ server_port = 7860
20
+
21
+ # Params below is used when ui_mode = "modelscope"
22
+ model_name = "models/Diffusion_Transformer/CogVideoX-Fun-V1.1-2b-InP"
23
+ # "Inpaint" or "Control"
24
+ model_type = "Inpaint"
25
+ # Save dir of this model
26
+ savedir_sample = "samples"
27
+
28
+ if ui_mode == "modelscope":
29
+ demo, controller = ui_modelscope(model_name, model_type, savedir_sample, low_gpu_memory_mode, weight_dtype)
30
+ elif ui_mode == "eas":
31
+ demo, controller = ui_eas(model_name, savedir_sample)
32
+ else:
33
+ demo, controller = ui(low_gpu_memory_mode, weight_dtype)
34
+
35
+ # launch gradio
36
+ app, _, _ = demo.queue(status_update_rate=1).launch(
37
+ server_name=server_name,
38
+ server_port=server_port,
39
+ prevent_thread_lock=True
40
+ )
41
+
42
+ # launch api
43
+ infer_forward_api(None, app, controller)
44
+ update_diffusion_transformer_api(None, app, controller)
45
+ update_edition_api(None, app, controller)
46
+
47
+ # not close the python
48
+ while True:
49
+ time.sleep(5)
asset/2.png ADDED
asset/3.png ADDED
asset/4.png ADDED
asset/5.png ADDED
cogvideox/__init__.py ADDED
File without changes
cogvideox/api/api.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import gc
3
+ import base64
4
+ import torch
5
+ import gradio as gr
6
+ import tempfile
7
+ import hashlib
8
+ import os
9
+
10
+ from fastapi import FastAPI
11
+ from io import BytesIO
12
+ from PIL import Image
13
+
14
+ # Function to encode a file to Base64
15
+ def encode_file_to_base64(file_path):
16
+ with open(file_path, "rb") as file:
17
+ # Encode the data to Base64
18
+ file_base64 = base64.b64encode(file.read())
19
+ return file_base64
20
+
21
+ def update_edition_api(_: gr.Blocks, app: FastAPI, controller):
22
+ @app.post("/cogvideox_fun/update_edition")
23
+ def _update_edition_api(
24
+ datas: dict,
25
+ ):
26
+ edition = datas.get('edition', 'v2')
27
+
28
+ try:
29
+ controller.update_edition(
30
+ edition
31
+ )
32
+ comment = "Success"
33
+ except Exception as e:
34
+ torch.cuda.empty_cache()
35
+ comment = f"Error. error information is {str(e)}"
36
+
37
+ return {"message": comment}
38
+
39
+ def update_diffusion_transformer_api(_: gr.Blocks, app: FastAPI, controller):
40
+ @app.post("/cogvideox_fun/update_diffusion_transformer")
41
+ def _update_diffusion_transformer_api(
42
+ datas: dict,
43
+ ):
44
+ diffusion_transformer_path = datas.get('diffusion_transformer_path', 'none')
45
+
46
+ try:
47
+ controller.update_diffusion_transformer(
48
+ diffusion_transformer_path
49
+ )
50
+ comment = "Success"
51
+ except Exception as e:
52
+ torch.cuda.empty_cache()
53
+ comment = f"Error. error information is {str(e)}"
54
+
55
+ return {"message": comment}
56
+
57
+ def save_base64_video(base64_string):
58
+ video_data = base64.b64decode(base64_string)
59
+
60
+ md5_hash = hashlib.md5(video_data).hexdigest()
61
+ filename = f"{md5_hash}.mp4"
62
+
63
+ temp_dir = tempfile.gettempdir()
64
+ file_path = os.path.join(temp_dir, filename)
65
+
66
+ with open(file_path, 'wb') as video_file:
67
+ video_file.write(video_data)
68
+
69
+ return file_path
70
+
71
+ def save_base64_image(base64_string):
72
+ video_data = base64.b64decode(base64_string)
73
+
74
+ md5_hash = hashlib.md5(video_data).hexdigest()
75
+ filename = f"{md5_hash}.jpg"
76
+
77
+ temp_dir = tempfile.gettempdir()
78
+ file_path = os.path.join(temp_dir, filename)
79
+
80
+ with open(file_path, 'wb') as video_file:
81
+ video_file.write(video_data)
82
+
83
+ return file_path
84
+
85
+ def infer_forward_api(_: gr.Blocks, app: FastAPI, controller):
86
+ @app.post("/cogvideox_fun/infer_forward")
87
+ def _infer_forward_api(
88
+ datas: dict,
89
+ ):
90
+ base_model_path = datas.get('base_model_path', 'none')
91
+ lora_model_path = datas.get('lora_model_path', 'none')
92
+ lora_alpha_slider = datas.get('lora_alpha_slider', 0.55)
93
+ prompt_textbox = datas.get('prompt_textbox', None)
94
+ negative_prompt_textbox = datas.get('negative_prompt_textbox', 'The video is not of a high quality, it has a low resolution. Watermark present in each frame. The background is solid. Strange body and strange trajectory. Distortion. ')
95
+ sampler_dropdown = datas.get('sampler_dropdown', 'Euler')
96
+ sample_step_slider = datas.get('sample_step_slider', 30)
97
+ resize_method = datas.get('resize_method', "Generate by")
98
+ width_slider = datas.get('width_slider', 672)
99
+ height_slider = datas.get('height_slider', 384)
100
+ base_resolution = datas.get('base_resolution', 512)
101
+ is_image = datas.get('is_image', False)
102
+ generation_method = datas.get('generation_method', False)
103
+ length_slider = datas.get('length_slider', 49)
104
+ overlap_video_length = datas.get('overlap_video_length', 4)
105
+ partial_video_length = datas.get('partial_video_length', 72)
106
+ cfg_scale_slider = datas.get('cfg_scale_slider', 6)
107
+ start_image = datas.get('start_image', None)
108
+ end_image = datas.get('end_image', None)
109
+ validation_video = datas.get('validation_video', None)
110
+ validation_video_mask = datas.get('validation_video_mask', None)
111
+ control_video = datas.get('control_video', None)
112
+ denoise_strength = datas.get('denoise_strength', 0.70)
113
+ seed_textbox = datas.get("seed_textbox", 43)
114
+
115
+ generation_method = "Image Generation" if is_image else generation_method
116
+
117
+ if start_image is not None:
118
+ start_image = base64.b64decode(start_image)
119
+ start_image = [Image.open(BytesIO(start_image))]
120
+
121
+ if end_image is not None:
122
+ end_image = base64.b64decode(end_image)
123
+ end_image = [Image.open(BytesIO(end_image))]
124
+
125
+ if validation_video is not None:
126
+ validation_video = save_base64_video(validation_video)
127
+
128
+ if validation_video_mask is not None:
129
+ validation_video_mask = save_base64_image(validation_video_mask)
130
+
131
+ if control_video is not None:
132
+ control_video = save_base64_video(control_video)
133
+
134
+ try:
135
+ save_sample_path, comment = controller.generate(
136
+ "",
137
+ base_model_path,
138
+ lora_model_path,
139
+ lora_alpha_slider,
140
+ prompt_textbox,
141
+ negative_prompt_textbox,
142
+ sampler_dropdown,
143
+ sample_step_slider,
144
+ resize_method,
145
+ width_slider,
146
+ height_slider,
147
+ base_resolution,
148
+ generation_method,
149
+ length_slider,
150
+ overlap_video_length,
151
+ partial_video_length,
152
+ cfg_scale_slider,
153
+ start_image,
154
+ end_image,
155
+ validation_video,
156
+ validation_video_mask,
157
+ control_video,
158
+ denoise_strength,
159
+ seed_textbox,
160
+ is_api = True,
161
+ )
162
+ except Exception as e:
163
+ gc.collect()
164
+ torch.cuda.empty_cache()
165
+ torch.cuda.ipc_collect()
166
+ save_sample_path = ""
167
+ comment = f"Error. error information is {str(e)}"
168
+ return {"message": comment}
169
+
170
+ if save_sample_path != "":
171
+ return {"message": comment, "save_sample_path": save_sample_path, "base64_encoding": encode_file_to_base64(save_sample_path)}
172
+ else:
173
+ return {"message": comment, "save_sample_path": save_sample_path}
cogvideox/api/post_infer.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import json
3
+ import sys
4
+ import time
5
+ from datetime import datetime
6
+ from io import BytesIO
7
+
8
+ import cv2
9
+ import requests
10
+ import base64
11
+
12
+
13
+ def post_diffusion_transformer(diffusion_transformer_path, url='http://127.0.0.1:7860'):
14
+ datas = json.dumps({
15
+ "diffusion_transformer_path": diffusion_transformer_path
16
+ })
17
+ r = requests.post(f'{url}/cogvideox_fun/update_diffusion_transformer', data=datas, timeout=1500)
18
+ data = r.content.decode('utf-8')
19
+ return data
20
+
21
+ def post_update_edition(edition, url='http://0.0.0.0:7860'):
22
+ datas = json.dumps({
23
+ "edition": edition
24
+ })
25
+ r = requests.post(f'{url}/cogvideox_fun/update_edition', data=datas, timeout=1500)
26
+ data = r.content.decode('utf-8')
27
+ return data
28
+
29
+ def post_infer(generation_method, length_slider, url='http://127.0.0.1:7860'):
30
+ datas = json.dumps({
31
+ "base_model_path": "none",
32
+ "motion_module_path": "none",
33
+ "lora_model_path": "none",
34
+ "lora_alpha_slider": 0.55,
35
+ "prompt_textbox": "A young woman with beautiful and clear eyes and blonde hair standing and white dress in a forest wearing a crown. She seems to be lost in thought, and the camera focuses on her face. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
36
+ "negative_prompt_textbox": "The video is not of a high quality, it has a low resolution. Watermark present in each frame. The background is solid. Strange body and strange trajectory. Distortion. ",
37
+ "sampler_dropdown": "Euler",
38
+ "sample_step_slider": 50,
39
+ "width_slider": 672,
40
+ "height_slider": 384,
41
+ "generation_method": "Video Generation",
42
+ "length_slider": length_slider,
43
+ "cfg_scale_slider": 6,
44
+ "seed_textbox": 43,
45
+ })
46
+ r = requests.post(f'{url}/cogvideox_fun/infer_forward', data=datas, timeout=1500)
47
+ data = r.content.decode('utf-8')
48
+ return data
49
+
50
+ if __name__ == '__main__':
51
+ # initiate time
52
+ now_date = datetime.now()
53
+ time_start = time.time()
54
+
55
+ # -------------------------- #
56
+ # Step 1: update edition
57
+ # -------------------------- #
58
+ diffusion_transformer_path = "models/Diffusion_Transformer/CogVideoX-Fun-2b-InP"
59
+ outputs = post_diffusion_transformer(diffusion_transformer_path)
60
+ print('Output update edition: ', outputs)
61
+
62
+ # -------------------------- #
63
+ # Step 2: infer
64
+ # -------------------------- #
65
+ # "Video Generation" and "Image Generation"
66
+ generation_method = "Video Generation"
67
+ length_slider = 49
68
+ outputs = post_infer(generation_method, length_slider)
69
+
70
+ # Get decoded data
71
+ outputs = json.loads(outputs)
72
+ base64_encoding = outputs["base64_encoding"]
73
+ decoded_data = base64.b64decode(base64_encoding)
74
+
75
+ is_image = True if generation_method == "Image Generation" else False
76
+ if is_image or length_slider == 1:
77
+ file_path = "1.png"
78
+ else:
79
+ file_path = "1.mp4"
80
+ with open(file_path, "wb") as file:
81
+ file.write(decoded_data)
82
+
83
+ # End of record time
84
+ # The calculated time difference is the execution time of the program, expressed in seconds / s
85
+ time_end = time.time()
86
+ time_sum = (time_end - time_start) % 60
87
+ print('# --------------------------------------------------------- #')
88
+ print(f'# Total expenditure: {time_sum}s')
89
+ print('# --------------------------------------------------------- #')
cogvideox/data/bucket_sampler.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) OpenMMLab. All rights reserved.
2
+ import os
3
+ from typing import (Generic, Iterable, Iterator, List, Optional, Sequence,
4
+ Sized, TypeVar, Union)
5
+
6
+ import cv2
7
+ import numpy as np
8
+ import torch
9
+ from PIL import Image
10
+ from torch.utils.data import BatchSampler, Dataset, Sampler
11
+
12
+ ASPECT_RATIO_512 = {
13
+ '0.25': [256.0, 1024.0], '0.26': [256.0, 992.0], '0.27': [256.0, 960.0], '0.28': [256.0, 928.0],
14
+ '0.32': [288.0, 896.0], '0.33': [288.0, 864.0], '0.35': [288.0, 832.0], '0.4': [320.0, 800.0],
15
+ '0.42': [320.0, 768.0], '0.48': [352.0, 736.0], '0.5': [352.0, 704.0], '0.52': [352.0, 672.0],
16
+ '0.57': [384.0, 672.0], '0.6': [384.0, 640.0], '0.68': [416.0, 608.0], '0.72': [416.0, 576.0],
17
+ '0.78': [448.0, 576.0], '0.82': [448.0, 544.0], '0.88': [480.0, 544.0], '0.94': [480.0, 512.0],
18
+ '1.0': [512.0, 512.0], '1.07': [512.0, 480.0], '1.13': [544.0, 480.0], '1.21': [544.0, 448.0],
19
+ '1.29': [576.0, 448.0], '1.38': [576.0, 416.0], '1.46': [608.0, 416.0], '1.67': [640.0, 384.0],
20
+ '1.75': [672.0, 384.0], '2.0': [704.0, 352.0], '2.09': [736.0, 352.0], '2.4': [768.0, 320.0],
21
+ '2.5': [800.0, 320.0], '2.89': [832.0, 288.0], '3.0': [864.0, 288.0], '3.11': [896.0, 288.0],
22
+ '3.62': [928.0, 256.0], '3.75': [960.0, 256.0], '3.88': [992.0, 256.0], '4.0': [1024.0, 256.0]
23
+ }
24
+ ASPECT_RATIO_RANDOM_CROP_512 = {
25
+ '0.42': [320.0, 768.0], '0.5': [352.0, 704.0],
26
+ '0.57': [384.0, 672.0], '0.68': [416.0, 608.0], '0.78': [448.0, 576.0], '0.88': [480.0, 544.0],
27
+ '0.94': [480.0, 512.0], '1.0': [512.0, 512.0], '1.07': [512.0, 480.0],
28
+ '1.13': [544.0, 480.0], '1.29': [576.0, 448.0], '1.46': [608.0, 416.0], '1.75': [672.0, 384.0],
29
+ '2.0': [704.0, 352.0], '2.4': [768.0, 320.0]
30
+ }
31
+ ASPECT_RATIO_RANDOM_CROP_PROB = [
32
+ 1, 2,
33
+ 4, 4, 4, 4,
34
+ 8, 8, 8,
35
+ 4, 4, 4, 4,
36
+ 2, 1
37
+ ]
38
+ ASPECT_RATIO_RANDOM_CROP_PROB = np.array(ASPECT_RATIO_RANDOM_CROP_PROB) / sum(ASPECT_RATIO_RANDOM_CROP_PROB)
39
+
40
+ def get_closest_ratio(height: float, width: float, ratios: dict = ASPECT_RATIO_512):
41
+ aspect_ratio = height / width
42
+ closest_ratio = min(ratios.keys(), key=lambda ratio: abs(float(ratio) - aspect_ratio))
43
+ return ratios[closest_ratio], float(closest_ratio)
44
+
45
+ def get_image_size_without_loading(path):
46
+ with Image.open(path) as img:
47
+ return img.size # (width, height)
48
+
49
+ class RandomSampler(Sampler[int]):
50
+ r"""Samples elements randomly. If without replacement, then sample from a shuffled dataset.
51
+
52
+ If with replacement, then user can specify :attr:`num_samples` to draw.
53
+
54
+ Args:
55
+ data_source (Dataset): dataset to sample from
56
+ replacement (bool): samples are drawn on-demand with replacement if ``True``, default=``False``
57
+ num_samples (int): number of samples to draw, default=`len(dataset)`.
58
+ generator (Generator): Generator used in sampling.
59
+ """
60
+
61
+ data_source: Sized
62
+ replacement: bool
63
+
64
+ def __init__(self, data_source: Sized, replacement: bool = False,
65
+ num_samples: Optional[int] = None, generator=None) -> None:
66
+ self.data_source = data_source
67
+ self.replacement = replacement
68
+ self._num_samples = num_samples
69
+ self.generator = generator
70
+ self._pos_start = 0
71
+
72
+ if not isinstance(self.replacement, bool):
73
+ raise TypeError(f"replacement should be a boolean value, but got replacement={self.replacement}")
74
+
75
+ if not isinstance(self.num_samples, int) or self.num_samples <= 0:
76
+ raise ValueError(f"num_samples should be a positive integer value, but got num_samples={self.num_samples}")
77
+
78
+ @property
79
+ def num_samples(self) -> int:
80
+ # dataset size might change at runtime
81
+ if self._num_samples is None:
82
+ return len(self.data_source)
83
+ return self._num_samples
84
+
85
+ def __iter__(self) -> Iterator[int]:
86
+ n = len(self.data_source)
87
+ if self.generator is None:
88
+ seed = int(torch.empty((), dtype=torch.int64).random_().item())
89
+ generator = torch.Generator()
90
+ generator.manual_seed(seed)
91
+ else:
92
+ generator = self.generator
93
+
94
+ if self.replacement:
95
+ for _ in range(self.num_samples // 32):
96
+ yield from torch.randint(high=n, size=(32,), dtype=torch.int64, generator=generator).tolist()
97
+ yield from torch.randint(high=n, size=(self.num_samples % 32,), dtype=torch.int64, generator=generator).tolist()
98
+ else:
99
+ for _ in range(self.num_samples // n):
100
+ xx = torch.randperm(n, generator=generator).tolist()
101
+ if self._pos_start >= n:
102
+ self._pos_start = 0
103
+ print("xx top 10", xx[:10], self._pos_start)
104
+ for idx in range(self._pos_start, n):
105
+ yield xx[idx]
106
+ self._pos_start = (self._pos_start + 1) % n
107
+ self._pos_start = 0
108
+ yield from torch.randperm(n, generator=generator).tolist()[:self.num_samples % n]
109
+
110
+ def __len__(self) -> int:
111
+ return self.num_samples
112
+
113
+ class AspectRatioBatchImageSampler(BatchSampler):
114
+ """A sampler wrapper for grouping images with similar aspect ratio into a same batch.
115
+
116
+ Args:
117
+ sampler (Sampler): Base sampler.
118
+ dataset (Dataset): Dataset providing data information.
119
+ batch_size (int): Size of mini-batch.
120
+ drop_last (bool): If ``True``, the sampler will drop the last batch if
121
+ its size would be less than ``batch_size``.
122
+ aspect_ratios (dict): The predefined aspect ratios.
123
+ """
124
+ def __init__(
125
+ self,
126
+ sampler: Sampler,
127
+ dataset: Dataset,
128
+ batch_size: int,
129
+ train_folder: str = None,
130
+ aspect_ratios: dict = ASPECT_RATIO_512,
131
+ drop_last: bool = False,
132
+ config=None,
133
+ **kwargs
134
+ ) -> None:
135
+ if not isinstance(sampler, Sampler):
136
+ raise TypeError('sampler should be an instance of ``Sampler``, '
137
+ f'but got {sampler}')
138
+ if not isinstance(batch_size, int) or batch_size <= 0:
139
+ raise ValueError('batch_size should be a positive integer value, '
140
+ f'but got batch_size={batch_size}')
141
+ self.sampler = sampler
142
+ self.dataset = dataset
143
+ self.train_folder = train_folder
144
+ self.batch_size = batch_size
145
+ self.aspect_ratios = aspect_ratios
146
+ self.drop_last = drop_last
147
+ self.config = config
148
+ # buckets for each aspect ratio
149
+ self._aspect_ratio_buckets = {ratio: [] for ratio in aspect_ratios}
150
+ # [str(k) for k, v in aspect_ratios]
151
+ self.current_available_bucket_keys = list(aspect_ratios.keys())
152
+
153
+ def __iter__(self):
154
+ for idx in self.sampler:
155
+ try:
156
+ image_dict = self.dataset[idx]
157
+
158
+ width, height = image_dict.get("width", None), image_dict.get("height", None)
159
+ if width is None or height is None:
160
+ image_id, name = image_dict['file_path'], image_dict['text']
161
+ if self.train_folder is None:
162
+ image_dir = image_id
163
+ else:
164
+ image_dir = os.path.join(self.train_folder, image_id)
165
+
166
+ width, height = get_image_size_without_loading(image_dir)
167
+
168
+ ratio = height / width # self.dataset[idx]
169
+ else:
170
+ height = int(height)
171
+ width = int(width)
172
+ ratio = height / width # self.dataset[idx]
173
+ except Exception as e:
174
+ print(e)
175
+ continue
176
+ # find the closest aspect ratio
177
+ closest_ratio = min(self.aspect_ratios.keys(), key=lambda r: abs(float(r) - ratio))
178
+ if closest_ratio not in self.current_available_bucket_keys:
179
+ continue
180
+ bucket = self._aspect_ratio_buckets[closest_ratio]
181
+ bucket.append(idx)
182
+ # yield a batch of indices in the same aspect ratio group
183
+ if len(bucket) == self.batch_size:
184
+ yield bucket[:]
185
+ del bucket[:]
186
+
187
+ class AspectRatioBatchSampler(BatchSampler):
188
+ """A sampler wrapper for grouping images with similar aspect ratio into a same batch.
189
+
190
+ Args:
191
+ sampler (Sampler): Base sampler.
192
+ dataset (Dataset): Dataset providing data information.
193
+ batch_size (int): Size of mini-batch.
194
+ drop_last (bool): If ``True``, the sampler will drop the last batch if
195
+ its size would be less than ``batch_size``.
196
+ aspect_ratios (dict): The predefined aspect ratios.
197
+ """
198
+ def __init__(
199
+ self,
200
+ sampler: Sampler,
201
+ dataset: Dataset,
202
+ batch_size: int,
203
+ video_folder: str = None,
204
+ train_data_format: str = "webvid",
205
+ aspect_ratios: dict = ASPECT_RATIO_512,
206
+ drop_last: bool = False,
207
+ config=None,
208
+ **kwargs
209
+ ) -> None:
210
+ if not isinstance(sampler, Sampler):
211
+ raise TypeError('sampler should be an instance of ``Sampler``, '
212
+ f'but got {sampler}')
213
+ if not isinstance(batch_size, int) or batch_size <= 0:
214
+ raise ValueError('batch_size should be a positive integer value, '
215
+ f'but got batch_size={batch_size}')
216
+ self.sampler = sampler
217
+ self.dataset = dataset
218
+ self.video_folder = video_folder
219
+ self.train_data_format = train_data_format
220
+ self.batch_size = batch_size
221
+ self.aspect_ratios = aspect_ratios
222
+ self.drop_last = drop_last
223
+ self.config = config
224
+ # buckets for each aspect ratio
225
+ self._aspect_ratio_buckets = {ratio: [] for ratio in aspect_ratios}
226
+ # [str(k) for k, v in aspect_ratios]
227
+ self.current_available_bucket_keys = list(aspect_ratios.keys())
228
+
229
+ def __iter__(self):
230
+ for idx in self.sampler:
231
+ try:
232
+ video_dict = self.dataset[idx]
233
+ width, more = video_dict.get("width", None), video_dict.get("height", None)
234
+
235
+ if width is None or height is None:
236
+ if self.train_data_format == "normal":
237
+ video_id, name = video_dict['file_path'], video_dict['text']
238
+ if self.video_folder is None:
239
+ video_dir = video_id
240
+ else:
241
+ video_dir = os.path.join(self.video_folder, video_id)
242
+ else:
243
+ videoid, name, page_dir = video_dict['videoid'], video_dict['name'], video_dict['page_dir']
244
+ video_dir = os.path.join(self.video_folder, f"{videoid}.mp4")
245
+ cap = cv2.VideoCapture(video_dir)
246
+
247
+ # 获取视频尺寸
248
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 浮点数转换为整数
249
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 浮点数转换为整数
250
+
251
+ ratio = height / width # self.dataset[idx]
252
+ else:
253
+ height = int(height)
254
+ width = int(width)
255
+ ratio = height / width # self.dataset[idx]
256
+ except Exception as e:
257
+ print(e)
258
+ continue
259
+ # find the closest aspect ratio
260
+ closest_ratio = min(self.aspect_ratios.keys(), key=lambda r: abs(float(r) - ratio))
261
+ if closest_ratio not in self.current_available_bucket_keys:
262
+ continue
263
+ bucket = self._aspect_ratio_buckets[closest_ratio]
264
+ bucket.append(idx)
265
+ # yield a batch of indices in the same aspect ratio group
266
+ if len(bucket) == self.batch_size:
267
+ yield bucket[:]
268
+ del bucket[:]
269
+
270
+ class AspectRatioBatchImageVideoSampler(BatchSampler):
271
+ """A sampler wrapper for grouping images with similar aspect ratio into a same batch.
272
+
273
+ Args:
274
+ sampler (Sampler): Base sampler.
275
+ dataset (Dataset): Dataset providing data information.
276
+ batch_size (int): Size of mini-batch.
277
+ drop_last (bool): If ``True``, the sampler will drop the last batch if
278
+ its size would be less than ``batch_size``.
279
+ aspect_ratios (dict): The predefined aspect ratios.
280
+ """
281
+
282
+ def __init__(self,
283
+ sampler: Sampler,
284
+ dataset: Dataset,
285
+ batch_size: int,
286
+ train_folder: str = None,
287
+ aspect_ratios: dict = ASPECT_RATIO_512,
288
+ drop_last: bool = False
289
+ ) -> None:
290
+ if not isinstance(sampler, Sampler):
291
+ raise TypeError('sampler should be an instance of ``Sampler``, '
292
+ f'but got {sampler}')
293
+ if not isinstance(batch_size, int) or batch_size <= 0:
294
+ raise ValueError('batch_size should be a positive integer value, '
295
+ f'but got batch_size={batch_size}')
296
+ self.sampler = sampler
297
+ self.dataset = dataset
298
+ self.train_folder = train_folder
299
+ self.batch_size = batch_size
300
+ self.aspect_ratios = aspect_ratios
301
+ self.drop_last = drop_last
302
+
303
+ # buckets for each aspect ratio
304
+ self.current_available_bucket_keys = list(aspect_ratios.keys())
305
+ self.bucket = {
306
+ 'image':{ratio: [] for ratio in aspect_ratios},
307
+ 'video':{ratio: [] for ratio in aspect_ratios}
308
+ }
309
+
310
+ def __iter__(self):
311
+ for idx in self.sampler:
312
+ content_type = self.dataset[idx].get('type', 'image')
313
+ if content_type == 'image':
314
+ try:
315
+ image_dict = self.dataset[idx]
316
+
317
+ width, height = image_dict.get("width", None), image_dict.get("height", None)
318
+ if width is None or height is None:
319
+ image_id, name = image_dict['file_path'], image_dict['text']
320
+ if self.train_folder is None:
321
+ image_dir = image_id
322
+ else:
323
+ image_dir = os.path.join(self.train_folder, image_id)
324
+
325
+ width, height = get_image_size_without_loading(image_dir)
326
+
327
+ ratio = height / width # self.dataset[idx]
328
+ else:
329
+ height = int(height)
330
+ width = int(width)
331
+ ratio = height / width # self.dataset[idx]
332
+ except Exception as e:
333
+ print(e)
334
+ continue
335
+ # find the closest aspect ratio
336
+ closest_ratio = min(self.aspect_ratios.keys(), key=lambda r: abs(float(r) - ratio))
337
+ if closest_ratio not in self.current_available_bucket_keys:
338
+ continue
339
+ bucket = self.bucket['image'][closest_ratio]
340
+ bucket.append(idx)
341
+ # yield a batch of indices in the same aspect ratio group
342
+ if len(bucket) == self.batch_size:
343
+ yield bucket[:]
344
+ del bucket[:]
345
+ else:
346
+ try:
347
+ video_dict = self.dataset[idx]
348
+ width, height = video_dict.get("width", None), video_dict.get("height", None)
349
+
350
+ if width is None or height is None:
351
+ video_id, name = video_dict['file_path'], video_dict['text']
352
+ if self.train_folder is None:
353
+ video_dir = video_id
354
+ else:
355
+ video_dir = os.path.join(self.train_folder, video_id)
356
+ cap = cv2.VideoCapture(video_dir)
357
+
358
+ # 获取视频尺寸
359
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # 浮点数转换为整数
360
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 浮点数转换为整数
361
+
362
+ ratio = height / width # self.dataset[idx]
363
+ else:
364
+ height = int(height)
365
+ width = int(width)
366
+ ratio = height / width # self.dataset[idx]
367
+ except Exception as e:
368
+ print(e)
369
+ continue
370
+ # find the closest aspect ratio
371
+ closest_ratio = min(self.aspect_ratios.keys(), key=lambda r: abs(float(r) - ratio))
372
+ if closest_ratio not in self.current_available_bucket_keys:
373
+ continue
374
+ bucket = self.bucket['video'][closest_ratio]
375
+ bucket.append(idx)
376
+ # yield a batch of indices in the same aspect ratio group
377
+ if len(bucket) == self.batch_size:
378
+ yield bucket[:]
379
+ del bucket[:]
cogvideox/data/dataset_image.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import random
4
+
5
+ import numpy as np
6
+ import torch
7
+ import torchvision.transforms as transforms
8
+ from PIL import Image
9
+ from torch.utils.data.dataset import Dataset
10
+
11
+
12
+ class CC15M(Dataset):
13
+ def __init__(
14
+ self,
15
+ json_path,
16
+ video_folder=None,
17
+ resolution=512,
18
+ enable_bucket=False,
19
+ ):
20
+ print(f"loading annotations from {json_path} ...")
21
+ self.dataset = json.load(open(json_path, 'r'))
22
+ self.length = len(self.dataset)
23
+ print(f"data scale: {self.length}")
24
+
25
+ self.enable_bucket = enable_bucket
26
+ self.video_folder = video_folder
27
+
28
+ resolution = tuple(resolution) if not isinstance(resolution, int) else (resolution, resolution)
29
+ self.pixel_transforms = transforms.Compose([
30
+ transforms.Resize(resolution[0]),
31
+ transforms.CenterCrop(resolution),
32
+ transforms.ToTensor(),
33
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
34
+ ])
35
+
36
+ def get_batch(self, idx):
37
+ video_dict = self.dataset[idx]
38
+ video_id, name = video_dict['file_path'], video_dict['text']
39
+
40
+ if self.video_folder is None:
41
+ video_dir = video_id
42
+ else:
43
+ video_dir = os.path.join(self.video_folder, video_id)
44
+
45
+ pixel_values = Image.open(video_dir).convert("RGB")
46
+ return pixel_values, name
47
+
48
+ def __len__(self):
49
+ return self.length
50
+
51
+ def __getitem__(self, idx):
52
+ while True:
53
+ try:
54
+ pixel_values, name = self.get_batch(idx)
55
+ break
56
+ except Exception as e:
57
+ print(e)
58
+ idx = random.randint(0, self.length-1)
59
+
60
+ if not self.enable_bucket:
61
+ pixel_values = self.pixel_transforms(pixel_values)
62
+ else:
63
+ pixel_values = np.array(pixel_values)
64
+
65
+ sample = dict(pixel_values=pixel_values, text=name)
66
+ return sample
67
+
68
+ if __name__ == "__main__":
69
+ dataset = CC15M(
70
+ csv_path="/mnt_wg/zhoumo.xjq/CCUtils/cc15m_add_index.json",
71
+ resolution=512,
72
+ )
73
+
74
+ dataloader = torch.utils.data.DataLoader(dataset, batch_size=4, num_workers=0,)
75
+ for idx, batch in enumerate(dataloader):
76
+ print(batch["pixel_values"].shape, len(batch["text"]))
cogvideox/data/dataset_image_video.py ADDED
@@ -0,0 +1,550 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import io
3
+ import json
4
+ import math
5
+ import os
6
+ import random
7
+ from threading import Thread
8
+
9
+ import albumentations
10
+ import cv2
11
+ import gc
12
+ import numpy as np
13
+ import torch
14
+ import torchvision.transforms as transforms
15
+
16
+ from func_timeout import func_timeout, FunctionTimedOut
17
+ from decord import VideoReader
18
+ from PIL import Image
19
+ from torch.utils.data import BatchSampler, Sampler
20
+ from torch.utils.data.dataset import Dataset
21
+ from contextlib import contextmanager
22
+
23
+ VIDEO_READER_TIMEOUT = 20
24
+
25
+ def get_random_mask(shape):
26
+ f, c, h, w = shape
27
+
28
+ if f != 1:
29
+ mask_index = np.random.choice([0, 1, 2, 3, 4], p = [0.05, 0.3, 0.3, 0.3, 0.05]) # np.random.randint(0, 5)
30
+ else:
31
+ mask_index = np.random.choice([0, 1], p = [0.2, 0.8]) # np.random.randint(0, 2)
32
+ mask = torch.zeros((f, 1, h, w), dtype=torch.uint8)
33
+
34
+ if mask_index == 0:
35
+ center_x = torch.randint(0, w, (1,)).item()
36
+ center_y = torch.randint(0, h, (1,)).item()
37
+ block_size_x = torch.randint(w // 4, w // 4 * 3, (1,)).item() # 方块的宽度范围
38
+ block_size_y = torch.randint(h // 4, h // 4 * 3, (1,)).item() # 方块的高度范围
39
+
40
+ start_x = max(center_x - block_size_x // 2, 0)
41
+ end_x = min(center_x + block_size_x // 2, w)
42
+ start_y = max(center_y - block_size_y // 2, 0)
43
+ end_y = min(center_y + block_size_y // 2, h)
44
+ mask[:, :, start_y:end_y, start_x:end_x] = 1
45
+ elif mask_index == 1:
46
+ mask[:, :, :, :] = 1
47
+ elif mask_index == 2:
48
+ mask_frame_index = np.random.randint(1, 5)
49
+ mask[mask_frame_index:, :, :, :] = 1
50
+ elif mask_index == 3:
51
+ mask_frame_index = np.random.randint(1, 5)
52
+ mask[mask_frame_index:-mask_frame_index, :, :, :] = 1
53
+ elif mask_index == 4:
54
+ center_x = torch.randint(0, w, (1,)).item()
55
+ center_y = torch.randint(0, h, (1,)).item()
56
+ block_size_x = torch.randint(w // 4, w // 4 * 3, (1,)).item() # 方块的宽度范围
57
+ block_size_y = torch.randint(h // 4, h // 4 * 3, (1,)).item() # 方块的高度范围
58
+
59
+ start_x = max(center_x - block_size_x // 2, 0)
60
+ end_x = min(center_x + block_size_x // 2, w)
61
+ start_y = max(center_y - block_size_y // 2, 0)
62
+ end_y = min(center_y + block_size_y // 2, h)
63
+
64
+ mask_frame_before = np.random.randint(0, f // 2)
65
+ mask_frame_after = np.random.randint(f // 2, f)
66
+ mask[mask_frame_before:mask_frame_after, :, start_y:end_y, start_x:end_x] = 1
67
+ else:
68
+ raise ValueError(f"The mask_index {mask_index} is not define")
69
+ return mask
70
+
71
+ class ImageVideoSampler(BatchSampler):
72
+ """A sampler wrapper for grouping images with similar aspect ratio into a same batch.
73
+
74
+ Args:
75
+ sampler (Sampler): Base sampler.
76
+ dataset (Dataset): Dataset providing data information.
77
+ batch_size (int): Size of mini-batch.
78
+ drop_last (bool): If ``True``, the sampler will drop the last batch if
79
+ its size would be less than ``batch_size``.
80
+ aspect_ratios (dict): The predefined aspect ratios.
81
+ """
82
+
83
+ def __init__(self,
84
+ sampler: Sampler,
85
+ dataset: Dataset,
86
+ batch_size: int,
87
+ drop_last: bool = False
88
+ ) -> None:
89
+ if not isinstance(sampler, Sampler):
90
+ raise TypeError('sampler should be an instance of ``Sampler``, '
91
+ f'but got {sampler}')
92
+ if not isinstance(batch_size, int) or batch_size <= 0:
93
+ raise ValueError('batch_size should be a positive integer value, '
94
+ f'but got batch_size={batch_size}')
95
+ self.sampler = sampler
96
+ self.dataset = dataset
97
+ self.batch_size = batch_size
98
+ self.drop_last = drop_last
99
+
100
+ # buckets for each aspect ratio
101
+ self.bucket = {'image':[], 'video':[]}
102
+
103
+ def __iter__(self):
104
+ for idx in self.sampler:
105
+ content_type = self.dataset.dataset[idx].get('type', 'image')
106
+ self.bucket[content_type].append(idx)
107
+
108
+ # yield a batch of indices in the same aspect ratio group
109
+ if len(self.bucket['video']) == self.batch_size:
110
+ bucket = self.bucket['video']
111
+ yield bucket[:]
112
+ del bucket[:]
113
+ elif len(self.bucket['image']) == self.batch_size:
114
+ bucket = self.bucket['image']
115
+ yield bucket[:]
116
+ del bucket[:]
117
+
118
+ @contextmanager
119
+ def VideoReader_contextmanager(*args, **kwargs):
120
+ vr = VideoReader(*args, **kwargs)
121
+ try:
122
+ yield vr
123
+ finally:
124
+ del vr
125
+ gc.collect()
126
+
127
+ def get_video_reader_batch(video_reader, batch_index):
128
+ frames = video_reader.get_batch(batch_index).asnumpy()
129
+ return frames
130
+
131
+ def resize_frame(frame, target_short_side):
132
+ h, w, _ = frame.shape
133
+ if h < w:
134
+ if target_short_side > h:
135
+ return frame
136
+ new_h = target_short_side
137
+ new_w = int(target_short_side * w / h)
138
+ else:
139
+ if target_short_side > w:
140
+ return frame
141
+ new_w = target_short_side
142
+ new_h = int(target_short_side * h / w)
143
+
144
+ resized_frame = cv2.resize(frame, (new_w, new_h))
145
+ return resized_frame
146
+
147
+ class ImageVideoDataset(Dataset):
148
+ def __init__(
149
+ self,
150
+ ann_path, data_root=None,
151
+ video_sample_size=512, video_sample_stride=4, video_sample_n_frames=16,
152
+ image_sample_size=512,
153
+ video_repeat=0,
154
+ text_drop_ratio=-1,
155
+ enable_bucket=False,
156
+ video_length_drop_start=0.1,
157
+ video_length_drop_end=0.9,
158
+ enable_inpaint=False,
159
+ ):
160
+ # Loading annotations from files
161
+ print(f"loading annotations from {ann_path} ...")
162
+ if ann_path.endswith('.csv'):
163
+ with open(ann_path, 'r') as csvfile:
164
+ dataset = list(csv.DictReader(csvfile))
165
+ elif ann_path.endswith('.json'):
166
+ dataset = json.load(open(ann_path))
167
+
168
+ self.data_root = data_root
169
+
170
+ # It's used to balance num of images and videos.
171
+ self.dataset = []
172
+ for data in dataset:
173
+ if data.get('type', 'image') != 'video':
174
+ self.dataset.append(data)
175
+ if video_repeat > 0:
176
+ for _ in range(video_repeat):
177
+ for data in dataset:
178
+ if data.get('type', 'image') == 'video':
179
+ self.dataset.append(data)
180
+ del dataset
181
+
182
+ self.length = len(self.dataset)
183
+ print(f"data scale: {self.length}")
184
+ # TODO: enable bucket training
185
+ self.enable_bucket = enable_bucket
186
+ self.text_drop_ratio = text_drop_ratio
187
+ self.enable_inpaint = enable_inpaint
188
+
189
+ self.video_length_drop_start = video_length_drop_start
190
+ self.video_length_drop_end = video_length_drop_end
191
+
192
+ # Video params
193
+ self.video_sample_stride = video_sample_stride
194
+ self.video_sample_n_frames = video_sample_n_frames
195
+ self.video_sample_size = tuple(video_sample_size) if not isinstance(video_sample_size, int) else (video_sample_size, video_sample_size)
196
+ self.video_transforms = transforms.Compose(
197
+ [
198
+ transforms.Resize(min(self.video_sample_size)),
199
+ transforms.CenterCrop(self.video_sample_size),
200
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
201
+ ]
202
+ )
203
+
204
+ # Image params
205
+ self.image_sample_size = tuple(image_sample_size) if not isinstance(image_sample_size, int) else (image_sample_size, image_sample_size)
206
+ self.image_transforms = transforms.Compose([
207
+ transforms.Resize(min(self.image_sample_size)),
208
+ transforms.CenterCrop(self.image_sample_size),
209
+ transforms.ToTensor(),
210
+ transforms.Normalize([0.5, 0.5, 0.5],[0.5, 0.5, 0.5])
211
+ ])
212
+
213
+ self.larger_side_of_image_and_video = max(min(self.image_sample_size), min(self.video_sample_size))
214
+
215
+ def get_batch(self, idx):
216
+ data_info = self.dataset[idx % len(self.dataset)]
217
+
218
+ if data_info.get('type', 'image')=='video':
219
+ video_id, text = data_info['file_path'], data_info['text']
220
+
221
+ if self.data_root is None:
222
+ video_dir = video_id
223
+ else:
224
+ video_dir = os.path.join(self.data_root, video_id)
225
+
226
+ with VideoReader_contextmanager(video_dir, num_threads=2) as video_reader:
227
+ min_sample_n_frames = min(
228
+ self.video_sample_n_frames,
229
+ int(len(video_reader) * (self.video_length_drop_end - self.video_length_drop_start) // self.video_sample_stride)
230
+ )
231
+ if min_sample_n_frames == 0:
232
+ raise ValueError(f"No Frames in video.")
233
+
234
+ video_length = int(self.video_length_drop_end * len(video_reader))
235
+ clip_length = min(video_length, (min_sample_n_frames - 1) * self.video_sample_stride + 1)
236
+ start_idx = random.randint(int(self.video_length_drop_start * video_length), video_length - clip_length) if video_length != clip_length else 0
237
+ batch_index = np.linspace(start_idx, start_idx + clip_length - 1, min_sample_n_frames, dtype=int)
238
+
239
+ try:
240
+ sample_args = (video_reader, batch_index)
241
+ pixel_values = func_timeout(
242
+ VIDEO_READER_TIMEOUT, get_video_reader_batch, args=sample_args
243
+ )
244
+ resized_frames = []
245
+ for i in range(len(pixel_values)):
246
+ frame = pixel_values[i]
247
+ resized_frame = resize_frame(frame, self.larger_side_of_image_and_video)
248
+ resized_frames.append(resized_frame)
249
+ pixel_values = np.array(resized_frames)
250
+ except FunctionTimedOut:
251
+ raise ValueError(f"Read {idx} timeout.")
252
+ except Exception as e:
253
+ raise ValueError(f"Failed to extract frames from video. Error is {e}.")
254
+
255
+ if not self.enable_bucket:
256
+ pixel_values = torch.from_numpy(pixel_values).permute(0, 3, 1, 2).contiguous()
257
+ pixel_values = pixel_values / 255.
258
+ del video_reader
259
+ else:
260
+ pixel_values = pixel_values
261
+
262
+ if not self.enable_bucket:
263
+ pixel_values = self.video_transforms(pixel_values)
264
+
265
+ # Random use no text generation
266
+ if random.random() < self.text_drop_ratio:
267
+ text = ''
268
+ return pixel_values, text, 'video'
269
+ else:
270
+ image_path, text = data_info['file_path'], data_info['text']
271
+ if self.data_root is not None:
272
+ image_path = os.path.join(self.data_root, image_path)
273
+ image = Image.open(image_path).convert('RGB')
274
+ if not self.enable_bucket:
275
+ image = self.image_transforms(image).unsqueeze(0)
276
+ else:
277
+ image = np.expand_dims(np.array(image), 0)
278
+ if random.random() < self.text_drop_ratio:
279
+ text = ''
280
+ return image, text, 'image'
281
+
282
+ def __len__(self):
283
+ return self.length
284
+
285
+ def __getitem__(self, idx):
286
+ data_info = self.dataset[idx % len(self.dataset)]
287
+ data_type = data_info.get('type', 'image')
288
+ while True:
289
+ sample = {}
290
+ try:
291
+ data_info_local = self.dataset[idx % len(self.dataset)]
292
+ data_type_local = data_info_local.get('type', 'image')
293
+ if data_type_local != data_type:
294
+ raise ValueError("data_type_local != data_type")
295
+
296
+ pixel_values, name, data_type = self.get_batch(idx)
297
+ sample["pixel_values"] = pixel_values
298
+ sample["text"] = name
299
+ sample["data_type"] = data_type
300
+ sample["idx"] = idx
301
+
302
+ if len(sample) > 0:
303
+ break
304
+ except Exception as e:
305
+ print(e, self.dataset[idx % len(self.dataset)])
306
+ idx = random.randint(0, self.length-1)
307
+
308
+ if self.enable_inpaint and not self.enable_bucket:
309
+ mask = get_random_mask(pixel_values.size())
310
+ mask_pixel_values = pixel_values * (1 - mask) + torch.ones_like(pixel_values) * -1 * mask
311
+ sample["mask_pixel_values"] = mask_pixel_values
312
+ sample["mask"] = mask
313
+
314
+ clip_pixel_values = sample["pixel_values"][0].permute(1, 2, 0).contiguous()
315
+ clip_pixel_values = (clip_pixel_values * 0.5 + 0.5) * 255
316
+ sample["clip_pixel_values"] = clip_pixel_values
317
+
318
+ ref_pixel_values = sample["pixel_values"][0].unsqueeze(0)
319
+ if (mask == 1).all():
320
+ ref_pixel_values = torch.ones_like(ref_pixel_values) * -1
321
+ sample["ref_pixel_values"] = ref_pixel_values
322
+
323
+ return sample
324
+
325
+
326
+ class ImageVideoControlDataset(Dataset):
327
+ def __init__(
328
+ self,
329
+ ann_path, data_root=None,
330
+ video_sample_size=512, video_sample_stride=4, video_sample_n_frames=16,
331
+ image_sample_size=512,
332
+ video_repeat=0,
333
+ text_drop_ratio=-1,
334
+ enable_bucket=False,
335
+ video_length_drop_start=0.1,
336
+ video_length_drop_end=0.9,
337
+ enable_inpaint=False,
338
+ ):
339
+ # Loading annotations from files
340
+ print(f"loading annotations from {ann_path} ...")
341
+ if ann_path.endswith('.csv'):
342
+ with open(ann_path, 'r') as csvfile:
343
+ dataset = list(csv.DictReader(csvfile))
344
+ elif ann_path.endswith('.json'):
345
+ dataset = json.load(open(ann_path))
346
+
347
+ self.data_root = data_root
348
+
349
+ # It's used to balance num of images and videos.
350
+ self.dataset = []
351
+ for data in dataset:
352
+ if data.get('type', 'image') != 'video':
353
+ self.dataset.append(data)
354
+ if video_repeat > 0:
355
+ for _ in range(video_repeat):
356
+ for data in dataset:
357
+ if data.get('type', 'image') == 'video':
358
+ self.dataset.append(data)
359
+ del dataset
360
+
361
+ self.length = len(self.dataset)
362
+ print(f"data scale: {self.length}")
363
+ # TODO: enable bucket training
364
+ self.enable_bucket = enable_bucket
365
+ self.text_drop_ratio = text_drop_ratio
366
+ self.enable_inpaint = enable_inpaint
367
+
368
+ self.video_length_drop_start = video_length_drop_start
369
+ self.video_length_drop_end = video_length_drop_end
370
+
371
+ # Video params
372
+ self.video_sample_stride = video_sample_stride
373
+ self.video_sample_n_frames = video_sample_n_frames
374
+ self.video_sample_size = tuple(video_sample_size) if not isinstance(video_sample_size, int) else (video_sample_size, video_sample_size)
375
+ self.video_transforms = transforms.Compose(
376
+ [
377
+ transforms.Resize(min(self.video_sample_size)),
378
+ transforms.CenterCrop(self.video_sample_size),
379
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
380
+ ]
381
+ )
382
+
383
+ # Image params
384
+ self.image_sample_size = tuple(image_sample_size) if not isinstance(image_sample_size, int) else (image_sample_size, image_sample_size)
385
+ self.image_transforms = transforms.Compose([
386
+ transforms.Resize(min(self.image_sample_size)),
387
+ transforms.CenterCrop(self.image_sample_size),
388
+ transforms.ToTensor(),
389
+ transforms.Normalize([0.5, 0.5, 0.5],[0.5, 0.5, 0.5])
390
+ ])
391
+
392
+ self.larger_side_of_image_and_video = max(min(self.image_sample_size), min(self.video_sample_size))
393
+
394
+ def get_batch(self, idx):
395
+ data_info = self.dataset[idx % len(self.dataset)]
396
+ video_id, text = data_info['file_path'], data_info['text']
397
+
398
+ if data_info.get('type', 'image')=='video':
399
+ if self.data_root is None:
400
+ video_dir = video_id
401
+ else:
402
+ video_dir = os.path.join(self.data_root, video_id)
403
+
404
+ with VideoReader_contextmanager(video_dir, num_threads=2) as video_reader:
405
+ min_sample_n_frames = min(
406
+ self.video_sample_n_frames,
407
+ int(len(video_reader) * (self.video_length_drop_end - self.video_length_drop_start) // self.video_sample_stride)
408
+ )
409
+ if min_sample_n_frames == 0:
410
+ raise ValueError(f"No Frames in video.")
411
+
412
+ video_length = int(self.video_length_drop_end * len(video_reader))
413
+ clip_length = min(video_length, (min_sample_n_frames - 1) * self.video_sample_stride + 1)
414
+ start_idx = random.randint(int(self.video_length_drop_start * video_length), video_length - clip_length) if video_length != clip_length else 0
415
+ batch_index = np.linspace(start_idx, start_idx + clip_length - 1, min_sample_n_frames, dtype=int)
416
+
417
+ try:
418
+ sample_args = (video_reader, batch_index)
419
+ pixel_values = func_timeout(
420
+ VIDEO_READER_TIMEOUT, get_video_reader_batch, args=sample_args
421
+ )
422
+ resized_frames = []
423
+ for i in range(len(pixel_values)):
424
+ frame = pixel_values[i]
425
+ resized_frame = resize_frame(frame, self.larger_side_of_image_and_video)
426
+ resized_frames.append(resized_frame)
427
+ pixel_values = np.array(resized_frames)
428
+ except FunctionTimedOut:
429
+ raise ValueError(f"Read {idx} timeout.")
430
+ except Exception as e:
431
+ raise ValueError(f"Failed to extract frames from video. Error is {e}.")
432
+
433
+ if not self.enable_bucket:
434
+ pixel_values = torch.from_numpy(pixel_values).permute(0, 3, 1, 2).contiguous()
435
+ pixel_values = pixel_values / 255.
436
+ del video_reader
437
+ else:
438
+ pixel_values = pixel_values
439
+
440
+ if not self.enable_bucket:
441
+ pixel_values = self.video_transforms(pixel_values)
442
+
443
+ # Random use no text generation
444
+ if random.random() < self.text_drop_ratio:
445
+ text = ''
446
+
447
+ control_video_id = data_info['control_file_path']
448
+
449
+ if self.data_root is None:
450
+ control_video_id = control_video_id
451
+ else:
452
+ control_video_id = os.path.join(self.data_root, control_video_id)
453
+
454
+ with VideoReader_contextmanager(control_video_id, num_threads=2) as control_video_reader:
455
+ try:
456
+ sample_args = (control_video_reader, batch_index)
457
+ control_pixel_values = func_timeout(
458
+ VIDEO_READER_TIMEOUT, get_video_reader_batch, args=sample_args
459
+ )
460
+ resized_frames = []
461
+ for i in range(len(control_pixel_values)):
462
+ frame = control_pixel_values[i]
463
+ resized_frame = resize_frame(frame, self.larger_side_of_image_and_video)
464
+ resized_frames.append(resized_frame)
465
+ control_pixel_values = np.array(resized_frames)
466
+ except FunctionTimedOut:
467
+ raise ValueError(f"Read {idx} timeout.")
468
+ except Exception as e:
469
+ raise ValueError(f"Failed to extract frames from video. Error is {e}.")
470
+
471
+ if not self.enable_bucket:
472
+ control_pixel_values = torch.from_numpy(control_pixel_values).permute(0, 3, 1, 2).contiguous()
473
+ control_pixel_values = control_pixel_values / 255.
474
+ del control_video_reader
475
+ else:
476
+ control_pixel_values = control_pixel_values
477
+
478
+ if not self.enable_bucket:
479
+ control_pixel_values = self.video_transforms(control_pixel_values)
480
+ return pixel_values, control_pixel_values, text, "video"
481
+ else:
482
+ image_path, text = data_info['file_path'], data_info['text']
483
+ if self.data_root is not None:
484
+ image_path = os.path.join(self.data_root, image_path)
485
+ image = Image.open(image_path).convert('RGB')
486
+ if not self.enable_bucket:
487
+ image = self.image_transforms(image).unsqueeze(0)
488
+ else:
489
+ image = np.expand_dims(np.array(image), 0)
490
+
491
+ if random.random() < self.text_drop_ratio:
492
+ text = ''
493
+
494
+ control_image_id = data_info['control_file_path']
495
+
496
+ if self.data_root is None:
497
+ control_image_id = control_image_id
498
+ else:
499
+ control_image_id = os.path.join(self.data_root, control_image_id)
500
+
501
+ control_image = Image.open(control_image_id).convert('RGB')
502
+ if not self.enable_bucket:
503
+ control_image = self.image_transforms(control_image).unsqueeze(0)
504
+ else:
505
+ control_image = np.expand_dims(np.array(control_image), 0)
506
+ return image, control_image, text, 'image'
507
+
508
+ def __len__(self):
509
+ return self.length
510
+
511
+ def __getitem__(self, idx):
512
+ data_info = self.dataset[idx % len(self.dataset)]
513
+ data_type = data_info.get('type', 'image')
514
+ while True:
515
+ sample = {}
516
+ try:
517
+ data_info_local = self.dataset[idx % len(self.dataset)]
518
+ data_type_local = data_info_local.get('type', 'image')
519
+ if data_type_local != data_type:
520
+ raise ValueError("data_type_local != data_type")
521
+
522
+ pixel_values, control_pixel_values, name, data_type = self.get_batch(idx)
523
+ sample["pixel_values"] = pixel_values
524
+ sample["control_pixel_values"] = control_pixel_values
525
+ sample["text"] = name
526
+ sample["data_type"] = data_type
527
+ sample["idx"] = idx
528
+
529
+ if len(sample) > 0:
530
+ break
531
+ except Exception as e:
532
+ print(e, self.dataset[idx % len(self.dataset)])
533
+ idx = random.randint(0, self.length-1)
534
+
535
+ if self.enable_inpaint and not self.enable_bucket:
536
+ mask = get_random_mask(pixel_values.size())
537
+ mask_pixel_values = pixel_values * (1 - mask) + torch.ones_like(pixel_values) * -1 * mask
538
+ sample["mask_pixel_values"] = mask_pixel_values
539
+ sample["mask"] = mask
540
+
541
+ clip_pixel_values = sample["pixel_values"][0].permute(1, 2, 0).contiguous()
542
+ clip_pixel_values = (clip_pixel_values * 0.5 + 0.5) * 255
543
+ sample["clip_pixel_values"] = clip_pixel_values
544
+
545
+ ref_pixel_values = sample["pixel_values"][0].unsqueeze(0)
546
+ if (mask == 1).all():
547
+ ref_pixel_values = torch.ones_like(ref_pixel_values) * -1
548
+ sample["ref_pixel_values"] = ref_pixel_values
549
+
550
+ return sample
cogvideox/data/dataset_video.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import csv
2
+ import gc
3
+ import io
4
+ import json
5
+ import math
6
+ import os
7
+ import random
8
+ from contextlib import contextmanager
9
+ from threading import Thread
10
+
11
+ import albumentations
12
+ import cv2
13
+ import numpy as np
14
+ import torch
15
+ import torchvision.transforms as transforms
16
+ from decord import VideoReader
17
+ from einops import rearrange
18
+ from func_timeout import FunctionTimedOut, func_timeout
19
+ from PIL import Image
20
+ from torch.utils.data import BatchSampler, Sampler
21
+ from torch.utils.data.dataset import Dataset
22
+
23
+ VIDEO_READER_TIMEOUT = 20
24
+
25
+ def get_random_mask(shape):
26
+ f, c, h, w = shape
27
+
28
+ mask_index = np.random.randint(0, 4)
29
+ mask = torch.zeros((f, 1, h, w), dtype=torch.uint8)
30
+ if mask_index == 0:
31
+ mask[1:, :, :, :] = 1
32
+ elif mask_index == 1:
33
+ mask_frame_index = 1
34
+ mask[mask_frame_index:-mask_frame_index, :, :, :] = 1
35
+ elif mask_index == 2:
36
+ center_x = torch.randint(0, w, (1,)).item()
37
+ center_y = torch.randint(0, h, (1,)).item()
38
+ block_size_x = torch.randint(w // 4, w // 4 * 3, (1,)).item() # 方块的宽度范围
39
+ block_size_y = torch.randint(h // 4, h // 4 * 3, (1,)).item() # 方块的高度范围
40
+
41
+ start_x = max(center_x - block_size_x // 2, 0)
42
+ end_x = min(center_x + block_size_x // 2, w)
43
+ start_y = max(center_y - block_size_y // 2, 0)
44
+ end_y = min(center_y + block_size_y // 2, h)
45
+ mask[:, :, start_y:end_y, start_x:end_x] = 1
46
+ elif mask_index == 3:
47
+ center_x = torch.randint(0, w, (1,)).item()
48
+ center_y = torch.randint(0, h, (1,)).item()
49
+ block_size_x = torch.randint(w // 4, w // 4 * 3, (1,)).item() # 方块的宽度范围
50
+ block_size_y = torch.randint(h // 4, h // 4 * 3, (1,)).item() # 方块的高度范围
51
+
52
+ start_x = max(center_x - block_size_x // 2, 0)
53
+ end_x = min(center_x + block_size_x // 2, w)
54
+ start_y = max(center_y - block_size_y // 2, 0)
55
+ end_y = min(center_y + block_size_y // 2, h)
56
+
57
+ mask_frame_before = np.random.randint(0, f // 2)
58
+ mask_frame_after = np.random.randint(f // 2, f)
59
+ mask[mask_frame_before:mask_frame_after, :, start_y:end_y, start_x:end_x] = 1
60
+ else:
61
+ raise ValueError(f"The mask_index {mask_index} is not define")
62
+ return mask
63
+
64
+
65
+ @contextmanager
66
+ def VideoReader_contextmanager(*args, **kwargs):
67
+ vr = VideoReader(*args, **kwargs)
68
+ try:
69
+ yield vr
70
+ finally:
71
+ del vr
72
+ gc.collect()
73
+
74
+
75
+ def get_video_reader_batch(video_reader, batch_index):
76
+ frames = video_reader.get_batch(batch_index).asnumpy()
77
+ return frames
78
+
79
+
80
+ class WebVid10M(Dataset):
81
+ def __init__(
82
+ self,
83
+ csv_path, video_folder,
84
+ sample_size=256, sample_stride=4, sample_n_frames=16,
85
+ enable_bucket=False, enable_inpaint=False, is_image=False,
86
+ ):
87
+ print(f"loading annotations from {csv_path} ...")
88
+ with open(csv_path, 'r') as csvfile:
89
+ self.dataset = list(csv.DictReader(csvfile))
90
+ self.length = len(self.dataset)
91
+ print(f"data scale: {self.length}")
92
+
93
+ self.video_folder = video_folder
94
+ self.sample_stride = sample_stride
95
+ self.sample_n_frames = sample_n_frames
96
+ self.enable_bucket = enable_bucket
97
+ self.enable_inpaint = enable_inpaint
98
+ self.is_image = is_image
99
+
100
+ sample_size = tuple(sample_size) if not isinstance(sample_size, int) else (sample_size, sample_size)
101
+ self.pixel_transforms = transforms.Compose([
102
+ transforms.Resize(sample_size[0]),
103
+ transforms.CenterCrop(sample_size),
104
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
105
+ ])
106
+
107
+ def get_batch(self, idx):
108
+ video_dict = self.dataset[idx]
109
+ videoid, name, page_dir = video_dict['videoid'], video_dict['name'], video_dict['page_dir']
110
+
111
+ video_dir = os.path.join(self.video_folder, f"{videoid}.mp4")
112
+ video_reader = VideoReader(video_dir)
113
+ video_length = len(video_reader)
114
+
115
+ if not self.is_image:
116
+ clip_length = min(video_length, (self.sample_n_frames - 1) * self.sample_stride + 1)
117
+ start_idx = random.randint(0, video_length - clip_length)
118
+ batch_index = np.linspace(start_idx, start_idx + clip_length - 1, self.sample_n_frames, dtype=int)
119
+ else:
120
+ batch_index = [random.randint(0, video_length - 1)]
121
+
122
+ if not self.enable_bucket:
123
+ pixel_values = torch.from_numpy(video_reader.get_batch(batch_index).asnumpy()).permute(0, 3, 1, 2).contiguous()
124
+ pixel_values = pixel_values / 255.
125
+ del video_reader
126
+ else:
127
+ pixel_values = video_reader.get_batch(batch_index).asnumpy()
128
+
129
+ if self.is_image:
130
+ pixel_values = pixel_values[0]
131
+ return pixel_values, name
132
+
133
+ def __len__(self):
134
+ return self.length
135
+
136
+ def __getitem__(self, idx):
137
+ while True:
138
+ try:
139
+ pixel_values, name = self.get_batch(idx)
140
+ break
141
+
142
+ except Exception as e:
143
+ print("Error info:", e)
144
+ idx = random.randint(0, self.length-1)
145
+
146
+ if not self.enable_bucket:
147
+ pixel_values = self.pixel_transforms(pixel_values)
148
+ if self.enable_inpaint:
149
+ mask = get_random_mask(pixel_values.size())
150
+ mask_pixel_values = pixel_values * (1 - mask) + torch.ones_like(pixel_values) * -1 * mask
151
+ sample = dict(pixel_values=pixel_values, mask_pixel_values=mask_pixel_values, mask=mask, text=name)
152
+ else:
153
+ sample = dict(pixel_values=pixel_values, text=name)
154
+ return sample
155
+
156
+
157
+ class VideoDataset(Dataset):
158
+ def __init__(
159
+ self,
160
+ json_path, video_folder=None,
161
+ sample_size=256, sample_stride=4, sample_n_frames=16,
162
+ enable_bucket=False, enable_inpaint=False
163
+ ):
164
+ print(f"loading annotations from {json_path} ...")
165
+ self.dataset = json.load(open(json_path, 'r'))
166
+ self.length = len(self.dataset)
167
+ print(f"data scale: {self.length}")
168
+
169
+ self.video_folder = video_folder
170
+ self.sample_stride = sample_stride
171
+ self.sample_n_frames = sample_n_frames
172
+ self.enable_bucket = enable_bucket
173
+ self.enable_inpaint = enable_inpaint
174
+
175
+ sample_size = tuple(sample_size) if not isinstance(sample_size, int) else (sample_size, sample_size)
176
+ self.pixel_transforms = transforms.Compose(
177
+ [
178
+ transforms.Resize(sample_size[0]),
179
+ transforms.CenterCrop(sample_size),
180
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
181
+ ]
182
+ )
183
+
184
+ def get_batch(self, idx):
185
+ video_dict = self.dataset[idx]
186
+ video_id, name = video_dict['file_path'], video_dict['text']
187
+
188
+ if self.video_folder is None:
189
+ video_dir = video_id
190
+ else:
191
+ video_dir = os.path.join(self.video_folder, video_id)
192
+
193
+ with VideoReader_contextmanager(video_dir, num_threads=2) as video_reader:
194
+ video_length = len(video_reader)
195
+
196
+ clip_length = min(video_length, (self.sample_n_frames - 1) * self.sample_stride + 1)
197
+ start_idx = random.randint(0, video_length - clip_length)
198
+ batch_index = np.linspace(start_idx, start_idx + clip_length - 1, self.sample_n_frames, dtype=int)
199
+
200
+ try:
201
+ sample_args = (video_reader, batch_index)
202
+ pixel_values = func_timeout(
203
+ VIDEO_READER_TIMEOUT, get_video_reader_batch, args=sample_args
204
+ )
205
+ except FunctionTimedOut:
206
+ raise ValueError(f"Read {idx} timeout.")
207
+ except Exception as e:
208
+ raise ValueError(f"Failed to extract frames from video. Error is {e}.")
209
+
210
+ if not self.enable_bucket:
211
+ pixel_values = torch.from_numpy(pixel_values).permute(0, 3, 1, 2).contiguous()
212
+ pixel_values = pixel_values / 255.
213
+ del video_reader
214
+ else:
215
+ pixel_values = pixel_values
216
+
217
+ return pixel_values, name
218
+
219
+ def __len__(self):
220
+ return self.length
221
+
222
+ def __getitem__(self, idx):
223
+ while True:
224
+ try:
225
+ pixel_values, name = self.get_batch(idx)
226
+ break
227
+
228
+ except Exception as e:
229
+ print("Error info:", e)
230
+ idx = random.randint(0, self.length-1)
231
+
232
+ if not self.enable_bucket:
233
+ pixel_values = self.pixel_transforms(pixel_values)
234
+ if self.enable_inpaint:
235
+ mask = get_random_mask(pixel_values.size())
236
+ mask_pixel_values = pixel_values * (1 - mask) + torch.ones_like(pixel_values) * -1 * mask
237
+ sample = dict(pixel_values=pixel_values, mask_pixel_values=mask_pixel_values, mask=mask, text=name)
238
+ else:
239
+ sample = dict(pixel_values=pixel_values, text=name)
240
+ return sample
241
+
242
+
243
+ if __name__ == "__main__":
244
+ if 1:
245
+ dataset = VideoDataset(
246
+ json_path="/home/zhoumo.xjq/disk3/datasets/webvidval/results_2M_val.json",
247
+ sample_size=256,
248
+ sample_stride=4, sample_n_frames=16,
249
+ )
250
+
251
+ if 0:
252
+ dataset = WebVid10M(
253
+ csv_path="/mnt/petrelfs/guoyuwei/projects/datasets/webvid/results_2M_val.csv",
254
+ video_folder="/mnt/petrelfs/guoyuwei/projects/datasets/webvid/2M_val",
255
+ sample_size=256,
256
+ sample_stride=4, sample_n_frames=16,
257
+ is_image=False,
258
+ )
259
+
260
+ dataloader = torch.utils.data.DataLoader(dataset, batch_size=4, num_workers=0,)
261
+ for idx, batch in enumerate(dataloader):
262
+ print(batch["pixel_values"].shape, len(batch["text"]))
cogvideox/models/autoencoder_magvit.py ADDED
@@ -0,0 +1,1296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 The CogVideoX team, Tsinghua University & ZhipuAI and The HuggingFace Team.
2
+ # All rights reserved.
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 typing import Optional, Tuple, Union
17
+
18
+ import numpy as np
19
+ import torch
20
+ import torch.nn as nn
21
+ import torch.nn.functional as F
22
+
23
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
24
+ from diffusers.loaders.single_file_model import FromOriginalModelMixin
25
+ from diffusers.utils import logging
26
+ from diffusers.utils.accelerate_utils import apply_forward_hook
27
+ from diffusers.models.activations import get_activation
28
+ from diffusers.models.downsampling import CogVideoXDownsample3D
29
+ from diffusers.models.modeling_outputs import AutoencoderKLOutput
30
+ from diffusers.models.modeling_utils import ModelMixin
31
+ from diffusers.models.upsampling import CogVideoXUpsample3D
32
+ from diffusers.models.autoencoders.vae import DecoderOutput, DiagonalGaussianDistribution
33
+
34
+
35
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
36
+
37
+
38
+ class CogVideoXSafeConv3d(nn.Conv3d):
39
+ r"""
40
+ A 3D convolution layer that splits the input tensor into smaller parts to avoid OOM in CogVideoX Model.
41
+ """
42
+
43
+ def forward(self, input: torch.Tensor) -> torch.Tensor:
44
+ memory_count = torch.prod(torch.tensor(input.shape)).item() * 2 / 1024**3
45
+
46
+ # Set to 2GB, suitable for CuDNN
47
+ if memory_count > 2:
48
+ kernel_size = self.kernel_size[0]
49
+ part_num = int(memory_count / 2) + 1
50
+ input_chunks = torch.chunk(input, part_num, dim=2)
51
+
52
+ if kernel_size > 1:
53
+ input_chunks = [input_chunks[0]] + [
54
+ torch.cat((input_chunks[i - 1][:, :, -kernel_size + 1 :], input_chunks[i]), dim=2)
55
+ for i in range(1, len(input_chunks))
56
+ ]
57
+
58
+ output_chunks = []
59
+ for input_chunk in input_chunks:
60
+ output_chunks.append(super().forward(input_chunk))
61
+ output = torch.cat(output_chunks, dim=2)
62
+ return output
63
+ else:
64
+ return super().forward(input)
65
+
66
+
67
+ class CogVideoXCausalConv3d(nn.Module):
68
+ r"""A 3D causal convolution layer that pads the input tensor to ensure causality in CogVideoX Model.
69
+
70
+ Args:
71
+ in_channels (`int`): Number of channels in the input tensor.
72
+ out_channels (`int`): Number of output channels produced by the convolution.
73
+ kernel_size (`int` or `Tuple[int, int, int]`): Kernel size of the convolutional kernel.
74
+ stride (`int`, defaults to `1`): Stride of the convolution.
75
+ dilation (`int`, defaults to `1`): Dilation rate of the convolution.
76
+ pad_mode (`str`, defaults to `"constant"`): Padding mode.
77
+ """
78
+
79
+ def __init__(
80
+ self,
81
+ in_channels: int,
82
+ out_channels: int,
83
+ kernel_size: Union[int, Tuple[int, int, int]],
84
+ stride: int = 1,
85
+ dilation: int = 1,
86
+ pad_mode: str = "constant",
87
+ ):
88
+ super().__init__()
89
+
90
+ if isinstance(kernel_size, int):
91
+ kernel_size = (kernel_size,) * 3
92
+
93
+ time_kernel_size, height_kernel_size, width_kernel_size = kernel_size
94
+
95
+ self.pad_mode = pad_mode
96
+ time_pad = dilation * (time_kernel_size - 1) + (1 - stride)
97
+ height_pad = height_kernel_size // 2
98
+ width_pad = width_kernel_size // 2
99
+
100
+ self.height_pad = height_pad
101
+ self.width_pad = width_pad
102
+ self.time_pad = time_pad
103
+ self.time_causal_padding = (width_pad, width_pad, height_pad, height_pad, time_pad, 0)
104
+
105
+ self.temporal_dim = 2
106
+ self.time_kernel_size = time_kernel_size
107
+
108
+ stride = (stride, 1, 1)
109
+ dilation = (dilation, 1, 1)
110
+ self.conv = CogVideoXSafeConv3d(
111
+ in_channels=in_channels,
112
+ out_channels=out_channels,
113
+ kernel_size=kernel_size,
114
+ stride=stride,
115
+ dilation=dilation,
116
+ )
117
+
118
+ self.conv_cache = None
119
+
120
+ def fake_context_parallel_forward(self, inputs: torch.Tensor) -> torch.Tensor:
121
+ kernel_size = self.time_kernel_size
122
+ if kernel_size > 1:
123
+ cached_inputs = (
124
+ [self.conv_cache] if self.conv_cache is not None else [inputs[:, :, :1]] * (kernel_size - 1)
125
+ )
126
+ inputs = torch.cat(cached_inputs + [inputs], dim=2)
127
+ return inputs
128
+
129
+ def _clear_fake_context_parallel_cache(self):
130
+ del self.conv_cache
131
+ self.conv_cache = None
132
+
133
+ def forward(self, inputs: torch.Tensor) -> torch.Tensor:
134
+ inputs = self.fake_context_parallel_forward(inputs)
135
+
136
+ self._clear_fake_context_parallel_cache()
137
+ # Note: we could move these to the cpu for a lower maximum memory usage but its only a few
138
+ # hundred megabytes and so let's not do it for now
139
+ self.conv_cache = inputs[:, :, -self.time_kernel_size + 1 :].clone()
140
+
141
+ padding_2d = (self.width_pad, self.width_pad, self.height_pad, self.height_pad)
142
+ inputs = F.pad(inputs, padding_2d, mode="constant", value=0)
143
+
144
+ output = self.conv(inputs)
145
+ return output
146
+
147
+
148
+ class CogVideoXSpatialNorm3D(nn.Module):
149
+ r"""
150
+ Spatially conditioned normalization as defined in https://arxiv.org/abs/2209.09002. This implementation is specific
151
+ to 3D-video like data.
152
+
153
+ CogVideoXSafeConv3d is used instead of nn.Conv3d to avoid OOM in CogVideoX Model.
154
+
155
+ Args:
156
+ f_channels (`int`):
157
+ The number of channels for input to group normalization layer, and output of the spatial norm layer.
158
+ zq_channels (`int`):
159
+ The number of channels for the quantized vector as described in the paper.
160
+ groups (`int`):
161
+ Number of groups to separate the channels into for group normalization.
162
+ """
163
+
164
+ def __init__(
165
+ self,
166
+ f_channels: int,
167
+ zq_channels: int,
168
+ groups: int = 32,
169
+ ):
170
+ super().__init__()
171
+ self.norm_layer = nn.GroupNorm(num_channels=f_channels, num_groups=groups, eps=1e-6, affine=True)
172
+ self.conv_y = CogVideoXCausalConv3d(zq_channels, f_channels, kernel_size=1, stride=1)
173
+ self.conv_b = CogVideoXCausalConv3d(zq_channels, f_channels, kernel_size=1, stride=1)
174
+
175
+ def forward(self, f: torch.Tensor, zq: torch.Tensor) -> torch.Tensor:
176
+ if f.shape[2] > 1 and f.shape[2] % 2 == 1:
177
+ f_first, f_rest = f[:, :, :1], f[:, :, 1:]
178
+ f_first_size, f_rest_size = f_first.shape[-3:], f_rest.shape[-3:]
179
+ z_first, z_rest = zq[:, :, :1], zq[:, :, 1:]
180
+ z_first = F.interpolate(z_first, size=f_first_size)
181
+ z_rest = F.interpolate(z_rest, size=f_rest_size)
182
+ zq = torch.cat([z_first, z_rest], dim=2)
183
+ else:
184
+ zq = F.interpolate(zq, size=f.shape[-3:])
185
+
186
+ norm_f = self.norm_layer(f)
187
+ new_f = norm_f * self.conv_y(zq) + self.conv_b(zq)
188
+ return new_f
189
+
190
+
191
+ class CogVideoXResnetBlock3D(nn.Module):
192
+ r"""
193
+ A 3D ResNet block used in the CogVideoX model.
194
+
195
+ Args:
196
+ in_channels (`int`):
197
+ Number of input channels.
198
+ out_channels (`int`, *optional*):
199
+ Number of output channels. If None, defaults to `in_channels`.
200
+ dropout (`float`, defaults to `0.0`):
201
+ Dropout rate.
202
+ temb_channels (`int`, defaults to `512`):
203
+ Number of time embedding channels.
204
+ groups (`int`, defaults to `32`):
205
+ Number of groups to separate the channels into for group normalization.
206
+ eps (`float`, defaults to `1e-6`):
207
+ Epsilon value for normalization layers.
208
+ non_linearity (`str`, defaults to `"swish"`):
209
+ Activation function to use.
210
+ conv_shortcut (bool, defaults to `False`):
211
+ Whether or not to use a convolution shortcut.
212
+ spatial_norm_dim (`int`, *optional*):
213
+ The dimension to use for spatial norm if it is to be used instead of group norm.
214
+ pad_mode (str, defaults to `"first"`):
215
+ Padding mode.
216
+ """
217
+
218
+ def __init__(
219
+ self,
220
+ in_channels: int,
221
+ out_channels: Optional[int] = None,
222
+ dropout: float = 0.0,
223
+ temb_channels: int = 512,
224
+ groups: int = 32,
225
+ eps: float = 1e-6,
226
+ non_linearity: str = "swish",
227
+ conv_shortcut: bool = False,
228
+ spatial_norm_dim: Optional[int] = None,
229
+ pad_mode: str = "first",
230
+ ):
231
+ super().__init__()
232
+
233
+ out_channels = out_channels or in_channels
234
+
235
+ self.in_channels = in_channels
236
+ self.out_channels = out_channels
237
+ self.nonlinearity = get_activation(non_linearity)
238
+ self.use_conv_shortcut = conv_shortcut
239
+
240
+ if spatial_norm_dim is None:
241
+ self.norm1 = nn.GroupNorm(num_channels=in_channels, num_groups=groups, eps=eps)
242
+ self.norm2 = nn.GroupNorm(num_channels=out_channels, num_groups=groups, eps=eps)
243
+ else:
244
+ self.norm1 = CogVideoXSpatialNorm3D(
245
+ f_channels=in_channels,
246
+ zq_channels=spatial_norm_dim,
247
+ groups=groups,
248
+ )
249
+ self.norm2 = CogVideoXSpatialNorm3D(
250
+ f_channels=out_channels,
251
+ zq_channels=spatial_norm_dim,
252
+ groups=groups,
253
+ )
254
+
255
+ self.conv1 = CogVideoXCausalConv3d(
256
+ in_channels=in_channels, out_channels=out_channels, kernel_size=3, pad_mode=pad_mode
257
+ )
258
+
259
+ if temb_channels > 0:
260
+ self.temb_proj = nn.Linear(in_features=temb_channels, out_features=out_channels)
261
+
262
+ self.dropout = nn.Dropout(dropout)
263
+ self.conv2 = CogVideoXCausalConv3d(
264
+ in_channels=out_channels, out_channels=out_channels, kernel_size=3, pad_mode=pad_mode
265
+ )
266
+
267
+ if self.in_channels != self.out_channels:
268
+ if self.use_conv_shortcut:
269
+ self.conv_shortcut = CogVideoXCausalConv3d(
270
+ in_channels=in_channels, out_channels=out_channels, kernel_size=3, pad_mode=pad_mode
271
+ )
272
+ else:
273
+ self.conv_shortcut = CogVideoXSafeConv3d(
274
+ in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1, padding=0
275
+ )
276
+
277
+ def forward(
278
+ self,
279
+ inputs: torch.Tensor,
280
+ temb: Optional[torch.Tensor] = None,
281
+ zq: Optional[torch.Tensor] = None,
282
+ ) -> torch.Tensor:
283
+ hidden_states = inputs
284
+
285
+ if zq is not None:
286
+ hidden_states = self.norm1(hidden_states, zq)
287
+ else:
288
+ hidden_states = self.norm1(hidden_states)
289
+
290
+ hidden_states = self.nonlinearity(hidden_states)
291
+ hidden_states = self.conv1(hidden_states)
292
+
293
+ if temb is not None:
294
+ hidden_states = hidden_states + self.temb_proj(self.nonlinearity(temb))[:, :, None, None, None]
295
+
296
+ if zq is not None:
297
+ hidden_states = self.norm2(hidden_states, zq)
298
+ else:
299
+ hidden_states = self.norm2(hidden_states)
300
+
301
+ hidden_states = self.nonlinearity(hidden_states)
302
+ hidden_states = self.dropout(hidden_states)
303
+ hidden_states = self.conv2(hidden_states)
304
+
305
+ if self.in_channels != self.out_channels:
306
+ inputs = self.conv_shortcut(inputs)
307
+
308
+ hidden_states = hidden_states + inputs
309
+ return hidden_states
310
+
311
+
312
+ class CogVideoXDownBlock3D(nn.Module):
313
+ r"""
314
+ A downsampling block used in the CogVideoX model.
315
+
316
+ Args:
317
+ in_channels (`int`):
318
+ Number of input channels.
319
+ out_channels (`int`, *optional*):
320
+ Number of output channels. If None, defaults to `in_channels`.
321
+ temb_channels (`int`, defaults to `512`):
322
+ Number of time embedding channels.
323
+ num_layers (`int`, defaults to `1`):
324
+ Number of resnet layers.
325
+ dropout (`float`, defaults to `0.0`):
326
+ Dropout rate.
327
+ resnet_eps (`float`, defaults to `1e-6`):
328
+ Epsilon value for normalization layers.
329
+ resnet_act_fn (`str`, defaults to `"swish"`):
330
+ Activation function to use.
331
+ resnet_groups (`int`, defaults to `32`):
332
+ Number of groups to separate the channels into for group normalization.
333
+ add_downsample (`bool`, defaults to `True`):
334
+ Whether or not to use a downsampling layer. If not used, output dimension would be same as input dimension.
335
+ compress_time (`bool`, defaults to `False`):
336
+ Whether or not to downsample across temporal dimension.
337
+ pad_mode (str, defaults to `"first"`):
338
+ Padding mode.
339
+ """
340
+
341
+ _supports_gradient_checkpointing = True
342
+
343
+ def __init__(
344
+ self,
345
+ in_channels: int,
346
+ out_channels: int,
347
+ temb_channels: int,
348
+ dropout: float = 0.0,
349
+ num_layers: int = 1,
350
+ resnet_eps: float = 1e-6,
351
+ resnet_act_fn: str = "swish",
352
+ resnet_groups: int = 32,
353
+ add_downsample: bool = True,
354
+ downsample_padding: int = 0,
355
+ compress_time: bool = False,
356
+ pad_mode: str = "first",
357
+ ):
358
+ super().__init__()
359
+
360
+ resnets = []
361
+ for i in range(num_layers):
362
+ in_channel = in_channels if i == 0 else out_channels
363
+ resnets.append(
364
+ CogVideoXResnetBlock3D(
365
+ in_channels=in_channel,
366
+ out_channels=out_channels,
367
+ dropout=dropout,
368
+ temb_channels=temb_channels,
369
+ groups=resnet_groups,
370
+ eps=resnet_eps,
371
+ non_linearity=resnet_act_fn,
372
+ pad_mode=pad_mode,
373
+ )
374
+ )
375
+
376
+ self.resnets = nn.ModuleList(resnets)
377
+ self.downsamplers = None
378
+
379
+ if add_downsample:
380
+ self.downsamplers = nn.ModuleList(
381
+ [
382
+ CogVideoXDownsample3D(
383
+ out_channels, out_channels, padding=downsample_padding, compress_time=compress_time
384
+ )
385
+ ]
386
+ )
387
+
388
+ self.gradient_checkpointing = False
389
+
390
+ def forward(
391
+ self,
392
+ hidden_states: torch.Tensor,
393
+ temb: Optional[torch.Tensor] = None,
394
+ zq: Optional[torch.Tensor] = None,
395
+ ) -> torch.Tensor:
396
+ for resnet in self.resnets:
397
+ if self.training and self.gradient_checkpointing:
398
+
399
+ def create_custom_forward(module):
400
+ def create_forward(*inputs):
401
+ return module(*inputs)
402
+
403
+ return create_forward
404
+
405
+ hidden_states = torch.utils.checkpoint.checkpoint(
406
+ create_custom_forward(resnet), hidden_states, temb, zq
407
+ )
408
+ else:
409
+ hidden_states = resnet(hidden_states, temb, zq)
410
+
411
+ if self.downsamplers is not None:
412
+ for downsampler in self.downsamplers:
413
+ hidden_states = downsampler(hidden_states)
414
+
415
+ return hidden_states
416
+
417
+
418
+ class CogVideoXMidBlock3D(nn.Module):
419
+ r"""
420
+ A middle block used in the CogVideoX model.
421
+
422
+ Args:
423
+ in_channels (`int`):
424
+ Number of input channels.
425
+ temb_channels (`int`, defaults to `512`):
426
+ Number of time embedding channels.
427
+ dropout (`float`, defaults to `0.0`):
428
+ Dropout rate.
429
+ num_layers (`int`, defaults to `1`):
430
+ Number of resnet layers.
431
+ resnet_eps (`float`, defaults to `1e-6`):
432
+ Epsilon value for normalization layers.
433
+ resnet_act_fn (`str`, defaults to `"swish"`):
434
+ Activation function to use.
435
+ resnet_groups (`int`, defaults to `32`):
436
+ Number of groups to separate the channels into for group normalization.
437
+ spatial_norm_dim (`int`, *optional*):
438
+ The dimension to use for spatial norm if it is to be used instead of group norm.
439
+ pad_mode (str, defaults to `"first"`):
440
+ Padding mode.
441
+ """
442
+
443
+ _supports_gradient_checkpointing = True
444
+
445
+ def __init__(
446
+ self,
447
+ in_channels: int,
448
+ temb_channels: int,
449
+ dropout: float = 0.0,
450
+ num_layers: int = 1,
451
+ resnet_eps: float = 1e-6,
452
+ resnet_act_fn: str = "swish",
453
+ resnet_groups: int = 32,
454
+ spatial_norm_dim: Optional[int] = None,
455
+ pad_mode: str = "first",
456
+ ):
457
+ super().__init__()
458
+
459
+ resnets = []
460
+ for _ in range(num_layers):
461
+ resnets.append(
462
+ CogVideoXResnetBlock3D(
463
+ in_channels=in_channels,
464
+ out_channels=in_channels,
465
+ dropout=dropout,
466
+ temb_channels=temb_channels,
467
+ groups=resnet_groups,
468
+ eps=resnet_eps,
469
+ spatial_norm_dim=spatial_norm_dim,
470
+ non_linearity=resnet_act_fn,
471
+ pad_mode=pad_mode,
472
+ )
473
+ )
474
+ self.resnets = nn.ModuleList(resnets)
475
+
476
+ self.gradient_checkpointing = False
477
+
478
+ def forward(
479
+ self,
480
+ hidden_states: torch.Tensor,
481
+ temb: Optional[torch.Tensor] = None,
482
+ zq: Optional[torch.Tensor] = None,
483
+ ) -> torch.Tensor:
484
+ for resnet in self.resnets:
485
+ if self.training and self.gradient_checkpointing:
486
+
487
+ def create_custom_forward(module):
488
+ def create_forward(*inputs):
489
+ return module(*inputs)
490
+
491
+ return create_forward
492
+
493
+ hidden_states = torch.utils.checkpoint.checkpoint(
494
+ create_custom_forward(resnet), hidden_states, temb, zq
495
+ )
496
+ else:
497
+ hidden_states = resnet(hidden_states, temb, zq)
498
+
499
+ return hidden_states
500
+
501
+
502
+ class CogVideoXUpBlock3D(nn.Module):
503
+ r"""
504
+ An upsampling block used in the CogVideoX model.
505
+
506
+ Args:
507
+ in_channels (`int`):
508
+ Number of input channels.
509
+ out_channels (`int`, *optional*):
510
+ Number of output channels. If None, defaults to `in_channels`.
511
+ temb_channels (`int`, defaults to `512`):
512
+ Number of time embedding channels.
513
+ dropout (`float`, defaults to `0.0`):
514
+ Dropout rate.
515
+ num_layers (`int`, defaults to `1`):
516
+ Number of resnet layers.
517
+ resnet_eps (`float`, defaults to `1e-6`):
518
+ Epsilon value for normalization layers.
519
+ resnet_act_fn (`str`, defaults to `"swish"`):
520
+ Activation function to use.
521
+ resnet_groups (`int`, defaults to `32`):
522
+ Number of groups to separate the channels into for group normalization.
523
+ spatial_norm_dim (`int`, defaults to `16`):
524
+ The dimension to use for spatial norm if it is to be used instead of group norm.
525
+ add_upsample (`bool`, defaults to `True`):
526
+ Whether or not to use a upsampling layer. If not used, output dimension would be same as input dimension.
527
+ compress_time (`bool`, defaults to `False`):
528
+ Whether or not to downsample across temporal dimension.
529
+ pad_mode (str, defaults to `"first"`):
530
+ Padding mode.
531
+ """
532
+
533
+ def __init__(
534
+ self,
535
+ in_channels: int,
536
+ out_channels: int,
537
+ temb_channels: int,
538
+ dropout: float = 0.0,
539
+ num_layers: int = 1,
540
+ resnet_eps: float = 1e-6,
541
+ resnet_act_fn: str = "swish",
542
+ resnet_groups: int = 32,
543
+ spatial_norm_dim: int = 16,
544
+ add_upsample: bool = True,
545
+ upsample_padding: int = 1,
546
+ compress_time: bool = False,
547
+ pad_mode: str = "first",
548
+ ):
549
+ super().__init__()
550
+
551
+ resnets = []
552
+ for i in range(num_layers):
553
+ in_channel = in_channels if i == 0 else out_channels
554
+ resnets.append(
555
+ CogVideoXResnetBlock3D(
556
+ in_channels=in_channel,
557
+ out_channels=out_channels,
558
+ dropout=dropout,
559
+ temb_channels=temb_channels,
560
+ groups=resnet_groups,
561
+ eps=resnet_eps,
562
+ non_linearity=resnet_act_fn,
563
+ spatial_norm_dim=spatial_norm_dim,
564
+ pad_mode=pad_mode,
565
+ )
566
+ )
567
+
568
+ self.resnets = nn.ModuleList(resnets)
569
+ self.upsamplers = None
570
+
571
+ if add_upsample:
572
+ self.upsamplers = nn.ModuleList(
573
+ [
574
+ CogVideoXUpsample3D(
575
+ out_channels, out_channels, padding=upsample_padding, compress_time=compress_time
576
+ )
577
+ ]
578
+ )
579
+
580
+ self.gradient_checkpointing = False
581
+
582
+ def forward(
583
+ self,
584
+ hidden_states: torch.Tensor,
585
+ temb: Optional[torch.Tensor] = None,
586
+ zq: Optional[torch.Tensor] = None,
587
+ ) -> torch.Tensor:
588
+ r"""Forward method of the `CogVideoXUpBlock3D` class."""
589
+ for resnet in self.resnets:
590
+ if self.training and self.gradient_checkpointing:
591
+
592
+ def create_custom_forward(module):
593
+ def create_forward(*inputs):
594
+ return module(*inputs)
595
+
596
+ return create_forward
597
+
598
+ hidden_states = torch.utils.checkpoint.checkpoint(
599
+ create_custom_forward(resnet), hidden_states, temb, zq
600
+ )
601
+ else:
602
+ hidden_states = resnet(hidden_states, temb, zq)
603
+
604
+ if self.upsamplers is not None:
605
+ for upsampler in self.upsamplers:
606
+ hidden_states = upsampler(hidden_states)
607
+
608
+ return hidden_states
609
+
610
+
611
+ class CogVideoXEncoder3D(nn.Module):
612
+ r"""
613
+ The `CogVideoXEncoder3D` layer of a variational autoencoder that encodes its input into a latent representation.
614
+
615
+ Args:
616
+ in_channels (`int`, *optional*, defaults to 3):
617
+ The number of input channels.
618
+ out_channels (`int`, *optional*, defaults to 3):
619
+ The number of output channels.
620
+ down_block_types (`Tuple[str, ...]`, *optional*, defaults to `("DownEncoderBlock2D",)`):
621
+ The types of down blocks to use. See `~diffusers.models.unet_2d_blocks.get_down_block` for available
622
+ options.
623
+ block_out_channels (`Tuple[int, ...]`, *optional*, defaults to `(64,)`):
624
+ The number of output channels for each block.
625
+ act_fn (`str`, *optional*, defaults to `"silu"`):
626
+ The activation function to use. See `~diffusers.models.activations.get_activation` for available options.
627
+ layers_per_block (`int`, *optional*, defaults to 2):
628
+ The number of layers per block.
629
+ norm_num_groups (`int`, *optional*, defaults to 32):
630
+ The number of groups for normalization.
631
+ """
632
+
633
+ _supports_gradient_checkpointing = True
634
+
635
+ def __init__(
636
+ self,
637
+ in_channels: int = 3,
638
+ out_channels: int = 16,
639
+ down_block_types: Tuple[str, ...] = (
640
+ "CogVideoXDownBlock3D",
641
+ "CogVideoXDownBlock3D",
642
+ "CogVideoXDownBlock3D",
643
+ "CogVideoXDownBlock3D",
644
+ ),
645
+ block_out_channels: Tuple[int, ...] = (128, 256, 256, 512),
646
+ layers_per_block: int = 3,
647
+ act_fn: str = "silu",
648
+ norm_eps: float = 1e-6,
649
+ norm_num_groups: int = 32,
650
+ dropout: float = 0.0,
651
+ pad_mode: str = "first",
652
+ temporal_compression_ratio: float = 4,
653
+ ):
654
+ super().__init__()
655
+
656
+ # log2 of temporal_compress_times
657
+ temporal_compress_level = int(np.log2(temporal_compression_ratio))
658
+
659
+ self.conv_in = CogVideoXCausalConv3d(in_channels, block_out_channels[0], kernel_size=3, pad_mode=pad_mode)
660
+ self.down_blocks = nn.ModuleList([])
661
+
662
+ # down blocks
663
+ output_channel = block_out_channels[0]
664
+ for i, down_block_type in enumerate(down_block_types):
665
+ input_channel = output_channel
666
+ output_channel = block_out_channels[i]
667
+ is_final_block = i == len(block_out_channels) - 1
668
+ compress_time = i < temporal_compress_level
669
+
670
+ if down_block_type == "CogVideoXDownBlock3D":
671
+ down_block = CogVideoXDownBlock3D(
672
+ in_channels=input_channel,
673
+ out_channels=output_channel,
674
+ temb_channels=0,
675
+ dropout=dropout,
676
+ num_layers=layers_per_block,
677
+ resnet_eps=norm_eps,
678
+ resnet_act_fn=act_fn,
679
+ resnet_groups=norm_num_groups,
680
+ add_downsample=not is_final_block,
681
+ compress_time=compress_time,
682
+ )
683
+ else:
684
+ raise ValueError("Invalid `down_block_type` encountered. Must be `CogVideoXDownBlock3D`")
685
+
686
+ self.down_blocks.append(down_block)
687
+
688
+ # mid block
689
+ self.mid_block = CogVideoXMidBlock3D(
690
+ in_channels=block_out_channels[-1],
691
+ temb_channels=0,
692
+ dropout=dropout,
693
+ num_layers=2,
694
+ resnet_eps=norm_eps,
695
+ resnet_act_fn=act_fn,
696
+ resnet_groups=norm_num_groups,
697
+ pad_mode=pad_mode,
698
+ )
699
+
700
+ self.norm_out = nn.GroupNorm(norm_num_groups, block_out_channels[-1], eps=1e-6)
701
+ self.conv_act = nn.SiLU()
702
+ self.conv_out = CogVideoXCausalConv3d(
703
+ block_out_channels[-1], 2 * out_channels, kernel_size=3, pad_mode=pad_mode
704
+ )
705
+
706
+ self.gradient_checkpointing = False
707
+
708
+ def forward(self, sample: torch.Tensor, temb: Optional[torch.Tensor] = None) -> torch.Tensor:
709
+ r"""The forward method of the `CogVideoXEncoder3D` class."""
710
+ hidden_states = self.conv_in(sample)
711
+
712
+ if self.training and self.gradient_checkpointing:
713
+
714
+ def create_custom_forward(module):
715
+ def custom_forward(*inputs):
716
+ return module(*inputs)
717
+
718
+ return custom_forward
719
+
720
+ # 1. Down
721
+ for down_block in self.down_blocks:
722
+ hidden_states = torch.utils.checkpoint.checkpoint(
723
+ create_custom_forward(down_block), hidden_states, temb, None
724
+ )
725
+
726
+ # 2. Mid
727
+ hidden_states = torch.utils.checkpoint.checkpoint(
728
+ create_custom_forward(self.mid_block), hidden_states, temb, None
729
+ )
730
+ else:
731
+ # 1. Down
732
+ for down_block in self.down_blocks:
733
+ hidden_states = down_block(hidden_states, temb, None)
734
+
735
+ # 2. Mid
736
+ hidden_states = self.mid_block(hidden_states, temb, None)
737
+
738
+ # 3. Post-process
739
+ hidden_states = self.norm_out(hidden_states)
740
+ hidden_states = self.conv_act(hidden_states)
741
+ hidden_states = self.conv_out(hidden_states)
742
+ return hidden_states
743
+
744
+
745
+ class CogVideoXDecoder3D(nn.Module):
746
+ r"""
747
+ The `CogVideoXDecoder3D` layer of a variational autoencoder that decodes its latent representation into an output
748
+ sample.
749
+
750
+ Args:
751
+ in_channels (`int`, *optional*, defaults to 3):
752
+ The number of input channels.
753
+ out_channels (`int`, *optional*, defaults to 3):
754
+ The number of output channels.
755
+ up_block_types (`Tuple[str, ...]`, *optional*, defaults to `("UpDecoderBlock2D",)`):
756
+ The types of up blocks to use. See `~diffusers.models.unet_2d_blocks.get_up_block` for available options.
757
+ block_out_channels (`Tuple[int, ...]`, *optional*, defaults to `(64,)`):
758
+ The number of output channels for each block.
759
+ act_fn (`str`, *optional*, defaults to `"silu"`):
760
+ The activation function to use. See `~diffusers.models.activations.get_activation` for available options.
761
+ layers_per_block (`int`, *optional*, defaults to 2):
762
+ The number of layers per block.
763
+ norm_num_groups (`int`, *optional*, defaults to 32):
764
+ The number of groups for normalization.
765
+ """
766
+
767
+ _supports_gradient_checkpointing = True
768
+
769
+ def __init__(
770
+ self,
771
+ in_channels: int = 16,
772
+ out_channels: int = 3,
773
+ up_block_types: Tuple[str, ...] = (
774
+ "CogVideoXUpBlock3D",
775
+ "CogVideoXUpBlock3D",
776
+ "CogVideoXUpBlock3D",
777
+ "CogVideoXUpBlock3D",
778
+ ),
779
+ block_out_channels: Tuple[int, ...] = (128, 256, 256, 512),
780
+ layers_per_block: int = 3,
781
+ act_fn: str = "silu",
782
+ norm_eps: float = 1e-6,
783
+ norm_num_groups: int = 32,
784
+ dropout: float = 0.0,
785
+ pad_mode: str = "first",
786
+ temporal_compression_ratio: float = 4,
787
+ ):
788
+ super().__init__()
789
+
790
+ reversed_block_out_channels = list(reversed(block_out_channels))
791
+
792
+ self.conv_in = CogVideoXCausalConv3d(
793
+ in_channels, reversed_block_out_channels[0], kernel_size=3, pad_mode=pad_mode
794
+ )
795
+
796
+ # mid block
797
+ self.mid_block = CogVideoXMidBlock3D(
798
+ in_channels=reversed_block_out_channels[0],
799
+ temb_channels=0,
800
+ num_layers=2,
801
+ resnet_eps=norm_eps,
802
+ resnet_act_fn=act_fn,
803
+ resnet_groups=norm_num_groups,
804
+ spatial_norm_dim=in_channels,
805
+ pad_mode=pad_mode,
806
+ )
807
+
808
+ # up blocks
809
+ self.up_blocks = nn.ModuleList([])
810
+
811
+ output_channel = reversed_block_out_channels[0]
812
+ temporal_compress_level = int(np.log2(temporal_compression_ratio))
813
+
814
+ for i, up_block_type in enumerate(up_block_types):
815
+ prev_output_channel = output_channel
816
+ output_channel = reversed_block_out_channels[i]
817
+ is_final_block = i == len(block_out_channels) - 1
818
+ compress_time = i < temporal_compress_level
819
+
820
+ if up_block_type == "CogVideoXUpBlock3D":
821
+ up_block = CogVideoXUpBlock3D(
822
+ in_channels=prev_output_channel,
823
+ out_channels=output_channel,
824
+ temb_channels=0,
825
+ dropout=dropout,
826
+ num_layers=layers_per_block + 1,
827
+ resnet_eps=norm_eps,
828
+ resnet_act_fn=act_fn,
829
+ resnet_groups=norm_num_groups,
830
+ spatial_norm_dim=in_channels,
831
+ add_upsample=not is_final_block,
832
+ compress_time=compress_time,
833
+ pad_mode=pad_mode,
834
+ )
835
+ prev_output_channel = output_channel
836
+ else:
837
+ raise ValueError("Invalid `up_block_type` encountered. Must be `CogVideoXUpBlock3D`")
838
+
839
+ self.up_blocks.append(up_block)
840
+
841
+ self.norm_out = CogVideoXSpatialNorm3D(reversed_block_out_channels[-1], in_channels, groups=norm_num_groups)
842
+ self.conv_act = nn.SiLU()
843
+ self.conv_out = CogVideoXCausalConv3d(
844
+ reversed_block_out_channels[-1], out_channels, kernel_size=3, pad_mode=pad_mode
845
+ )
846
+
847
+ self.gradient_checkpointing = False
848
+
849
+ def forward(self, sample: torch.Tensor, temb: Optional[torch.Tensor] = None) -> torch.Tensor:
850
+ r"""The forward method of the `CogVideoXDecoder3D` class."""
851
+ hidden_states = self.conv_in(sample)
852
+
853
+ if self.training and self.gradient_checkpointing:
854
+
855
+ def create_custom_forward(module):
856
+ def custom_forward(*inputs):
857
+ return module(*inputs)
858
+
859
+ return custom_forward
860
+
861
+ # 1. Mid
862
+ hidden_states = torch.utils.checkpoint.checkpoint(
863
+ create_custom_forward(self.mid_block), hidden_states, temb, sample
864
+ )
865
+
866
+ # 2. Up
867
+ for up_block in self.up_blocks:
868
+ hidden_states = torch.utils.checkpoint.checkpoint(
869
+ create_custom_forward(up_block), hidden_states, temb, sample
870
+ )
871
+ else:
872
+ # 1. Mid
873
+ hidden_states = self.mid_block(hidden_states, temb, sample)
874
+
875
+ # 2. Up
876
+ for up_block in self.up_blocks:
877
+ hidden_states = up_block(hidden_states, temb, sample)
878
+
879
+ # 3. Post-process
880
+ hidden_states = self.norm_out(hidden_states, sample)
881
+ hidden_states = self.conv_act(hidden_states)
882
+ hidden_states = self.conv_out(hidden_states)
883
+ return hidden_states
884
+
885
+
886
+ class AutoencoderKLCogVideoX(ModelMixin, ConfigMixin, FromOriginalModelMixin):
887
+ r"""
888
+ A VAE model with KL loss for encoding images into latents and decoding latent representations into images. Used in
889
+ [CogVideoX](https://github.com/THUDM/CogVideo).
890
+
891
+ This model inherits from [`ModelMixin`]. Check the superclass documentation for it's generic methods implemented
892
+ for all models (such as downloading or saving).
893
+
894
+ Parameters:
895
+ in_channels (int, *optional*, defaults to 3): Number of channels in the input image.
896
+ out_channels (int, *optional*, defaults to 3): Number of channels in the output.
897
+ down_block_types (`Tuple[str]`, *optional*, defaults to `("DownEncoderBlock2D",)`):
898
+ Tuple of downsample block types.
899
+ up_block_types (`Tuple[str]`, *optional*, defaults to `("UpDecoderBlock2D",)`):
900
+ Tuple of upsample block types.
901
+ block_out_channels (`Tuple[int]`, *optional*, defaults to `(64,)`):
902
+ Tuple of block output channels.
903
+ act_fn (`str`, *optional*, defaults to `"silu"`): The activation function to use.
904
+ sample_size (`int`, *optional*, defaults to `32`): Sample input size.
905
+ scaling_factor (`float`, *optional*, defaults to `1.15258426`):
906
+ The component-wise standard deviation of the trained latent space computed using the first batch of the
907
+ training set. This is used to scale the latent space to have unit variance when training the diffusion
908
+ model. The latents are scaled with the formula `z = z * scaling_factor` before being passed to the
909
+ diffusion model. When decoding, the latents are scaled back to the original scale with the formula: `z = 1
910
+ / scaling_factor * z`. For more details, refer to sections 4.3.2 and D.1 of the [High-Resolution Image
911
+ Synthesis with Latent Diffusion Models](https://arxiv.org/abs/2112.10752) paper.
912
+ force_upcast (`bool`, *optional*, default to `True`):
913
+ If enabled it will force the VAE to run in float32 for high image resolution pipelines, such as SD-XL. VAE
914
+ can be fine-tuned / trained to a lower range without loosing too much precision in which case
915
+ `force_upcast` can be set to `False` - see: https://huggingface.co/madebyollin/sdxl-vae-fp16-fix
916
+ """
917
+
918
+ _supports_gradient_checkpointing = True
919
+ _no_split_modules = ["CogVideoXResnetBlock3D"]
920
+
921
+ @register_to_config
922
+ def __init__(
923
+ self,
924
+ in_channels: int = 3,
925
+ out_channels: int = 3,
926
+ down_block_types: Tuple[str] = (
927
+ "CogVideoXDownBlock3D",
928
+ "CogVideoXDownBlock3D",
929
+ "CogVideoXDownBlock3D",
930
+ "CogVideoXDownBlock3D",
931
+ ),
932
+ up_block_types: Tuple[str] = (
933
+ "CogVideoXUpBlock3D",
934
+ "CogVideoXUpBlock3D",
935
+ "CogVideoXUpBlock3D",
936
+ "CogVideoXUpBlock3D",
937
+ ),
938
+ block_out_channels: Tuple[int] = (128, 256, 256, 512),
939
+ latent_channels: int = 16,
940
+ layers_per_block: int = 3,
941
+ act_fn: str = "silu",
942
+ norm_eps: float = 1e-6,
943
+ norm_num_groups: int = 32,
944
+ temporal_compression_ratio: float = 4,
945
+ sample_height: int = 480,
946
+ sample_width: int = 720,
947
+ scaling_factor: float = 1.15258426,
948
+ shift_factor: Optional[float] = None,
949
+ latents_mean: Optional[Tuple[float]] = None,
950
+ latents_std: Optional[Tuple[float]] = None,
951
+ force_upcast: float = True,
952
+ use_quant_conv: bool = False,
953
+ use_post_quant_conv: bool = False,
954
+ ):
955
+ super().__init__()
956
+
957
+ self.encoder = CogVideoXEncoder3D(
958
+ in_channels=in_channels,
959
+ out_channels=latent_channels,
960
+ down_block_types=down_block_types,
961
+ block_out_channels=block_out_channels,
962
+ layers_per_block=layers_per_block,
963
+ act_fn=act_fn,
964
+ norm_eps=norm_eps,
965
+ norm_num_groups=norm_num_groups,
966
+ temporal_compression_ratio=temporal_compression_ratio,
967
+ )
968
+ self.decoder = CogVideoXDecoder3D(
969
+ in_channels=latent_channels,
970
+ out_channels=out_channels,
971
+ up_block_types=up_block_types,
972
+ block_out_channels=block_out_channels,
973
+ layers_per_block=layers_per_block,
974
+ act_fn=act_fn,
975
+ norm_eps=norm_eps,
976
+ norm_num_groups=norm_num_groups,
977
+ temporal_compression_ratio=temporal_compression_ratio,
978
+ )
979
+ self.quant_conv = CogVideoXSafeConv3d(2 * out_channels, 2 * out_channels, 1) if use_quant_conv else None
980
+ self.post_quant_conv = CogVideoXSafeConv3d(out_channels, out_channels, 1) if use_post_quant_conv else None
981
+
982
+ self.use_slicing = False
983
+ self.use_tiling = False
984
+
985
+ # Can be increased to decode more latent frames at once, but comes at a reasonable memory cost and it is not
986
+ # recommended because the temporal parts of the VAE, here, are tricky to understand.
987
+ # If you decode X latent frames together, the number of output frames is:
988
+ # (X + (2 conv cache) + (2 time upscale_1) + (4 time upscale_2) - (2 causal conv downscale)) => X + 6 frames
989
+ #
990
+ # Example with num_latent_frames_batch_size = 2:
991
+ # - 12 latent frames: (0, 1), (2, 3), (4, 5), (6, 7), (8, 9), (10, 11) are processed together
992
+ # => (12 // 2 frame slices) * ((2 num_latent_frames_batch_size) + (2 conv cache) + (2 time upscale_1) + (4 time upscale_2) - (2 causal conv downscale))
993
+ # => 6 * 8 = 48 frames
994
+ # - 13 latent frames: (0, 1, 2) (special case), (3, 4), (5, 6), (7, 8), (9, 10), (11, 12) are processed together
995
+ # => (1 frame slice) * ((3 num_latent_frames_batch_size) + (2 conv cache) + (2 time upscale_1) + (4 time upscale_2) - (2 causal conv downscale)) +
996
+ # ((13 - 3) // 2) * ((2 num_latent_frames_batch_size) + (2 conv cache) + (2 time upscale_1) + (4 time upscale_2) - (2 causal conv downscale))
997
+ # => 1 * 9 + 5 * 8 = 49 frames
998
+ # It has been implemented this way so as to not have "magic values" in the code base that would be hard to explain. Note that
999
+ # setting it to anything other than 2 would give poor results because the VAE hasn't been trained to be adaptive with different
1000
+ # number of temporal frames.
1001
+ self.num_latent_frames_batch_size = 2
1002
+
1003
+ # We make the minimum height and width of sample for tiling half that of the generally supported
1004
+ self.tile_sample_min_height = sample_height // 2
1005
+ self.tile_sample_min_width = sample_width // 2
1006
+ self.tile_latent_min_height = int(
1007
+ self.tile_sample_min_height / (2 ** (len(self.config.block_out_channels) - 1))
1008
+ )
1009
+ self.tile_latent_min_width = int(self.tile_sample_min_width / (2 ** (len(self.config.block_out_channels) - 1)))
1010
+
1011
+ # These are experimental overlap factors that were chosen based on experimentation and seem to work best for
1012
+ # 720x480 (WxH) resolution. The above resolution is the strongly recommended generation resolution in CogVideoX
1013
+ # and so the tiling implementation has only been tested on those specific resolutions.
1014
+ self.tile_overlap_factor_height = 1 / 6
1015
+ self.tile_overlap_factor_width = 1 / 5
1016
+
1017
+ def _set_gradient_checkpointing(self, module, value=False):
1018
+ if isinstance(module, (CogVideoXEncoder3D, CogVideoXDecoder3D)):
1019
+ module.gradient_checkpointing = value
1020
+
1021
+ def _clear_fake_context_parallel_cache(self):
1022
+ for name, module in self.named_modules():
1023
+ if isinstance(module, CogVideoXCausalConv3d):
1024
+ logger.debug(f"Clearing fake Context Parallel cache for layer: {name}")
1025
+ module._clear_fake_context_parallel_cache()
1026
+
1027
+ def enable_tiling(
1028
+ self,
1029
+ tile_sample_min_height: Optional[int] = None,
1030
+ tile_sample_min_width: Optional[int] = None,
1031
+ tile_overlap_factor_height: Optional[float] = None,
1032
+ tile_overlap_factor_width: Optional[float] = None,
1033
+ ) -> None:
1034
+ r"""
1035
+ Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to
1036
+ compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow
1037
+ processing larger images.
1038
+
1039
+ Args:
1040
+ tile_sample_min_height (`int`, *optional*):
1041
+ The minimum height required for a sample to be separated into tiles across the height dimension.
1042
+ tile_sample_min_width (`int`, *optional*):
1043
+ The minimum width required for a sample to be separated into tiles across the width dimension.
1044
+ tile_overlap_factor_height (`int`, *optional*):
1045
+ The minimum amount of overlap between two consecutive vertical tiles. This is to ensure that there are
1046
+ no tiling artifacts produced across the height dimension. Must be between 0 and 1. Setting a higher
1047
+ value might cause more tiles to be processed leading to slow down of the decoding process.
1048
+ tile_overlap_factor_width (`int`, *optional*):
1049
+ The minimum amount of overlap between two consecutive horizontal tiles. This is to ensure that there
1050
+ are no tiling artifacts produced across the width dimension. Must be between 0 and 1. Setting a higher
1051
+ value might cause more tiles to be processed leading to slow down of the decoding process.
1052
+ """
1053
+ self.use_tiling = True
1054
+ self.tile_sample_min_height = tile_sample_min_height or self.tile_sample_min_height
1055
+ self.tile_sample_min_width = tile_sample_min_width or self.tile_sample_min_width
1056
+ self.tile_latent_min_height = int(
1057
+ self.tile_sample_min_height / (2 ** (len(self.config.block_out_channels) - 1))
1058
+ )
1059
+ self.tile_latent_min_width = int(self.tile_sample_min_width / (2 ** (len(self.config.block_out_channels) - 1)))
1060
+ self.tile_overlap_factor_height = tile_overlap_factor_height or self.tile_overlap_factor_height
1061
+ self.tile_overlap_factor_width = tile_overlap_factor_width or self.tile_overlap_factor_width
1062
+
1063
+ def disable_tiling(self) -> None:
1064
+ r"""
1065
+ Disable tiled VAE decoding. If `enable_tiling` was previously enabled, this method will go back to computing
1066
+ decoding in one step.
1067
+ """
1068
+ self.use_tiling = False
1069
+
1070
+ def enable_slicing(self) -> None:
1071
+ r"""
1072
+ Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to
1073
+ compute decoding in several steps. This is useful to save some memory and allow larger batch sizes.
1074
+ """
1075
+ self.use_slicing = True
1076
+
1077
+ def disable_slicing(self) -> None:
1078
+ r"""
1079
+ Disable sliced VAE decoding. If `enable_slicing` was previously enabled, this method will go back to computing
1080
+ decoding in one step.
1081
+ """
1082
+ self.use_slicing = False
1083
+
1084
+ @apply_forward_hook
1085
+ def encode(
1086
+ self, x: torch.Tensor, return_dict: bool = True
1087
+ ) -> Union[AutoencoderKLOutput, Tuple[DiagonalGaussianDistribution]]:
1088
+ """
1089
+ Encode a batch of images into latents.
1090
+
1091
+ Args:
1092
+ x (`torch.Tensor`): Input batch of images.
1093
+ return_dict (`bool`, *optional*, defaults to `True`):
1094
+ Whether to return a [`~models.autoencoder_kl.AutoencoderKLOutput`] instead of a plain tuple.
1095
+
1096
+ Returns:
1097
+ The latent representations of the encoded images. If `return_dict` is True, a
1098
+ [`~models.autoencoder_kl.AutoencoderKLOutput`] is returned, otherwise a plain `tuple` is returned.
1099
+ """
1100
+ batch_size, num_channels, num_frames, height, width = x.shape
1101
+ if num_frames == 1:
1102
+ h = self.encoder(x)
1103
+ if self.quant_conv is not None:
1104
+ h = self.quant_conv(h)
1105
+ posterior = DiagonalGaussianDistribution(h)
1106
+ else:
1107
+ frame_batch_size = 4
1108
+ h = []
1109
+ for i in range(num_frames // frame_batch_size):
1110
+ remaining_frames = num_frames % frame_batch_size
1111
+ start_frame = frame_batch_size * i + (0 if i == 0 else remaining_frames)
1112
+ end_frame = frame_batch_size * (i + 1) + remaining_frames
1113
+ z_intermediate = x[:, :, start_frame:end_frame]
1114
+ z_intermediate = self.encoder(z_intermediate)
1115
+ if self.quant_conv is not None:
1116
+ z_intermediate = self.quant_conv(z_intermediate)
1117
+ h.append(z_intermediate)
1118
+ self._clear_fake_context_parallel_cache()
1119
+ h = torch.cat(h, dim=2)
1120
+ posterior = DiagonalGaussianDistribution(h)
1121
+ if not return_dict:
1122
+ return (posterior,)
1123
+ return AutoencoderKLOutput(latent_dist=posterior)
1124
+
1125
+ def _decode(self, z: torch.Tensor, return_dict: bool = True) -> Union[DecoderOutput, torch.Tensor]:
1126
+ batch_size, num_channels, num_frames, height, width = z.shape
1127
+
1128
+ if self.use_tiling and (width > self.tile_latent_min_width or height > self.tile_latent_min_height):
1129
+ return self.tiled_decode(z, return_dict=return_dict)
1130
+
1131
+ if num_frames == 1:
1132
+ dec = []
1133
+ z_intermediate = z
1134
+ if self.post_quant_conv is not None:
1135
+ z_intermediate = self.post_quant_conv(z_intermediate)
1136
+ z_intermediate = self.decoder(z_intermediate)
1137
+ dec.append(z_intermediate)
1138
+ else:
1139
+ frame_batch_size = self.num_latent_frames_batch_size
1140
+ dec = []
1141
+ for i in range(num_frames // frame_batch_size):
1142
+ remaining_frames = num_frames % frame_batch_size
1143
+ start_frame = frame_batch_size * i + (0 if i == 0 else remaining_frames)
1144
+ end_frame = frame_batch_size * (i + 1) + remaining_frames
1145
+ z_intermediate = z[:, :, start_frame:end_frame]
1146
+ if self.post_quant_conv is not None:
1147
+ z_intermediate = self.post_quant_conv(z_intermediate)
1148
+ z_intermediate = self.decoder(z_intermediate)
1149
+ dec.append(z_intermediate)
1150
+
1151
+ self._clear_fake_context_parallel_cache()
1152
+ dec = torch.cat(dec, dim=2)
1153
+
1154
+ if not return_dict:
1155
+ return (dec,)
1156
+
1157
+ return DecoderOutput(sample=dec)
1158
+
1159
+ @apply_forward_hook
1160
+ def decode(self, z: torch.Tensor, return_dict: bool = True) -> Union[DecoderOutput, torch.Tensor]:
1161
+ """
1162
+ Decode a batch of images.
1163
+
1164
+ Args:
1165
+ z (`torch.Tensor`): Input batch of latent vectors.
1166
+ return_dict (`bool`, *optional*, defaults to `True`):
1167
+ Whether to return a [`~models.vae.DecoderOutput`] instead of a plain tuple.
1168
+
1169
+ Returns:
1170
+ [`~models.vae.DecoderOutput`] or `tuple`:
1171
+ If return_dict is True, a [`~models.vae.DecoderOutput`] is returned, otherwise a plain `tuple` is
1172
+ returned.
1173
+ """
1174
+ if self.use_slicing and z.shape[0] > 1:
1175
+ decoded_slices = [self._decode(z_slice).sample for z_slice in z.split(1)]
1176
+ decoded = torch.cat(decoded_slices)
1177
+ else:
1178
+ decoded = self._decode(z).sample
1179
+
1180
+ if not return_dict:
1181
+ return (decoded,)
1182
+ return DecoderOutput(sample=decoded)
1183
+
1184
+ def blend_v(self, a: torch.Tensor, b: torch.Tensor, blend_extent: int) -> torch.Tensor:
1185
+ blend_extent = min(a.shape[3], b.shape[3], blend_extent)
1186
+ for y in range(blend_extent):
1187
+ b[:, :, :, y, :] = a[:, :, :, -blend_extent + y, :] * (1 - y / blend_extent) + b[:, :, :, y, :] * (
1188
+ y / blend_extent
1189
+ )
1190
+ return b
1191
+
1192
+ def blend_h(self, a: torch.Tensor, b: torch.Tensor, blend_extent: int) -> torch.Tensor:
1193
+ blend_extent = min(a.shape[4], b.shape[4], blend_extent)
1194
+ for x in range(blend_extent):
1195
+ b[:, :, :, :, x] = a[:, :, :, :, -blend_extent + x] * (1 - x / blend_extent) + b[:, :, :, :, x] * (
1196
+ x / blend_extent
1197
+ )
1198
+ return b
1199
+
1200
+ def tiled_decode(self, z: torch.Tensor, return_dict: bool = True) -> Union[DecoderOutput, torch.Tensor]:
1201
+ r"""
1202
+ Decode a batch of images using a tiled decoder.
1203
+
1204
+ Args:
1205
+ z (`torch.Tensor`): Input batch of latent vectors.
1206
+ return_dict (`bool`, *optional*, defaults to `True`):
1207
+ Whether or not to return a [`~models.vae.DecoderOutput`] instead of a plain tuple.
1208
+
1209
+ Returns:
1210
+ [`~models.vae.DecoderOutput`] or `tuple`:
1211
+ If return_dict is True, a [`~models.vae.DecoderOutput`] is returned, otherwise a plain `tuple` is
1212
+ returned.
1213
+ """
1214
+ # Rough memory assessment:
1215
+ # - In CogVideoX-2B, there are a total of 24 CausalConv3d layers.
1216
+ # - The biggest intermediate dimensions are: [1, 128, 9, 480, 720].
1217
+ # - Assume fp16 (2 bytes per value).
1218
+ # Memory required: 1 * 128 * 9 * 480 * 720 * 24 * 2 / 1024**3 = 17.8 GB
1219
+ #
1220
+ # Memory assessment when using tiling:
1221
+ # - Assume everything as above but now HxW is 240x360 by tiling in half
1222
+ # Memory required: 1 * 128 * 9 * 240 * 360 * 24 * 2 / 1024**3 = 4.5 GB
1223
+
1224
+ batch_size, num_channels, num_frames, height, width = z.shape
1225
+
1226
+ overlap_height = int(self.tile_latent_min_height * (1 - self.tile_overlap_factor_height))
1227
+ overlap_width = int(self.tile_latent_min_width * (1 - self.tile_overlap_factor_width))
1228
+ blend_extent_height = int(self.tile_sample_min_height * self.tile_overlap_factor_height)
1229
+ blend_extent_width = int(self.tile_sample_min_width * self.tile_overlap_factor_width)
1230
+ row_limit_height = self.tile_sample_min_height - blend_extent_height
1231
+ row_limit_width = self.tile_sample_min_width - blend_extent_width
1232
+ frame_batch_size = self.num_latent_frames_batch_size
1233
+
1234
+ # Split z into overlapping tiles and decode them separately.
1235
+ # The tiles have an overlap to avoid seams between tiles.
1236
+ rows = []
1237
+ for i in range(0, height, overlap_height):
1238
+ row = []
1239
+ for j in range(0, width, overlap_width):
1240
+ time = []
1241
+ for k in range(num_frames // frame_batch_size):
1242
+ remaining_frames = num_frames % frame_batch_size
1243
+ start_frame = frame_batch_size * k + (0 if k == 0 else remaining_frames)
1244
+ end_frame = frame_batch_size * (k + 1) + remaining_frames
1245
+ tile = z[
1246
+ :,
1247
+ :,
1248
+ start_frame:end_frame,
1249
+ i : i + self.tile_latent_min_height,
1250
+ j : j + self.tile_latent_min_width,
1251
+ ]
1252
+ if self.post_quant_conv is not None:
1253
+ tile = self.post_quant_conv(tile)
1254
+ tile = self.decoder(tile)
1255
+ time.append(tile)
1256
+ self._clear_fake_context_parallel_cache()
1257
+ row.append(torch.cat(time, dim=2))
1258
+ rows.append(row)
1259
+
1260
+ result_rows = []
1261
+ for i, row in enumerate(rows):
1262
+ result_row = []
1263
+ for j, tile in enumerate(row):
1264
+ # blend the above tile and the left tile
1265
+ # to the current tile and add the current tile to the result row
1266
+ if i > 0:
1267
+ tile = self.blend_v(rows[i - 1][j], tile, blend_extent_height)
1268
+ if j > 0:
1269
+ tile = self.blend_h(row[j - 1], tile, blend_extent_width)
1270
+ result_row.append(tile[:, :, :, :row_limit_height, :row_limit_width])
1271
+ result_rows.append(torch.cat(result_row, dim=4))
1272
+
1273
+ dec = torch.cat(result_rows, dim=3)
1274
+
1275
+ if not return_dict:
1276
+ return (dec,)
1277
+
1278
+ return DecoderOutput(sample=dec)
1279
+
1280
+ def forward(
1281
+ self,
1282
+ sample: torch.Tensor,
1283
+ sample_posterior: bool = False,
1284
+ return_dict: bool = True,
1285
+ generator: Optional[torch.Generator] = None,
1286
+ ) -> Union[torch.Tensor, torch.Tensor]:
1287
+ x = sample
1288
+ posterior = self.encode(x).latent_dist
1289
+ if sample_posterior:
1290
+ z = posterior.sample(generator=generator)
1291
+ else:
1292
+ z = posterior.mode()
1293
+ dec = self.decode(z)
1294
+ if not return_dict:
1295
+ return (dec,)
1296
+ return dec
cogvideox/models/transformer3d.py ADDED
@@ -0,0 +1,609 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 The CogVideoX team, Tsinghua University & ZhipuAI and The HuggingFace Team.
2
+ # All rights reserved.
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 typing import Any, Dict, Optional, Tuple, Union
17
+
18
+ import os
19
+ import json
20
+ import torch
21
+ import glob
22
+ import torch.nn.functional as F
23
+ from torch import nn
24
+
25
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
26
+ from diffusers.utils import is_torch_version, logging
27
+ from diffusers.utils.torch_utils import maybe_allow_in_graph
28
+ from diffusers.models.attention import Attention, FeedForward
29
+ from diffusers.models.attention_processor import AttentionProcessor, CogVideoXAttnProcessor2_0, FusedCogVideoXAttnProcessor2_0
30
+ from diffusers.models.embeddings import TimestepEmbedding, Timesteps, get_3d_sincos_pos_embed
31
+ from diffusers.models.modeling_outputs import Transformer2DModelOutput
32
+ from diffusers.models.modeling_utils import ModelMixin
33
+ from diffusers.models.normalization import AdaLayerNorm, CogVideoXLayerNormZero
34
+
35
+
36
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
37
+
38
+ class CogVideoXPatchEmbed(nn.Module):
39
+ def __init__(
40
+ self,
41
+ patch_size: int = 2,
42
+ in_channels: int = 16,
43
+ embed_dim: int = 1920,
44
+ text_embed_dim: int = 4096,
45
+ bias: bool = True,
46
+ ) -> None:
47
+ super().__init__()
48
+ self.patch_size = patch_size
49
+
50
+ self.proj = nn.Conv2d(
51
+ in_channels, embed_dim, kernel_size=(patch_size, patch_size), stride=patch_size, bias=bias
52
+ )
53
+ self.text_proj = nn.Linear(text_embed_dim, embed_dim)
54
+
55
+ def forward(self, text_embeds: torch.Tensor, image_embeds: torch.Tensor):
56
+ r"""
57
+ Args:
58
+ text_embeds (`torch.Tensor`):
59
+ Input text embeddings. Expected shape: (batch_size, seq_length, embedding_dim).
60
+ image_embeds (`torch.Tensor`):
61
+ Input image embeddings. Expected shape: (batch_size, num_frames, channels, height, width).
62
+ """
63
+ text_embeds = self.text_proj(text_embeds)
64
+
65
+ batch, num_frames, channels, height, width = image_embeds.shape
66
+ image_embeds = image_embeds.reshape(-1, channels, height, width)
67
+ image_embeds = self.proj(image_embeds)
68
+ image_embeds = image_embeds.view(batch, num_frames, *image_embeds.shape[1:])
69
+ image_embeds = image_embeds.flatten(3).transpose(2, 3) # [batch, num_frames, height x width, channels]
70
+ image_embeds = image_embeds.flatten(1, 2) # [batch, num_frames x height x width, channels]
71
+
72
+ embeds = torch.cat(
73
+ [text_embeds, image_embeds], dim=1
74
+ ).contiguous() # [batch, seq_length + num_frames x height x width, channels]
75
+ return embeds
76
+
77
+ @maybe_allow_in_graph
78
+ class CogVideoXBlock(nn.Module):
79
+ r"""
80
+ Transformer block used in [CogVideoX](https://github.com/THUDM/CogVideo) model.
81
+
82
+ Parameters:
83
+ dim (`int`):
84
+ The number of channels in the input and output.
85
+ num_attention_heads (`int`):
86
+ The number of heads to use for multi-head attention.
87
+ attention_head_dim (`int`):
88
+ The number of channels in each head.
89
+ time_embed_dim (`int`):
90
+ The number of channels in timestep embedding.
91
+ dropout (`float`, defaults to `0.0`):
92
+ The dropout probability to use.
93
+ activation_fn (`str`, defaults to `"gelu-approximate"`):
94
+ Activation function to be used in feed-forward.
95
+ attention_bias (`bool`, defaults to `False`):
96
+ Whether or not to use bias in attention projection layers.
97
+ qk_norm (`bool`, defaults to `True`):
98
+ Whether or not to use normalization after query and key projections in Attention.
99
+ norm_elementwise_affine (`bool`, defaults to `True`):
100
+ Whether to use learnable elementwise affine parameters for normalization.
101
+ norm_eps (`float`, defaults to `1e-5`):
102
+ Epsilon value for normalization layers.
103
+ final_dropout (`bool` defaults to `False`):
104
+ Whether to apply a final dropout after the last feed-forward layer.
105
+ ff_inner_dim (`int`, *optional*, defaults to `None`):
106
+ Custom hidden dimension of Feed-forward layer. If not provided, `4 * dim` is used.
107
+ ff_bias (`bool`, defaults to `True`):
108
+ Whether or not to use bias in Feed-forward layer.
109
+ attention_out_bias (`bool`, defaults to `True`):
110
+ Whether or not to use bias in Attention output projection layer.
111
+ """
112
+
113
+ def __init__(
114
+ self,
115
+ dim: int,
116
+ num_attention_heads: int,
117
+ attention_head_dim: int,
118
+ time_embed_dim: int,
119
+ dropout: float = 0.0,
120
+ activation_fn: str = "gelu-approximate",
121
+ attention_bias: bool = False,
122
+ qk_norm: bool = True,
123
+ norm_elementwise_affine: bool = True,
124
+ norm_eps: float = 1e-5,
125
+ final_dropout: bool = True,
126
+ ff_inner_dim: Optional[int] = None,
127
+ ff_bias: bool = True,
128
+ attention_out_bias: bool = True,
129
+ ):
130
+ super().__init__()
131
+
132
+ # 1. Self Attention
133
+ self.norm1 = CogVideoXLayerNormZero(time_embed_dim, dim, norm_elementwise_affine, norm_eps, bias=True)
134
+
135
+ self.attn1 = Attention(
136
+ query_dim=dim,
137
+ dim_head=attention_head_dim,
138
+ heads=num_attention_heads,
139
+ qk_norm="layer_norm" if qk_norm else None,
140
+ eps=1e-6,
141
+ bias=attention_bias,
142
+ out_bias=attention_out_bias,
143
+ processor=CogVideoXAttnProcessor2_0(),
144
+ )
145
+
146
+ # 2. Feed Forward
147
+ self.norm2 = CogVideoXLayerNormZero(time_embed_dim, dim, norm_elementwise_affine, norm_eps, bias=True)
148
+
149
+ self.ff = FeedForward(
150
+ dim,
151
+ dropout=dropout,
152
+ activation_fn=activation_fn,
153
+ final_dropout=final_dropout,
154
+ inner_dim=ff_inner_dim,
155
+ bias=ff_bias,
156
+ )
157
+
158
+ def forward(
159
+ self,
160
+ hidden_states: torch.Tensor,
161
+ encoder_hidden_states: torch.Tensor,
162
+ temb: torch.Tensor,
163
+ image_rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
164
+ ) -> torch.Tensor:
165
+ text_seq_length = encoder_hidden_states.size(1)
166
+
167
+ # norm & modulate
168
+ norm_hidden_states, norm_encoder_hidden_states, gate_msa, enc_gate_msa = self.norm1(
169
+ hidden_states, encoder_hidden_states, temb
170
+ )
171
+
172
+ # attention
173
+ attn_hidden_states, attn_encoder_hidden_states = self.attn1(
174
+ hidden_states=norm_hidden_states,
175
+ encoder_hidden_states=norm_encoder_hidden_states,
176
+ image_rotary_emb=image_rotary_emb,
177
+ )
178
+
179
+ hidden_states = hidden_states + gate_msa * attn_hidden_states
180
+ encoder_hidden_states = encoder_hidden_states + enc_gate_msa * attn_encoder_hidden_states
181
+
182
+ # norm & modulate
183
+ norm_hidden_states, norm_encoder_hidden_states, gate_ff, enc_gate_ff = self.norm2(
184
+ hidden_states, encoder_hidden_states, temb
185
+ )
186
+
187
+ # feed-forward
188
+ norm_hidden_states = torch.cat([norm_encoder_hidden_states, norm_hidden_states], dim=1)
189
+ ff_output = self.ff(norm_hidden_states)
190
+
191
+ hidden_states = hidden_states + gate_ff * ff_output[:, text_seq_length:]
192
+ encoder_hidden_states = encoder_hidden_states + enc_gate_ff * ff_output[:, :text_seq_length]
193
+
194
+ return hidden_states, encoder_hidden_states
195
+
196
+
197
+ class CogVideoXTransformer3DModel(ModelMixin, ConfigMixin):
198
+ """
199
+ A Transformer model for video-like data in [CogVideoX](https://github.com/THUDM/CogVideo).
200
+
201
+ Parameters:
202
+ num_attention_heads (`int`, defaults to `30`):
203
+ The number of heads to use for multi-head attention.
204
+ attention_head_dim (`int`, defaults to `64`):
205
+ The number of channels in each head.
206
+ in_channels (`int`, defaults to `16`):
207
+ The number of channels in the input.
208
+ out_channels (`int`, *optional*, defaults to `16`):
209
+ The number of channels in the output.
210
+ flip_sin_to_cos (`bool`, defaults to `True`):
211
+ Whether to flip the sin to cos in the time embedding.
212
+ time_embed_dim (`int`, defaults to `512`):
213
+ Output dimension of timestep embeddings.
214
+ text_embed_dim (`int`, defaults to `4096`):
215
+ Input dimension of text embeddings from the text encoder.
216
+ num_layers (`int`, defaults to `30`):
217
+ The number of layers of Transformer blocks to use.
218
+ dropout (`float`, defaults to `0.0`):
219
+ The dropout probability to use.
220
+ attention_bias (`bool`, defaults to `True`):
221
+ Whether or not to use bias in the attention projection layers.
222
+ sample_width (`int`, defaults to `90`):
223
+ The width of the input latents.
224
+ sample_height (`int`, defaults to `60`):
225
+ The height of the input latents.
226
+ sample_frames (`int`, defaults to `49`):
227
+ The number of frames in the input latents. Note that this parameter was incorrectly initialized to 49
228
+ instead of 13 because CogVideoX processed 13 latent frames at once in its default and recommended settings,
229
+ but cannot be changed to the correct value to ensure backwards compatibility. To create a transformer with
230
+ K latent frames, the correct value to pass here would be: ((K - 1) * temporal_compression_ratio + 1).
231
+ patch_size (`int`, defaults to `2`):
232
+ The size of the patches to use in the patch embedding layer.
233
+ temporal_compression_ratio (`int`, defaults to `4`):
234
+ The compression ratio across the temporal dimension. See documentation for `sample_frames`.
235
+ max_text_seq_length (`int`, defaults to `226`):
236
+ The maximum sequence length of the input text embeddings.
237
+ activation_fn (`str`, defaults to `"gelu-approximate"`):
238
+ Activation function to use in feed-forward.
239
+ timestep_activation_fn (`str`, defaults to `"silu"`):
240
+ Activation function to use when generating the timestep embeddings.
241
+ norm_elementwise_affine (`bool`, defaults to `True`):
242
+ Whether or not to use elementwise affine in normalization layers.
243
+ norm_eps (`float`, defaults to `1e-5`):
244
+ The epsilon value to use in normalization layers.
245
+ spatial_interpolation_scale (`float`, defaults to `1.875`):
246
+ Scaling factor to apply in 3D positional embeddings across spatial dimensions.
247
+ temporal_interpolation_scale (`float`, defaults to `1.0`):
248
+ Scaling factor to apply in 3D positional embeddings across temporal dimensions.
249
+ """
250
+
251
+ _supports_gradient_checkpointing = True
252
+
253
+ @register_to_config
254
+ def __init__(
255
+ self,
256
+ num_attention_heads: int = 30,
257
+ attention_head_dim: int = 64,
258
+ in_channels: int = 16,
259
+ out_channels: Optional[int] = 16,
260
+ flip_sin_to_cos: bool = True,
261
+ freq_shift: int = 0,
262
+ time_embed_dim: int = 512,
263
+ text_embed_dim: int = 4096,
264
+ num_layers: int = 30,
265
+ dropout: float = 0.0,
266
+ attention_bias: bool = True,
267
+ sample_width: int = 90,
268
+ sample_height: int = 60,
269
+ sample_frames: int = 49,
270
+ patch_size: int = 2,
271
+ temporal_compression_ratio: int = 4,
272
+ max_text_seq_length: int = 226,
273
+ activation_fn: str = "gelu-approximate",
274
+ timestep_activation_fn: str = "silu",
275
+ norm_elementwise_affine: bool = True,
276
+ norm_eps: float = 1e-5,
277
+ spatial_interpolation_scale: float = 1.875,
278
+ temporal_interpolation_scale: float = 1.0,
279
+ use_rotary_positional_embeddings: bool = False,
280
+ add_noise_in_inpaint_model: bool = False,
281
+ ):
282
+ super().__init__()
283
+ inner_dim = num_attention_heads * attention_head_dim
284
+
285
+ post_patch_height = sample_height // patch_size
286
+ post_patch_width = sample_width // patch_size
287
+ post_time_compression_frames = (sample_frames - 1) // temporal_compression_ratio + 1
288
+ self.num_patches = post_patch_height * post_patch_width * post_time_compression_frames
289
+ self.post_patch_height = post_patch_height
290
+ self.post_patch_width = post_patch_width
291
+ self.post_time_compression_frames = post_time_compression_frames
292
+ self.patch_size = patch_size
293
+
294
+ # 1. Patch embedding
295
+ self.patch_embed = CogVideoXPatchEmbed(patch_size, in_channels, inner_dim, text_embed_dim, bias=True)
296
+ self.embedding_dropout = nn.Dropout(dropout)
297
+
298
+ # 2. 3D positional embeddings
299
+ spatial_pos_embedding = get_3d_sincos_pos_embed(
300
+ inner_dim,
301
+ (post_patch_width, post_patch_height),
302
+ post_time_compression_frames,
303
+ spatial_interpolation_scale,
304
+ temporal_interpolation_scale,
305
+ )
306
+ spatial_pos_embedding = torch.from_numpy(spatial_pos_embedding).flatten(0, 1)
307
+ pos_embedding = torch.zeros(1, max_text_seq_length + self.num_patches, inner_dim, requires_grad=False)
308
+ pos_embedding.data[:, max_text_seq_length:].copy_(spatial_pos_embedding)
309
+ self.register_buffer("pos_embedding", pos_embedding, persistent=False)
310
+
311
+ # 3. Time embeddings
312
+ self.time_proj = Timesteps(inner_dim, flip_sin_to_cos, freq_shift)
313
+ self.time_embedding = TimestepEmbedding(inner_dim, time_embed_dim, timestep_activation_fn)
314
+
315
+ # 4. Define spatio-temporal transformers blocks
316
+ self.transformer_blocks = nn.ModuleList(
317
+ [
318
+ CogVideoXBlock(
319
+ dim=inner_dim,
320
+ num_attention_heads=num_attention_heads,
321
+ attention_head_dim=attention_head_dim,
322
+ time_embed_dim=time_embed_dim,
323
+ dropout=dropout,
324
+ activation_fn=activation_fn,
325
+ attention_bias=attention_bias,
326
+ norm_elementwise_affine=norm_elementwise_affine,
327
+ norm_eps=norm_eps,
328
+ )
329
+ for _ in range(num_layers)
330
+ ]
331
+ )
332
+ self.norm_final = nn.LayerNorm(inner_dim, norm_eps, norm_elementwise_affine)
333
+
334
+ # 5. Output blocks
335
+ self.norm_out = AdaLayerNorm(
336
+ embedding_dim=time_embed_dim,
337
+ output_dim=2 * inner_dim,
338
+ norm_elementwise_affine=norm_elementwise_affine,
339
+ norm_eps=norm_eps,
340
+ chunk_dim=1,
341
+ )
342
+ self.proj_out = nn.Linear(inner_dim, patch_size * patch_size * out_channels)
343
+
344
+ self.gradient_checkpointing = False
345
+
346
+ def _set_gradient_checkpointing(self, module, value=False):
347
+ self.gradient_checkpointing = value
348
+
349
+ @property
350
+ # Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.attn_processors
351
+ def attn_processors(self) -> Dict[str, AttentionProcessor]:
352
+ r"""
353
+ Returns:
354
+ `dict` of attention processors: A dictionary containing all attention processors used in the model with
355
+ indexed by its weight name.
356
+ """
357
+ # set recursively
358
+ processors = {}
359
+
360
+ def fn_recursive_add_processors(name: str, module: torch.nn.Module, processors: Dict[str, AttentionProcessor]):
361
+ if hasattr(module, "get_processor"):
362
+ processors[f"{name}.processor"] = module.get_processor()
363
+
364
+ for sub_name, child in module.named_children():
365
+ fn_recursive_add_processors(f"{name}.{sub_name}", child, processors)
366
+
367
+ return processors
368
+
369
+ for name, module in self.named_children():
370
+ fn_recursive_add_processors(name, module, processors)
371
+
372
+ return processors
373
+
374
+ # Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.set_attn_processor
375
+ def set_attn_processor(self, processor: Union[AttentionProcessor, Dict[str, AttentionProcessor]]):
376
+ r"""
377
+ Sets the attention processor to use to compute attention.
378
+
379
+ Parameters:
380
+ processor (`dict` of `AttentionProcessor` or only `AttentionProcessor`):
381
+ The instantiated processor class or a dictionary of processor classes that will be set as the processor
382
+ for **all** `Attention` layers.
383
+
384
+ If `processor` is a dict, the key needs to define the path to the corresponding cross attention
385
+ processor. This is strongly recommended when setting trainable attention processors.
386
+
387
+ """
388
+ count = len(self.attn_processors.keys())
389
+
390
+ if isinstance(processor, dict) and len(processor) != count:
391
+ raise ValueError(
392
+ f"A dict of processors was passed, but the number of processors {len(processor)} does not match the"
393
+ f" number of attention layers: {count}. Please make sure to pass {count} processor classes."
394
+ )
395
+
396
+ def fn_recursive_attn_processor(name: str, module: torch.nn.Module, processor):
397
+ if hasattr(module, "set_processor"):
398
+ if not isinstance(processor, dict):
399
+ module.set_processor(processor)
400
+ else:
401
+ module.set_processor(processor.pop(f"{name}.processor"))
402
+
403
+ for sub_name, child in module.named_children():
404
+ fn_recursive_attn_processor(f"{name}.{sub_name}", child, processor)
405
+
406
+ for name, module in self.named_children():
407
+ fn_recursive_attn_processor(name, module, processor)
408
+
409
+ # Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.fuse_qkv_projections with FusedAttnProcessor2_0->FusedCogVideoXAttnProcessor2_0
410
+ def fuse_qkv_projections(self):
411
+ """
412
+ Enables fused QKV projections. For self-attention modules, all projection matrices (i.e., query, key, value)
413
+ are fused. For cross-attention modules, key and value projection matrices are fused.
414
+
415
+ <Tip warning={true}>
416
+
417
+ This API is 🧪 experimental.
418
+
419
+ </Tip>
420
+ """
421
+ self.original_attn_processors = None
422
+
423
+ for _, attn_processor in self.attn_processors.items():
424
+ if "Added" in str(attn_processor.__class__.__name__):
425
+ raise ValueError("`fuse_qkv_projections()` is not supported for models having added KV projections.")
426
+
427
+ self.original_attn_processors = self.attn_processors
428
+
429
+ for module in self.modules():
430
+ if isinstance(module, Attention):
431
+ module.fuse_projections(fuse=True)
432
+
433
+ self.set_attn_processor(FusedCogVideoXAttnProcessor2_0())
434
+
435
+ # Copied from diffusers.models.unets.unet_2d_condition.UNet2DConditionModel.unfuse_qkv_projections
436
+ def unfuse_qkv_projections(self):
437
+ """Disables the fused QKV projection if enabled.
438
+
439
+ <Tip warning={true}>
440
+
441
+ This API is 🧪 experimental.
442
+
443
+ </Tip>
444
+
445
+ """
446
+ if self.original_attn_processors is not None:
447
+ self.set_attn_processor(self.original_attn_processors)
448
+
449
+ def forward(
450
+ self,
451
+ hidden_states: torch.Tensor,
452
+ encoder_hidden_states: torch.Tensor,
453
+ timestep: Union[int, float, torch.LongTensor],
454
+ timestep_cond: Optional[torch.Tensor] = None,
455
+ inpaint_latents: Optional[torch.Tensor] = None,
456
+ control_latents: Optional[torch.Tensor] = None,
457
+ image_rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
458
+ return_dict: bool = True,
459
+ ):
460
+ batch_size, num_frames, channels, height, width = hidden_states.shape
461
+
462
+ # 1. Time embedding
463
+ timesteps = timestep
464
+ t_emb = self.time_proj(timesteps)
465
+
466
+ # timesteps does not contain any weights and will always return f32 tensors
467
+ # but time_embedding might actually be running in fp16. so we need to cast here.
468
+ # there might be better ways to encapsulate this.
469
+ t_emb = t_emb.to(dtype=hidden_states.dtype)
470
+ emb = self.time_embedding(t_emb, timestep_cond)
471
+
472
+ # 2. Patch embedding
473
+ if inpaint_latents is not None:
474
+ hidden_states = torch.concat([hidden_states, inpaint_latents], 2)
475
+ if control_latents is not None:
476
+ hidden_states = torch.concat([hidden_states, control_latents], 2)
477
+ hidden_states = self.patch_embed(encoder_hidden_states, hidden_states)
478
+
479
+ # 3. Position embedding
480
+ text_seq_length = encoder_hidden_states.shape[1]
481
+ if not self.config.use_rotary_positional_embeddings:
482
+ seq_length = height * width * num_frames // (self.config.patch_size**2)
483
+ # pos_embeds = self.pos_embedding[:, : text_seq_length + seq_length]
484
+ pos_embeds = self.pos_embedding
485
+ emb_size = hidden_states.size()[-1]
486
+ pos_embeds_without_text = pos_embeds[:, text_seq_length: ].view(1, self.post_time_compression_frames, self.post_patch_height, self.post_patch_width, emb_size)
487
+ pos_embeds_without_text = pos_embeds_without_text.permute([0, 4, 1, 2, 3])
488
+ pos_embeds_without_text = F.interpolate(pos_embeds_without_text,size=[self.post_time_compression_frames, height // self.config.patch_size, width // self.config.patch_size],mode='trilinear',align_corners=False)
489
+ pos_embeds_without_text = pos_embeds_without_text.permute([0, 2, 3, 4, 1]).view(1, -1, emb_size)
490
+ pos_embeds = torch.cat([pos_embeds[:, :text_seq_length], pos_embeds_without_text], dim = 1)
491
+ pos_embeds = pos_embeds[:, : text_seq_length + seq_length]
492
+ hidden_states = hidden_states + pos_embeds
493
+ hidden_states = self.embedding_dropout(hidden_states)
494
+
495
+ encoder_hidden_states = hidden_states[:, :text_seq_length]
496
+ hidden_states = hidden_states[:, text_seq_length:]
497
+
498
+ # 4. Transformer blocks
499
+ for i, block in enumerate(self.transformer_blocks):
500
+ if self.training and self.gradient_checkpointing:
501
+
502
+ def create_custom_forward(module):
503
+ def custom_forward(*inputs):
504
+ return module(*inputs)
505
+
506
+ return custom_forward
507
+
508
+ ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {}
509
+ hidden_states, encoder_hidden_states = torch.utils.checkpoint.checkpoint(
510
+ create_custom_forward(block),
511
+ hidden_states,
512
+ encoder_hidden_states,
513
+ emb,
514
+ image_rotary_emb,
515
+ **ckpt_kwargs,
516
+ )
517
+ else:
518
+ hidden_states, encoder_hidden_states = block(
519
+ hidden_states=hidden_states,
520
+ encoder_hidden_states=encoder_hidden_states,
521
+ temb=emb,
522
+ image_rotary_emb=image_rotary_emb,
523
+ )
524
+
525
+ if not self.config.use_rotary_positional_embeddings:
526
+ # CogVideoX-2B
527
+ hidden_states = self.norm_final(hidden_states)
528
+ else:
529
+ # CogVideoX-5B
530
+ hidden_states = torch.cat([encoder_hidden_states, hidden_states], dim=1)
531
+ hidden_states = self.norm_final(hidden_states)
532
+ hidden_states = hidden_states[:, text_seq_length:]
533
+
534
+ # 5. Final block
535
+ hidden_states = self.norm_out(hidden_states, temb=emb)
536
+ hidden_states = self.proj_out(hidden_states)
537
+
538
+ # 6. Unpatchify
539
+ p = self.config.patch_size
540
+ output = hidden_states.reshape(batch_size, num_frames, height // p, width // p, channels, p, p)
541
+ output = output.permute(0, 1, 4, 2, 5, 3, 6).flatten(5, 6).flatten(3, 4)
542
+
543
+ if not return_dict:
544
+ return (output,)
545
+ return Transformer2DModelOutput(sample=output)
546
+
547
+ @classmethod
548
+ def from_pretrained_2d(cls, pretrained_model_path, subfolder=None, transformer_additional_kwargs={}):
549
+ if subfolder is not None:
550
+ pretrained_model_path = os.path.join(pretrained_model_path, subfolder)
551
+ print(f"loaded 3D transformer's pretrained weights from {pretrained_model_path} ...")
552
+
553
+ config_file = os.path.join(pretrained_model_path, 'config.json')
554
+ if not os.path.isfile(config_file):
555
+ raise RuntimeError(f"{config_file} does not exist")
556
+ with open(config_file, "r") as f:
557
+ config = json.load(f)
558
+
559
+ from diffusers.utils import WEIGHTS_NAME
560
+ model = cls.from_config(config, **transformer_additional_kwargs)
561
+ model_file = os.path.join(pretrained_model_path, WEIGHTS_NAME)
562
+ model_file_safetensors = model_file.replace(".bin", ".safetensors")
563
+ if os.path.exists(model_file):
564
+ state_dict = torch.load(model_file, map_location="cpu")
565
+ elif os.path.exists(model_file_safetensors):
566
+ from safetensors.torch import load_file, safe_open
567
+ state_dict = load_file(model_file_safetensors)
568
+ else:
569
+ from safetensors.torch import load_file, safe_open
570
+ model_files_safetensors = glob.glob(os.path.join(pretrained_model_path, "*.safetensors"))
571
+ state_dict = {}
572
+ for model_file_safetensors in model_files_safetensors:
573
+ _state_dict = load_file(model_file_safetensors)
574
+ for key in _state_dict:
575
+ state_dict[key] = _state_dict[key]
576
+
577
+ if model.state_dict()['patch_embed.proj.weight'].size() != state_dict['patch_embed.proj.weight'].size():
578
+ new_shape = model.state_dict()['patch_embed.proj.weight'].size()
579
+ if len(new_shape) == 5:
580
+ state_dict['patch_embed.proj.weight'] = state_dict['patch_embed.proj.weight'].unsqueeze(2).expand(new_shape).clone()
581
+ state_dict['patch_embed.proj.weight'][:, :, :-1] = 0
582
+ else:
583
+ if model.state_dict()['patch_embed.proj.weight'].size()[1] > state_dict['patch_embed.proj.weight'].size()[1]:
584
+ model.state_dict()['patch_embed.proj.weight'][:, :state_dict['patch_embed.proj.weight'].size()[1], :, :] = state_dict['patch_embed.proj.weight']
585
+ model.state_dict()['patch_embed.proj.weight'][:, state_dict['patch_embed.proj.weight'].size()[1]:, :, :] = 0
586
+ state_dict['patch_embed.proj.weight'] = model.state_dict()['patch_embed.proj.weight']
587
+ else:
588
+ model.state_dict()['patch_embed.proj.weight'][:, :, :, :] = state_dict['patch_embed.proj.weight'][:, :model.state_dict()['patch_embed.proj.weight'].size()[1], :, :]
589
+ state_dict['patch_embed.proj.weight'] = model.state_dict()['patch_embed.proj.weight']
590
+
591
+ tmp_state_dict = {}
592
+ for key in state_dict:
593
+ if key in model.state_dict().keys() and model.state_dict()[key].size() == state_dict[key].size():
594
+ tmp_state_dict[key] = state_dict[key]
595
+ else:
596
+ print(key, "Size don't match, skip")
597
+ state_dict = tmp_state_dict
598
+
599
+ m, u = model.load_state_dict(state_dict, strict=False)
600
+ print(f"### missing keys: {len(m)}; \n### unexpected keys: {len(u)};")
601
+ print(m)
602
+
603
+ params = [p.numel() if "mamba" in n else 0 for n, p in model.named_parameters()]
604
+ print(f"### Mamba Parameters: {sum(params) / 1e6} M")
605
+
606
+ params = [p.numel() if "attn1." in n else 0 for n, p in model.named_parameters()]
607
+ print(f"### attn1 Parameters: {sum(params) / 1e6} M")
608
+
609
+ return model
cogvideox/pipeline/pipeline_cogvideox.py ADDED
@@ -0,0 +1,751 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 The CogVideoX team, Tsinghua University & ZhipuAI and The HuggingFace Team.
2
+ # All rights reserved.
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 inspect
17
+ import math
18
+ from dataclasses import dataclass
19
+ from typing import Callable, Dict, List, Optional, Tuple, Union
20
+
21
+ import torch
22
+ from transformers import T5EncoderModel, T5Tokenizer
23
+
24
+ from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback
25
+ from diffusers.models import AutoencoderKLCogVideoX, CogVideoXTransformer3DModel
26
+ from diffusers.models.embeddings import get_3d_rotary_pos_embed
27
+ from diffusers.pipelines.pipeline_utils import DiffusionPipeline
28
+ from diffusers.schedulers import CogVideoXDDIMScheduler, CogVideoXDPMScheduler
29
+ from diffusers.utils import BaseOutput, logging, replace_example_docstring
30
+ from diffusers.utils.torch_utils import randn_tensor
31
+ from diffusers.video_processor import VideoProcessor
32
+
33
+
34
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
35
+
36
+
37
+ EXAMPLE_DOC_STRING = """
38
+ Examples:
39
+ ```python
40
+ >>> import torch
41
+ >>> from diffusers import CogVideoX_Fun_Pipeline
42
+ >>> from diffusers.utils import export_to_video
43
+
44
+ >>> # Models: "THUDM/CogVideoX-2b" or "THUDM/CogVideoX-5b"
45
+ >>> pipe = CogVideoX_Fun_Pipeline.from_pretrained("THUDM/CogVideoX-2b", torch_dtype=torch.float16).to("cuda")
46
+ >>> prompt = (
47
+ ... "A panda, dressed in a small, red jacket and a tiny hat, sits on a wooden stool in a serene bamboo forest. "
48
+ ... "The panda's fluffy paws strum a miniature acoustic guitar, producing soft, melodic tunes. Nearby, a few other "
49
+ ... "pandas gather, watching curiously and some clapping in rhythm. Sunlight filters through the tall bamboo, "
50
+ ... "casting a gentle glow on the scene. The panda's face is expressive, showing concentration and joy as it plays. "
51
+ ... "The background includes a small, flowing stream and vibrant green foliage, enhancing the peaceful and magical "
52
+ ... "atmosphere of this unique musical performance."
53
+ ... )
54
+ >>> video = pipe(prompt=prompt, guidance_scale=6, num_inference_steps=50).frames[0]
55
+ >>> export_to_video(video, "output.mp4", fps=8)
56
+ ```
57
+ """
58
+
59
+
60
+ # Similar to diffusers.pipelines.hunyuandit.pipeline_hunyuandit.get_resize_crop_region_for_grid
61
+ def get_resize_crop_region_for_grid(src, tgt_width, tgt_height):
62
+ tw = tgt_width
63
+ th = tgt_height
64
+ h, w = src
65
+ r = h / w
66
+ if r > (th / tw):
67
+ resize_height = th
68
+ resize_width = int(round(th / h * w))
69
+ else:
70
+ resize_width = tw
71
+ resize_height = int(round(tw / w * h))
72
+
73
+ crop_top = int(round((th - resize_height) / 2.0))
74
+ crop_left = int(round((tw - resize_width) / 2.0))
75
+
76
+ return (crop_top, crop_left), (crop_top + resize_height, crop_left + resize_width)
77
+
78
+
79
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
80
+ def retrieve_timesteps(
81
+ scheduler,
82
+ num_inference_steps: Optional[int] = None,
83
+ device: Optional[Union[str, torch.device]] = None,
84
+ timesteps: Optional[List[int]] = None,
85
+ sigmas: Optional[List[float]] = None,
86
+ **kwargs,
87
+ ):
88
+ """
89
+ Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles
90
+ custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`.
91
+
92
+ Args:
93
+ scheduler (`SchedulerMixin`):
94
+ The scheduler to get timesteps from.
95
+ num_inference_steps (`int`):
96
+ The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`
97
+ must be `None`.
98
+ device (`str` or `torch.device`, *optional*):
99
+ The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
100
+ timesteps (`List[int]`, *optional*):
101
+ Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,
102
+ `num_inference_steps` and `sigmas` must be `None`.
103
+ sigmas (`List[float]`, *optional*):
104
+ Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,
105
+ `num_inference_steps` and `timesteps` must be `None`.
106
+
107
+ Returns:
108
+ `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the
109
+ second element is the number of inference steps.
110
+ """
111
+ if timesteps is not None and sigmas is not None:
112
+ raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
113
+ if timesteps is not None:
114
+ accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
115
+ if not accepts_timesteps:
116
+ raise ValueError(
117
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
118
+ f" timestep schedules. Please check whether you are using the correct scheduler."
119
+ )
120
+ scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
121
+ timesteps = scheduler.timesteps
122
+ num_inference_steps = len(timesteps)
123
+ elif sigmas is not None:
124
+ accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
125
+ if not accept_sigmas:
126
+ raise ValueError(
127
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
128
+ f" sigmas schedules. Please check whether you are using the correct scheduler."
129
+ )
130
+ scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
131
+ timesteps = scheduler.timesteps
132
+ num_inference_steps = len(timesteps)
133
+ else:
134
+ scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
135
+ timesteps = scheduler.timesteps
136
+ return timesteps, num_inference_steps
137
+
138
+
139
+ @dataclass
140
+ class CogVideoX_Fun_PipelineOutput(BaseOutput):
141
+ r"""
142
+ Output class for CogVideo pipelines.
143
+
144
+ Args:
145
+ video (`torch.Tensor`, `np.ndarray`, or List[List[PIL.Image.Image]]):
146
+ List of video outputs - It can be a nested list of length `batch_size,` with each sub-list containing
147
+ denoised PIL image sequences of length `num_frames.` It can also be a NumPy array or Torch tensor of shape
148
+ `(batch_size, num_frames, channels, height, width)`.
149
+ """
150
+
151
+ videos: torch.Tensor
152
+
153
+
154
+ class CogVideoX_Fun_Pipeline(DiffusionPipeline):
155
+ r"""
156
+ Pipeline for text-to-video generation using CogVideoX_Fun.
157
+
158
+ This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the
159
+ library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.)
160
+
161
+ Args:
162
+ vae ([`AutoencoderKL`]):
163
+ Variational Auto-Encoder (VAE) Model to encode and decode videos to and from latent representations.
164
+ text_encoder ([`T5EncoderModel`]):
165
+ Frozen text-encoder. CogVideoX uses
166
+ [T5](https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5EncoderModel); specifically the
167
+ [t5-v1_1-xxl](https://huggingface.co/PixArt-alpha/PixArt-alpha/tree/main/t5-v1_1-xxl) variant.
168
+ tokenizer (`T5Tokenizer`):
169
+ Tokenizer of class
170
+ [T5Tokenizer](https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5Tokenizer).
171
+ transformer ([`CogVideoXTransformer3DModel`]):
172
+ A text conditioned `CogVideoXTransformer3DModel` to denoise the encoded video latents.
173
+ scheduler ([`SchedulerMixin`]):
174
+ A scheduler to be used in combination with `transformer` to denoise the encoded video latents.
175
+ """
176
+
177
+ _optional_components = []
178
+ model_cpu_offload_seq = "text_encoder->transformer->vae"
179
+
180
+ _callback_tensor_inputs = [
181
+ "latents",
182
+ "prompt_embeds",
183
+ "negative_prompt_embeds",
184
+ ]
185
+
186
+ def __init__(
187
+ self,
188
+ tokenizer: T5Tokenizer,
189
+ text_encoder: T5EncoderModel,
190
+ vae: AutoencoderKLCogVideoX,
191
+ transformer: CogVideoXTransformer3DModel,
192
+ scheduler: Union[CogVideoXDDIMScheduler, CogVideoXDPMScheduler],
193
+ ):
194
+ super().__init__()
195
+
196
+ self.register_modules(
197
+ tokenizer=tokenizer, text_encoder=text_encoder, vae=vae, transformer=transformer, scheduler=scheduler
198
+ )
199
+ self.vae_scale_factor_spatial = (
200
+ 2 ** (len(self.vae.config.block_out_channels) - 1) if hasattr(self, "vae") and self.vae is not None else 8
201
+ )
202
+ self.vae_scale_factor_temporal = (
203
+ self.vae.config.temporal_compression_ratio if hasattr(self, "vae") and self.vae is not None else 4
204
+ )
205
+
206
+ self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor_spatial)
207
+
208
+ def _get_t5_prompt_embeds(
209
+ self,
210
+ prompt: Union[str, List[str]] = None,
211
+ num_videos_per_prompt: int = 1,
212
+ max_sequence_length: int = 226,
213
+ device: Optional[torch.device] = None,
214
+ dtype: Optional[torch.dtype] = None,
215
+ ):
216
+ device = device or self._execution_device
217
+ dtype = dtype or self.text_encoder.dtype
218
+
219
+ prompt = [prompt] if isinstance(prompt, str) else prompt
220
+ batch_size = len(prompt)
221
+
222
+ text_inputs = self.tokenizer(
223
+ prompt,
224
+ padding="max_length",
225
+ max_length=max_sequence_length,
226
+ truncation=True,
227
+ add_special_tokens=True,
228
+ return_tensors="pt",
229
+ )
230
+ text_input_ids = text_inputs.input_ids
231
+ untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
232
+
233
+ if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
234
+ removed_text = self.tokenizer.batch_decode(untruncated_ids[:, max_sequence_length - 1 : -1])
235
+ logger.warning(
236
+ "The following part of your input was truncated because `max_sequence_length` is set to "
237
+ f" {max_sequence_length} tokens: {removed_text}"
238
+ )
239
+
240
+ prompt_embeds = self.text_encoder(text_input_ids.to(device))[0]
241
+ prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
242
+
243
+ # duplicate text embeddings for each generation per prompt, using mps friendly method
244
+ _, seq_len, _ = prompt_embeds.shape
245
+ prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1)
246
+ prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1)
247
+
248
+ return prompt_embeds
249
+
250
+ def encode_prompt(
251
+ self,
252
+ prompt: Union[str, List[str]],
253
+ negative_prompt: Optional[Union[str, List[str]]] = None,
254
+ do_classifier_free_guidance: bool = True,
255
+ num_videos_per_prompt: int = 1,
256
+ prompt_embeds: Optional[torch.Tensor] = None,
257
+ negative_prompt_embeds: Optional[torch.Tensor] = None,
258
+ max_sequence_length: int = 226,
259
+ device: Optional[torch.device] = None,
260
+ dtype: Optional[torch.dtype] = None,
261
+ ):
262
+ r"""
263
+ Encodes the prompt into text encoder hidden states.
264
+
265
+ Args:
266
+ prompt (`str` or `List[str]`, *optional*):
267
+ prompt to be encoded
268
+ negative_prompt (`str` or `List[str]`, *optional*):
269
+ The prompt or prompts not to guide the image generation. If not defined, one has to pass
270
+ `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is
271
+ less than `1`).
272
+ do_classifier_free_guidance (`bool`, *optional*, defaults to `True`):
273
+ Whether to use classifier free guidance or not.
274
+ num_videos_per_prompt (`int`, *optional*, defaults to 1):
275
+ Number of videos that should be generated per prompt. torch device to place the resulting embeddings on
276
+ prompt_embeds (`torch.Tensor`, *optional*):
277
+ Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
278
+ provided, text embeddings will be generated from `prompt` input argument.
279
+ negative_prompt_embeds (`torch.Tensor`, *optional*):
280
+ Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
281
+ weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input
282
+ argument.
283
+ device: (`torch.device`, *optional*):
284
+ torch device
285
+ dtype: (`torch.dtype`, *optional*):
286
+ torch dtype
287
+ """
288
+ device = device or self._execution_device
289
+
290
+ prompt = [prompt] if isinstance(prompt, str) else prompt
291
+ if prompt is not None:
292
+ batch_size = len(prompt)
293
+ else:
294
+ batch_size = prompt_embeds.shape[0]
295
+
296
+ if prompt_embeds is None:
297
+ prompt_embeds = self._get_t5_prompt_embeds(
298
+ prompt=prompt,
299
+ num_videos_per_prompt=num_videos_per_prompt,
300
+ max_sequence_length=max_sequence_length,
301
+ device=device,
302
+ dtype=dtype,
303
+ )
304
+
305
+ if do_classifier_free_guidance and negative_prompt_embeds is None:
306
+ negative_prompt = negative_prompt or ""
307
+ negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt
308
+
309
+ if prompt is not None and type(prompt) is not type(negative_prompt):
310
+ raise TypeError(
311
+ f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !="
312
+ f" {type(prompt)}."
313
+ )
314
+ elif batch_size != len(negative_prompt):
315
+ raise ValueError(
316
+ f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"
317
+ f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"
318
+ " the batch size of `prompt`."
319
+ )
320
+
321
+ negative_prompt_embeds = self._get_t5_prompt_embeds(
322
+ prompt=negative_prompt,
323
+ num_videos_per_prompt=num_videos_per_prompt,
324
+ max_sequence_length=max_sequence_length,
325
+ device=device,
326
+ dtype=dtype,
327
+ )
328
+
329
+ return prompt_embeds, negative_prompt_embeds
330
+
331
+ def prepare_latents(
332
+ self, batch_size, num_channels_latents, num_frames, height, width, dtype, device, generator, latents=None
333
+ ):
334
+ shape = (
335
+ batch_size,
336
+ (num_frames - 1) // self.vae_scale_factor_temporal + 1,
337
+ num_channels_latents,
338
+ height // self.vae_scale_factor_spatial,
339
+ width // self.vae_scale_factor_spatial,
340
+ )
341
+ if isinstance(generator, list) and len(generator) != batch_size:
342
+ raise ValueError(
343
+ f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
344
+ f" size of {batch_size}. Make sure the batch size matches the length of the generators."
345
+ )
346
+
347
+ if latents is None:
348
+ latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
349
+ else:
350
+ latents = latents.to(device)
351
+
352
+ # scale the initial noise by the standard deviation required by the scheduler
353
+ latents = latents * self.scheduler.init_noise_sigma
354
+ return latents
355
+
356
+ def decode_latents(self, latents: torch.Tensor) -> torch.Tensor:
357
+ latents = latents.permute(0, 2, 1, 3, 4) # [batch_size, num_channels, num_frames, height, width]
358
+ latents = 1 / self.vae.config.scaling_factor * latents
359
+
360
+ frames = self.vae.decode(latents).sample
361
+ frames = (frames / 2 + 0.5).clamp(0, 1)
362
+ # we always cast to float32 as this does not cause significant overhead and is compatible with bfloa16
363
+ frames = frames.cpu().float().numpy()
364
+ return frames
365
+
366
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs
367
+ def prepare_extra_step_kwargs(self, generator, eta):
368
+ # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
369
+ # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
370
+ # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
371
+ # and should be between [0, 1]
372
+
373
+ accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
374
+ extra_step_kwargs = {}
375
+ if accepts_eta:
376
+ extra_step_kwargs["eta"] = eta
377
+
378
+ # check if the scheduler accepts generator
379
+ accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
380
+ if accepts_generator:
381
+ extra_step_kwargs["generator"] = generator
382
+ return extra_step_kwargs
383
+
384
+ # Copied from diffusers.pipelines.latte.pipeline_latte.LattePipeline.check_inputs
385
+ def check_inputs(
386
+ self,
387
+ prompt,
388
+ height,
389
+ width,
390
+ negative_prompt,
391
+ callback_on_step_end_tensor_inputs,
392
+ prompt_embeds=None,
393
+ negative_prompt_embeds=None,
394
+ ):
395
+ if height % 8 != 0 or width % 8 != 0:
396
+ raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
397
+
398
+ if callback_on_step_end_tensor_inputs is not None and not all(
399
+ k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
400
+ ):
401
+ raise ValueError(
402
+ f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
403
+ )
404
+ if prompt is not None and prompt_embeds is not None:
405
+ raise ValueError(
406
+ f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
407
+ " only forward one of the two."
408
+ )
409
+ elif prompt is None and prompt_embeds is None:
410
+ raise ValueError(
411
+ "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
412
+ )
413
+ elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
414
+ raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
415
+
416
+ if prompt is not None and negative_prompt_embeds is not None:
417
+ raise ValueError(
418
+ f"Cannot forward both `prompt`: {prompt} and `negative_prompt_embeds`:"
419
+ f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
420
+ )
421
+
422
+ if negative_prompt is not None and negative_prompt_embeds is not None:
423
+ raise ValueError(
424
+ f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
425
+ f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
426
+ )
427
+
428
+ if prompt_embeds is not None and negative_prompt_embeds is not None:
429
+ if prompt_embeds.shape != negative_prompt_embeds.shape:
430
+ raise ValueError(
431
+ "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
432
+ f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
433
+ f" {negative_prompt_embeds.shape}."
434
+ )
435
+
436
+ def fuse_qkv_projections(self) -> None:
437
+ r"""Enables fused QKV projections."""
438
+ self.fusing_transformer = True
439
+ self.transformer.fuse_qkv_projections()
440
+
441
+ def unfuse_qkv_projections(self) -> None:
442
+ r"""Disable QKV projection fusion if enabled."""
443
+ if not self.fusing_transformer:
444
+ logger.warning("The Transformer was not initially fused for QKV projections. Doing nothing.")
445
+ else:
446
+ self.transformer.unfuse_qkv_projections()
447
+ self.fusing_transformer = False
448
+
449
+ def _prepare_rotary_positional_embeddings(
450
+ self,
451
+ height: int,
452
+ width: int,
453
+ num_frames: int,
454
+ device: torch.device,
455
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
456
+ grid_height = height // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
457
+ grid_width = width // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
458
+ base_size_width = 720 // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
459
+ base_size_height = 480 // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
460
+
461
+ grid_crops_coords = get_resize_crop_region_for_grid(
462
+ (grid_height, grid_width), base_size_width, base_size_height
463
+ )
464
+ freqs_cos, freqs_sin = get_3d_rotary_pos_embed(
465
+ embed_dim=self.transformer.config.attention_head_dim,
466
+ crops_coords=grid_crops_coords,
467
+ grid_size=(grid_height, grid_width),
468
+ temporal_size=num_frames,
469
+ use_real=True,
470
+ )
471
+
472
+ freqs_cos = freqs_cos.to(device=device)
473
+ freqs_sin = freqs_sin.to(device=device)
474
+ return freqs_cos, freqs_sin
475
+
476
+ @property
477
+ def guidance_scale(self):
478
+ return self._guidance_scale
479
+
480
+ @property
481
+ def num_timesteps(self):
482
+ return self._num_timesteps
483
+
484
+ @property
485
+ def interrupt(self):
486
+ return self._interrupt
487
+
488
+ @torch.no_grad()
489
+ @replace_example_docstring(EXAMPLE_DOC_STRING)
490
+ def __call__(
491
+ self,
492
+ prompt: Optional[Union[str, List[str]]] = None,
493
+ negative_prompt: Optional[Union[str, List[str]]] = None,
494
+ height: int = 480,
495
+ width: int = 720,
496
+ num_frames: int = 49,
497
+ num_inference_steps: int = 50,
498
+ timesteps: Optional[List[int]] = None,
499
+ guidance_scale: float = 6,
500
+ use_dynamic_cfg: bool = False,
501
+ num_videos_per_prompt: int = 1,
502
+ eta: float = 0.0,
503
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
504
+ latents: Optional[torch.FloatTensor] = None,
505
+ prompt_embeds: Optional[torch.FloatTensor] = None,
506
+ negative_prompt_embeds: Optional[torch.FloatTensor] = None,
507
+ output_type: str = "numpy",
508
+ return_dict: bool = False,
509
+ callback_on_step_end: Optional[
510
+ Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks]
511
+ ] = None,
512
+ callback_on_step_end_tensor_inputs: List[str] = ["latents"],
513
+ max_sequence_length: int = 226,
514
+ ) -> Union[CogVideoX_Fun_PipelineOutput, Tuple]:
515
+ """
516
+ Function invoked when calling the pipeline for generation.
517
+
518
+ Args:
519
+ prompt (`str` or `List[str]`, *optional*):
520
+ The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`.
521
+ instead.
522
+ negative_prompt (`str` or `List[str]`, *optional*):
523
+ The prompt or prompts not to guide the image generation. If not defined, one has to pass
524
+ `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is
525
+ less than `1`).
526
+ height (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
527
+ The height in pixels of the generated image. This is set to 1024 by default for the best results.
528
+ width (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
529
+ The width in pixels of the generated image. This is set to 1024 by default for the best results.
530
+ num_frames (`int`, defaults to `48`):
531
+ Number of frames to generate. Must be divisible by self.vae_scale_factor_temporal. Generated video will
532
+ contain 1 extra frame because CogVideoX is conditioned with (num_seconds * fps + 1) frames where
533
+ num_seconds is 6 and fps is 4. However, since videos can be saved at any fps, the only condition that
534
+ needs to be satisfied is that of divisibility mentioned above.
535
+ num_inference_steps (`int`, *optional*, defaults to 50):
536
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
537
+ expense of slower inference.
538
+ timesteps (`List[int]`, *optional*):
539
+ Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument
540
+ in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is
541
+ passed will be used. Must be in descending order.
542
+ guidance_scale (`float`, *optional*, defaults to 7.0):
543
+ Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).
544
+ `guidance_scale` is defined as `w` of equation 2. of [Imagen
545
+ Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >
546
+ 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
547
+ usually at the expense of lower image quality.
548
+ num_videos_per_prompt (`int`, *optional*, defaults to 1):
549
+ The number of videos to generate per prompt.
550
+ generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
551
+ One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)
552
+ to make generation deterministic.
553
+ latents (`torch.FloatTensor`, *optional*):
554
+ Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image
555
+ generation. Can be used to tweak the same generation with different prompts. If not provided, a latents
556
+ tensor will ge generated by sampling using the supplied random `generator`.
557
+ prompt_embeds (`torch.FloatTensor`, *optional*):
558
+ Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
559
+ provided, text embeddings will be generated from `prompt` input argument.
560
+ negative_prompt_embeds (`torch.FloatTensor`, *optional*):
561
+ Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
562
+ weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input
563
+ argument.
564
+ output_type (`str`, *optional*, defaults to `"pil"`):
565
+ The output format of the generate image. Choose between
566
+ [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`.
567
+ return_dict (`bool`, *optional*, defaults to `True`):
568
+ Whether or not to return a [`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] instead
569
+ of a plain tuple.
570
+ callback_on_step_end (`Callable`, *optional*):
571
+ A function that calls at the end of each denoising steps during the inference. The function is called
572
+ with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int,
573
+ callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by
574
+ `callback_on_step_end_tensor_inputs`.
575
+ callback_on_step_end_tensor_inputs (`List`, *optional*):
576
+ The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list
577
+ will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the
578
+ `._callback_tensor_inputs` attribute of your pipeline class.
579
+ max_sequence_length (`int`, defaults to `226`):
580
+ Maximum sequence length in encoded prompt. Must be consistent with
581
+ `self.transformer.config.max_text_seq_length` otherwise may lead to poor results.
582
+
583
+ Examples:
584
+
585
+ Returns:
586
+ [`~pipelines.cogvideo.pipeline_cogvideox.CogVideoX_Fun_PipelineOutput`] or `tuple`:
587
+ [`~pipelines.cogvideo.pipeline_cogvideox.CogVideoX_Fun_PipelineOutput`] if `return_dict` is True, otherwise a
588
+ `tuple`. When returning a tuple, the first element is a list with the generated images.
589
+ """
590
+
591
+ if num_frames > 49:
592
+ raise ValueError(
593
+ "The number of frames must be less than 49 for now due to static positional embeddings. This will be updated in the future to remove this limitation."
594
+ )
595
+
596
+ if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)):
597
+ callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs
598
+
599
+ height = height or self.transformer.config.sample_size * self.vae_scale_factor_spatial
600
+ width = width or self.transformer.config.sample_size * self.vae_scale_factor_spatial
601
+ num_videos_per_prompt = 1
602
+
603
+ # 1. Check inputs. Raise error if not correct
604
+ self.check_inputs(
605
+ prompt,
606
+ height,
607
+ width,
608
+ negative_prompt,
609
+ callback_on_step_end_tensor_inputs,
610
+ prompt_embeds,
611
+ negative_prompt_embeds,
612
+ )
613
+ self._guidance_scale = guidance_scale
614
+ self._interrupt = False
615
+
616
+ # 2. Default call parameters
617
+ if prompt is not None and isinstance(prompt, str):
618
+ batch_size = 1
619
+ elif prompt is not None and isinstance(prompt, list):
620
+ batch_size = len(prompt)
621
+ else:
622
+ batch_size = prompt_embeds.shape[0]
623
+
624
+ device = self._execution_device
625
+
626
+ # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
627
+ # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
628
+ # corresponds to doing no classifier free guidance.
629
+ do_classifier_free_guidance = guidance_scale > 1.0
630
+
631
+ # 3. Encode input prompt
632
+ prompt_embeds, negative_prompt_embeds = self.encode_prompt(
633
+ prompt,
634
+ negative_prompt,
635
+ do_classifier_free_guidance,
636
+ num_videos_per_prompt=num_videos_per_prompt,
637
+ prompt_embeds=prompt_embeds,
638
+ negative_prompt_embeds=negative_prompt_embeds,
639
+ max_sequence_length=max_sequence_length,
640
+ device=device,
641
+ )
642
+ if do_classifier_free_guidance:
643
+ prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0)
644
+
645
+ # 4. Prepare timesteps
646
+ timesteps, num_inference_steps = retrieve_timesteps(self.scheduler, num_inference_steps, device, timesteps)
647
+ self._num_timesteps = len(timesteps)
648
+
649
+ # 5. Prepare latents.
650
+ latent_channels = self.transformer.config.in_channels
651
+ latents = self.prepare_latents(
652
+ batch_size * num_videos_per_prompt,
653
+ latent_channels,
654
+ num_frames,
655
+ height,
656
+ width,
657
+ prompt_embeds.dtype,
658
+ device,
659
+ generator,
660
+ latents,
661
+ )
662
+
663
+ # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline
664
+ extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
665
+
666
+ # 7. Create rotary embeds if required
667
+ image_rotary_emb = (
668
+ self._prepare_rotary_positional_embeddings(height, width, latents.size(1), device)
669
+ if self.transformer.config.use_rotary_positional_embeddings
670
+ else None
671
+ )
672
+
673
+ # 8. Denoising loop
674
+ num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
675
+
676
+ with self.progress_bar(total=num_inference_steps) as progress_bar:
677
+ # for DPM-solver++
678
+ old_pred_original_sample = None
679
+ for i, t in enumerate(timesteps):
680
+ if self.interrupt:
681
+ continue
682
+
683
+ latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
684
+ latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
685
+
686
+ # broadcast to batch dimension in a way that's compatible with ONNX/Core ML
687
+ timestep = t.expand(latent_model_input.shape[0])
688
+
689
+ # predict noise model_output
690
+ noise_pred = self.transformer(
691
+ hidden_states=latent_model_input,
692
+ encoder_hidden_states=prompt_embeds,
693
+ timestep=timestep,
694
+ image_rotary_emb=image_rotary_emb,
695
+ return_dict=False,
696
+ )[0]
697
+ noise_pred = noise_pred.float()
698
+
699
+ # perform guidance
700
+ if use_dynamic_cfg:
701
+ self._guidance_scale = 1 + guidance_scale * (
702
+ (1 - math.cos(math.pi * ((num_inference_steps - t.item()) / num_inference_steps) ** 5.0)) / 2
703
+ )
704
+ if do_classifier_free_guidance:
705
+ noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
706
+ noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond)
707
+
708
+ # compute the previous noisy sample x_t -> x_t-1
709
+ if not isinstance(self.scheduler, CogVideoXDPMScheduler):
710
+ latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0]
711
+ else:
712
+ latents, old_pred_original_sample = self.scheduler.step(
713
+ noise_pred,
714
+ old_pred_original_sample,
715
+ t,
716
+ timesteps[i - 1] if i > 0 else None,
717
+ latents,
718
+ **extra_step_kwargs,
719
+ return_dict=False,
720
+ )
721
+ latents = latents.to(prompt_embeds.dtype)
722
+
723
+ # call the callback, if provided
724
+ if callback_on_step_end is not None:
725
+ callback_kwargs = {}
726
+ for k in callback_on_step_end_tensor_inputs:
727
+ callback_kwargs[k] = locals()[k]
728
+ callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
729
+
730
+ latents = callback_outputs.pop("latents", latents)
731
+ prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
732
+ negative_prompt_embeds = callback_outputs.pop("negative_prompt_embeds", negative_prompt_embeds)
733
+
734
+ if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
735
+ progress_bar.update()
736
+
737
+ if output_type == "numpy":
738
+ video = self.decode_latents(latents)
739
+ elif not output_type == "latent":
740
+ video = self.decode_latents(latents)
741
+ video = self.video_processor.postprocess_video(video=video, output_type=output_type)
742
+ else:
743
+ video = latents
744
+
745
+ # Offload all models
746
+ self.maybe_free_model_hooks()
747
+
748
+ if not return_dict:
749
+ video = torch.from_numpy(video)
750
+
751
+ return CogVideoX_Fun_PipelineOutput(videos=video)
cogvideox/pipeline/pipeline_cogvideox_control.py ADDED
@@ -0,0 +1,843 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 The CogVideoX team, Tsinghua University & ZhipuAI and The HuggingFace Team.
2
+ # All rights reserved.
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 inspect
17
+ import math
18
+ from dataclasses import dataclass
19
+ from typing import Callable, Dict, List, Optional, Tuple, Union
20
+
21
+ import torch
22
+ import torch.nn.functional as F
23
+ from einops import rearrange
24
+ from transformers import T5EncoderModel, T5Tokenizer
25
+
26
+ from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback
27
+ from diffusers.models import AutoencoderKLCogVideoX, CogVideoXTransformer3DModel
28
+ from diffusers.models.embeddings import get_3d_rotary_pos_embed
29
+ from diffusers.pipelines.pipeline_utils import DiffusionPipeline
30
+ from diffusers.schedulers import CogVideoXDDIMScheduler, CogVideoXDPMScheduler
31
+ from diffusers.utils import BaseOutput, logging, replace_example_docstring
32
+ from diffusers.utils.torch_utils import randn_tensor
33
+ from diffusers.video_processor import VideoProcessor
34
+ from diffusers.image_processor import VaeImageProcessor
35
+ from einops import rearrange
36
+
37
+
38
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
39
+
40
+
41
+ EXAMPLE_DOC_STRING = """
42
+ Examples:
43
+ ```python
44
+ >>> import torch
45
+ >>> from diffusers import CogVideoX_Fun_Pipeline
46
+ >>> from diffusers.utils import export_to_video
47
+
48
+ >>> # Models: "THUDM/CogVideoX-2b" or "THUDM/CogVideoX-5b"
49
+ >>> pipe = CogVideoX_Fun_Pipeline.from_pretrained("THUDM/CogVideoX-2b", torch_dtype=torch.float16).to("cuda")
50
+ >>> prompt = (
51
+ ... "A panda, dressed in a small, red jacket and a tiny hat, sits on a wooden stool in a serene bamboo forest. "
52
+ ... "The panda's fluffy paws strum a miniature acoustic guitar, producing soft, melodic tunes. Nearby, a few other "
53
+ ... "pandas gather, watching curiously and some clapping in rhythm. Sunlight filters through the tall bamboo, "
54
+ ... "casting a gentle glow on the scene. The panda's face is expressive, showing concentration and joy as it plays. "
55
+ ... "The background includes a small, flowing stream and vibrant green foliage, enhancing the peaceful and magical "
56
+ ... "atmosphere of this unique musical performance."
57
+ ... )
58
+ >>> video = pipe(prompt=prompt, guidance_scale=6, num_inference_steps=50).frames[0]
59
+ >>> export_to_video(video, "output.mp4", fps=8)
60
+ ```
61
+ """
62
+
63
+
64
+ # Similar to diffusers.pipelines.hunyuandit.pipeline_hunyuandit.get_resize_crop_region_for_grid
65
+ def get_resize_crop_region_for_grid(src, tgt_width, tgt_height):
66
+ tw = tgt_width
67
+ th = tgt_height
68
+ h, w = src
69
+ r = h / w
70
+ if r > (th / tw):
71
+ resize_height = th
72
+ resize_width = int(round(th / h * w))
73
+ else:
74
+ resize_width = tw
75
+ resize_height = int(round(tw / w * h))
76
+
77
+ crop_top = int(round((th - resize_height) / 2.0))
78
+ crop_left = int(round((tw - resize_width) / 2.0))
79
+
80
+ return (crop_top, crop_left), (crop_top + resize_height, crop_left + resize_width)
81
+
82
+
83
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
84
+ def retrieve_timesteps(
85
+ scheduler,
86
+ num_inference_steps: Optional[int] = None,
87
+ device: Optional[Union[str, torch.device]] = None,
88
+ timesteps: Optional[List[int]] = None,
89
+ sigmas: Optional[List[float]] = None,
90
+ **kwargs,
91
+ ):
92
+ """
93
+ Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles
94
+ custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`.
95
+
96
+ Args:
97
+ scheduler (`SchedulerMixin`):
98
+ The scheduler to get timesteps from.
99
+ num_inference_steps (`int`):
100
+ The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`
101
+ must be `None`.
102
+ device (`str` or `torch.device`, *optional*):
103
+ The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
104
+ timesteps (`List[int]`, *optional*):
105
+ Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,
106
+ `num_inference_steps` and `sigmas` must be `None`.
107
+ sigmas (`List[float]`, *optional*):
108
+ Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,
109
+ `num_inference_steps` and `timesteps` must be `None`.
110
+
111
+ Returns:
112
+ `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the
113
+ second element is the number of inference steps.
114
+ """
115
+ if timesteps is not None and sigmas is not None:
116
+ raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
117
+ if timesteps is not None:
118
+ accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
119
+ if not accepts_timesteps:
120
+ raise ValueError(
121
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
122
+ f" timestep schedules. Please check whether you are using the correct scheduler."
123
+ )
124
+ scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
125
+ timesteps = scheduler.timesteps
126
+ num_inference_steps = len(timesteps)
127
+ elif sigmas is not None:
128
+ accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
129
+ if not accept_sigmas:
130
+ raise ValueError(
131
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
132
+ f" sigmas schedules. Please check whether you are using the correct scheduler."
133
+ )
134
+ scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
135
+ timesteps = scheduler.timesteps
136
+ num_inference_steps = len(timesteps)
137
+ else:
138
+ scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
139
+ timesteps = scheduler.timesteps
140
+ return timesteps, num_inference_steps
141
+
142
+
143
+ @dataclass
144
+ class CogVideoX_Fun_PipelineOutput(BaseOutput):
145
+ r"""
146
+ Output class for CogVideo pipelines.
147
+
148
+ Args:
149
+ video (`torch.Tensor`, `np.ndarray`, or List[List[PIL.Image.Image]]):
150
+ List of video outputs - It can be a nested list of length `batch_size,` with each sub-list containing
151
+ denoised PIL image sequences of length `num_frames.` It can also be a NumPy array or Torch tensor of shape
152
+ `(batch_size, num_frames, channels, height, width)`.
153
+ """
154
+
155
+ videos: torch.Tensor
156
+
157
+
158
+ class CogVideoX_Fun_Pipeline_Control(DiffusionPipeline):
159
+ r"""
160
+ Pipeline for text-to-video generation using CogVideoX.
161
+
162
+ This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the
163
+ library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.)
164
+
165
+ Args:
166
+ vae ([`AutoencoderKL`]):
167
+ Variational Auto-Encoder (VAE) Model to encode and decode videos to and from latent representations.
168
+ text_encoder ([`T5EncoderModel`]):
169
+ Frozen text-encoder. CogVideoX_Fun uses
170
+ [T5](https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5EncoderModel); specifically the
171
+ [t5-v1_1-xxl](https://huggingface.co/PixArt-alpha/PixArt-alpha/tree/main/t5-v1_1-xxl) variant.
172
+ tokenizer (`T5Tokenizer`):
173
+ Tokenizer of class
174
+ [T5Tokenizer](https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5Tokenizer).
175
+ transformer ([`CogVideoXTransformer3DModel`]):
176
+ A text conditioned `CogVideoXTransformer3DModel` to denoise the encoded video latents.
177
+ scheduler ([`SchedulerMixin`]):
178
+ A scheduler to be used in combination with `transformer` to denoise the encoded video latents.
179
+ """
180
+
181
+ _optional_components = []
182
+ model_cpu_offload_seq = "text_encoder->transformer->vae"
183
+
184
+ _callback_tensor_inputs = [
185
+ "latents",
186
+ "prompt_embeds",
187
+ "negative_prompt_embeds",
188
+ ]
189
+
190
+ def __init__(
191
+ self,
192
+ tokenizer: T5Tokenizer,
193
+ text_encoder: T5EncoderModel,
194
+ vae: AutoencoderKLCogVideoX,
195
+ transformer: CogVideoXTransformer3DModel,
196
+ scheduler: Union[CogVideoXDDIMScheduler, CogVideoXDPMScheduler],
197
+ ):
198
+ super().__init__()
199
+
200
+ self.register_modules(
201
+ tokenizer=tokenizer, text_encoder=text_encoder, vae=vae, transformer=transformer, scheduler=scheduler
202
+ )
203
+ self.vae_scale_factor_spatial = (
204
+ 2 ** (len(self.vae.config.block_out_channels) - 1) if hasattr(self, "vae") and self.vae is not None else 8
205
+ )
206
+ self.vae_scale_factor_temporal = (
207
+ self.vae.config.temporal_compression_ratio if hasattr(self, "vae") and self.vae is not None else 4
208
+ )
209
+
210
+ self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor_spatial)
211
+
212
+ self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
213
+ self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
214
+ self.mask_processor = VaeImageProcessor(
215
+ vae_scale_factor=self.vae_scale_factor, do_normalize=False, do_binarize=True, do_convert_grayscale=True
216
+ )
217
+
218
+ def _get_t5_prompt_embeds(
219
+ self,
220
+ prompt: Union[str, List[str]] = None,
221
+ num_videos_per_prompt: int = 1,
222
+ max_sequence_length: int = 226,
223
+ device: Optional[torch.device] = None,
224
+ dtype: Optional[torch.dtype] = None,
225
+ ):
226
+ device = device or self._execution_device
227
+ dtype = dtype or self.text_encoder.dtype
228
+
229
+ prompt = [prompt] if isinstance(prompt, str) else prompt
230
+ batch_size = len(prompt)
231
+
232
+ text_inputs = self.tokenizer(
233
+ prompt,
234
+ padding="max_length",
235
+ max_length=max_sequence_length,
236
+ truncation=True,
237
+ add_special_tokens=True,
238
+ return_tensors="pt",
239
+ )
240
+ text_input_ids = text_inputs.input_ids
241
+ untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
242
+
243
+ if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
244
+ removed_text = self.tokenizer.batch_decode(untruncated_ids[:, max_sequence_length - 1 : -1])
245
+ logger.warning(
246
+ "The following part of your input was truncated because `max_sequence_length` is set to "
247
+ f" {max_sequence_length} tokens: {removed_text}"
248
+ )
249
+
250
+ prompt_embeds = self.text_encoder(text_input_ids.to(device))[0]
251
+ prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
252
+
253
+ # duplicate text embeddings for each generation per prompt, using mps friendly method
254
+ _, seq_len, _ = prompt_embeds.shape
255
+ prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1)
256
+ prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1)
257
+
258
+ return prompt_embeds
259
+
260
+ def encode_prompt(
261
+ self,
262
+ prompt: Union[str, List[str]],
263
+ negative_prompt: Optional[Union[str, List[str]]] = None,
264
+ do_classifier_free_guidance: bool = True,
265
+ num_videos_per_prompt: int = 1,
266
+ prompt_embeds: Optional[torch.Tensor] = None,
267
+ negative_prompt_embeds: Optional[torch.Tensor] = None,
268
+ max_sequence_length: int = 226,
269
+ device: Optional[torch.device] = None,
270
+ dtype: Optional[torch.dtype] = None,
271
+ ):
272
+ r"""
273
+ Encodes the prompt into text encoder hidden states.
274
+
275
+ Args:
276
+ prompt (`str` or `List[str]`, *optional*):
277
+ prompt to be encoded
278
+ negative_prompt (`str` or `List[str]`, *optional*):
279
+ The prompt or prompts not to guide the image generation. If not defined, one has to pass
280
+ `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is
281
+ less than `1`).
282
+ do_classifier_free_guidance (`bool`, *optional*, defaults to `True`):
283
+ Whether to use classifier free guidance or not.
284
+ num_videos_per_prompt (`int`, *optional*, defaults to 1):
285
+ Number of videos that should be generated per prompt. torch device to place the resulting embeddings on
286
+ prompt_embeds (`torch.Tensor`, *optional*):
287
+ Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
288
+ provided, text embeddings will be generated from `prompt` input argument.
289
+ negative_prompt_embeds (`torch.Tensor`, *optional*):
290
+ Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
291
+ weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input
292
+ argument.
293
+ device: (`torch.device`, *optional*):
294
+ torch device
295
+ dtype: (`torch.dtype`, *optional*):
296
+ torch dtype
297
+ """
298
+ device = device or self._execution_device
299
+
300
+ prompt = [prompt] if isinstance(prompt, str) else prompt
301
+ if prompt is not None:
302
+ batch_size = len(prompt)
303
+ else:
304
+ batch_size = prompt_embeds.shape[0]
305
+
306
+ if prompt_embeds is None:
307
+ prompt_embeds = self._get_t5_prompt_embeds(
308
+ prompt=prompt,
309
+ num_videos_per_prompt=num_videos_per_prompt,
310
+ max_sequence_length=max_sequence_length,
311
+ device=device,
312
+ dtype=dtype,
313
+ )
314
+
315
+ if do_classifier_free_guidance and negative_prompt_embeds is None:
316
+ negative_prompt = negative_prompt or ""
317
+ negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt
318
+
319
+ if prompt is not None and type(prompt) is not type(negative_prompt):
320
+ raise TypeError(
321
+ f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !="
322
+ f" {type(prompt)}."
323
+ )
324
+ elif batch_size != len(negative_prompt):
325
+ raise ValueError(
326
+ f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"
327
+ f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"
328
+ " the batch size of `prompt`."
329
+ )
330
+
331
+ negative_prompt_embeds = self._get_t5_prompt_embeds(
332
+ prompt=negative_prompt,
333
+ num_videos_per_prompt=num_videos_per_prompt,
334
+ max_sequence_length=max_sequence_length,
335
+ device=device,
336
+ dtype=dtype,
337
+ )
338
+
339
+ return prompt_embeds, negative_prompt_embeds
340
+
341
+ def prepare_latents(
342
+ self, batch_size, num_channels_latents, num_frames, height, width, dtype, device, generator, latents=None
343
+ ):
344
+ shape = (
345
+ batch_size,
346
+ (num_frames - 1) // self.vae_scale_factor_temporal + 1,
347
+ num_channels_latents,
348
+ height // self.vae_scale_factor_spatial,
349
+ width // self.vae_scale_factor_spatial,
350
+ )
351
+ if isinstance(generator, list) and len(generator) != batch_size:
352
+ raise ValueError(
353
+ f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
354
+ f" size of {batch_size}. Make sure the batch size matches the length of the generators."
355
+ )
356
+
357
+ if latents is None:
358
+ latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
359
+ else:
360
+ latents = latents.to(device)
361
+
362
+ # scale the initial noise by the standard deviation required by the scheduler
363
+ latents = latents * self.scheduler.init_noise_sigma
364
+ return latents
365
+
366
+ def prepare_control_latents(
367
+ self, mask, masked_image, batch_size, height, width, dtype, device, generator, do_classifier_free_guidance
368
+ ):
369
+ # resize the mask to latents shape as we concatenate the mask to the latents
370
+ # we do that before converting to dtype to avoid breaking in case we're using cpu_offload
371
+ # and half precision
372
+
373
+ if mask is not None:
374
+ mask = mask.to(device=device, dtype=self.vae.dtype)
375
+ bs = 1
376
+ new_mask = []
377
+ for i in range(0, mask.shape[0], bs):
378
+ mask_bs = mask[i : i + bs]
379
+ mask_bs = self.vae.encode(mask_bs)[0]
380
+ mask_bs = mask_bs.mode()
381
+ new_mask.append(mask_bs)
382
+ mask = torch.cat(new_mask, dim = 0)
383
+ mask = mask * self.vae.config.scaling_factor
384
+
385
+ if masked_image is not None:
386
+ masked_image = masked_image.to(device=device, dtype=self.vae.dtype)
387
+ bs = 1
388
+ new_mask_pixel_values = []
389
+ for i in range(0, masked_image.shape[0], bs):
390
+ mask_pixel_values_bs = masked_image[i : i + bs]
391
+ mask_pixel_values_bs = self.vae.encode(mask_pixel_values_bs)[0]
392
+ mask_pixel_values_bs = mask_pixel_values_bs.mode()
393
+ new_mask_pixel_values.append(mask_pixel_values_bs)
394
+ masked_image_latents = torch.cat(new_mask_pixel_values, dim = 0)
395
+ masked_image_latents = masked_image_latents * self.vae.config.scaling_factor
396
+ else:
397
+ masked_image_latents = None
398
+
399
+ return mask, masked_image_latents
400
+
401
+ def decode_latents(self, latents: torch.Tensor) -> torch.Tensor:
402
+ latents = latents.permute(0, 2, 1, 3, 4) # [batch_size, num_channels, num_frames, height, width]
403
+ latents = 1 / self.vae.config.scaling_factor * latents
404
+
405
+ frames = self.vae.decode(latents).sample
406
+ frames = (frames / 2 + 0.5).clamp(0, 1)
407
+ # we always cast to float32 as this does not cause significant overhead and is compatible with bfloa16
408
+ frames = frames.cpu().float().numpy()
409
+ return frames
410
+
411
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs
412
+ def prepare_extra_step_kwargs(self, generator, eta):
413
+ # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
414
+ # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
415
+ # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
416
+ # and should be between [0, 1]
417
+
418
+ accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
419
+ extra_step_kwargs = {}
420
+ if accepts_eta:
421
+ extra_step_kwargs["eta"] = eta
422
+
423
+ # check if the scheduler accepts generator
424
+ accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
425
+ if accepts_generator:
426
+ extra_step_kwargs["generator"] = generator
427
+ return extra_step_kwargs
428
+
429
+ # Copied from diffusers.pipelines.latte.pipeline_latte.LattePipeline.check_inputs
430
+ def check_inputs(
431
+ self,
432
+ prompt,
433
+ height,
434
+ width,
435
+ negative_prompt,
436
+ callback_on_step_end_tensor_inputs,
437
+ prompt_embeds=None,
438
+ negative_prompt_embeds=None,
439
+ ):
440
+ if height % 8 != 0 or width % 8 != 0:
441
+ raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
442
+
443
+ if callback_on_step_end_tensor_inputs is not None and not all(
444
+ k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
445
+ ):
446
+ raise ValueError(
447
+ f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
448
+ )
449
+ if prompt is not None and prompt_embeds is not None:
450
+ raise ValueError(
451
+ f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
452
+ " only forward one of the two."
453
+ )
454
+ elif prompt is None and prompt_embeds is None:
455
+ raise ValueError(
456
+ "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
457
+ )
458
+ elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
459
+ raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
460
+
461
+ if prompt is not None and negative_prompt_embeds is not None:
462
+ raise ValueError(
463
+ f"Cannot forward both `prompt`: {prompt} and `negative_prompt_embeds`:"
464
+ f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
465
+ )
466
+
467
+ if negative_prompt is not None and negative_prompt_embeds is not None:
468
+ raise ValueError(
469
+ f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
470
+ f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
471
+ )
472
+
473
+ if prompt_embeds is not None and negative_prompt_embeds is not None:
474
+ if prompt_embeds.shape != negative_prompt_embeds.shape:
475
+ raise ValueError(
476
+ "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
477
+ f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
478
+ f" {negative_prompt_embeds.shape}."
479
+ )
480
+
481
+ def fuse_qkv_projections(self) -> None:
482
+ r"""Enables fused QKV projections."""
483
+ self.fusing_transformer = True
484
+ self.transformer.fuse_qkv_projections()
485
+
486
+ def unfuse_qkv_projections(self) -> None:
487
+ r"""Disable QKV projection fusion if enabled."""
488
+ if not self.fusing_transformer:
489
+ logger.warning("The Transformer was not initially fused for QKV projections. Doing nothing.")
490
+ else:
491
+ self.transformer.unfuse_qkv_projections()
492
+ self.fusing_transformer = False
493
+
494
+ def _prepare_rotary_positional_embeddings(
495
+ self,
496
+ height: int,
497
+ width: int,
498
+ num_frames: int,
499
+ device: torch.device,
500
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
501
+ grid_height = height // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
502
+ grid_width = width // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
503
+ base_size_width = 720 // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
504
+ base_size_height = 480 // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
505
+
506
+ grid_crops_coords = get_resize_crop_region_for_grid(
507
+ (grid_height, grid_width), base_size_width, base_size_height
508
+ )
509
+ freqs_cos, freqs_sin = get_3d_rotary_pos_embed(
510
+ embed_dim=self.transformer.config.attention_head_dim,
511
+ crops_coords=grid_crops_coords,
512
+ grid_size=(grid_height, grid_width),
513
+ temporal_size=num_frames,
514
+ use_real=True,
515
+ )
516
+
517
+ freqs_cos = freqs_cos.to(device=device)
518
+ freqs_sin = freqs_sin.to(device=device)
519
+ return freqs_cos, freqs_sin
520
+
521
+ @property
522
+ def guidance_scale(self):
523
+ return self._guidance_scale
524
+
525
+ @property
526
+ def num_timesteps(self):
527
+ return self._num_timesteps
528
+
529
+ @property
530
+ def interrupt(self):
531
+ return self._interrupt
532
+
533
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.StableDiffusionImg2ImgPipeline.get_timesteps
534
+ def get_timesteps(self, num_inference_steps, strength, device):
535
+ # get the original timestep using init_timestep
536
+ init_timestep = min(int(num_inference_steps * strength), num_inference_steps)
537
+
538
+ t_start = max(num_inference_steps - init_timestep, 0)
539
+ timesteps = self.scheduler.timesteps[t_start * self.scheduler.order :]
540
+
541
+ return timesteps, num_inference_steps - t_start
542
+
543
+ @torch.no_grad()
544
+ @replace_example_docstring(EXAMPLE_DOC_STRING)
545
+ def __call__(
546
+ self,
547
+ prompt: Optional[Union[str, List[str]]] = None,
548
+ negative_prompt: Optional[Union[str, List[str]]] = None,
549
+ height: int = 480,
550
+ width: int = 720,
551
+ video: Union[torch.FloatTensor] = None,
552
+ control_video: Union[torch.FloatTensor] = None,
553
+ num_frames: int = 49,
554
+ num_inference_steps: int = 50,
555
+ timesteps: Optional[List[int]] = None,
556
+ guidance_scale: float = 6,
557
+ use_dynamic_cfg: bool = False,
558
+ num_videos_per_prompt: int = 1,
559
+ eta: float = 0.0,
560
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
561
+ latents: Optional[torch.FloatTensor] = None,
562
+ prompt_embeds: Optional[torch.FloatTensor] = None,
563
+ negative_prompt_embeds: Optional[torch.FloatTensor] = None,
564
+ output_type: str = "numpy",
565
+ return_dict: bool = False,
566
+ callback_on_step_end: Optional[
567
+ Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks]
568
+ ] = None,
569
+ callback_on_step_end_tensor_inputs: List[str] = ["latents"],
570
+ max_sequence_length: int = 226,
571
+ comfyui_progressbar: bool = False,
572
+ ) -> Union[CogVideoX_Fun_PipelineOutput, Tuple]:
573
+ """
574
+ Function invoked when calling the pipeline for generation.
575
+
576
+ Args:
577
+ prompt (`str` or `List[str]`, *optional*):
578
+ The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`.
579
+ instead.
580
+ negative_prompt (`str` or `List[str]`, *optional*):
581
+ The prompt or prompts not to guide the image generation. If not defined, one has to pass
582
+ `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is
583
+ less than `1`).
584
+ height (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
585
+ The height in pixels of the generated image. This is set to 1024 by default for the best results.
586
+ width (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
587
+ The width in pixels of the generated image. This is set to 1024 by default for the best results.
588
+ num_frames (`int`, defaults to `48`):
589
+ Number of frames to generate. Must be divisible by self.vae_scale_factor_temporal. Generated video will
590
+ contain 1 extra frame because CogVideoX_Fun is conditioned with (num_seconds * fps + 1) frames where
591
+ num_seconds is 6 and fps is 4. However, since videos can be saved at any fps, the only condition that
592
+ needs to be satisfied is that of divisibility mentioned above.
593
+ num_inference_steps (`int`, *optional*, defaults to 50):
594
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
595
+ expense of slower inference.
596
+ timesteps (`List[int]`, *optional*):
597
+ Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument
598
+ in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is
599
+ passed will be used. Must be in descending order.
600
+ guidance_scale (`float`, *optional*, defaults to 7.0):
601
+ Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).
602
+ `guidance_scale` is defined as `w` of equation 2. of [Imagen
603
+ Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >
604
+ 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
605
+ usually at the expense of lower image quality.
606
+ num_videos_per_prompt (`int`, *optional*, defaults to 1):
607
+ The number of videos to generate per prompt.
608
+ generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
609
+ One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)
610
+ to make generation deterministic.
611
+ latents (`torch.FloatTensor`, *optional*):
612
+ Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image
613
+ generation. Can be used to tweak the same generation with different prompts. If not provided, a latents
614
+ tensor will ge generated by sampling using the supplied random `generator`.
615
+ prompt_embeds (`torch.FloatTensor`, *optional*):
616
+ Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
617
+ provided, text embeddings will be generated from `prompt` input argument.
618
+ negative_prompt_embeds (`torch.FloatTensor`, *optional*):
619
+ Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
620
+ weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input
621
+ argument.
622
+ output_type (`str`, *optional*, defaults to `"pil"`):
623
+ The output format of the generate image. Choose between
624
+ [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`.
625
+ return_dict (`bool`, *optional*, defaults to `True`):
626
+ Whether or not to return a [`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] instead
627
+ of a plain tuple.
628
+ callback_on_step_end (`Callable`, *optional*):
629
+ A function that calls at the end of each denoising steps during the inference. The function is called
630
+ with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int,
631
+ callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by
632
+ `callback_on_step_end_tensor_inputs`.
633
+ callback_on_step_end_tensor_inputs (`List`, *optional*):
634
+ The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list
635
+ will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the
636
+ `._callback_tensor_inputs` attribute of your pipeline class.
637
+ max_sequence_length (`int`, defaults to `226`):
638
+ Maximum sequence length in encoded prompt. Must be consistent with
639
+ `self.transformer.config.max_text_seq_length` otherwise may lead to poor results.
640
+
641
+ Examples:
642
+
643
+ Returns:
644
+ [`~pipelines.cogvideo.pipeline_cogvideox.CogVideoX_Fun_PipelineOutput`] or `tuple`:
645
+ [`~pipelines.cogvideo.pipeline_cogvideox.CogVideoX_Fun_PipelineOutput`] if `return_dict` is True, otherwise a
646
+ `tuple`. When returning a tuple, the first element is a list with the generated images.
647
+ """
648
+
649
+ if num_frames > 49:
650
+ raise ValueError(
651
+ "The number of frames must be less than 49 for now due to static positional embeddings. This will be updated in the future to remove this limitation."
652
+ )
653
+
654
+ if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)):
655
+ callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs
656
+
657
+ height = height or self.transformer.config.sample_size * self.vae_scale_factor_spatial
658
+ width = width or self.transformer.config.sample_size * self.vae_scale_factor_spatial
659
+ num_videos_per_prompt = 1
660
+
661
+ # 1. Check inputs. Raise error if not correct
662
+ self.check_inputs(
663
+ prompt,
664
+ height,
665
+ width,
666
+ negative_prompt,
667
+ callback_on_step_end_tensor_inputs,
668
+ prompt_embeds,
669
+ negative_prompt_embeds,
670
+ )
671
+ self._guidance_scale = guidance_scale
672
+ self._interrupt = False
673
+
674
+ # 2. Default call parameters
675
+ if prompt is not None and isinstance(prompt, str):
676
+ batch_size = 1
677
+ elif prompt is not None and isinstance(prompt, list):
678
+ batch_size = len(prompt)
679
+ else:
680
+ batch_size = prompt_embeds.shape[0]
681
+
682
+ device = self._execution_device
683
+
684
+ # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
685
+ # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
686
+ # corresponds to doing no classifier free guidance.
687
+ do_classifier_free_guidance = guidance_scale > 1.0
688
+
689
+ # 3. Encode input prompt
690
+ prompt_embeds, negative_prompt_embeds = self.encode_prompt(
691
+ prompt,
692
+ negative_prompt,
693
+ do_classifier_free_guidance,
694
+ num_videos_per_prompt=num_videos_per_prompt,
695
+ prompt_embeds=prompt_embeds,
696
+ negative_prompt_embeds=negative_prompt_embeds,
697
+ max_sequence_length=max_sequence_length,
698
+ device=device,
699
+ )
700
+ if do_classifier_free_guidance:
701
+ prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0)
702
+
703
+ # 4. Prepare timesteps
704
+ timesteps, num_inference_steps = retrieve_timesteps(self.scheduler, num_inference_steps, device, timesteps)
705
+ self._num_timesteps = len(timesteps)
706
+ if comfyui_progressbar:
707
+ from comfy.utils import ProgressBar
708
+ pbar = ProgressBar(num_inference_steps + 2)
709
+
710
+ # 5. Prepare latents.
711
+ latent_channels = self.vae.config.latent_channels
712
+ latents = self.prepare_latents(
713
+ batch_size * num_videos_per_prompt,
714
+ latent_channels,
715
+ num_frames,
716
+ height,
717
+ width,
718
+ prompt_embeds.dtype,
719
+ device,
720
+ generator,
721
+ latents,
722
+ )
723
+ if comfyui_progressbar:
724
+ pbar.update(1)
725
+
726
+ if control_video is not None:
727
+ video_length = control_video.shape[2]
728
+ control_video = self.image_processor.preprocess(rearrange(control_video, "b c f h w -> (b f) c h w"), height=height, width=width)
729
+ control_video = control_video.to(dtype=torch.float32)
730
+ control_video = rearrange(control_video, "(b f) c h w -> b c f h w", f=video_length)
731
+ else:
732
+ control_video = None
733
+ control_video_latents = self.prepare_control_latents(
734
+ None,
735
+ control_video,
736
+ batch_size,
737
+ height,
738
+ width,
739
+ prompt_embeds.dtype,
740
+ device,
741
+ generator,
742
+ do_classifier_free_guidance
743
+ )[1]
744
+ control_video_latents_input = (
745
+ torch.cat([control_video_latents] * 2) if do_classifier_free_guidance else control_video_latents
746
+ )
747
+ control_latents = rearrange(control_video_latents_input, "b c f h w -> b f c h w")
748
+
749
+ if comfyui_progressbar:
750
+ pbar.update(1)
751
+
752
+ # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline
753
+ extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
754
+
755
+ # 7. Create rotary embeds if required
756
+ image_rotary_emb = (
757
+ self._prepare_rotary_positional_embeddings(height, width, latents.size(1), device)
758
+ if self.transformer.config.use_rotary_positional_embeddings
759
+ else None
760
+ )
761
+
762
+ # 8. Denoising loop
763
+ num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
764
+
765
+ with self.progress_bar(total=num_inference_steps) as progress_bar:
766
+ # for DPM-solver++
767
+ old_pred_original_sample = None
768
+ for i, t in enumerate(timesteps):
769
+ if self.interrupt:
770
+ continue
771
+
772
+ latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
773
+ latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
774
+
775
+ # broadcast to batch dimension in a way that's compatible with ONNX/Core ML
776
+ timestep = t.expand(latent_model_input.shape[0])
777
+
778
+ # predict noise model_output
779
+ noise_pred = self.transformer(
780
+ hidden_states=latent_model_input,
781
+ encoder_hidden_states=prompt_embeds,
782
+ timestep=timestep,
783
+ image_rotary_emb=image_rotary_emb,
784
+ return_dict=False,
785
+ control_latents=control_latents,
786
+ )[0]
787
+ noise_pred = noise_pred.float()
788
+
789
+ # perform guidance
790
+ if use_dynamic_cfg:
791
+ self._guidance_scale = 1 + guidance_scale * (
792
+ (1 - math.cos(math.pi * ((num_inference_steps - t.item()) / num_inference_steps) ** 5.0)) / 2
793
+ )
794
+ if do_classifier_free_guidance:
795
+ noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
796
+ noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond)
797
+
798
+ # compute the previous noisy sample x_t -> x_t-1
799
+ if not isinstance(self.scheduler, CogVideoXDPMScheduler):
800
+ latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0]
801
+ else:
802
+ latents, old_pred_original_sample = self.scheduler.step(
803
+ noise_pred,
804
+ old_pred_original_sample,
805
+ t,
806
+ timesteps[i - 1] if i > 0 else None,
807
+ latents,
808
+ **extra_step_kwargs,
809
+ return_dict=False,
810
+ )
811
+ latents = latents.to(prompt_embeds.dtype)
812
+
813
+ # call the callback, if provided
814
+ if callback_on_step_end is not None:
815
+ callback_kwargs = {}
816
+ for k in callback_on_step_end_tensor_inputs:
817
+ callback_kwargs[k] = locals()[k]
818
+ callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
819
+
820
+ latents = callback_outputs.pop("latents", latents)
821
+ prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
822
+ negative_prompt_embeds = callback_outputs.pop("negative_prompt_embeds", negative_prompt_embeds)
823
+
824
+ if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
825
+ progress_bar.update()
826
+ if comfyui_progressbar:
827
+ pbar.update(1)
828
+
829
+ if output_type == "numpy":
830
+ video = self.decode_latents(latents)
831
+ elif not output_type == "latent":
832
+ video = self.decode_latents(latents)
833
+ video = self.video_processor.postprocess_video(video=video, output_type=output_type)
834
+ else:
835
+ video = latents
836
+
837
+ # Offload all models
838
+ self.maybe_free_model_hooks()
839
+
840
+ if not return_dict:
841
+ video = torch.from_numpy(video)
842
+
843
+ return CogVideoX_Fun_PipelineOutput(videos=video)
cogvideox/pipeline/pipeline_cogvideox_inpaint.py ADDED
@@ -0,0 +1,1020 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 The CogVideoX team, Tsinghua University & ZhipuAI and The HuggingFace Team.
2
+ # All rights reserved.
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 inspect
17
+ import math
18
+ from dataclasses import dataclass
19
+ from typing import Callable, Dict, List, Optional, Tuple, Union
20
+
21
+ import torch
22
+ import torch.nn.functional as F
23
+ from einops import rearrange
24
+ from transformers import T5EncoderModel, T5Tokenizer
25
+
26
+ from diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback
27
+ from diffusers.models import AutoencoderKLCogVideoX, CogVideoXTransformer3DModel
28
+ from diffusers.models.embeddings import get_3d_rotary_pos_embed
29
+ from diffusers.pipelines.pipeline_utils import DiffusionPipeline
30
+ from diffusers.schedulers import CogVideoXDDIMScheduler, CogVideoXDPMScheduler
31
+ from diffusers.utils import BaseOutput, logging, replace_example_docstring
32
+ from diffusers.utils.torch_utils import randn_tensor
33
+ from diffusers.video_processor import VideoProcessor
34
+ from diffusers.image_processor import VaeImageProcessor
35
+ from einops import rearrange
36
+
37
+
38
+ logger = logging.get_logger(__name__) # pylint: disable=invalid-name
39
+
40
+
41
+ EXAMPLE_DOC_STRING = """
42
+ Examples:
43
+ ```python
44
+ >>> import torch
45
+ >>> from diffusers import CogVideoX_Fun_Pipeline
46
+ >>> from diffusers.utils import export_to_video
47
+
48
+ >>> # Models: "THUDM/CogVideoX-2b" or "THUDM/CogVideoX-5b"
49
+ >>> pipe = CogVideoX_Fun_Pipeline.from_pretrained("THUDM/CogVideoX-2b", torch_dtype=torch.float16).to("cuda")
50
+ >>> prompt = (
51
+ ... "A panda, dressed in a small, red jacket and a tiny hat, sits on a wooden stool in a serene bamboo forest. "
52
+ ... "The panda's fluffy paws strum a miniature acoustic guitar, producing soft, melodic tunes. Nearby, a few other "
53
+ ... "pandas gather, watching curiously and some clapping in rhythm. Sunlight filters through the tall bamboo, "
54
+ ... "casting a gentle glow on the scene. The panda's face is expressive, showing concentration and joy as it plays. "
55
+ ... "The background includes a small, flowing stream and vibrant green foliage, enhancing the peaceful and magical "
56
+ ... "atmosphere of this unique musical performance."
57
+ ... )
58
+ >>> video = pipe(prompt=prompt, guidance_scale=6, num_inference_steps=50).frames[0]
59
+ >>> export_to_video(video, "output.mp4", fps=8)
60
+ ```
61
+ """
62
+
63
+
64
+ # Similar to diffusers.pipelines.hunyuandit.pipeline_hunyuandit.get_resize_crop_region_for_grid
65
+ def get_resize_crop_region_for_grid(src, tgt_width, tgt_height):
66
+ tw = tgt_width
67
+ th = tgt_height
68
+ h, w = src
69
+ r = h / w
70
+ if r > (th / tw):
71
+ resize_height = th
72
+ resize_width = int(round(th / h * w))
73
+ else:
74
+ resize_width = tw
75
+ resize_height = int(round(tw / w * h))
76
+
77
+ crop_top = int(round((th - resize_height) / 2.0))
78
+ crop_left = int(round((tw - resize_width) / 2.0))
79
+
80
+ return (crop_top, crop_left), (crop_top + resize_height, crop_left + resize_width)
81
+
82
+
83
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps
84
+ def retrieve_timesteps(
85
+ scheduler,
86
+ num_inference_steps: Optional[int] = None,
87
+ device: Optional[Union[str, torch.device]] = None,
88
+ timesteps: Optional[List[int]] = None,
89
+ sigmas: Optional[List[float]] = None,
90
+ **kwargs,
91
+ ):
92
+ """
93
+ Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles
94
+ custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`.
95
+
96
+ Args:
97
+ scheduler (`SchedulerMixin`):
98
+ The scheduler to get timesteps from.
99
+ num_inference_steps (`int`):
100
+ The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`
101
+ must be `None`.
102
+ device (`str` or `torch.device`, *optional*):
103
+ The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.
104
+ timesteps (`List[int]`, *optional*):
105
+ Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,
106
+ `num_inference_steps` and `sigmas` must be `None`.
107
+ sigmas (`List[float]`, *optional*):
108
+ Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,
109
+ `num_inference_steps` and `timesteps` must be `None`.
110
+
111
+ Returns:
112
+ `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the
113
+ second element is the number of inference steps.
114
+ """
115
+ if timesteps is not None and sigmas is not None:
116
+ raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values")
117
+ if timesteps is not None:
118
+ accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
119
+ if not accepts_timesteps:
120
+ raise ValueError(
121
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
122
+ f" timestep schedules. Please check whether you are using the correct scheduler."
123
+ )
124
+ scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
125
+ timesteps = scheduler.timesteps
126
+ num_inference_steps = len(timesteps)
127
+ elif sigmas is not None:
128
+ accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())
129
+ if not accept_sigmas:
130
+ raise ValueError(
131
+ f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom"
132
+ f" sigmas schedules. Please check whether you are using the correct scheduler."
133
+ )
134
+ scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
135
+ timesteps = scheduler.timesteps
136
+ num_inference_steps = len(timesteps)
137
+ else:
138
+ scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
139
+ timesteps = scheduler.timesteps
140
+ return timesteps, num_inference_steps
141
+
142
+
143
+ def resize_mask(mask, latent, process_first_frame_only=True):
144
+ latent_size = latent.size()
145
+ batch_size, channels, num_frames, height, width = mask.shape
146
+
147
+ if process_first_frame_only:
148
+ target_size = list(latent_size[2:])
149
+ target_size[0] = 1
150
+ first_frame_resized = F.interpolate(
151
+ mask[:, :, 0:1, :, :],
152
+ size=target_size,
153
+ mode='trilinear',
154
+ align_corners=False
155
+ )
156
+
157
+ target_size = list(latent_size[2:])
158
+ target_size[0] = target_size[0] - 1
159
+ if target_size[0] != 0:
160
+ remaining_frames_resized = F.interpolate(
161
+ mask[:, :, 1:, :, :],
162
+ size=target_size,
163
+ mode='trilinear',
164
+ align_corners=False
165
+ )
166
+ resized_mask = torch.cat([first_frame_resized, remaining_frames_resized], dim=2)
167
+ else:
168
+ resized_mask = first_frame_resized
169
+ else:
170
+ target_size = list(latent_size[2:])
171
+ resized_mask = F.interpolate(
172
+ mask,
173
+ size=target_size,
174
+ mode='trilinear',
175
+ align_corners=False
176
+ )
177
+ return resized_mask
178
+
179
+
180
+ def add_noise_to_reference_video(image, ratio=None):
181
+ if ratio is None:
182
+ sigma = torch.normal(mean=-3.0, std=0.5, size=(image.shape[0],)).to(image.device)
183
+ sigma = torch.exp(sigma).to(image.dtype)
184
+ else:
185
+ sigma = torch.ones((image.shape[0],)).to(image.device, image.dtype) * ratio
186
+
187
+ image_noise = torch.randn_like(image) * sigma[:, None, None, None, None]
188
+ image_noise = torch.where(image==-1, torch.zeros_like(image), image_noise)
189
+ image = image + image_noise
190
+ return image
191
+
192
+
193
+ @dataclass
194
+ class CogVideoX_Fun_PipelineOutput(BaseOutput):
195
+ r"""
196
+ Output class for CogVideo pipelines.
197
+
198
+ Args:
199
+ video (`torch.Tensor`, `np.ndarray`, or List[List[PIL.Image.Image]]):
200
+ List of video outputs - It can be a nested list of length `batch_size,` with each sub-list containing
201
+ denoised PIL image sequences of length `num_frames.` It can also be a NumPy array or Torch tensor of shape
202
+ `(batch_size, num_frames, channels, height, width)`.
203
+ """
204
+
205
+ videos: torch.Tensor
206
+
207
+
208
+ class CogVideoX_Fun_Pipeline_Inpaint(DiffusionPipeline):
209
+ r"""
210
+ Pipeline for text-to-video generation using CogVideoX.
211
+
212
+ This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the
213
+ library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.)
214
+
215
+ Args:
216
+ vae ([`AutoencoderKL`]):
217
+ Variational Auto-Encoder (VAE) Model to encode and decode videos to and from latent representations.
218
+ text_encoder ([`T5EncoderModel`]):
219
+ Frozen text-encoder. CogVideoX_Fun uses
220
+ [T5](https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5EncoderModel); specifically the
221
+ [t5-v1_1-xxl](https://huggingface.co/PixArt-alpha/PixArt-alpha/tree/main/t5-v1_1-xxl) variant.
222
+ tokenizer (`T5Tokenizer`):
223
+ Tokenizer of class
224
+ [T5Tokenizer](https://huggingface.co/docs/transformers/model_doc/t5#transformers.T5Tokenizer).
225
+ transformer ([`CogVideoXTransformer3DModel`]):
226
+ A text conditioned `CogVideoXTransformer3DModel` to denoise the encoded video latents.
227
+ scheduler ([`SchedulerMixin`]):
228
+ A scheduler to be used in combination with `transformer` to denoise the encoded video latents.
229
+ """
230
+
231
+ _optional_components = []
232
+ model_cpu_offload_seq = "text_encoder->transformer->vae"
233
+
234
+ _callback_tensor_inputs = [
235
+ "latents",
236
+ "prompt_embeds",
237
+ "negative_prompt_embeds",
238
+ ]
239
+
240
+ def __init__(
241
+ self,
242
+ tokenizer: T5Tokenizer,
243
+ text_encoder: T5EncoderModel,
244
+ vae: AutoencoderKLCogVideoX,
245
+ transformer: CogVideoXTransformer3DModel,
246
+ scheduler: Union[CogVideoXDDIMScheduler, CogVideoXDPMScheduler],
247
+ ):
248
+ super().__init__()
249
+
250
+ self.register_modules(
251
+ tokenizer=tokenizer, text_encoder=text_encoder, vae=vae, transformer=transformer, scheduler=scheduler
252
+ )
253
+ self.vae_scale_factor_spatial = (
254
+ 2 ** (len(self.vae.config.block_out_channels) - 1) if hasattr(self, "vae") and self.vae is not None else 8
255
+ )
256
+ self.vae_scale_factor_temporal = (
257
+ self.vae.config.temporal_compression_ratio if hasattr(self, "vae") and self.vae is not None else 4
258
+ )
259
+
260
+ self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor_spatial)
261
+
262
+ self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
263
+ self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
264
+ self.mask_processor = VaeImageProcessor(
265
+ vae_scale_factor=self.vae_scale_factor, do_normalize=False, do_binarize=True, do_convert_grayscale=True
266
+ )
267
+
268
+ def _get_t5_prompt_embeds(
269
+ self,
270
+ prompt: Union[str, List[str]] = None,
271
+ num_videos_per_prompt: int = 1,
272
+ max_sequence_length: int = 226,
273
+ device: Optional[torch.device] = None,
274
+ dtype: Optional[torch.dtype] = None,
275
+ ):
276
+ device = device or self._execution_device
277
+ dtype = dtype or self.text_encoder.dtype
278
+
279
+ prompt = [prompt] if isinstance(prompt, str) else prompt
280
+ batch_size = len(prompt)
281
+
282
+ text_inputs = self.tokenizer(
283
+ prompt,
284
+ padding="max_length",
285
+ max_length=max_sequence_length,
286
+ truncation=True,
287
+ add_special_tokens=True,
288
+ return_tensors="pt",
289
+ )
290
+ text_input_ids = text_inputs.input_ids
291
+ untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
292
+
293
+ if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids):
294
+ removed_text = self.tokenizer.batch_decode(untruncated_ids[:, max_sequence_length - 1 : -1])
295
+ logger.warning(
296
+ "The following part of your input was truncated because `max_sequence_length` is set to "
297
+ f" {max_sequence_length} tokens: {removed_text}"
298
+ )
299
+
300
+ prompt_embeds = self.text_encoder(text_input_ids.to(device))[0]
301
+ prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
302
+
303
+ # duplicate text embeddings for each generation per prompt, using mps friendly method
304
+ _, seq_len, _ = prompt_embeds.shape
305
+ prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1)
306
+ prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1)
307
+
308
+ return prompt_embeds
309
+
310
+ def encode_prompt(
311
+ self,
312
+ prompt: Union[str, List[str]],
313
+ negative_prompt: Optional[Union[str, List[str]]] = None,
314
+ do_classifier_free_guidance: bool = True,
315
+ num_videos_per_prompt: int = 1,
316
+ prompt_embeds: Optional[torch.Tensor] = None,
317
+ negative_prompt_embeds: Optional[torch.Tensor] = None,
318
+ max_sequence_length: int = 226,
319
+ device: Optional[torch.device] = None,
320
+ dtype: Optional[torch.dtype] = None,
321
+ ):
322
+ r"""
323
+ Encodes the prompt into text encoder hidden states.
324
+
325
+ Args:
326
+ prompt (`str` or `List[str]`, *optional*):
327
+ prompt to be encoded
328
+ negative_prompt (`str` or `List[str]`, *optional*):
329
+ The prompt or prompts not to guide the image generation. If not defined, one has to pass
330
+ `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is
331
+ less than `1`).
332
+ do_classifier_free_guidance (`bool`, *optional*, defaults to `True`):
333
+ Whether to use classifier free guidance or not.
334
+ num_videos_per_prompt (`int`, *optional*, defaults to 1):
335
+ Number of videos that should be generated per prompt. torch device to place the resulting embeddings on
336
+ prompt_embeds (`torch.Tensor`, *optional*):
337
+ Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
338
+ provided, text embeddings will be generated from `prompt` input argument.
339
+ negative_prompt_embeds (`torch.Tensor`, *optional*):
340
+ Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
341
+ weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input
342
+ argument.
343
+ device: (`torch.device`, *optional*):
344
+ torch device
345
+ dtype: (`torch.dtype`, *optional*):
346
+ torch dtype
347
+ """
348
+ device = device or self._execution_device
349
+
350
+ prompt = [prompt] if isinstance(prompt, str) else prompt
351
+ if prompt is not None:
352
+ batch_size = len(prompt)
353
+ else:
354
+ batch_size = prompt_embeds.shape[0]
355
+
356
+ if prompt_embeds is None:
357
+ prompt_embeds = self._get_t5_prompt_embeds(
358
+ prompt=prompt,
359
+ num_videos_per_prompt=num_videos_per_prompt,
360
+ max_sequence_length=max_sequence_length,
361
+ device=device,
362
+ dtype=dtype,
363
+ )
364
+
365
+ if do_classifier_free_guidance and negative_prompt_embeds is None:
366
+ negative_prompt = negative_prompt or ""
367
+ negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt
368
+
369
+ if prompt is not None and type(prompt) is not type(negative_prompt):
370
+ raise TypeError(
371
+ f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !="
372
+ f" {type(prompt)}."
373
+ )
374
+ elif batch_size != len(negative_prompt):
375
+ raise ValueError(
376
+ f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"
377
+ f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"
378
+ " the batch size of `prompt`."
379
+ )
380
+
381
+ negative_prompt_embeds = self._get_t5_prompt_embeds(
382
+ prompt=negative_prompt,
383
+ num_videos_per_prompt=num_videos_per_prompt,
384
+ max_sequence_length=max_sequence_length,
385
+ device=device,
386
+ dtype=dtype,
387
+ )
388
+
389
+ return prompt_embeds, negative_prompt_embeds
390
+
391
+ def prepare_latents(
392
+ self,
393
+ batch_size,
394
+ num_channels_latents,
395
+ height,
396
+ width,
397
+ video_length,
398
+ dtype,
399
+ device,
400
+ generator,
401
+ latents=None,
402
+ video=None,
403
+ timestep=None,
404
+ is_strength_max=True,
405
+ return_noise=False,
406
+ return_video_latents=False,
407
+ ):
408
+ shape = (
409
+ batch_size,
410
+ (video_length - 1) // self.vae_scale_factor_temporal + 1,
411
+ num_channels_latents,
412
+ height // self.vae_scale_factor_spatial,
413
+ width // self.vae_scale_factor_spatial,
414
+ )
415
+ if isinstance(generator, list) and len(generator) != batch_size:
416
+ raise ValueError(
417
+ f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
418
+ f" size of {batch_size}. Make sure the batch size matches the length of the generators."
419
+ )
420
+
421
+ if return_video_latents or (latents is None and not is_strength_max):
422
+ video = video.to(device=device, dtype=self.vae.dtype)
423
+
424
+ bs = 1
425
+ new_video = []
426
+ for i in range(0, video.shape[0], bs):
427
+ video_bs = video[i : i + bs]
428
+ video_bs = self.vae.encode(video_bs)[0]
429
+ video_bs = video_bs.sample()
430
+ new_video.append(video_bs)
431
+ video = torch.cat(new_video, dim = 0)
432
+ video = video * self.vae.config.scaling_factor
433
+
434
+ video_latents = video.repeat(batch_size // video.shape[0], 1, 1, 1, 1)
435
+ video_latents = video_latents.to(device=device, dtype=dtype)
436
+ video_latents = rearrange(video_latents, "b c f h w -> b f c h w")
437
+
438
+ if latents is None:
439
+ noise = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
440
+ # if strength is 1. then initialise the latents to noise, else initial to image + noise
441
+ latents = noise if is_strength_max else self.scheduler.add_noise(video_latents, noise, timestep)
442
+ # if pure noise then scale the initial latents by the Scheduler's init sigma
443
+ latents = latents * self.scheduler.init_noise_sigma if is_strength_max else latents
444
+ else:
445
+ noise = latents.to(device)
446
+ latents = noise * self.scheduler.init_noise_sigma
447
+
448
+ # scale the initial noise by the standard deviation required by the scheduler
449
+ outputs = (latents,)
450
+
451
+ if return_noise:
452
+ outputs += (noise,)
453
+
454
+ if return_video_latents:
455
+ outputs += (video_latents,)
456
+
457
+ return outputs
458
+
459
+ def prepare_mask_latents(
460
+ self, mask, masked_image, batch_size, height, width, dtype, device, generator, do_classifier_free_guidance, noise_aug_strength
461
+ ):
462
+ # resize the mask to latents shape as we concatenate the mask to the latents
463
+ # we do that before converting to dtype to avoid breaking in case we're using cpu_offload
464
+ # and half precision
465
+
466
+ if mask is not None:
467
+ mask = mask.to(device=device, dtype=self.vae.dtype)
468
+ bs = 1
469
+ new_mask = []
470
+ for i in range(0, mask.shape[0], bs):
471
+ mask_bs = mask[i : i + bs]
472
+ mask_bs = self.vae.encode(mask_bs)[0]
473
+ mask_bs = mask_bs.mode()
474
+ new_mask.append(mask_bs)
475
+ mask = torch.cat(new_mask, dim = 0)
476
+ mask = mask * self.vae.config.scaling_factor
477
+
478
+ if masked_image is not None:
479
+ if self.transformer.config.add_noise_in_inpaint_model:
480
+ masked_image = add_noise_to_reference_video(masked_image, ratio=noise_aug_strength)
481
+ masked_image = masked_image.to(device=device, dtype=self.vae.dtype)
482
+ bs = 1
483
+ new_mask_pixel_values = []
484
+ for i in range(0, masked_image.shape[0], bs):
485
+ mask_pixel_values_bs = masked_image[i : i + bs]
486
+ mask_pixel_values_bs = self.vae.encode(mask_pixel_values_bs)[0]
487
+ mask_pixel_values_bs = mask_pixel_values_bs.mode()
488
+ new_mask_pixel_values.append(mask_pixel_values_bs)
489
+ masked_image_latents = torch.cat(new_mask_pixel_values, dim = 0)
490
+ masked_image_latents = masked_image_latents * self.vae.config.scaling_factor
491
+ else:
492
+ masked_image_latents = None
493
+
494
+ return mask, masked_image_latents
495
+
496
+ def decode_latents(self, latents: torch.Tensor) -> torch.Tensor:
497
+ latents = latents.permute(0, 2, 1, 3, 4) # [batch_size, num_channels, num_frames, height, width]
498
+ latents = 1 / self.vae.config.scaling_factor * latents
499
+
500
+ frames = self.vae.decode(latents).sample
501
+ frames = (frames / 2 + 0.5).clamp(0, 1)
502
+ # we always cast to float32 as this does not cause significant overhead and is compatible with bfloa16
503
+ frames = frames.cpu().float().numpy()
504
+ return frames
505
+
506
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs
507
+ def prepare_extra_step_kwargs(self, generator, eta):
508
+ # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
509
+ # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
510
+ # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
511
+ # and should be between [0, 1]
512
+
513
+ accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
514
+ extra_step_kwargs = {}
515
+ if accepts_eta:
516
+ extra_step_kwargs["eta"] = eta
517
+
518
+ # check if the scheduler accepts generator
519
+ accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
520
+ if accepts_generator:
521
+ extra_step_kwargs["generator"] = generator
522
+ return extra_step_kwargs
523
+
524
+ # Copied from diffusers.pipelines.latte.pipeline_latte.LattePipeline.check_inputs
525
+ def check_inputs(
526
+ self,
527
+ prompt,
528
+ height,
529
+ width,
530
+ negative_prompt,
531
+ callback_on_step_end_tensor_inputs,
532
+ prompt_embeds=None,
533
+ negative_prompt_embeds=None,
534
+ ):
535
+ if height % 8 != 0 or width % 8 != 0:
536
+ raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
537
+
538
+ if callback_on_step_end_tensor_inputs is not None and not all(
539
+ k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs
540
+ ):
541
+ raise ValueError(
542
+ f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}"
543
+ )
544
+ if prompt is not None and prompt_embeds is not None:
545
+ raise ValueError(
546
+ f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
547
+ " only forward one of the two."
548
+ )
549
+ elif prompt is None and prompt_embeds is None:
550
+ raise ValueError(
551
+ "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
552
+ )
553
+ elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
554
+ raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
555
+
556
+ if prompt is not None and negative_prompt_embeds is not None:
557
+ raise ValueError(
558
+ f"Cannot forward both `prompt`: {prompt} and `negative_prompt_embeds`:"
559
+ f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
560
+ )
561
+
562
+ if negative_prompt is not None and negative_prompt_embeds is not None:
563
+ raise ValueError(
564
+ f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
565
+ f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
566
+ )
567
+
568
+ if prompt_embeds is not None and negative_prompt_embeds is not None:
569
+ if prompt_embeds.shape != negative_prompt_embeds.shape:
570
+ raise ValueError(
571
+ "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
572
+ f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
573
+ f" {negative_prompt_embeds.shape}."
574
+ )
575
+
576
+ def fuse_qkv_projections(self) -> None:
577
+ r"""Enables fused QKV projections."""
578
+ self.fusing_transformer = True
579
+ self.transformer.fuse_qkv_projections()
580
+
581
+ def unfuse_qkv_projections(self) -> None:
582
+ r"""Disable QKV projection fusion if enabled."""
583
+ if not self.fusing_transformer:
584
+ logger.warning("The Transformer was not initially fused for QKV projections. Doing nothing.")
585
+ else:
586
+ self.transformer.unfuse_qkv_projections()
587
+ self.fusing_transformer = False
588
+
589
+ def _prepare_rotary_positional_embeddings(
590
+ self,
591
+ height: int,
592
+ width: int,
593
+ num_frames: int,
594
+ device: torch.device,
595
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
596
+ grid_height = height // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
597
+ grid_width = width // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
598
+ base_size_width = 720 // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
599
+ base_size_height = 480 // (self.vae_scale_factor_spatial * self.transformer.config.patch_size)
600
+
601
+ grid_crops_coords = get_resize_crop_region_for_grid(
602
+ (grid_height, grid_width), base_size_width, base_size_height
603
+ )
604
+ freqs_cos, freqs_sin = get_3d_rotary_pos_embed(
605
+ embed_dim=self.transformer.config.attention_head_dim,
606
+ crops_coords=grid_crops_coords,
607
+ grid_size=(grid_height, grid_width),
608
+ temporal_size=num_frames,
609
+ use_real=True,
610
+ )
611
+
612
+ freqs_cos = freqs_cos.to(device=device)
613
+ freqs_sin = freqs_sin.to(device=device)
614
+ return freqs_cos, freqs_sin
615
+
616
+ @property
617
+ def guidance_scale(self):
618
+ return self._guidance_scale
619
+
620
+ @property
621
+ def num_timesteps(self):
622
+ return self._num_timesteps
623
+
624
+ @property
625
+ def interrupt(self):
626
+ return self._interrupt
627
+
628
+ # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.StableDiffusionImg2ImgPipeline.get_timesteps
629
+ def get_timesteps(self, num_inference_steps, strength, device):
630
+ # get the original timestep using init_timestep
631
+ init_timestep = min(int(num_inference_steps * strength), num_inference_steps)
632
+
633
+ t_start = max(num_inference_steps - init_timestep, 0)
634
+ timesteps = self.scheduler.timesteps[t_start * self.scheduler.order :]
635
+
636
+ return timesteps, num_inference_steps - t_start
637
+
638
+ @torch.no_grad()
639
+ @replace_example_docstring(EXAMPLE_DOC_STRING)
640
+ def __call__(
641
+ self,
642
+ prompt: Optional[Union[str, List[str]]] = None,
643
+ negative_prompt: Optional[Union[str, List[str]]] = None,
644
+ height: int = 480,
645
+ width: int = 720,
646
+ video: Union[torch.FloatTensor] = None,
647
+ mask_video: Union[torch.FloatTensor] = None,
648
+ masked_video_latents: Union[torch.FloatTensor] = None,
649
+ num_frames: int = 49,
650
+ num_inference_steps: int = 50,
651
+ timesteps: Optional[List[int]] = None,
652
+ guidance_scale: float = 6,
653
+ use_dynamic_cfg: bool = False,
654
+ num_videos_per_prompt: int = 1,
655
+ eta: float = 0.0,
656
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
657
+ latents: Optional[torch.FloatTensor] = None,
658
+ prompt_embeds: Optional[torch.FloatTensor] = None,
659
+ negative_prompt_embeds: Optional[torch.FloatTensor] = None,
660
+ output_type: str = "numpy",
661
+ return_dict: bool = False,
662
+ callback_on_step_end: Optional[
663
+ Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks]
664
+ ] = None,
665
+ callback_on_step_end_tensor_inputs: List[str] = ["latents"],
666
+ max_sequence_length: int = 226,
667
+ strength: float = 1,
668
+ noise_aug_strength: float = 0.0563,
669
+ comfyui_progressbar: bool = False,
670
+ ) -> Union[CogVideoX_Fun_PipelineOutput, Tuple]:
671
+ """
672
+ Function invoked when calling the pipeline for generation.
673
+
674
+ Args:
675
+ prompt (`str` or `List[str]`, *optional*):
676
+ The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`.
677
+ instead.
678
+ negative_prompt (`str` or `List[str]`, *optional*):
679
+ The prompt or prompts not to guide the image generation. If not defined, one has to pass
680
+ `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is
681
+ less than `1`).
682
+ height (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
683
+ The height in pixels of the generated image. This is set to 1024 by default for the best results.
684
+ width (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor):
685
+ The width in pixels of the generated image. This is set to 1024 by default for the best results.
686
+ num_frames (`int`, defaults to `48`):
687
+ Number of frames to generate. Must be divisible by self.vae_scale_factor_temporal. Generated video will
688
+ contain 1 extra frame because CogVideoX_Fun is conditioned with (num_seconds * fps + 1) frames where
689
+ num_seconds is 6 and fps is 4. However, since videos can be saved at any fps, the only condition that
690
+ needs to be satisfied is that of divisibility mentioned above.
691
+ num_inference_steps (`int`, *optional*, defaults to 50):
692
+ The number of denoising steps. More denoising steps usually lead to a higher quality image at the
693
+ expense of slower inference.
694
+ timesteps (`List[int]`, *optional*):
695
+ Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument
696
+ in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is
697
+ passed will be used. Must be in descending order.
698
+ guidance_scale (`float`, *optional*, defaults to 7.0):
699
+ Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598).
700
+ `guidance_scale` is defined as `w` of equation 2. of [Imagen
701
+ Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale >
702
+ 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`,
703
+ usually at the expense of lower image quality.
704
+ num_videos_per_prompt (`int`, *optional*, defaults to 1):
705
+ The number of videos to generate per prompt.
706
+ generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
707
+ One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html)
708
+ to make generation deterministic.
709
+ latents (`torch.FloatTensor`, *optional*):
710
+ Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image
711
+ generation. Can be used to tweak the same generation with different prompts. If not provided, a latents
712
+ tensor will ge generated by sampling using the supplied random `generator`.
713
+ prompt_embeds (`torch.FloatTensor`, *optional*):
714
+ Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
715
+ provided, text embeddings will be generated from `prompt` input argument.
716
+ negative_prompt_embeds (`torch.FloatTensor`, *optional*):
717
+ Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
718
+ weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input
719
+ argument.
720
+ output_type (`str`, *optional*, defaults to `"pil"`):
721
+ The output format of the generate image. Choose between
722
+ [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`.
723
+ return_dict (`bool`, *optional*, defaults to `True`):
724
+ Whether or not to return a [`~pipelines.stable_diffusion_xl.StableDiffusionXLPipelineOutput`] instead
725
+ of a plain tuple.
726
+ callback_on_step_end (`Callable`, *optional*):
727
+ A function that calls at the end of each denoising steps during the inference. The function is called
728
+ with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int,
729
+ callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by
730
+ `callback_on_step_end_tensor_inputs`.
731
+ callback_on_step_end_tensor_inputs (`List`, *optional*):
732
+ The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list
733
+ will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the
734
+ `._callback_tensor_inputs` attribute of your pipeline class.
735
+ max_sequence_length (`int`, defaults to `226`):
736
+ Maximum sequence length in encoded prompt. Must be consistent with
737
+ `self.transformer.config.max_text_seq_length` otherwise may lead to poor results.
738
+
739
+ Examples:
740
+
741
+ Returns:
742
+ [`~pipelines.cogvideo.pipeline_cogvideox.CogVideoX_Fun_PipelineOutput`] or `tuple`:
743
+ [`~pipelines.cogvideo.pipeline_cogvideox.CogVideoX_Fun_PipelineOutput`] if `return_dict` is True, otherwise a
744
+ `tuple`. When returning a tuple, the first element is a list with the generated images.
745
+ """
746
+
747
+ if num_frames > 49:
748
+ raise ValueError(
749
+ "The number of frames must be less than 49 for now due to static positional embeddings. This will be updated in the future to remove this limitation."
750
+ )
751
+
752
+ if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)):
753
+ callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs
754
+
755
+ height = height or self.transformer.config.sample_size * self.vae_scale_factor_spatial
756
+ width = width or self.transformer.config.sample_size * self.vae_scale_factor_spatial
757
+ num_videos_per_prompt = 1
758
+
759
+ # 1. Check inputs. Raise error if not correct
760
+ self.check_inputs(
761
+ prompt,
762
+ height,
763
+ width,
764
+ negative_prompt,
765
+ callback_on_step_end_tensor_inputs,
766
+ prompt_embeds,
767
+ negative_prompt_embeds,
768
+ )
769
+ self._guidance_scale = guidance_scale
770
+ self._interrupt = False
771
+
772
+ # 2. Default call parameters
773
+ if prompt is not None and isinstance(prompt, str):
774
+ batch_size = 1
775
+ elif prompt is not None and isinstance(prompt, list):
776
+ batch_size = len(prompt)
777
+ else:
778
+ batch_size = prompt_embeds.shape[0]
779
+
780
+ device = self._execution_device
781
+
782
+ # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
783
+ # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
784
+ # corresponds to doing no classifier free guidance.
785
+ do_classifier_free_guidance = guidance_scale > 1.0
786
+
787
+ # 3. Encode input prompt
788
+ prompt_embeds, negative_prompt_embeds = self.encode_prompt(
789
+ prompt,
790
+ negative_prompt,
791
+ do_classifier_free_guidance,
792
+ num_videos_per_prompt=num_videos_per_prompt,
793
+ prompt_embeds=prompt_embeds,
794
+ negative_prompt_embeds=negative_prompt_embeds,
795
+ max_sequence_length=max_sequence_length,
796
+ device=device,
797
+ )
798
+ if do_classifier_free_guidance:
799
+ prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0)
800
+
801
+ # 4. set timesteps
802
+ self.scheduler.set_timesteps(num_inference_steps, device=device)
803
+ timesteps, num_inference_steps = self.get_timesteps(
804
+ num_inference_steps=num_inference_steps, strength=strength, device=device
805
+ )
806
+ self._num_timesteps = len(timesteps)
807
+ if comfyui_progressbar:
808
+ from comfy.utils import ProgressBar
809
+ pbar = ProgressBar(num_inference_steps + 2)
810
+ # at which timestep to set the initial noise (n.b. 50% if strength is 0.5)
811
+ latent_timestep = timesteps[:1].repeat(batch_size * num_videos_per_prompt)
812
+ # create a boolean to check if the strength is set to 1. if so then initialise the latents with pure noise
813
+ is_strength_max = strength == 1.0
814
+
815
+ # 5. Prepare latents.
816
+ if video is not None:
817
+ video_length = video.shape[2]
818
+ init_video = self.image_processor.preprocess(rearrange(video, "b c f h w -> (b f) c h w"), height=height, width=width)
819
+ init_video = init_video.to(dtype=torch.float32)
820
+ init_video = rearrange(init_video, "(b f) c h w -> b c f h w", f=video_length)
821
+ else:
822
+ init_video = None
823
+
824
+ num_channels_latents = self.vae.config.latent_channels
825
+ num_channels_transformer = self.transformer.config.in_channels
826
+ return_image_latents = num_channels_transformer == num_channels_latents
827
+
828
+ latents_outputs = self.prepare_latents(
829
+ batch_size * num_videos_per_prompt,
830
+ num_channels_latents,
831
+ height,
832
+ width,
833
+ video_length,
834
+ prompt_embeds.dtype,
835
+ device,
836
+ generator,
837
+ latents,
838
+ video=init_video,
839
+ timestep=latent_timestep,
840
+ is_strength_max=is_strength_max,
841
+ return_noise=True,
842
+ return_video_latents=return_image_latents,
843
+ )
844
+ if return_image_latents:
845
+ latents, noise, image_latents = latents_outputs
846
+ else:
847
+ latents, noise = latents_outputs
848
+ if comfyui_progressbar:
849
+ pbar.update(1)
850
+
851
+ if mask_video is not None:
852
+ if (mask_video == 255).all():
853
+ mask_latents = torch.zeros_like(latents)[:, :, :1].to(latents.device, latents.dtype)
854
+ masked_video_latents = torch.zeros_like(latents).to(latents.device, latents.dtype)
855
+
856
+ mask_input = torch.cat([mask_latents] * 2) if do_classifier_free_guidance else mask_latents
857
+ masked_video_latents_input = (
858
+ torch.cat([masked_video_latents] * 2) if do_classifier_free_guidance else masked_video_latents
859
+ )
860
+ inpaint_latents = torch.cat([mask_input, masked_video_latents_input], dim=2).to(latents.dtype)
861
+ else:
862
+ # Prepare mask latent variables
863
+ video_length = video.shape[2]
864
+ mask_condition = self.mask_processor.preprocess(rearrange(mask_video, "b c f h w -> (b f) c h w"), height=height, width=width)
865
+ mask_condition = mask_condition.to(dtype=torch.float32)
866
+ mask_condition = rearrange(mask_condition, "(b f) c h w -> b c f h w", f=video_length)
867
+
868
+ if num_channels_transformer != num_channels_latents:
869
+ mask_condition_tile = torch.tile(mask_condition, [1, 3, 1, 1, 1])
870
+ if masked_video_latents is None:
871
+ masked_video = init_video * (mask_condition_tile < 0.5) + torch.ones_like(init_video) * (mask_condition_tile > 0.5) * -1
872
+ else:
873
+ masked_video = masked_video_latents
874
+
875
+ _, masked_video_latents = self.prepare_mask_latents(
876
+ None,
877
+ masked_video,
878
+ batch_size,
879
+ height,
880
+ width,
881
+ prompt_embeds.dtype,
882
+ device,
883
+ generator,
884
+ do_classifier_free_guidance,
885
+ noise_aug_strength=noise_aug_strength,
886
+ )
887
+ mask_latents = resize_mask(1 - mask_condition, masked_video_latents)
888
+ mask_latents = mask_latents.to(masked_video_latents.device) * self.vae.config.scaling_factor
889
+
890
+ mask = torch.tile(mask_condition, [1, num_channels_latents, 1, 1, 1])
891
+ mask = F.interpolate(mask, size=latents.size()[-3:], mode='trilinear', align_corners=True).to(latents.device, latents.dtype)
892
+
893
+ mask_input = torch.cat([mask_latents] * 2) if do_classifier_free_guidance else mask_latents
894
+ masked_video_latents_input = (
895
+ torch.cat([masked_video_latents] * 2) if do_classifier_free_guidance else masked_video_latents
896
+ )
897
+
898
+ mask = rearrange(mask, "b c f h w -> b f c h w")
899
+ mask_input = rearrange(mask_input, "b c f h w -> b f c h w")
900
+ masked_video_latents_input = rearrange(masked_video_latents_input, "b c f h w -> b f c h w")
901
+
902
+ inpaint_latents = torch.cat([mask_input, masked_video_latents_input], dim=2).to(latents.dtype)
903
+ else:
904
+ mask = torch.tile(mask_condition, [1, num_channels_latents, 1, 1, 1])
905
+ mask = F.interpolate(mask, size=latents.size()[-3:], mode='trilinear', align_corners=True).to(latents.device, latents.dtype)
906
+ mask = rearrange(mask, "b c f h w -> b f c h w")
907
+
908
+ inpaint_latents = None
909
+ else:
910
+ if num_channels_transformer != num_channels_latents:
911
+ mask = torch.zeros_like(latents).to(latents.device, latents.dtype)
912
+ masked_video_latents = torch.zeros_like(latents).to(latents.device, latents.dtype)
913
+
914
+ mask_input = torch.cat([mask] * 2) if do_classifier_free_guidance else mask
915
+ masked_video_latents_input = (
916
+ torch.cat([masked_video_latents] * 2) if do_classifier_free_guidance else masked_video_latents
917
+ )
918
+ inpaint_latents = torch.cat([mask_input, masked_video_latents_input], dim=1).to(latents.dtype)
919
+ else:
920
+ mask = torch.zeros_like(init_video[:, :1])
921
+ mask = torch.tile(mask, [1, num_channels_latents, 1, 1, 1])
922
+ mask = F.interpolate(mask, size=latents.size()[-3:], mode='trilinear', align_corners=True).to(latents.device, latents.dtype)
923
+ mask = rearrange(mask, "b c f h w -> b f c h w")
924
+
925
+ inpaint_latents = None
926
+ if comfyui_progressbar:
927
+ pbar.update(1)
928
+
929
+ # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline
930
+ extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
931
+
932
+ # 7. Create rotary embeds if required
933
+ image_rotary_emb = (
934
+ self._prepare_rotary_positional_embeddings(height, width, latents.size(1), device)
935
+ if self.transformer.config.use_rotary_positional_embeddings
936
+ else None
937
+ )
938
+
939
+ # 8. Denoising loop
940
+ num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0)
941
+
942
+ with self.progress_bar(total=num_inference_steps) as progress_bar:
943
+ # for DPM-solver++
944
+ old_pred_original_sample = None
945
+ for i, t in enumerate(timesteps):
946
+ if self.interrupt:
947
+ continue
948
+
949
+ latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
950
+ latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
951
+
952
+ # broadcast to batch dimension in a way that's compatible with ONNX/Core ML
953
+ timestep = t.expand(latent_model_input.shape[0])
954
+
955
+ # predict noise model_output
956
+ noise_pred = self.transformer(
957
+ hidden_states=latent_model_input,
958
+ encoder_hidden_states=prompt_embeds,
959
+ timestep=timestep,
960
+ image_rotary_emb=image_rotary_emb,
961
+ return_dict=False,
962
+ inpaint_latents=inpaint_latents,
963
+ )[0]
964
+ noise_pred = noise_pred.float()
965
+
966
+ # perform guidance
967
+ if use_dynamic_cfg:
968
+ self._guidance_scale = 1 + guidance_scale * (
969
+ (1 - math.cos(math.pi * ((num_inference_steps - t.item()) / num_inference_steps) ** 5.0)) / 2
970
+ )
971
+ if do_classifier_free_guidance:
972
+ noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
973
+ noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond)
974
+
975
+ # compute the previous noisy sample x_t -> x_t-1
976
+ if not isinstance(self.scheduler, CogVideoXDPMScheduler):
977
+ latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0]
978
+ else:
979
+ latents, old_pred_original_sample = self.scheduler.step(
980
+ noise_pred,
981
+ old_pred_original_sample,
982
+ t,
983
+ timesteps[i - 1] if i > 0 else None,
984
+ latents,
985
+ **extra_step_kwargs,
986
+ return_dict=False,
987
+ )
988
+ latents = latents.to(prompt_embeds.dtype)
989
+
990
+ # call the callback, if provided
991
+ if callback_on_step_end is not None:
992
+ callback_kwargs = {}
993
+ for k in callback_on_step_end_tensor_inputs:
994
+ callback_kwargs[k] = locals()[k]
995
+ callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
996
+
997
+ latents = callback_outputs.pop("latents", latents)
998
+ prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds)
999
+ negative_prompt_embeds = callback_outputs.pop("negative_prompt_embeds", negative_prompt_embeds)
1000
+
1001
+ if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
1002
+ progress_bar.update()
1003
+ if comfyui_progressbar:
1004
+ pbar.update(1)
1005
+
1006
+ if output_type == "numpy":
1007
+ video = self.decode_latents(latents)
1008
+ elif not output_type == "latent":
1009
+ video = self.decode_latents(latents)
1010
+ video = self.video_processor.postprocess_video(video=video, output_type=output_type)
1011
+ else:
1012
+ video = latents
1013
+
1014
+ # Offload all models
1015
+ self.maybe_free_model_hooks()
1016
+
1017
+ if not return_dict:
1018
+ video = torch.from_numpy(video)
1019
+
1020
+ return CogVideoX_Fun_PipelineOutput(videos=video)
cogvideox/ui/ui.py ADDED
@@ -0,0 +1,1614 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Modified from https://github.com/guoyww/AnimateDiff/blob/main/app.py
2
+ """
3
+ import base64
4
+ import gc
5
+ import json
6
+ import os
7
+ import random
8
+ from datetime import datetime
9
+ from glob import glob
10
+
11
+ import cv2
12
+ import gradio as gr
13
+ import numpy as np
14
+ import pkg_resources
15
+ import requests
16
+ import torch
17
+ from diffusers import (AutoencoderKL, AutoencoderKLCogVideoX,
18
+ CogVideoXDDIMScheduler, DDIMScheduler,
19
+ DPMSolverMultistepScheduler,
20
+ EulerAncestralDiscreteScheduler, EulerDiscreteScheduler,
21
+ PNDMScheduler)
22
+ from diffusers.utils.import_utils import is_xformers_available
23
+ from omegaconf import OmegaConf
24
+ from PIL import Image
25
+ from safetensors import safe_open
26
+ from transformers import (CLIPImageProcessor, CLIPVisionModelWithProjection,
27
+ T5EncoderModel, T5Tokenizer)
28
+
29
+ from cogvideox.data.bucket_sampler import ASPECT_RATIO_512, get_closest_ratio
30
+ from cogvideox.models.autoencoder_magvit import AutoencoderKLCogVideoX
31
+ from cogvideox.models.transformer3d import CogVideoXTransformer3DModel
32
+ from cogvideox.pipeline.pipeline_cogvideox import CogVideoX_Fun_Pipeline
33
+ from cogvideox.pipeline.pipeline_cogvideox_control import \
34
+ CogVideoX_Fun_Pipeline_Control
35
+ from cogvideox.pipeline.pipeline_cogvideox_inpaint import \
36
+ CogVideoX_Fun_Pipeline_Inpaint
37
+ from cogvideox.utils.lora_utils import merge_lora, unmerge_lora
38
+ from cogvideox.utils.utils import (
39
+ get_image_to_video_latent, get_video_to_video_latent,
40
+ get_width_and_height_from_image_and_base_resolution, save_videos_grid)
41
+
42
+ scheduler_dict = {
43
+ "Euler": EulerDiscreteScheduler,
44
+ "Euler A": EulerAncestralDiscreteScheduler,
45
+ "DPM++": DPMSolverMultistepScheduler,
46
+ "PNDM": PNDMScheduler,
47
+ "DDIM_Cog": CogVideoXDDIMScheduler,
48
+ "DDIM_Origin": DDIMScheduler,
49
+ }
50
+
51
+ gradio_version = pkg_resources.get_distribution("gradio").version
52
+ gradio_version_is_above_4 = True if int(gradio_version.split('.')[0]) >= 4 else False
53
+
54
+ css = """
55
+ .toolbutton {
56
+ margin-buttom: 0em 0em 0em 0em;
57
+ max-width: 2.5em;
58
+ min-width: 2.5em !important;
59
+ height: 2.5em;
60
+ }
61
+ """
62
+
63
+ class CogVideoX_Fun_Controller:
64
+ def __init__(self, low_gpu_memory_mode, weight_dtype):
65
+ # config dirs
66
+ self.basedir = os.getcwd()
67
+ self.config_dir = os.path.join(self.basedir, "config")
68
+ self.diffusion_transformer_dir = os.path.join(self.basedir, "models", "Diffusion_Transformer")
69
+ self.motion_module_dir = os.path.join(self.basedir, "models", "Motion_Module")
70
+ self.personalized_model_dir = os.path.join(self.basedir, "models", "Personalized_Model")
71
+ self.savedir = os.path.join(self.basedir, "samples", datetime.now().strftime("Gradio-%Y-%m-%dT%H-%M-%S"))
72
+ self.savedir_sample = os.path.join(self.savedir, "sample")
73
+ self.model_type = "Inpaint"
74
+ os.makedirs(self.savedir, exist_ok=True)
75
+
76
+ self.diffusion_transformer_list = []
77
+ self.motion_module_list = []
78
+ self.personalized_model_list = []
79
+
80
+ self.refresh_diffusion_transformer()
81
+ self.refresh_motion_module()
82
+ self.refresh_personalized_model()
83
+
84
+ # config models
85
+ self.tokenizer = None
86
+ self.text_encoder = None
87
+ self.vae = None
88
+ self.transformer = None
89
+ self.pipeline = None
90
+ self.motion_module_path = "none"
91
+ self.base_model_path = "none"
92
+ self.lora_model_path = "none"
93
+ self.low_gpu_memory_mode = low_gpu_memory_mode
94
+
95
+ self.weight_dtype = weight_dtype
96
+
97
+ def refresh_diffusion_transformer(self):
98
+ self.diffusion_transformer_list = sorted(glob(os.path.join(self.diffusion_transformer_dir, "*/")))
99
+
100
+ def refresh_motion_module(self):
101
+ motion_module_list = sorted(glob(os.path.join(self.motion_module_dir, "*.safetensors")))
102
+ self.motion_module_list = [os.path.basename(p) for p in motion_module_list]
103
+
104
+ def refresh_personalized_model(self):
105
+ personalized_model_list = sorted(glob(os.path.join(self.personalized_model_dir, "*.safetensors")))
106
+ self.personalized_model_list = [os.path.basename(p) for p in personalized_model_list]
107
+
108
+ def update_model_type(self, model_type):
109
+ self.model_type = model_type
110
+
111
+ def update_diffusion_transformer(self, diffusion_transformer_dropdown):
112
+ print("Update diffusion transformer")
113
+ if diffusion_transformer_dropdown == "none":
114
+ return gr.update()
115
+ self.vae = AutoencoderKLCogVideoX.from_pretrained(
116
+ diffusion_transformer_dropdown,
117
+ subfolder="vae",
118
+ ).to(self.weight_dtype)
119
+
120
+ # Get Transformer
121
+ self.transformer = CogVideoXTransformer3DModel.from_pretrained_2d(
122
+ diffusion_transformer_dropdown,
123
+ subfolder="transformer",
124
+ ).to(self.weight_dtype)
125
+
126
+ # Get pipeline
127
+ if self.model_type == "Inpaint":
128
+ if self.transformer.config.in_channels != self.vae.config.latent_channels:
129
+ self.pipeline = CogVideoX_Fun_Pipeline_Inpaint.from_pretrained(
130
+ diffusion_transformer_dropdown,
131
+ vae=self.vae,
132
+ transformer=self.transformer,
133
+ scheduler=scheduler_dict["Euler"].from_pretrained(diffusion_transformer_dropdown, subfolder="scheduler"),
134
+ torch_dtype=self.weight_dtype
135
+ )
136
+ else:
137
+ self.pipeline = CogVideoX_Fun_Pipeline.from_pretrained(
138
+ diffusion_transformer_dropdown,
139
+ vae=self.vae,
140
+ transformer=self.transformer,
141
+ scheduler=scheduler_dict["Euler"].from_pretrained(diffusion_transformer_dropdown, subfolder="scheduler"),
142
+ torch_dtype=self.weight_dtype
143
+ )
144
+ else:
145
+ self.pipeline = CogVideoX_Fun_Pipeline_Control.from_pretrained(
146
+ diffusion_transformer_dropdown,
147
+ vae=self.vae,
148
+ transformer=self.transformer,
149
+ scheduler=scheduler_dict["Euler"].from_pretrained(diffusion_transformer_dropdown, subfolder="scheduler"),
150
+ torch_dtype=self.weight_dtype
151
+ )
152
+
153
+ if self.low_gpu_memory_mode:
154
+ self.pipeline.enable_sequential_cpu_offload()
155
+ else:
156
+ self.pipeline.enable_model_cpu_offload()
157
+ print("Update diffusion transformer done")
158
+ return gr.update()
159
+
160
+ def update_base_model(self, base_model_dropdown):
161
+ self.base_model_path = base_model_dropdown
162
+ print("Update base model")
163
+ if base_model_dropdown == "none":
164
+ return gr.update()
165
+ if self.transformer is None:
166
+ gr.Info(f"Please select a pretrained model path.")
167
+ return gr.update(value=None)
168
+ else:
169
+ base_model_dropdown = os.path.join(self.personalized_model_dir, base_model_dropdown)
170
+ base_model_state_dict = {}
171
+ with safe_open(base_model_dropdown, framework="pt", device="cpu") as f:
172
+ for key in f.keys():
173
+ base_model_state_dict[key] = f.get_tensor(key)
174
+ self.transformer.load_state_dict(base_model_state_dict, strict=False)
175
+ print("Update base done")
176
+ return gr.update()
177
+
178
+ def update_lora_model(self, lora_model_dropdown):
179
+ print("Update lora model")
180
+ if lora_model_dropdown == "none":
181
+ self.lora_model_path = "none"
182
+ return gr.update()
183
+ lora_model_dropdown = os.path.join(self.personalized_model_dir, lora_model_dropdown)
184
+ self.lora_model_path = lora_model_dropdown
185
+ return gr.update()
186
+
187
+ def generate(
188
+ self,
189
+ diffusion_transformer_dropdown,
190
+ base_model_dropdown,
191
+ lora_model_dropdown,
192
+ lora_alpha_slider,
193
+ prompt_textbox,
194
+ negative_prompt_textbox,
195
+ sampler_dropdown,
196
+ sample_step_slider,
197
+ resize_method,
198
+ width_slider,
199
+ height_slider,
200
+ base_resolution,
201
+ generation_method,
202
+ length_slider,
203
+ overlap_video_length,
204
+ partial_video_length,
205
+ cfg_scale_slider,
206
+ start_image,
207
+ end_image,
208
+ validation_video,
209
+ validation_video_mask,
210
+ control_video,
211
+ denoise_strength,
212
+ seed_textbox,
213
+ is_api = False,
214
+ ):
215
+ gc.collect()
216
+ torch.cuda.empty_cache()
217
+ torch.cuda.ipc_collect()
218
+
219
+ if self.transformer is None:
220
+ raise gr.Error(f"Please select a pretrained model path.")
221
+
222
+ if self.base_model_path != base_model_dropdown:
223
+ self.update_base_model(base_model_dropdown)
224
+
225
+ if self.lora_model_path != lora_model_dropdown:
226
+ print("Update lora model")
227
+ self.update_lora_model(lora_model_dropdown)
228
+
229
+ if control_video is not None and self.model_type == "Inpaint":
230
+ if is_api:
231
+ return "", f"If specifying the control video, please set the model_type == \"Control\". "
232
+ else:
233
+ raise gr.Error(f"If specifying the control video, please set the model_type == \"Control\". ")
234
+
235
+ if control_video is None and self.model_type == "Control":
236
+ if is_api:
237
+ return "", f"If set the model_type == \"Control\", please specifying the control video. "
238
+ else:
239
+ raise gr.Error(f"If set the model_type == \"Control\", please specifying the control video. ")
240
+
241
+ if resize_method == "Resize according to Reference":
242
+ if start_image is None and validation_video is None and control_video is None:
243
+ if is_api:
244
+ return "", f"Please upload an image when using \"Resize according to Reference\"."
245
+ else:
246
+ raise gr.Error(f"Please upload an image when using \"Resize according to Reference\".")
247
+
248
+ aspect_ratio_sample_size = {key : [x / 512 * base_resolution for x in ASPECT_RATIO_512[key]] for key in ASPECT_RATIO_512.keys()}
249
+ if self.model_type == "Inpaint":
250
+ if validation_video is not None:
251
+ original_width, original_height = Image.fromarray(cv2.VideoCapture(validation_video).read()[1]).size
252
+ else:
253
+ original_width, original_height = start_image[0].size if type(start_image) is list else Image.open(start_image).size
254
+ else:
255
+ original_width, original_height = Image.fromarray(cv2.VideoCapture(control_video).read()[1]).size
256
+ closest_size, closest_ratio = get_closest_ratio(original_height, original_width, ratios=aspect_ratio_sample_size)
257
+ height_slider, width_slider = [int(x / 16) * 16 for x in closest_size]
258
+
259
+ if self.transformer.config.in_channels == self.vae.config.latent_channels and start_image is not None:
260
+ if is_api:
261
+ return "", f"Please select an image to video pretrained model while using image to video."
262
+ else:
263
+ raise gr.Error(f"Please select an image to video pretrained model while using image to video.")
264
+
265
+ if self.transformer.config.in_channels == self.vae.config.latent_channels and generation_method == "Long Video Generation":
266
+ if is_api:
267
+ return "", f"Please select an image to video pretrained model while using long video generation."
268
+ else:
269
+ raise gr.Error(f"Please select an image to video pretrained model while using long video generation.")
270
+
271
+ if start_image is None and end_image is not None:
272
+ if is_api:
273
+ return "", f"If specifying the ending image of the video, please specify a starting image of the video."
274
+ else:
275
+ raise gr.Error(f"If specifying the ending image of the video, please specify a starting image of the video.")
276
+
277
+ is_image = True if generation_method == "Image Generation" else False
278
+
279
+ self.pipeline.scheduler = scheduler_dict[sampler_dropdown].from_config(self.pipeline.scheduler.config)
280
+ if self.lora_model_path != "none":
281
+ # lora part
282
+ self.pipeline = merge_lora(self.pipeline, self.lora_model_path, multiplier=lora_alpha_slider)
283
+
284
+ if int(seed_textbox) != -1 and seed_textbox != "": torch.manual_seed(int(seed_textbox))
285
+ else: seed_textbox = np.random.randint(0, 1e10)
286
+ generator = torch.Generator(device="cuda").manual_seed(int(seed_textbox))
287
+
288
+ try:
289
+ if self.model_type == "Inpaint":
290
+ if self.transformer.config.in_channels != self.vae.config.latent_channels:
291
+ if generation_method == "Long Video Generation":
292
+ if validation_video is not None:
293
+ raise gr.Error(f"Video to Video is not Support Long Video Generation now.")
294
+ init_frames = 0
295
+ last_frames = init_frames + partial_video_length
296
+ while init_frames < length_slider:
297
+ if last_frames >= length_slider:
298
+ _partial_video_length = length_slider - init_frames
299
+ _partial_video_length = int((_partial_video_length - 1) // self.vae.config.temporal_compression_ratio * self.vae.config.temporal_compression_ratio) + 1
300
+
301
+ if _partial_video_length <= 0:
302
+ break
303
+ else:
304
+ _partial_video_length = partial_video_length
305
+
306
+ if last_frames >= length_slider:
307
+ input_video, input_video_mask, clip_image = get_image_to_video_latent(start_image, end_image, video_length=_partial_video_length, sample_size=(height_slider, width_slider))
308
+ else:
309
+ input_video, input_video_mask, clip_image = get_image_to_video_latent(start_image, None, video_length=_partial_video_length, sample_size=(height_slider, width_slider))
310
+
311
+ with torch.no_grad():
312
+ sample = self.pipeline(
313
+ prompt_textbox,
314
+ negative_prompt = negative_prompt_textbox,
315
+ num_inference_steps = sample_step_slider,
316
+ guidance_scale = cfg_scale_slider,
317
+ width = width_slider,
318
+ height = height_slider,
319
+ num_frames = _partial_video_length,
320
+ generator = generator,
321
+
322
+ video = input_video,
323
+ mask_video = input_video_mask,
324
+ strength = 1,
325
+ ).videos
326
+
327
+ if init_frames != 0:
328
+ mix_ratio = torch.from_numpy(
329
+ np.array([float(_index) / float(overlap_video_length) for _index in range(overlap_video_length)], np.float32)
330
+ ).unsqueeze(0).unsqueeze(0).unsqueeze(-1).unsqueeze(-1)
331
+
332
+ new_sample[:, :, -overlap_video_length:] = new_sample[:, :, -overlap_video_length:] * (1 - mix_ratio) + \
333
+ sample[:, :, :overlap_video_length] * mix_ratio
334
+ new_sample = torch.cat([new_sample, sample[:, :, overlap_video_length:]], dim = 2)
335
+
336
+ sample = new_sample
337
+ else:
338
+ new_sample = sample
339
+
340
+ if last_frames >= length_slider:
341
+ break
342
+
343
+ start_image = [
344
+ Image.fromarray(
345
+ (sample[0, :, _index].transpose(0, 1).transpose(1, 2) * 255).numpy().astype(np.uint8)
346
+ ) for _index in range(-overlap_video_length, 0)
347
+ ]
348
+
349
+ init_frames = init_frames + _partial_video_length - overlap_video_length
350
+ last_frames = init_frames + _partial_video_length
351
+ else:
352
+ if validation_video is not None:
353
+ input_video, input_video_mask, clip_image = get_video_to_video_latent(validation_video, length_slider if not is_image else 1, sample_size=(height_slider, width_slider), validation_video_mask=validation_video_mask, fps=8)
354
+ strength = denoise_strength
355
+ else:
356
+ input_video, input_video_mask, clip_image = get_image_to_video_latent(start_image, end_image, length_slider if not is_image else 1, sample_size=(height_slider, width_slider))
357
+ strength = 1
358
+
359
+ sample = self.pipeline(
360
+ prompt_textbox,
361
+ negative_prompt = negative_prompt_textbox,
362
+ num_inference_steps = sample_step_slider,
363
+ guidance_scale = cfg_scale_slider,
364
+ width = width_slider,
365
+ height = height_slider,
366
+ num_frames = length_slider if not is_image else 1,
367
+ generator = generator,
368
+
369
+ video = input_video,
370
+ mask_video = input_video_mask,
371
+ strength = strength,
372
+ ).videos
373
+ else:
374
+ sample = self.pipeline(
375
+ prompt_textbox,
376
+ negative_prompt = negative_prompt_textbox,
377
+ num_inference_steps = sample_step_slider,
378
+ guidance_scale = cfg_scale_slider,
379
+ width = width_slider,
380
+ height = height_slider,
381
+ num_frames = length_slider if not is_image else 1,
382
+ generator = generator
383
+ ).videos
384
+ else:
385
+ input_video, input_video_mask, clip_image = get_video_to_video_latent(control_video, length_slider if not is_image else 1, sample_size=(height_slider, width_slider), fps=8)
386
+
387
+ sample = self.pipeline(
388
+ prompt_textbox,
389
+ negative_prompt = negative_prompt_textbox,
390
+ num_inference_steps = sample_step_slider,
391
+ guidance_scale = cfg_scale_slider,
392
+ width = width_slider,
393
+ height = height_slider,
394
+ num_frames = length_slider if not is_image else 1,
395
+ generator = generator,
396
+
397
+ control_video = input_video,
398
+ ).videos
399
+ except Exception as e:
400
+ gc.collect()
401
+ torch.cuda.empty_cache()
402
+ torch.cuda.ipc_collect()
403
+ if self.lora_model_path != "none":
404
+ self.pipeline = unmerge_lora(self.pipeline, self.lora_model_path, multiplier=lora_alpha_slider)
405
+ if is_api:
406
+ return "", f"Error. error information is {str(e)}"
407
+ else:
408
+ return gr.update(), gr.update(), f"Error. error information is {str(e)}"
409
+
410
+ gc.collect()
411
+ torch.cuda.empty_cache()
412
+ torch.cuda.ipc_collect()
413
+
414
+ # lora part
415
+ if self.lora_model_path != "none":
416
+ self.pipeline = unmerge_lora(self.pipeline, self.lora_model_path, multiplier=lora_alpha_slider)
417
+
418
+ sample_config = {
419
+ "prompt": prompt_textbox,
420
+ "n_prompt": negative_prompt_textbox,
421
+ "sampler": sampler_dropdown,
422
+ "num_inference_steps": sample_step_slider,
423
+ "guidance_scale": cfg_scale_slider,
424
+ "width": width_slider,
425
+ "height": height_slider,
426
+ "video_length": length_slider,
427
+ "seed_textbox": seed_textbox
428
+ }
429
+ json_str = json.dumps(sample_config, indent=4)
430
+ with open(os.path.join(self.savedir, "logs.json"), "a") as f:
431
+ f.write(json_str)
432
+ f.write("\n\n")
433
+
434
+ if not os.path.exists(self.savedir_sample):
435
+ os.makedirs(self.savedir_sample, exist_ok=True)
436
+ index = len([path for path in os.listdir(self.savedir_sample)]) + 1
437
+ prefix = str(index).zfill(3)
438
+
439
+ gc.collect()
440
+ torch.cuda.empty_cache()
441
+ torch.cuda.ipc_collect()
442
+ if is_image or length_slider == 1:
443
+ save_sample_path = os.path.join(self.savedir_sample, prefix + f".png")
444
+
445
+ image = sample[0, :, 0]
446
+ image = image.transpose(0, 1).transpose(1, 2)
447
+ image = (image * 255).numpy().astype(np.uint8)
448
+ image = Image.fromarray(image)
449
+ image.save(save_sample_path)
450
+
451
+ if is_api:
452
+ return save_sample_path, "Success"
453
+ else:
454
+ if gradio_version_is_above_4:
455
+ return gr.Image(value=save_sample_path, visible=True), gr.Video(value=None, visible=False), "Success"
456
+ else:
457
+ return gr.Image.update(value=save_sample_path, visible=True), gr.Video.update(value=None, visible=False), "Success"
458
+ else:
459
+ save_sample_path = os.path.join(self.savedir_sample, prefix + f".mp4")
460
+ save_videos_grid(sample, save_sample_path, fps=8)
461
+
462
+ if is_api:
463
+ return save_sample_path, "Success"
464
+ else:
465
+ if gradio_version_is_above_4:
466
+ return gr.Image(visible=False, value=None), gr.Video(value=save_sample_path, visible=True), "Success"
467
+ else:
468
+ return gr.Image.update(visible=False, value=None), gr.Video.update(value=save_sample_path, visible=True), "Success"
469
+
470
+
471
+ def ui(low_gpu_memory_mode, weight_dtype):
472
+ controller = CogVideoX_Fun_Controller(low_gpu_memory_mode, weight_dtype)
473
+
474
+ with gr.Blocks(css=css) as demo:
475
+ gr.Markdown(
476
+ """
477
+ # CogVideoX-Fun:
478
+
479
+ A CogVideoX with more flexible generation conditions, capable of producing videos of different resolutions, around 6 seconds, and fps 8 (frames 1 to 49), as well as image generated videos.
480
+
481
+ [Github](https://github.com/aigc-apps/CogVideoX-Fun/)
482
+ """
483
+ )
484
+ with gr.Column(variant="panel"):
485
+ gr.Markdown(
486
+ """
487
+ ### 1. CogVideoX-Fun Model Type (CogVideoX-Fun模型的种类,正常模型还是控制模型).
488
+ """
489
+ )
490
+ with gr.Row():
491
+ model_type = gr.Dropdown(
492
+ label="The model type of CogVideoX-Fun (CogVideoX-Fun模型的种类,正常模型还是控制模型)",
493
+ choices=["Inpaint", "Control"],
494
+ value="Inpaint",
495
+ interactive=True,
496
+ )
497
+
498
+ gr.Markdown(
499
+ """
500
+ ### 2. Model checkpoints (模型路径).
501
+ """
502
+ )
503
+ with gr.Row():
504
+ diffusion_transformer_dropdown = gr.Dropdown(
505
+ label="Pretrained Model Path (预训练模型路径)",
506
+ choices=controller.diffusion_transformer_list,
507
+ value="none",
508
+ interactive=True,
509
+ )
510
+ diffusion_transformer_dropdown.change(
511
+ fn=controller.update_diffusion_transformer,
512
+ inputs=[diffusion_transformer_dropdown],
513
+ outputs=[diffusion_transformer_dropdown]
514
+ )
515
+
516
+ diffusion_transformer_refresh_button = gr.Button(value="\U0001F503", elem_classes="toolbutton")
517
+ def refresh_diffusion_transformer():
518
+ controller.refresh_diffusion_transformer()
519
+ return gr.update(choices=controller.diffusion_transformer_list)
520
+ diffusion_transformer_refresh_button.click(fn=refresh_diffusion_transformer, inputs=[], outputs=[diffusion_transformer_dropdown])
521
+
522
+ with gr.Row():
523
+ base_model_dropdown = gr.Dropdown(
524
+ label="Select base Dreambooth model (选���基模型[非必需])",
525
+ choices=controller.personalized_model_list,
526
+ value="none",
527
+ interactive=True,
528
+ )
529
+
530
+ lora_model_dropdown = gr.Dropdown(
531
+ label="Select LoRA model (选择LoRA模型[非必需])",
532
+ choices=["none"] + controller.personalized_model_list,
533
+ value="none",
534
+ interactive=True,
535
+ )
536
+
537
+ lora_alpha_slider = gr.Slider(label="LoRA alpha (LoRA权重)", value=0.55, minimum=0, maximum=2, interactive=True)
538
+
539
+ personalized_refresh_button = gr.Button(value="\U0001F503", elem_classes="toolbutton")
540
+ def update_personalized_model():
541
+ controller.refresh_personalized_model()
542
+ return [
543
+ gr.update(choices=controller.personalized_model_list),
544
+ gr.update(choices=["none"] + controller.personalized_model_list)
545
+ ]
546
+ personalized_refresh_button.click(fn=update_personalized_model, inputs=[], outputs=[base_model_dropdown, lora_model_dropdown])
547
+
548
+ with gr.Column(variant="panel"):
549
+ gr.Markdown(
550
+ """
551
+ ### 3. Configs for Generation (生成参数配置).
552
+ """
553
+ )
554
+
555
+ prompt_textbox = gr.Textbox(label="Prompt (正向提示词)", lines=2, value="A young woman with beautiful and clear eyes and blonde hair standing and white dress in a forest wearing a crown. She seems to be lost in thought, and the camera focuses on her face. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.")
556
+ negative_prompt_textbox = gr.Textbox(label="Negative prompt (负向提示词)", lines=2, value="The video is not of a high quality, it has a low resolution. Watermark present in each frame. The background is solid. Strange body and strange trajectory. Distortion. " )
557
+
558
+ with gr.Row():
559
+ with gr.Column():
560
+ with gr.Row():
561
+ sampler_dropdown = gr.Dropdown(label="Sampling method (采样器种类)", choices=list(scheduler_dict.keys()), value=list(scheduler_dict.keys())[0])
562
+ sample_step_slider = gr.Slider(label="Sampling steps (生成步数)", value=50, minimum=10, maximum=100, step=1)
563
+
564
+ resize_method = gr.Radio(
565
+ ["Generate by", "Resize according to Reference"],
566
+ value="Generate by",
567
+ show_label=False,
568
+ )
569
+ width_slider = gr.Slider(label="Width (视频宽度)", value=672, minimum=128, maximum=1344, step=16)
570
+ height_slider = gr.Slider(label="Height (视频高度)", value=384, minimum=128, maximum=1344, step=16)
571
+ base_resolution = gr.Radio(label="Base Resolution of Pretrained Models", value=512, choices=[512, 768, 960], visible=False)
572
+
573
+ with gr.Group():
574
+ generation_method = gr.Radio(
575
+ ["Video Generation", "Image Generation", "Long Video Generation"],
576
+ value="Video Generation",
577
+ show_label=False,
578
+ )
579
+ with gr.Row():
580
+ length_slider = gr.Slider(label="Animation length (视频帧数)", value=49, minimum=1, maximum=49, step=4)
581
+ overlap_video_length = gr.Slider(label="Overlap length (视频续写的重叠帧数)", value=4, minimum=1, maximum=4, step=1, visible=False)
582
+ partial_video_length = gr.Slider(label="Partial video generation length (每个部分的视频生成帧数)", value=25, minimum=5, maximum=49, step=4, visible=False)
583
+
584
+ source_method = gr.Radio(
585
+ ["Text to Video (文本到视频)", "Image to Video (图片到视频)", "Video to Video (视频到视频)", "Video Control (视频控制)"],
586
+ value="Text to Video (文本到视频)",
587
+ show_label=False,
588
+ )
589
+ with gr.Column(visible = False) as image_to_video_col:
590
+ start_image = gr.Image(
591
+ label="The image at the beginning of the video (图片到视频的开始图片)", show_label=True,
592
+ elem_id="i2v_start", sources="upload", type="filepath",
593
+ )
594
+
595
+ template_gallery_path = ["asset/1.png", "asset/2.png", "asset/3.png", "asset/4.png", "asset/5.png"]
596
+ def select_template(evt: gr.SelectData):
597
+ text = {
598
+ "asset/1.png": "The dog is shaking head. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
599
+ "asset/2.png": "a sailboat sailing in rough seas with a dramatic sunset. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
600
+ "asset/3.png": "a beautiful woman with long hair and a dress blowing in the wind. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
601
+ "asset/4.png": "a man in an astronaut suit playing a guitar. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
602
+ "asset/5.png": "fireworks display over night city. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
603
+ }[template_gallery_path[evt.index]]
604
+ return template_gallery_path[evt.index], text
605
+
606
+ template_gallery = gr.Gallery(
607
+ template_gallery_path,
608
+ columns=5, rows=1,
609
+ height=140,
610
+ allow_preview=False,
611
+ container=False,
612
+ label="Template Examples",
613
+ )
614
+ template_gallery.select(select_template, None, [start_image, prompt_textbox])
615
+
616
+ with gr.Accordion("The image at the ending of the video (图片到视频的结束图片[非必需, Optional])", open=False):
617
+ end_image = gr.Image(label="The image at the ending of the video (图片到视频的结束图片[非必需, Optional])", show_label=False, elem_id="i2v_end", sources="upload", type="filepath")
618
+
619
+ with gr.Column(visible = False) as video_to_video_col:
620
+ with gr.Row():
621
+ validation_video = gr.Video(
622
+ label="The video to convert (视频转视频的参考视频)", show_label=True,
623
+ elem_id="v2v", sources="upload",
624
+ )
625
+ with gr.Accordion("The mask of the video to inpaint (视频重新绘制的mask[非必需, Optional])", open=False):
626
+ gr.Markdown(
627
+ """
628
+ - Please set a larger denoise_strength when using validation_video_mask, such as 1.00 instead of 0.70
629
+ - (请设置更大的denoise_strength,当使用validation_video_mask的时候,比如1而不是0.70)
630
+ """
631
+ )
632
+ validation_video_mask = gr.Image(
633
+ label="The mask of the video to inpaint (视频重新绘制的mask[非必需, Optional])",
634
+ show_label=False, elem_id="v2v_mask", sources="upload", type="filepath"
635
+ )
636
+ denoise_strength = gr.Slider(label="Denoise strength (重绘系数)", value=0.70, minimum=0.10, maximum=1.00, step=0.01)
637
+
638
+ with gr.Column(visible = False) as control_video_col:
639
+ gr.Markdown(
640
+ """
641
+ Demo pose control video can be downloaded here [URL](https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/cogvideox_fun/asset/v1.1/pose.mp4).
642
+ """
643
+ )
644
+ control_video = gr.Video(
645
+ label="The control video (用于提供控制信号的video)", show_label=True,
646
+ elem_id="v2v_control", sources="upload",
647
+ )
648
+
649
+ cfg_scale_slider = gr.Slider(label="CFG Scale (引导系数)", value=6.0, minimum=0, maximum=20)
650
+
651
+ with gr.Row():
652
+ seed_textbox = gr.Textbox(label="Seed (随机种子)", value=43)
653
+ seed_button = gr.Button(value="\U0001F3B2", elem_classes="toolbutton")
654
+ seed_button.click(
655
+ fn=lambda: gr.Textbox(value=random.randint(1, 1e8)) if gradio_version_is_above_4 else gr.Textbox.update(value=random.randint(1, 1e8)),
656
+ inputs=[],
657
+ outputs=[seed_textbox]
658
+ )
659
+
660
+ generate_button = gr.Button(value="Generate (生成)", variant='primary')
661
+
662
+ with gr.Column():
663
+ result_image = gr.Image(label="Generated Image (生成图片)", interactive=False, visible=False)
664
+ result_video = gr.Video(label="Generated Animation (生成视频)", interactive=False)
665
+ infer_progress = gr.Textbox(
666
+ label="Generation Info (生成信息)",
667
+ value="No task currently",
668
+ interactive=False
669
+ )
670
+
671
+ model_type.change(
672
+ fn=controller.update_model_type,
673
+ inputs=[model_type],
674
+ outputs=[]
675
+ )
676
+
677
+ def upload_generation_method(generation_method):
678
+ if generation_method == "Video Generation":
679
+ return [gr.update(visible=True, maximum=49, value=49), gr.update(visible=False), gr.update(visible=False)]
680
+ elif generation_method == "Image Generation":
681
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)]
682
+ else:
683
+ return [gr.update(visible=True, maximum=1344), gr.update(visible=True), gr.update(visible=True)]
684
+ generation_method.change(
685
+ upload_generation_method, generation_method, [length_slider, overlap_video_length, partial_video_length]
686
+ )
687
+
688
+ def upload_source_method(source_method):
689
+ if source_method == "Text to Video (文本到视频)":
690
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value=None), gr.update(value=None), gr.update(value=None), gr.update(value=None), gr.update(value=None)]
691
+ elif source_method == "Image to Video (图片到视频)":
692
+ return [gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(), gr.update(), gr.update(value=None), gr.update(value=None), gr.update(value=None)]
693
+ elif source_method == "Video to Video (视频到视频)":
694
+ return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(value=None), gr.update(value=None), gr.update(), gr.update(), gr.update(value=None)]
695
+ else:
696
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), gr.update(value=None), gr.update(value=None), gr.update(value=None), gr.update(value=None), gr.update()]
697
+ source_method.change(
698
+ upload_source_method, source_method, [
699
+ image_to_video_col, video_to_video_col, control_video_col, start_image, end_image,
700
+ validation_video, validation_video_mask, control_video
701
+ ]
702
+ )
703
+
704
+ def upload_resize_method(resize_method):
705
+ if resize_method == "Generate by":
706
+ return [gr.update(visible=True), gr.update(visible=True), gr.update(visible=False)]
707
+ else:
708
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)]
709
+ resize_method.change(
710
+ upload_resize_method, resize_method, [width_slider, height_slider, base_resolution]
711
+ )
712
+
713
+ generate_button.click(
714
+ fn=controller.generate,
715
+ inputs=[
716
+ diffusion_transformer_dropdown,
717
+ base_model_dropdown,
718
+ lora_model_dropdown,
719
+ lora_alpha_slider,
720
+ prompt_textbox,
721
+ negative_prompt_textbox,
722
+ sampler_dropdown,
723
+ sample_step_slider,
724
+ resize_method,
725
+ width_slider,
726
+ height_slider,
727
+ base_resolution,
728
+ generation_method,
729
+ length_slider,
730
+ overlap_video_length,
731
+ partial_video_length,
732
+ cfg_scale_slider,
733
+ start_image,
734
+ end_image,
735
+ validation_video,
736
+ validation_video_mask,
737
+ control_video,
738
+ denoise_strength,
739
+ seed_textbox,
740
+ ],
741
+ outputs=[result_image, result_video, infer_progress]
742
+ )
743
+ return demo, controller
744
+
745
+
746
+ class CogVideoX_Fun_Controller_Modelscope:
747
+ def __init__(self, model_name, model_type, savedir_sample, low_gpu_memory_mode, weight_dtype):
748
+ # Basic dir
749
+ self.basedir = os.getcwd()
750
+ self.personalized_model_dir = os.path.join(self.basedir, "models", "Personalized_Model")
751
+ self.lora_model_path = "none"
752
+ self.savedir_sample = savedir_sample
753
+ self.refresh_personalized_model()
754
+ os.makedirs(self.savedir_sample, exist_ok=True)
755
+
756
+ # model path
757
+ self.model_type = model_type
758
+ self.weight_dtype = weight_dtype
759
+
760
+ self.vae = AutoencoderKLCogVideoX.from_pretrained(
761
+ model_name,
762
+ subfolder="vae",
763
+ ).to(self.weight_dtype)
764
+
765
+ # Get Transformer
766
+ self.transformer = CogVideoXTransformer3DModel.from_pretrained_2d(
767
+ model_name,
768
+ subfolder="transformer",
769
+ ).to(self.weight_dtype)
770
+
771
+ # Get pipeline
772
+ if model_type == "Inpaint":
773
+ if self.transformer.config.in_channels != self.vae.config.latent_channels:
774
+ self.pipeline = CogVideoX_Fun_Pipeline_Inpaint.from_pretrained(
775
+ model_name,
776
+ vae=self.vae,
777
+ transformer=self.transformer,
778
+ scheduler=scheduler_dict["Euler"].from_pretrained(model_name, subfolder="scheduler"),
779
+ torch_dtype=self.weight_dtype
780
+ )
781
+ else:
782
+ self.pipeline = CogVideoX_Fun_Pipeline.from_pretrained(
783
+ model_name,
784
+ vae=self.vae,
785
+ transformer=self.transformer,
786
+ scheduler=scheduler_dict["Euler"].from_pretrained(model_name, subfolder="scheduler"),
787
+ torch_dtype=self.weight_dtype
788
+ )
789
+ else:
790
+ self.pipeline = CogVideoX_Fun_Pipeline_Control.from_pretrained(
791
+ model_name,
792
+ vae=self.vae,
793
+ transformer=self.transformer,
794
+ scheduler=scheduler_dict["Euler"].from_pretrained(model_name, subfolder="scheduler"),
795
+ torch_dtype=self.weight_dtype
796
+ )
797
+
798
+ if low_gpu_memory_mode:
799
+ self.pipeline.enable_sequential_cpu_offload()
800
+ else:
801
+ self.pipeline.enable_model_cpu_offload()
802
+ print("Update diffusion transformer done")
803
+
804
+
805
+ def refresh_personalized_model(self):
806
+ personalized_model_list = sorted(glob(os.path.join(self.personalized_model_dir, "*.safetensors")))
807
+ self.personalized_model_list = [os.path.basename(p) for p in personalized_model_list]
808
+
809
+
810
+ def update_lora_model(self, lora_model_dropdown):
811
+ print("Update lora model")
812
+ if lora_model_dropdown == "none":
813
+ self.lora_model_path = "none"
814
+ return gr.update()
815
+ lora_model_dropdown = os.path.join(self.personalized_model_dir, lora_model_dropdown)
816
+ self.lora_model_path = lora_model_dropdown
817
+ return gr.update()
818
+
819
+
820
+ def generate(
821
+ self,
822
+ diffusion_transformer_dropdown,
823
+ base_model_dropdown,
824
+ lora_model_dropdown,
825
+ lora_alpha_slider,
826
+ prompt_textbox,
827
+ negative_prompt_textbox,
828
+ sampler_dropdown,
829
+ sample_step_slider,
830
+ resize_method,
831
+ width_slider,
832
+ height_slider,
833
+ base_resolution,
834
+ generation_method,
835
+ length_slider,
836
+ overlap_video_length,
837
+ partial_video_length,
838
+ cfg_scale_slider,
839
+ start_image,
840
+ end_image,
841
+ validation_video,
842
+ validation_video_mask,
843
+ control_video,
844
+ denoise_strength,
845
+ seed_textbox,
846
+ is_api = False,
847
+ ):
848
+ gc.collect()
849
+ torch.cuda.empty_cache()
850
+ torch.cuda.ipc_collect()
851
+
852
+ if self.transformer is None:
853
+ raise gr.Error(f"Please select a pretrained model path.")
854
+
855
+ if self.lora_model_path != lora_model_dropdown:
856
+ print("Update lora model")
857
+ self.update_lora_model(lora_model_dropdown)
858
+
859
+ if control_video is not None and self.model_type == "Inpaint":
860
+ if is_api:
861
+ return "", f"If specifying the control video, please set the model_type == \"Control\". "
862
+ else:
863
+ raise gr.Error(f"If specifying the control video, please set the model_type == \"Control\". ")
864
+
865
+ if control_video is None and self.model_type == "Control":
866
+ if is_api:
867
+ return "", f"If set the model_type == \"Control\", please specifying the control video. "
868
+ else:
869
+ raise gr.Error(f"If set the model_type == \"Control\", please specifying the control video. ")
870
+
871
+ if resize_method == "Resize according to Reference":
872
+ if start_image is None and validation_video is None and control_video is None:
873
+ if is_api:
874
+ return "", f"Please upload an image when using \"Resize according to Reference\"."
875
+ else:
876
+ raise gr.Error(f"Please upload an image when using \"Resize according to Reference\".")
877
+
878
+ aspect_ratio_sample_size = {key : [x / 512 * base_resolution for x in ASPECT_RATIO_512[key]] for key in ASPECT_RATIO_512.keys()}
879
+ if self.model_type == "Inpaint":
880
+ if validation_video is not None:
881
+ original_width, original_height = Image.fromarray(cv2.VideoCapture(validation_video).read()[1]).size
882
+ else:
883
+ original_width, original_height = start_image[0].size if type(start_image) is list else Image.open(start_image).size
884
+ else:
885
+ original_width, original_height = Image.fromarray(cv2.VideoCapture(control_video).read()[1]).size
886
+ closest_size, closest_ratio = get_closest_ratio(original_height, original_width, ratios=aspect_ratio_sample_size)
887
+ height_slider, width_slider = [int(x / 16) * 16 for x in closest_size]
888
+
889
+ if self.transformer.config.in_channels == self.vae.config.latent_channels and start_image is not None:
890
+ if is_api:
891
+ return "", f"Please select an image to video pretrained model while using image to video."
892
+ else:
893
+ raise gr.Error(f"Please select an image to video pretrained model while using image to video.")
894
+
895
+ if start_image is None and end_image is not None:
896
+ if is_api:
897
+ return "", f"If specifying the ending image of the video, please specify a starting image of the video."
898
+ else:
899
+ raise gr.Error(f"If specifying the ending image of the video, please specify a starting image of the video.")
900
+
901
+ is_image = True if generation_method == "Image Generation" else False
902
+
903
+ self.pipeline.scheduler = scheduler_dict[sampler_dropdown].from_config(self.pipeline.scheduler.config)
904
+ if self.lora_model_path != "none":
905
+ # lora part
906
+ self.pipeline = merge_lora(self.pipeline, self.lora_model_path, multiplier=lora_alpha_slider)
907
+
908
+ if int(seed_textbox) != -1 and seed_textbox != "": torch.manual_seed(int(seed_textbox))
909
+ else: seed_textbox = np.random.randint(0, 1e10)
910
+ generator = torch.Generator(device="cuda").manual_seed(int(seed_textbox))
911
+
912
+ try:
913
+ if self.model_type == "Inpaint":
914
+ if self.transformer.config.in_channels != self.vae.config.latent_channels:
915
+ if validation_video is not None:
916
+ input_video, input_video_mask, clip_image = get_video_to_video_latent(validation_video, length_slider if not is_image else 1, sample_size=(height_slider, width_slider), validation_video_mask=validation_video_mask, fps=8)
917
+ strength = denoise_strength
918
+ else:
919
+ input_video, input_video_mask, clip_image = get_image_to_video_latent(start_image, end_image, length_slider if not is_image else 1, sample_size=(height_slider, width_slider))
920
+ strength = 1
921
+
922
+ sample = self.pipeline(
923
+ prompt_textbox,
924
+ negative_prompt = negative_prompt_textbox,
925
+ num_inference_steps = sample_step_slider,
926
+ guidance_scale = cfg_scale_slider,
927
+ width = width_slider,
928
+ height = height_slider,
929
+ num_frames = length_slider if not is_image else 1,
930
+ generator = generator,
931
+
932
+ video = input_video,
933
+ mask_video = input_video_mask,
934
+ strength = strength,
935
+ ).videos
936
+ else:
937
+ sample = self.pipeline(
938
+ prompt_textbox,
939
+ negative_prompt = negative_prompt_textbox,
940
+ num_inference_steps = sample_step_slider,
941
+ guidance_scale = cfg_scale_slider,
942
+ width = width_slider,
943
+ height = height_slider,
944
+ num_frames = length_slider if not is_image else 1,
945
+ generator = generator
946
+ ).videos
947
+ else:
948
+ input_video, input_video_mask, clip_image = get_video_to_video_latent(control_video, length_slider if not is_image else 1, sample_size=(height_slider, width_slider), fps=8)
949
+
950
+ sample = self.pipeline(
951
+ prompt_textbox,
952
+ negative_prompt = negative_prompt_textbox,
953
+ num_inference_steps = sample_step_slider,
954
+ guidance_scale = cfg_scale_slider,
955
+ width = width_slider,
956
+ height = height_slider,
957
+ num_frames = length_slider if not is_image else 1,
958
+ generator = generator,
959
+
960
+ control_video = input_video,
961
+ ).videos
962
+ except Exception as e:
963
+ gc.collect()
964
+ torch.cuda.empty_cache()
965
+ torch.cuda.ipc_collect()
966
+ if self.lora_model_path != "none":
967
+ self.pipeline = unmerge_lora(self.pipeline, self.lora_model_path, multiplier=lora_alpha_slider)
968
+ if is_api:
969
+ return "", f"Error. error information is {str(e)}"
970
+ else:
971
+ return gr.update(), gr.update(), f"Error. error information is {str(e)}"
972
+
973
+ gc.collect()
974
+ torch.cuda.empty_cache()
975
+ torch.cuda.ipc_collect()
976
+
977
+ # lora part
978
+ if self.lora_model_path != "none":
979
+ self.pipeline = unmerge_lora(self.pipeline, self.lora_model_path, multiplier=lora_alpha_slider)
980
+
981
+ if not os.path.exists(self.savedir_sample):
982
+ os.makedirs(self.savedir_sample, exist_ok=True)
983
+ index = len([path for path in os.listdir(self.savedir_sample)]) + 1
984
+ prefix = str(index).zfill(3)
985
+
986
+ gc.collect()
987
+ torch.cuda.empty_cache()
988
+ torch.cuda.ipc_collect()
989
+ if is_image or length_slider == 1:
990
+ save_sample_path = os.path.join(self.savedir_sample, prefix + f".png")
991
+
992
+ image = sample[0, :, 0]
993
+ image = image.transpose(0, 1).transpose(1, 2)
994
+ image = (image * 255).numpy().astype(np.uint8)
995
+ image = Image.fromarray(image)
996
+ image.save(save_sample_path)
997
+ if is_api:
998
+ return save_sample_path, "Success"
999
+ else:
1000
+ if gradio_version_is_above_4:
1001
+ return gr.Image(value=save_sample_path, visible=True), gr.Video(value=None, visible=False), "Success"
1002
+ else:
1003
+ return gr.Image.update(value=save_sample_path, visible=True), gr.Video.update(value=None, visible=False), "Success"
1004
+ else:
1005
+ save_sample_path = os.path.join(self.savedir_sample, prefix + f".mp4")
1006
+ save_videos_grid(sample, save_sample_path, fps=8)
1007
+ if is_api:
1008
+ return save_sample_path, "Success"
1009
+ else:
1010
+ if gradio_version_is_above_4:
1011
+ return gr.Image(visible=False, value=None), gr.Video(value=save_sample_path, visible=True), "Success"
1012
+ else:
1013
+ return gr.Image.update(visible=False, value=None), gr.Video.update(value=save_sample_path, visible=True), "Success"
1014
+
1015
+
1016
+ def ui_modelscope(model_name, model_type, savedir_sample, low_gpu_memory_mode, weight_dtype):
1017
+ controller = CogVideoX_Fun_Controller_Modelscope(model_name, model_type, savedir_sample, low_gpu_memory_mode, weight_dtype)
1018
+
1019
+ with gr.Blocks(css=css) as demo:
1020
+ gr.Markdown(
1021
+ """
1022
+ # CogVideoX-Fun
1023
+
1024
+ A CogVideoX with more flexible generation conditions, capable of producing videos of different resolutions, around 6 seconds, and fps 8 (frames 1 to 49), as well as image generated videos.
1025
+
1026
+ [Github](https://github.com/aigc-apps/CogVideoX-Fun/)
1027
+ """
1028
+ )
1029
+ with gr.Column(variant="panel"):
1030
+ gr.Markdown(
1031
+ """
1032
+ ### 1. CogVideoX-Fun Model Type (CogVideoX-Fun模型的种类,正常模型还是控制模型).
1033
+ """
1034
+ )
1035
+ with gr.Row():
1036
+ model_type = gr.Dropdown(
1037
+ label="The model type of CogVideoX-Fun (CogVideoX-Fun模型的种类,正常模型还是控制模型)",
1038
+ choices=[model_type],
1039
+ value=model_type,
1040
+ interactive=False,
1041
+ )
1042
+
1043
+ gr.Markdown(
1044
+ """
1045
+ ### 2. Model checkpoints (模型路径).
1046
+ """
1047
+ )
1048
+ with gr.Row():
1049
+ diffusion_transformer_dropdown = gr.Dropdown(
1050
+ label="Pretrained Model Path (预训练模型路径)",
1051
+ choices=[model_name],
1052
+ value=model_name,
1053
+ interactive=False,
1054
+ )
1055
+ with gr.Row():
1056
+ base_model_dropdown = gr.Dropdown(
1057
+ label="Select base Dreambooth model (选择基模型[非必需])",
1058
+ choices=["none"],
1059
+ value="none",
1060
+ interactive=False,
1061
+ visible=False
1062
+ )
1063
+ with gr.Column(visible=False):
1064
+ gr.Markdown(
1065
+ """
1066
+ ### Minimalism is an example portrait of Lora, triggered by specific prompt words. More details can be found on [Wiki](https://github.com/aigc-apps/CogVideoX-Fun/wiki/Training-Lora).
1067
+ """
1068
+ )
1069
+ with gr.Row():
1070
+ lora_model_dropdown = gr.Dropdown(
1071
+ label="Select LoRA model",
1072
+ choices=["none"],
1073
+ value="none",
1074
+ interactive=True,
1075
+ )
1076
+
1077
+ lora_alpha_slider = gr.Slider(label="LoRA alpha (LoRA权重)", value=0.55, minimum=0, maximum=2, interactive=True)
1078
+
1079
+ with gr.Column(variant="panel"):
1080
+ gr.Markdown(
1081
+ """
1082
+ ### 3. Configs for Generation (生成参数配置).
1083
+ """
1084
+ )
1085
+
1086
+ prompt_textbox = gr.Textbox(label="Prompt (正向提示词)", lines=2, value="A young woman with beautiful and clear eyes and blonde hair standing and white dress in a forest wearing a crown. She seems to be lost in thought, and the camera focuses on her face. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.")
1087
+ negative_prompt_textbox = gr.Textbox(label="Negative prompt (负向提示词)", lines=2, value="The video is not of a high quality, it has a low resolution. Watermark present in each frame. The background is solid. Strange body and strange trajectory. Distortion. " )
1088
+
1089
+ with gr.Row():
1090
+ with gr.Column():
1091
+ with gr.Row():
1092
+ sampler_dropdown = gr.Dropdown(label="Sampling method (采样器种类)", choices=list(scheduler_dict.keys()), value=list(scheduler_dict.keys())[0])
1093
+ sample_step_slider = gr.Slider(label="Sampling steps (生成步数)", value=50, minimum=10, maximum=50, step=1, interactive=False)
1094
+
1095
+ resize_method = gr.Radio(
1096
+ ["Generate by", "Resize according to Reference"],
1097
+ value="Generate by",
1098
+ show_label=False,
1099
+ )
1100
+ width_slider = gr.Slider(label="Width (视频宽度)", value=672, minimum=128, maximum=1280, step=16, interactive=False)
1101
+ height_slider = gr.Slider(label="Height (视频高度)", value=384, minimum=128, maximum=1280, step=16, interactive=False)
1102
+ base_resolution = gr.Radio(label="Base Resolution of Pretrained Models", value=512, choices=[512, 768, 960], interactive=False, visible=False)
1103
+
1104
+ with gr.Group():
1105
+ generation_method = gr.Radio(
1106
+ ["Video Generation", "Image Generation"],
1107
+ value="Video Generation",
1108
+ show_label=False,
1109
+ visible=True,
1110
+ )
1111
+ length_slider = gr.Slider(label="Animation length (视频帧数)", value=49, minimum=5, maximum=49, step=4)
1112
+ overlap_video_length = gr.Slider(label="Overlap length (视频续写的重叠帧数)", value=4, minimum=1, maximum=4, step=1, visible=False)
1113
+ partial_video_length = gr.Slider(label="Partial video generation length (每个部分的视频生成帧数)", value=25, minimum=5, maximum=49, step=4, visible=False)
1114
+
1115
+ source_method = gr.Radio(
1116
+ ["Text to Video (文本到视频)", "Image to Video (图片到视频)", "Video to Video (视频到视频)", "Video Control (视频控制)"],
1117
+ value="Text to Video (文本到视频)",
1118
+ show_label=False,
1119
+ )
1120
+ with gr.Column(visible = False) as image_to_video_col:
1121
+ with gr.Row():
1122
+ start_image = gr.Image(label="The image at the beginning of the video (图片到视频的开始图片)", show_label=True, elem_id="i2v_start", sources="upload", type="filepath")
1123
+
1124
+ template_gallery_path = ["asset/1.png", "asset/2.png", "asset/3.png", "asset/4.png", "asset/5.png"]
1125
+ def select_template(evt: gr.SelectData):
1126
+ text = {
1127
+ "asset/1.png": "The dog is shaking head. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
1128
+ "asset/2.png": "a sailboat sailing in rough seas with a dramatic sunset. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
1129
+ "asset/3.png": "a beautiful woman with long hair and a dress blowing in the wind. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
1130
+ "asset/4.png": "a man in an astronaut suit playing a guitar. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
1131
+ "asset/5.png": "fireworks display over night city. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
1132
+ }[template_gallery_path[evt.index]]
1133
+ return template_gallery_path[evt.index], text
1134
+
1135
+ template_gallery = gr.Gallery(
1136
+ template_gallery_path,
1137
+ columns=5, rows=1,
1138
+ height=140,
1139
+ allow_preview=False,
1140
+ container=False,
1141
+ label="Template Examples",
1142
+ )
1143
+ template_gallery.select(select_template, None, [start_image, prompt_textbox])
1144
+
1145
+ with gr.Accordion("The image at the ending of the video (图片到视频的结束图片[非必需, Optional])", open=False):
1146
+ end_image = gr.Image(label="The image at the ending of the video (图片到视频的结束图片[非必需, Optional])", show_label=False, elem_id="i2v_end", sources="upload", type="filepath")
1147
+
1148
+ with gr.Column(visible = False) as video_to_video_col:
1149
+ with gr.Row():
1150
+ validation_video = gr.Video(
1151
+ label="The video to convert (视频转视频的参考视频)", show_label=True,
1152
+ elem_id="v2v", sources="upload",
1153
+ )
1154
+ with gr.Accordion("The mask of the video to inpaint (视频重新绘制的mask[非必需, Optional])", open=False):
1155
+ gr.Markdown(
1156
+ """
1157
+ - Please set a larger denoise_strength when using validation_video_mask, such as 1.00 instead of 0.70
1158
+ - (请设置更大的denoise_strength,当使用validation_video_mask的时候,比如1而不是0.70)
1159
+ """
1160
+ )
1161
+ validation_video_mask = gr.Image(
1162
+ label="The mask of the video to inpaint (视频重新绘制的mask[非必需, Optional])",
1163
+ show_label=False, elem_id="v2v_mask", sources="upload", type="filepath"
1164
+ )
1165
+ denoise_strength = gr.Slider(label="Denoise strength (重绘系数)", value=0.70, minimum=0.10, maximum=1.00, step=0.01)
1166
+
1167
+ with gr.Column(visible = False) as control_video_col:
1168
+ gr.Markdown(
1169
+ """
1170
+ Demo pose control video can be downloaded here [URL](https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/cogvideox_fun/asset/v1.1/pose.mp4).
1171
+ """
1172
+ )
1173
+ control_video = gr.Video(
1174
+ label="The control video (用于提供控制信号的video)", show_label=True,
1175
+ elem_id="v2v_control", sources="upload",
1176
+ )
1177
+
1178
+ cfg_scale_slider = gr.Slider(label="CFG Scale (引导系数)", value=6.0, minimum=0, maximum=20)
1179
+
1180
+ with gr.Row():
1181
+ seed_textbox = gr.Textbox(label="Seed (随机种子)", value=43)
1182
+ seed_button = gr.Button(value="\U0001F3B2", elem_classes="toolbutton")
1183
+ seed_button.click(
1184
+ fn=lambda: gr.Textbox(value=random.randint(1, 1e8)) if gradio_version_is_above_4 else gr.Textbox.update(value=random.randint(1, 1e8)),
1185
+ inputs=[],
1186
+ outputs=[seed_textbox]
1187
+ )
1188
+
1189
+ generate_button = gr.Button(value="Generate (生成)", variant='primary')
1190
+
1191
+ with gr.Column():
1192
+ result_image = gr.Image(label="Generated Image (生成图片)", interactive=False, visible=False)
1193
+ result_video = gr.Video(label="Generated Animation (生成视频)", interactive=False)
1194
+ infer_progress = gr.Textbox(
1195
+ label="Generation Info (生成信息)",
1196
+ value="No task currently",
1197
+ interactive=False
1198
+ )
1199
+
1200
+ def upload_generation_method(generation_method):
1201
+ if generation_method == "Video Generation":
1202
+ return gr.update(visible=True, minimum=8, maximum=49, value=49, interactive=True)
1203
+ elif generation_method == "Image Generation":
1204
+ return gr.update(minimum=1, maximum=1, value=1, interactive=False)
1205
+ generation_method.change(
1206
+ upload_generation_method, generation_method, [length_slider]
1207
+ )
1208
+
1209
+ def upload_source_method(source_method):
1210
+ if source_method == "Text to Video (文本到视频)":
1211
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(value=None), gr.update(value=None), gr.update(value=None), gr.update(value=None), gr.update(value=None)]
1212
+ elif source_method == "Image to Video (图片到视频)":
1213
+ return [gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(), gr.update(), gr.update(value=None), gr.update(value=None), gr.update(value=None)]
1214
+ elif source_method == "Video to Video (视频到视频)":
1215
+ return [gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(value=None), gr.update(value=None), gr.update(), gr.update(), gr.update(value=None)]
1216
+ else:
1217
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), gr.update(value=None), gr.update(value=None), gr.update(value=None), gr.update(value=None), gr.update()]
1218
+ source_method.change(
1219
+ upload_source_method, source_method, [
1220
+ image_to_video_col, video_to_video_col, control_video_col, start_image, end_image,
1221
+ validation_video, validation_video_mask, control_video
1222
+ ]
1223
+ )
1224
+
1225
+ def upload_resize_method(resize_method):
1226
+ if resize_method == "Generate by":
1227
+ return [gr.update(visible=True), gr.update(visible=True), gr.update(visible=False)]
1228
+ else:
1229
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)]
1230
+ resize_method.change(
1231
+ upload_resize_method, resize_method, [width_slider, height_slider, base_resolution]
1232
+ )
1233
+
1234
+ generate_button.click(
1235
+ fn=controller.generate,
1236
+ inputs=[
1237
+ diffusion_transformer_dropdown,
1238
+ base_model_dropdown,
1239
+ lora_model_dropdown,
1240
+ lora_alpha_slider,
1241
+ prompt_textbox,
1242
+ negative_prompt_textbox,
1243
+ sampler_dropdown,
1244
+ sample_step_slider,
1245
+ resize_method,
1246
+ width_slider,
1247
+ height_slider,
1248
+ base_resolution,
1249
+ generation_method,
1250
+ length_slider,
1251
+ overlap_video_length,
1252
+ partial_video_length,
1253
+ cfg_scale_slider,
1254
+ start_image,
1255
+ end_image,
1256
+ validation_video,
1257
+ validation_video_mask,
1258
+ control_video,
1259
+ denoise_strength,
1260
+ seed_textbox,
1261
+ ],
1262
+ outputs=[result_image, result_video, infer_progress]
1263
+ )
1264
+ return demo, controller
1265
+
1266
+
1267
+ def post_eas(
1268
+ diffusion_transformer_dropdown,
1269
+ base_model_dropdown, lora_model_dropdown, lora_alpha_slider,
1270
+ prompt_textbox, negative_prompt_textbox,
1271
+ sampler_dropdown, sample_step_slider, resize_method, width_slider, height_slider,
1272
+ base_resolution, generation_method, length_slider, cfg_scale_slider,
1273
+ start_image, end_image, validation_video, validation_video_mask, denoise_strength, seed_textbox,
1274
+ ):
1275
+ if start_image is not None:
1276
+ with open(start_image, 'rb') as file:
1277
+ file_content = file.read()
1278
+ start_image_encoded_content = base64.b64encode(file_content)
1279
+ start_image = start_image_encoded_content.decode('utf-8')
1280
+
1281
+ if end_image is not None:
1282
+ with open(end_image, 'rb') as file:
1283
+ file_content = file.read()
1284
+ end_image_encoded_content = base64.b64encode(file_content)
1285
+ end_image = end_image_encoded_content.decode('utf-8')
1286
+
1287
+ if validation_video is not None:
1288
+ with open(validation_video, 'rb') as file:
1289
+ file_content = file.read()
1290
+ validation_video_encoded_content = base64.b64encode(file_content)
1291
+ validation_video = validation_video_encoded_content.decode('utf-8')
1292
+
1293
+ if validation_video_mask is not None:
1294
+ with open(validation_video_mask, 'rb') as file:
1295
+ file_content = file.read()
1296
+ validation_video_mask_encoded_content = base64.b64encode(file_content)
1297
+ validation_video_mask = validation_video_mask_encoded_content.decode('utf-8')
1298
+
1299
+ datas = {
1300
+ "base_model_path": base_model_dropdown,
1301
+ "lora_model_path": lora_model_dropdown,
1302
+ "lora_alpha_slider": lora_alpha_slider,
1303
+ "prompt_textbox": prompt_textbox,
1304
+ "negative_prompt_textbox": negative_prompt_textbox,
1305
+ "sampler_dropdown": sampler_dropdown,
1306
+ "sample_step_slider": sample_step_slider,
1307
+ "resize_method": resize_method,
1308
+ "width_slider": width_slider,
1309
+ "height_slider": height_slider,
1310
+ "base_resolution": base_resolution,
1311
+ "generation_method": generation_method,
1312
+ "length_slider": length_slider,
1313
+ "cfg_scale_slider": cfg_scale_slider,
1314
+ "start_image": start_image,
1315
+ "end_image": end_image,
1316
+ "validation_video": validation_video,
1317
+ "validation_video_mask": validation_video_mask,
1318
+ "denoise_strength": denoise_strength,
1319
+ "seed_textbox": seed_textbox,
1320
+ }
1321
+
1322
+ session = requests.session()
1323
+ session.headers.update({"Authorization": os.environ.get("EAS_TOKEN")})
1324
+
1325
+ response = session.post(url=f'{os.environ.get("EAS_URL")}/cogvideox_fun/infer_forward', json=datas, timeout=300)
1326
+
1327
+ outputs = response.json()
1328
+ return outputs
1329
+
1330
+
1331
+ class CogVideoX_Fun_Controller_EAS:
1332
+ def __init__(self, model_name, savedir_sample):
1333
+ self.savedir_sample = savedir_sample
1334
+ os.makedirs(self.savedir_sample, exist_ok=True)
1335
+
1336
+ def generate(
1337
+ self,
1338
+ diffusion_transformer_dropdown,
1339
+ base_model_dropdown,
1340
+ lora_model_dropdown,
1341
+ lora_alpha_slider,
1342
+ prompt_textbox,
1343
+ negative_prompt_textbox,
1344
+ sampler_dropdown,
1345
+ sample_step_slider,
1346
+ resize_method,
1347
+ width_slider,
1348
+ height_slider,
1349
+ base_resolution,
1350
+ generation_method,
1351
+ length_slider,
1352
+ cfg_scale_slider,
1353
+ start_image,
1354
+ end_image,
1355
+ validation_video,
1356
+ validation_video_mask,
1357
+ denoise_strength,
1358
+ seed_textbox
1359
+ ):
1360
+ is_image = True if generation_method == "Image Generation" else False
1361
+
1362
+ outputs = post_eas(
1363
+ diffusion_transformer_dropdown,
1364
+ base_model_dropdown, lora_model_dropdown, lora_alpha_slider,
1365
+ prompt_textbox, negative_prompt_textbox,
1366
+ sampler_dropdown, sample_step_slider, resize_method, width_slider, height_slider,
1367
+ base_resolution, generation_method, length_slider, cfg_scale_slider,
1368
+ start_image, end_image, validation_video, validation_video_mask, denoise_strength,
1369
+ seed_textbox
1370
+ )
1371
+ try:
1372
+ base64_encoding = outputs["base64_encoding"]
1373
+ except:
1374
+ return gr.Image(visible=False, value=None), gr.Video(None, visible=True), outputs["message"]
1375
+
1376
+ decoded_data = base64.b64decode(base64_encoding)
1377
+
1378
+ if not os.path.exists(self.savedir_sample):
1379
+ os.makedirs(self.savedir_sample, exist_ok=True)
1380
+ index = len([path for path in os.listdir(self.savedir_sample)]) + 1
1381
+ prefix = str(index).zfill(3)
1382
+
1383
+ if is_image or length_slider == 1:
1384
+ save_sample_path = os.path.join(self.savedir_sample, prefix + f".png")
1385
+ with open(save_sample_path, "wb") as file:
1386
+ file.write(decoded_data)
1387
+ if gradio_version_is_above_4:
1388
+ return gr.Image(value=save_sample_path, visible=True), gr.Video(value=None, visible=False), "Success"
1389
+ else:
1390
+ return gr.Image.update(value=save_sample_path, visible=True), gr.Video.update(value=None, visible=False), "Success"
1391
+ else:
1392
+ save_sample_path = os.path.join(self.savedir_sample, prefix + f".mp4")
1393
+ with open(save_sample_path, "wb") as file:
1394
+ file.write(decoded_data)
1395
+ if gradio_version_is_above_4:
1396
+ return gr.Image(visible=False, value=None), gr.Video(value=save_sample_path, visible=True), "Success"
1397
+ else:
1398
+ return gr.Image.update(visible=False, value=None), gr.Video.update(value=save_sample_path, visible=True), "Success"
1399
+
1400
+
1401
+ def ui_eas(model_name, savedir_sample):
1402
+ controller = CogVideoX_Fun_Controller_EAS(model_name, savedir_sample)
1403
+
1404
+ with gr.Blocks(css=css) as demo:
1405
+ gr.Markdown(
1406
+ """
1407
+ # CogVideoX-Fun
1408
+
1409
+ A CogVideoX with more flexible generation conditions, capable of producing videos of different resolutions, around 6 seconds, and fps 8 (frames 1 to 49), as well as image generated videos.
1410
+
1411
+ [Github](https://github.com/aigc-apps/CogVideoX-Fun/)
1412
+ """
1413
+ )
1414
+ with gr.Column(variant="panel"):
1415
+ gr.Markdown(
1416
+ """
1417
+ ### 1. Model checkpoints (模型路径).
1418
+ """
1419
+ )
1420
+ with gr.Row():
1421
+ diffusion_transformer_dropdown = gr.Dropdown(
1422
+ label="Pretrained Model Path",
1423
+ choices=[model_name],
1424
+ value=model_name,
1425
+ interactive=False,
1426
+ )
1427
+ with gr.Row():
1428
+ base_model_dropdown = gr.Dropdown(
1429
+ label="Select base Dreambooth model",
1430
+ choices=["none"],
1431
+ value="none",
1432
+ interactive=False,
1433
+ visible=False
1434
+ )
1435
+ with gr.Column(visible=False):
1436
+ gr.Markdown(
1437
+ """
1438
+ ### Minimalism is an example portrait of Lora, triggered by specific prompt words. More details can be found on [Wiki](https://github.com/aigc-apps/CogVideoX-Fun/wiki/Training-Lora).
1439
+ """
1440
+ )
1441
+ with gr.Row():
1442
+ lora_model_dropdown = gr.Dropdown(
1443
+ label="Select LoRA model",
1444
+ choices=["none"],
1445
+ value="none",
1446
+ interactive=True,
1447
+ )
1448
+
1449
+ lora_alpha_slider = gr.Slider(label="LoRA alpha (LoRA权重)", value=0.55, minimum=0, maximum=2, interactive=True)
1450
+
1451
+ with gr.Column(variant="panel"):
1452
+ gr.Markdown(
1453
+ """
1454
+ ### 2. Configs for Generation.
1455
+ """
1456
+ )
1457
+
1458
+ prompt_textbox = gr.Textbox(label="Prompt", lines=2, value="A young woman with beautiful and clear eyes and blonde hair standing and white dress in a forest wearing a crown. She seems to be lost in thought, and the camera focuses on her face. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.")
1459
+ negative_prompt_textbox = gr.Textbox(label="Negative prompt", lines=2, value="The video is not of a high quality, it has a low resolution. Watermark present in each frame. The background is solid. Strange body and strange trajectory. Distortion. " )
1460
+
1461
+ with gr.Row():
1462
+ with gr.Column():
1463
+ with gr.Row():
1464
+ sampler_dropdown = gr.Dropdown(label="Sampling method", choices=list(scheduler_dict.keys()), value=list(scheduler_dict.keys())[0])
1465
+ sample_step_slider = gr.Slider(label="Sampling steps", value=50, minimum=10, maximum=50, step=1, interactive=False)
1466
+
1467
+ resize_method = gr.Radio(
1468
+ ["Generate by", "Resize according to Reference"],
1469
+ value="Generate by",
1470
+ show_label=False,
1471
+ )
1472
+ width_slider = gr.Slider(label="Width (视频宽度)", value=672, minimum=128, maximum=1280, step=16, interactive=False)
1473
+ height_slider = gr.Slider(label="Height (视频高度)", value=384, minimum=128, maximum=1280, step=16, interactive=False)
1474
+ base_resolution = gr.Radio(label="Base Resolution of Pretrained Models", value=512, choices=[512, 768, 960], interactive=False, visible=False)
1475
+
1476
+ with gr.Group():
1477
+ generation_method = gr.Radio(
1478
+ ["Video Generation", "Image Generation"],
1479
+ value="Video Generation",
1480
+ show_label=False,
1481
+ visible=True,
1482
+ )
1483
+ length_slider = gr.Slider(label="Animation length (视频帧数)", value=49, minimum=5, maximum=49, step=4)
1484
+
1485
+ source_method = gr.Radio(
1486
+ ["Text to Video (文本到视频)", "Image to Video (图片到视频)", "Video to Video (视频到视频)"],
1487
+ value="Text to Video (文本到视频)",
1488
+ show_label=False,
1489
+ )
1490
+ with gr.Column(visible = False) as image_to_video_col:
1491
+ start_image = gr.Image(label="The image at the beginning of the video", show_label=True, elem_id="i2v_start", sources="upload", type="filepath")
1492
+
1493
+ template_gallery_path = ["asset/1.png", "asset/2.png", "asset/3.png", "asset/4.png", "asset/5.png"]
1494
+ def select_template(evt: gr.SelectData):
1495
+ text = {
1496
+ "asset/1.png": "The dog is shaking head. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
1497
+ "asset/2.png": "a sailboat sailing in rough seas with a dramatic sunset. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
1498
+ "asset/3.png": "a beautiful woman with long hair and a dress blowing in the wind. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
1499
+ "asset/4.png": "a man in an astronaut suit playing a guitar. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
1500
+ "asset/5.png": "fireworks display over night city. The video is of high quality, and the view is very clear. High quality, masterpiece, best quality, highres, ultra-detailed, fantastic.",
1501
+ }[template_gallery_path[evt.index]]
1502
+ return template_gallery_path[evt.index], text
1503
+
1504
+ template_gallery = gr.Gallery(
1505
+ template_gallery_path,
1506
+ columns=5, rows=1,
1507
+ height=140,
1508
+ allow_preview=False,
1509
+ container=False,
1510
+ label="Template Examples",
1511
+ )
1512
+ template_gallery.select(select_template, None, [start_image, prompt_textbox])
1513
+
1514
+ with gr.Accordion("The image at the ending of the video (Optional)", open=False):
1515
+ end_image = gr.Image(label="The image at the ending of the video (Optional)", show_label=True, elem_id="i2v_end", sources="upload", type="filepath")
1516
+
1517
+ with gr.Column(visible = False) as video_to_video_col:
1518
+ with gr.Row():
1519
+ validation_video = gr.Video(
1520
+ label="The video to convert (视频转视频的参考视频)", show_label=True,
1521
+ elem_id="v2v", sources="upload",
1522
+ )
1523
+ with gr.Accordion("The mask of the video to inpaint (视频重新绘制的mask[非必需, Optional])", open=False):
1524
+ gr.Markdown(
1525
+ """
1526
+ - Please set a larger denoise_strength when using validation_video_mask, such as 1.00 instead of 0.70
1527
+ - (请设置更大的denoise_strength,当使用validation_video_mask的时候,比如1而不是0.70)
1528
+ """
1529
+ )
1530
+ validation_video_mask = gr.Image(
1531
+ label="The mask of the video to inpaint (视频重新绘制的mask[非必需, Optional])",
1532
+ show_label=False, elem_id="v2v_mask", sources="upload", type="filepath"
1533
+ )
1534
+ denoise_strength = gr.Slider(label="Denoise strength (重绘系数)", value=0.70, minimum=0.10, maximum=1.00, step=0.01)
1535
+
1536
+ cfg_scale_slider = gr.Slider(label="CFG Scale (引导系数)", value=6.0, minimum=0, maximum=20)
1537
+
1538
+ with gr.Row():
1539
+ seed_textbox = gr.Textbox(label="Seed", value=43)
1540
+ seed_button = gr.Button(value="\U0001F3B2", elem_classes="toolbutton")
1541
+ seed_button.click(
1542
+ fn=lambda: gr.Textbox(value=random.randint(1, 1e8)) if gradio_version_is_above_4 else gr.Textbox.update(value=random.randint(1, 1e8)),
1543
+ inputs=[],
1544
+ outputs=[seed_textbox]
1545
+ )
1546
+
1547
+ generate_button = gr.Button(value="Generate", variant='primary')
1548
+
1549
+ with gr.Column():
1550
+ result_image = gr.Image(label="Generated Image", interactive=False, visible=False)
1551
+ result_video = gr.Video(label="Generated Animation", interactive=False)
1552
+ infer_progress = gr.Textbox(
1553
+ label="Generation Info",
1554
+ value="No task currently",
1555
+ interactive=False
1556
+ )
1557
+
1558
+ def upload_generation_method(generation_method):
1559
+ if generation_method == "Video Generation":
1560
+ return gr.update(visible=True, minimum=5, maximum=49, value=49, interactive=True)
1561
+ elif generation_method == "Image Generation":
1562
+ return gr.update(minimum=1, maximum=1, value=1, interactive=False)
1563
+ generation_method.change(
1564
+ upload_generation_method, generation_method, [length_slider]
1565
+ )
1566
+
1567
+ def upload_source_method(source_method):
1568
+ if source_method == "Text to Video (文本到视频)":
1569
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(value=None), gr.update(value=None), gr.update(value=None), gr.update(value=None)]
1570
+ elif source_method == "Image to Video (图片到视频)":
1571
+ return [gr.update(visible=True), gr.update(visible=False), gr.update(), gr.update(), gr.update(value=None), gr.update(value=None)]
1572
+ else:
1573
+ return [gr.update(visible=False), gr.update(visible=True), gr.update(value=None), gr.update(value=None), gr.update(), gr.update()]
1574
+ source_method.change(
1575
+ upload_source_method, source_method, [image_to_video_col, video_to_video_col, start_image, end_image, validation_video, validation_video_mask]
1576
+ )
1577
+
1578
+ def upload_resize_method(resize_method):
1579
+ if resize_method == "Generate by":
1580
+ return [gr.update(visible=True), gr.update(visible=True), gr.update(visible=False)]
1581
+ else:
1582
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)]
1583
+ resize_method.change(
1584
+ upload_resize_method, resize_method, [width_slider, height_slider, base_resolution]
1585
+ )
1586
+
1587
+ generate_button.click(
1588
+ fn=controller.generate,
1589
+ inputs=[
1590
+ diffusion_transformer_dropdown,
1591
+ base_model_dropdown,
1592
+ lora_model_dropdown,
1593
+ lora_alpha_slider,
1594
+ prompt_textbox,
1595
+ negative_prompt_textbox,
1596
+ sampler_dropdown,
1597
+ sample_step_slider,
1598
+ resize_method,
1599
+ width_slider,
1600
+ height_slider,
1601
+ base_resolution,
1602
+ generation_method,
1603
+ length_slider,
1604
+ cfg_scale_slider,
1605
+ start_image,
1606
+ end_image,
1607
+ validation_video,
1608
+ validation_video_mask,
1609
+ denoise_strength,
1610
+ seed_textbox,
1611
+ ],
1612
+ outputs=[result_image, result_video, infer_progress]
1613
+ )
1614
+ return demo, controller
cogvideox/utils/__init__.py ADDED
File without changes
cogvideox/utils/lora_utils.py ADDED
@@ -0,0 +1,477 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LoRA network module
2
+ # reference:
3
+ # https://github.com/microsoft/LoRA/blob/main/loralib/layers.py
4
+ # https://github.com/cloneofsimo/lora/blob/master/lora_diffusion/lora.py
5
+ # https://github.com/bmaltais/kohya_ss
6
+
7
+ import hashlib
8
+ import math
9
+ import os
10
+ from collections import defaultdict
11
+ from io import BytesIO
12
+ from typing import List, Optional, Type, Union
13
+
14
+ import safetensors.torch
15
+ import torch
16
+ import torch.utils.checkpoint
17
+ from diffusers.models.lora import LoRACompatibleConv, LoRACompatibleLinear
18
+ from safetensors.torch import load_file
19
+ from transformers import T5EncoderModel
20
+
21
+
22
+ class LoRAModule(torch.nn.Module):
23
+ """
24
+ replaces forward method of the original Linear, instead of replacing the original Linear module.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ lora_name,
30
+ org_module: torch.nn.Module,
31
+ multiplier=1.0,
32
+ lora_dim=4,
33
+ alpha=1,
34
+ dropout=None,
35
+ rank_dropout=None,
36
+ module_dropout=None,
37
+ ):
38
+ """if alpha == 0 or None, alpha is rank (no scaling)."""
39
+ super().__init__()
40
+ self.lora_name = lora_name
41
+
42
+ if org_module.__class__.__name__ == "Conv2d":
43
+ in_dim = org_module.in_channels
44
+ out_dim = org_module.out_channels
45
+ else:
46
+ in_dim = org_module.in_features
47
+ out_dim = org_module.out_features
48
+
49
+ self.lora_dim = lora_dim
50
+ if org_module.__class__.__name__ == "Conv2d":
51
+ kernel_size = org_module.kernel_size
52
+ stride = org_module.stride
53
+ padding = org_module.padding
54
+ self.lora_down = torch.nn.Conv2d(in_dim, self.lora_dim, kernel_size, stride, padding, bias=False)
55
+ self.lora_up = torch.nn.Conv2d(self.lora_dim, out_dim, (1, 1), (1, 1), bias=False)
56
+ else:
57
+ self.lora_down = torch.nn.Linear(in_dim, self.lora_dim, bias=False)
58
+ self.lora_up = torch.nn.Linear(self.lora_dim, out_dim, bias=False)
59
+
60
+ if type(alpha) == torch.Tensor:
61
+ alpha = alpha.detach().float().numpy() # without casting, bf16 causes error
62
+ alpha = self.lora_dim if alpha is None or alpha == 0 else alpha
63
+ self.scale = alpha / self.lora_dim
64
+ self.register_buffer("alpha", torch.tensor(alpha))
65
+
66
+ # same as microsoft's
67
+ torch.nn.init.kaiming_uniform_(self.lora_down.weight, a=math.sqrt(5))
68
+ torch.nn.init.zeros_(self.lora_up.weight)
69
+
70
+ self.multiplier = multiplier
71
+ self.org_module = org_module # remove in applying
72
+ self.dropout = dropout
73
+ self.rank_dropout = rank_dropout
74
+ self.module_dropout = module_dropout
75
+
76
+ def apply_to(self):
77
+ self.org_forward = self.org_module.forward
78
+ self.org_module.forward = self.forward
79
+ del self.org_module
80
+
81
+ def forward(self, x, *args, **kwargs):
82
+ weight_dtype = x.dtype
83
+ org_forwarded = self.org_forward(x)
84
+
85
+ # module dropout
86
+ if self.module_dropout is not None and self.training:
87
+ if torch.rand(1) < self.module_dropout:
88
+ return org_forwarded
89
+
90
+ lx = self.lora_down(x.to(self.lora_down.weight.dtype))
91
+
92
+ # normal dropout
93
+ if self.dropout is not None and self.training:
94
+ lx = torch.nn.functional.dropout(lx, p=self.dropout)
95
+
96
+ # rank dropout
97
+ if self.rank_dropout is not None and self.training:
98
+ mask = torch.rand((lx.size(0), self.lora_dim), device=lx.device) > self.rank_dropout
99
+ if len(lx.size()) == 3:
100
+ mask = mask.unsqueeze(1) # for Text Encoder
101
+ elif len(lx.size()) == 4:
102
+ mask = mask.unsqueeze(-1).unsqueeze(-1) # for Conv2d
103
+ lx = lx * mask
104
+
105
+ # scaling for rank dropout: treat as if the rank is changed
106
+ scale = self.scale * (1.0 / (1.0 - self.rank_dropout)) # redundant for readability
107
+ else:
108
+ scale = self.scale
109
+
110
+ lx = self.lora_up(lx)
111
+
112
+ return org_forwarded.to(weight_dtype) + lx.to(weight_dtype) * self.multiplier * scale
113
+
114
+
115
+ def addnet_hash_legacy(b):
116
+ """Old model hash used by sd-webui-additional-networks for .safetensors format files"""
117
+ m = hashlib.sha256()
118
+
119
+ b.seek(0x100000)
120
+ m.update(b.read(0x10000))
121
+ return m.hexdigest()[0:8]
122
+
123
+
124
+ def addnet_hash_safetensors(b):
125
+ """New model hash used by sd-webui-additional-networks for .safetensors format files"""
126
+ hash_sha256 = hashlib.sha256()
127
+ blksize = 1024 * 1024
128
+
129
+ b.seek(0)
130
+ header = b.read(8)
131
+ n = int.from_bytes(header, "little")
132
+
133
+ offset = n + 8
134
+ b.seek(offset)
135
+ for chunk in iter(lambda: b.read(blksize), b""):
136
+ hash_sha256.update(chunk)
137
+
138
+ return hash_sha256.hexdigest()
139
+
140
+
141
+ def precalculate_safetensors_hashes(tensors, metadata):
142
+ """Precalculate the model hashes needed by sd-webui-additional-networks to
143
+ save time on indexing the model later."""
144
+
145
+ # Because writing user metadata to the file can change the result of
146
+ # sd_models.model_hash(), only retain the training metadata for purposes of
147
+ # calculating the hash, as they are meant to be immutable
148
+ metadata = {k: v for k, v in metadata.items() if k.startswith("ss_")}
149
+
150
+ bytes = safetensors.torch.save(tensors, metadata)
151
+ b = BytesIO(bytes)
152
+
153
+ model_hash = addnet_hash_safetensors(b)
154
+ legacy_hash = addnet_hash_legacy(b)
155
+ return model_hash, legacy_hash
156
+
157
+
158
+ class LoRANetwork(torch.nn.Module):
159
+ TRANSFORMER_TARGET_REPLACE_MODULE = ["CogVideoXTransformer3DModel"]
160
+ TEXT_ENCODER_TARGET_REPLACE_MODULE = ["T5LayerSelfAttention", "T5LayerFF", "BertEncoder"]
161
+ LORA_PREFIX_TRANSFORMER = "lora_unet"
162
+ LORA_PREFIX_TEXT_ENCODER = "lora_te"
163
+ def __init__(
164
+ self,
165
+ text_encoder: Union[List[T5EncoderModel], T5EncoderModel],
166
+ unet,
167
+ multiplier: float = 1.0,
168
+ lora_dim: int = 4,
169
+ alpha: float = 1,
170
+ dropout: Optional[float] = None,
171
+ module_class: Type[object] = LoRAModule,
172
+ add_lora_in_attn_temporal: bool = False,
173
+ varbose: Optional[bool] = False,
174
+ ) -> None:
175
+ super().__init__()
176
+ self.multiplier = multiplier
177
+
178
+ self.lora_dim = lora_dim
179
+ self.alpha = alpha
180
+ self.dropout = dropout
181
+
182
+ print(f"create LoRA network. base dim (rank): {lora_dim}, alpha: {alpha}")
183
+ print(f"neuron dropout: p={self.dropout}")
184
+
185
+ # create module instances
186
+ def create_modules(
187
+ is_unet: bool,
188
+ root_module: torch.nn.Module,
189
+ target_replace_modules: List[torch.nn.Module],
190
+ ) -> List[LoRAModule]:
191
+ prefix = (
192
+ self.LORA_PREFIX_TRANSFORMER
193
+ if is_unet
194
+ else self.LORA_PREFIX_TEXT_ENCODER
195
+ )
196
+ loras = []
197
+ skipped = []
198
+ for name, module in root_module.named_modules():
199
+ if module.__class__.__name__ in target_replace_modules:
200
+ for child_name, child_module in module.named_modules():
201
+ is_linear = child_module.__class__.__name__ == "Linear" or child_module.__class__.__name__ == "LoRACompatibleLinear"
202
+ is_conv2d = child_module.__class__.__name__ == "Conv2d" or child_module.__class__.__name__ == "LoRACompatibleConv"
203
+ is_conv2d_1x1 = is_conv2d and child_module.kernel_size == (1, 1)
204
+
205
+ if not add_lora_in_attn_temporal:
206
+ if "attn_temporal" in child_name:
207
+ continue
208
+
209
+ if is_linear or is_conv2d:
210
+ lora_name = prefix + "." + name + "." + child_name
211
+ lora_name = lora_name.replace(".", "_")
212
+
213
+ dim = None
214
+ alpha = None
215
+
216
+ if is_linear or is_conv2d_1x1:
217
+ dim = self.lora_dim
218
+ alpha = self.alpha
219
+
220
+ if dim is None or dim == 0:
221
+ if is_linear or is_conv2d_1x1:
222
+ skipped.append(lora_name)
223
+ continue
224
+
225
+ lora = module_class(
226
+ lora_name,
227
+ child_module,
228
+ self.multiplier,
229
+ dim,
230
+ alpha,
231
+ dropout=dropout,
232
+ )
233
+ loras.append(lora)
234
+ return loras, skipped
235
+
236
+ text_encoders = text_encoder if type(text_encoder) == list else [text_encoder]
237
+
238
+ self.text_encoder_loras = []
239
+ skipped_te = []
240
+ for i, text_encoder in enumerate(text_encoders):
241
+ if text_encoder is not None:
242
+ text_encoder_loras, skipped = create_modules(False, text_encoder, LoRANetwork.TEXT_ENCODER_TARGET_REPLACE_MODULE)
243
+ self.text_encoder_loras.extend(text_encoder_loras)
244
+ skipped_te += skipped
245
+ print(f"create LoRA for Text Encoder: {len(self.text_encoder_loras)} modules.")
246
+
247
+ self.unet_loras, skipped_un = create_modules(True, unet, LoRANetwork.TRANSFORMER_TARGET_REPLACE_MODULE)
248
+ print(f"create LoRA for U-Net: {len(self.unet_loras)} modules.")
249
+
250
+ # assertion
251
+ names = set()
252
+ for lora in self.text_encoder_loras + self.unet_loras:
253
+ assert lora.lora_name not in names, f"duplicated lora name: {lora.lora_name}"
254
+ names.add(lora.lora_name)
255
+
256
+ def apply_to(self, text_encoder, unet, apply_text_encoder=True, apply_unet=True):
257
+ if apply_text_encoder:
258
+ print("enable LoRA for text encoder")
259
+ else:
260
+ self.text_encoder_loras = []
261
+
262
+ if apply_unet:
263
+ print("enable LoRA for U-Net")
264
+ else:
265
+ self.unet_loras = []
266
+
267
+ for lora in self.text_encoder_loras + self.unet_loras:
268
+ lora.apply_to()
269
+ self.add_module(lora.lora_name, lora)
270
+
271
+ def set_multiplier(self, multiplier):
272
+ self.multiplier = multiplier
273
+ for lora in self.text_encoder_loras + self.unet_loras:
274
+ lora.multiplier = self.multiplier
275
+
276
+ def load_weights(self, file):
277
+ if os.path.splitext(file)[1] == ".safetensors":
278
+ from safetensors.torch import load_file
279
+
280
+ weights_sd = load_file(file)
281
+ else:
282
+ weights_sd = torch.load(file, map_location="cpu")
283
+ info = self.load_state_dict(weights_sd, False)
284
+ return info
285
+
286
+ def prepare_optimizer_params(self, text_encoder_lr, unet_lr, default_lr):
287
+ self.requires_grad_(True)
288
+ all_params = []
289
+
290
+ def enumerate_params(loras):
291
+ params = []
292
+ for lora in loras:
293
+ params.extend(lora.parameters())
294
+ return params
295
+
296
+ if self.text_encoder_loras:
297
+ param_data = {"params": enumerate_params(self.text_encoder_loras)}
298
+ if text_encoder_lr is not None:
299
+ param_data["lr"] = text_encoder_lr
300
+ all_params.append(param_data)
301
+
302
+ if self.unet_loras:
303
+ param_data = {"params": enumerate_params(self.unet_loras)}
304
+ if unet_lr is not None:
305
+ param_data["lr"] = unet_lr
306
+ all_params.append(param_data)
307
+
308
+ return all_params
309
+
310
+ def enable_gradient_checkpointing(self):
311
+ pass
312
+
313
+ def get_trainable_params(self):
314
+ return self.parameters()
315
+
316
+ def save_weights(self, file, dtype, metadata):
317
+ if metadata is not None and len(metadata) == 0:
318
+ metadata = None
319
+
320
+ state_dict = self.state_dict()
321
+
322
+ if dtype is not None:
323
+ for key in list(state_dict.keys()):
324
+ v = state_dict[key]
325
+ v = v.detach().clone().to("cpu").to(dtype)
326
+ state_dict[key] = v
327
+
328
+ if os.path.splitext(file)[1] == ".safetensors":
329
+ from safetensors.torch import save_file
330
+
331
+ # Precalculate model hashes to save time on indexing
332
+ if metadata is None:
333
+ metadata = {}
334
+ model_hash, legacy_hash = precalculate_safetensors_hashes(state_dict, metadata)
335
+ metadata["sshs_model_hash"] = model_hash
336
+ metadata["sshs_legacy_hash"] = legacy_hash
337
+
338
+ save_file(state_dict, file, metadata)
339
+ else:
340
+ torch.save(state_dict, file)
341
+
342
+ def create_network(
343
+ multiplier: float,
344
+ network_dim: Optional[int],
345
+ network_alpha: Optional[float],
346
+ text_encoder: Union[T5EncoderModel, List[T5EncoderModel]],
347
+ transformer,
348
+ neuron_dropout: Optional[float] = None,
349
+ add_lora_in_attn_temporal: bool = False,
350
+ **kwargs,
351
+ ):
352
+ if network_dim is None:
353
+ network_dim = 4 # default
354
+ if network_alpha is None:
355
+ network_alpha = 1.0
356
+
357
+ network = LoRANetwork(
358
+ text_encoder,
359
+ transformer,
360
+ multiplier=multiplier,
361
+ lora_dim=network_dim,
362
+ alpha=network_alpha,
363
+ dropout=neuron_dropout,
364
+ add_lora_in_attn_temporal=add_lora_in_attn_temporal,
365
+ varbose=True,
366
+ )
367
+ return network
368
+
369
+ def merge_lora(pipeline, lora_path, multiplier, device='cpu', dtype=torch.float32, state_dict=None, transformer_only=False):
370
+ LORA_PREFIX_TRANSFORMER = "lora_unet"
371
+ LORA_PREFIX_TEXT_ENCODER = "lora_te"
372
+ if state_dict is None:
373
+ state_dict = load_file(lora_path, device=device)
374
+ else:
375
+ state_dict = state_dict
376
+ updates = defaultdict(dict)
377
+ for key, value in state_dict.items():
378
+ layer, elem = key.split('.', 1)
379
+ updates[layer][elem] = value
380
+
381
+ for layer, elems in updates.items():
382
+
383
+ if "lora_te" in layer:
384
+ if transformer_only:
385
+ continue
386
+ else:
387
+ layer_infos = layer.split(LORA_PREFIX_TEXT_ENCODER + "_")[-1].split("_")
388
+ curr_layer = pipeline.text_encoder
389
+ else:
390
+ layer_infos = layer.split(LORA_PREFIX_TRANSFORMER + "_")[-1].split("_")
391
+ curr_layer = pipeline.transformer
392
+
393
+ temp_name = layer_infos.pop(0)
394
+ while len(layer_infos) > -1:
395
+ try:
396
+ curr_layer = curr_layer.__getattr__(temp_name)
397
+ if len(layer_infos) > 0:
398
+ temp_name = layer_infos.pop(0)
399
+ elif len(layer_infos) == 0:
400
+ break
401
+ except Exception:
402
+ if len(layer_infos) == 0:
403
+ print('Error loading layer')
404
+ if len(temp_name) > 0:
405
+ temp_name += "_" + layer_infos.pop(0)
406
+ else:
407
+ temp_name = layer_infos.pop(0)
408
+
409
+ weight_up = elems['lora_up.weight'].to(dtype)
410
+ weight_down = elems['lora_down.weight'].to(dtype)
411
+ if 'alpha' in elems.keys():
412
+ alpha = elems['alpha'].item() / weight_up.shape[1]
413
+ else:
414
+ alpha = 1.0
415
+
416
+ curr_layer.weight.data = curr_layer.weight.data.to(device)
417
+ if len(weight_up.shape) == 4:
418
+ curr_layer.weight.data += multiplier * alpha * torch.mm(weight_up.squeeze(3).squeeze(2),
419
+ weight_down.squeeze(3).squeeze(2)).unsqueeze(
420
+ 2).unsqueeze(3)
421
+ else:
422
+ curr_layer.weight.data += multiplier * alpha * torch.mm(weight_up, weight_down)
423
+
424
+ return pipeline
425
+
426
+ # TODO: Refactor with merge_lora.
427
+ def unmerge_lora(pipeline, lora_path, multiplier=1, device="cpu", dtype=torch.float32):
428
+ """Unmerge state_dict in LoRANetwork from the pipeline in diffusers."""
429
+ LORA_PREFIX_UNET = "lora_unet"
430
+ LORA_PREFIX_TEXT_ENCODER = "lora_te"
431
+ state_dict = load_file(lora_path, device=device)
432
+
433
+ updates = defaultdict(dict)
434
+ for key, value in state_dict.items():
435
+ layer, elem = key.split('.', 1)
436
+ updates[layer][elem] = value
437
+
438
+ for layer, elems in updates.items():
439
+
440
+ if "lora_te" in layer:
441
+ layer_infos = layer.split(LORA_PREFIX_TEXT_ENCODER + "_")[-1].split("_")
442
+ curr_layer = pipeline.text_encoder
443
+ else:
444
+ layer_infos = layer.split(LORA_PREFIX_UNET + "_")[-1].split("_")
445
+ curr_layer = pipeline.transformer
446
+
447
+ temp_name = layer_infos.pop(0)
448
+ while len(layer_infos) > -1:
449
+ try:
450
+ curr_layer = curr_layer.__getattr__(temp_name)
451
+ if len(layer_infos) > 0:
452
+ temp_name = layer_infos.pop(0)
453
+ elif len(layer_infos) == 0:
454
+ break
455
+ except Exception:
456
+ if len(layer_infos) == 0:
457
+ print('Error loading layer')
458
+ if len(temp_name) > 0:
459
+ temp_name += "_" + layer_infos.pop(0)
460
+ else:
461
+ temp_name = layer_infos.pop(0)
462
+
463
+ weight_up = elems['lora_up.weight'].to(dtype)
464
+ weight_down = elems['lora_down.weight'].to(dtype)
465
+ if 'alpha' in elems.keys():
466
+ alpha = elems['alpha'].item() / weight_up.shape[1]
467
+ else:
468
+ alpha = 1.0
469
+
470
+ curr_layer.weight.data = curr_layer.weight.data.to(device)
471
+ if len(weight_up.shape) == 4:
472
+ curr_layer.weight.data -= multiplier * alpha * torch.mm(weight_up.squeeze(3).squeeze(2),
473
+ weight_down.squeeze(3).squeeze(2)).unsqueeze(2).unsqueeze(3)
474
+ else:
475
+ curr_layer.weight.data -= multiplier * alpha * torch.mm(weight_up, weight_down)
476
+
477
+ return pipeline
cogvideox/utils/utils.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gc
3
+ import imageio
4
+ import numpy as np
5
+ import torch
6
+ import torchvision
7
+ import cv2
8
+ from einops import rearrange
9
+ from PIL import Image
10
+
11
+ def get_width_and_height_from_image_and_base_resolution(image, base_resolution):
12
+ target_pixels = int(base_resolution) * int(base_resolution)
13
+ original_width, original_height = Image.open(image).size
14
+ ratio = (target_pixels / (original_width * original_height)) ** 0.5
15
+ width_slider = round(original_width * ratio)
16
+ height_slider = round(original_height * ratio)
17
+ return height_slider, width_slider
18
+
19
+ def color_transfer(sc, dc):
20
+ """
21
+ Transfer color distribution from of sc, referred to dc.
22
+
23
+ Args:
24
+ sc (numpy.ndarray): input image to be transfered.
25
+ dc (numpy.ndarray): reference image
26
+
27
+ Returns:
28
+ numpy.ndarray: Transferred color distribution on the sc.
29
+ """
30
+
31
+ def get_mean_and_std(img):
32
+ x_mean, x_std = cv2.meanStdDev(img)
33
+ x_mean = np.hstack(np.around(x_mean, 2))
34
+ x_std = np.hstack(np.around(x_std, 2))
35
+ return x_mean, x_std
36
+
37
+ sc = cv2.cvtColor(sc, cv2.COLOR_RGB2LAB)
38
+ s_mean, s_std = get_mean_and_std(sc)
39
+ dc = cv2.cvtColor(dc, cv2.COLOR_RGB2LAB)
40
+ t_mean, t_std = get_mean_and_std(dc)
41
+ img_n = ((sc - s_mean) * (t_std / s_std)) + t_mean
42
+ np.putmask(img_n, img_n > 255, 255)
43
+ np.putmask(img_n, img_n < 0, 0)
44
+ dst = cv2.cvtColor(cv2.convertScaleAbs(img_n), cv2.COLOR_LAB2RGB)
45
+ return dst
46
+
47
+ def save_videos_grid(videos: torch.Tensor, path: str, rescale=False, n_rows=6, fps=12, imageio_backend=True, color_transfer_post_process=False):
48
+ videos = rearrange(videos, "b c t h w -> t b c h w")
49
+ outputs = []
50
+ for x in videos:
51
+ x = torchvision.utils.make_grid(x, nrow=n_rows)
52
+ x = x.transpose(0, 1).transpose(1, 2).squeeze(-1)
53
+ if rescale:
54
+ x = (x + 1.0) / 2.0 # -1,1 -> 0,1
55
+ x = (x * 255).numpy().astype(np.uint8)
56
+ outputs.append(Image.fromarray(x))
57
+
58
+ if color_transfer_post_process:
59
+ for i in range(1, len(outputs)):
60
+ outputs[i] = Image.fromarray(color_transfer(np.uint8(outputs[i]), np.uint8(outputs[0])))
61
+
62
+ os.makedirs(os.path.dirname(path), exist_ok=True)
63
+ if imageio_backend:
64
+ if path.endswith("mp4"):
65
+ imageio.mimsave(path, outputs, fps=fps)
66
+ else:
67
+ imageio.mimsave(path, outputs, duration=(1000 * 1/fps))
68
+ else:
69
+ if path.endswith("mp4"):
70
+ path = path.replace('.mp4', '.gif')
71
+ outputs[0].save(path, format='GIF', append_images=outputs, save_all=True, duration=100, loop=0)
72
+
73
+ def get_image_to_video_latent(validation_image_start, validation_image_end, video_length, sample_size):
74
+ if validation_image_start is not None and validation_image_end is not None:
75
+ if type(validation_image_start) is str and os.path.isfile(validation_image_start):
76
+ image_start = clip_image = Image.open(validation_image_start).convert("RGB")
77
+ image_start = image_start.resize([sample_size[1], sample_size[0]])
78
+ clip_image = clip_image.resize([sample_size[1], sample_size[0]])
79
+ else:
80
+ image_start = clip_image = validation_image_start
81
+ image_start = [_image_start.resize([sample_size[1], sample_size[0]]) for _image_start in image_start]
82
+ clip_image = [_clip_image.resize([sample_size[1], sample_size[0]]) for _clip_image in clip_image]
83
+
84
+ if type(validation_image_end) is str and os.path.isfile(validation_image_end):
85
+ image_end = Image.open(validation_image_end).convert("RGB")
86
+ image_end = image_end.resize([sample_size[1], sample_size[0]])
87
+ else:
88
+ image_end = validation_image_end
89
+ image_end = [_image_end.resize([sample_size[1], sample_size[0]]) for _image_end in image_end]
90
+
91
+ if type(image_start) is list:
92
+ clip_image = clip_image[0]
93
+ start_video = torch.cat(
94
+ [torch.from_numpy(np.array(_image_start)).permute(2, 0, 1).unsqueeze(1).unsqueeze(0) for _image_start in image_start],
95
+ dim=2
96
+ )
97
+ input_video = torch.tile(start_video[:, :, :1], [1, 1, video_length, 1, 1])
98
+ input_video[:, :, :len(image_start)] = start_video
99
+
100
+ input_video_mask = torch.zeros_like(input_video[:, :1])
101
+ input_video_mask[:, :, len(image_start):] = 255
102
+ else:
103
+ input_video = torch.tile(
104
+ torch.from_numpy(np.array(image_start)).permute(2, 0, 1).unsqueeze(1).unsqueeze(0),
105
+ [1, 1, video_length, 1, 1]
106
+ )
107
+ input_video_mask = torch.zeros_like(input_video[:, :1])
108
+ input_video_mask[:, :, 1:] = 255
109
+
110
+ if type(image_end) is list:
111
+ image_end = [_image_end.resize(image_start[0].size if type(image_start) is list else image_start.size) for _image_end in image_end]
112
+ end_video = torch.cat(
113
+ [torch.from_numpy(np.array(_image_end)).permute(2, 0, 1).unsqueeze(1).unsqueeze(0) for _image_end in image_end],
114
+ dim=2
115
+ )
116
+ input_video[:, :, -len(end_video):] = end_video
117
+
118
+ input_video_mask[:, :, -len(image_end):] = 0
119
+ else:
120
+ image_end = image_end.resize(image_start[0].size if type(image_start) is list else image_start.size)
121
+ input_video[:, :, -1:] = torch.from_numpy(np.array(image_end)).permute(2, 0, 1).unsqueeze(1).unsqueeze(0)
122
+ input_video_mask[:, :, -1:] = 0
123
+
124
+ input_video = input_video / 255
125
+
126
+ elif validation_image_start is not None:
127
+ if type(validation_image_start) is str and os.path.isfile(validation_image_start):
128
+ image_start = clip_image = Image.open(validation_image_start).convert("RGB")
129
+ image_start = image_start.resize([sample_size[1], sample_size[0]])
130
+ clip_image = clip_image.resize([sample_size[1], sample_size[0]])
131
+ else:
132
+ image_start = clip_image = validation_image_start
133
+ image_start = [_image_start.resize([sample_size[1], sample_size[0]]) for _image_start in image_start]
134
+ clip_image = [_clip_image.resize([sample_size[1], sample_size[0]]) for _clip_image in clip_image]
135
+ image_end = None
136
+
137
+ if type(image_start) is list:
138
+ clip_image = clip_image[0]
139
+ start_video = torch.cat(
140
+ [torch.from_numpy(np.array(_image_start)).permute(2, 0, 1).unsqueeze(1).unsqueeze(0) for _image_start in image_start],
141
+ dim=2
142
+ )
143
+ input_video = torch.tile(start_video[:, :, :1], [1, 1, video_length, 1, 1])
144
+ input_video[:, :, :len(image_start)] = start_video
145
+ input_video = input_video / 255
146
+
147
+ input_video_mask = torch.zeros_like(input_video[:, :1])
148
+ input_video_mask[:, :, len(image_start):] = 255
149
+ else:
150
+ input_video = torch.tile(
151
+ torch.from_numpy(np.array(image_start)).permute(2, 0, 1).unsqueeze(1).unsqueeze(0),
152
+ [1, 1, video_length, 1, 1]
153
+ ) / 255
154
+ input_video_mask = torch.zeros_like(input_video[:, :1])
155
+ input_video_mask[:, :, 1:, ] = 255
156
+ else:
157
+ image_start = None
158
+ image_end = None
159
+ input_video = torch.zeros([1, 3, video_length, sample_size[0], sample_size[1]])
160
+ input_video_mask = torch.ones([1, 1, video_length, sample_size[0], sample_size[1]]) * 255
161
+ clip_image = None
162
+
163
+ del image_start
164
+ del image_end
165
+ gc.collect()
166
+
167
+ return input_video, input_video_mask, clip_image
168
+
169
+ def get_video_to_video_latent(input_video_path, video_length, sample_size, fps=None, validation_video_mask=None):
170
+ if isinstance(input_video_path, str):
171
+ cap = cv2.VideoCapture(input_video_path)
172
+ input_video = []
173
+
174
+ original_fps = cap.get(cv2.CAP_PROP_FPS)
175
+ frame_skip = 1 if fps is None else int(original_fps // fps)
176
+
177
+ frame_count = 0
178
+
179
+ while True:
180
+ ret, frame = cap.read()
181
+ if not ret:
182
+ break
183
+
184
+ if frame_count % frame_skip == 0:
185
+ frame = cv2.resize(frame, (sample_size[1], sample_size[0]))
186
+ input_video.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
187
+
188
+ frame_count += 1
189
+
190
+ cap.release()
191
+ else:
192
+ input_video = input_video_path
193
+
194
+ input_video = torch.from_numpy(np.array(input_video))[:video_length]
195
+ input_video = input_video.permute([3, 0, 1, 2]).unsqueeze(0) / 255
196
+
197
+ if validation_video_mask is not None:
198
+ validation_video_mask = Image.open(validation_video_mask).convert('L').resize((sample_size[1], sample_size[0]))
199
+ input_video_mask = np.where(np.array(validation_video_mask) < 240, 0, 255)
200
+
201
+ input_video_mask = torch.from_numpy(np.array(input_video_mask)).unsqueeze(0).unsqueeze(-1).permute([3, 0, 1, 2]).unsqueeze(0)
202
+ input_video_mask = torch.tile(input_video_mask, [1, 1, input_video.size()[2], 1, 1])
203
+ input_video_mask = input_video_mask.to(input_video.device, input_video.dtype)
204
+ else:
205
+ input_video_mask = torch.zeros_like(input_video[:, :1])
206
+ input_video_mask[:, :, :] = 255
207
+
208
+ return input_video, input_video_mask, None
cogvideox/video_caption/README.md ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Video Caption
2
+ English | [简体中文](./README_zh-CN.md)
3
+
4
+ The folder contains codes for dataset preprocessing (i.e., video splitting, filtering, and recaptioning), and beautiful prompt used by CogVideoX-Fun.
5
+ The entire process supports distributed parallel processing, capable of handling large-scale datasets.
6
+
7
+ Meanwhile, we are collaborating with [Data-Juicer](https://github.com/modelscope/data-juicer/blob/main/docs/DJ_SORA.md),
8
+ allowing you to easily perform video data processing on [Aliyun PAI-DLC](https://help.aliyun.com/zh/pai/user-guide/video-preprocessing/).
9
+
10
+ # Table of Content
11
+ - [Video Caption](#video-caption)
12
+ - [Table of Content](#table-of-content)
13
+ - [Quick Start](#quick-start)
14
+ - [Setup](#setup)
15
+ - [Data Preprocessing](#data-preprocessing)
16
+ - [Data Preparation](#data-preparation)
17
+ - [Video Splitting](#video-splitting)
18
+ - [Video Filtering](#video-filtering)
19
+ - [Video Recaptioning](#video-recaptioning)
20
+ - [Beautiful Prompt (For CogVideoX-Fun Inference)](#beautiful-prompt-for-cogvideox-inference)
21
+ - [Batched Inference](#batched-inference)
22
+ - [OpenAI Server](#openai-server)
23
+
24
+ ## Quick Start
25
+
26
+ ### Setup
27
+ AliyunDSW or Docker is recommended to setup the environment, please refer to [Quick Start](../../README.md#quick-start).
28
+ You can also refer to the image build process in the [Dockerfile](../../Dockerfile.ds) to configure the conda environment and other dependencies locally.
29
+
30
+ Since the video recaptioning depends on [llm-awq](https://github.com/mit-han-lab/llm-awq) for faster and memory efficient inference,
31
+ the minimum GPU requirment should be RTX 3060 or A2 (CUDA Compute Capability >= 8.0).
32
+
33
+ ```shell
34
+ # pull image
35
+ docker pull mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easycv/torch_cuda:cogvideox_fun
36
+
37
+ # enter image
38
+ docker run -it -p 7860:7860 --network host --gpus all --security-opt seccomp:unconfined --shm-size 200g mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easycv/torch_cuda:cogvideox_fun
39
+
40
+ # clone code
41
+ git clone https://github.com/aigc-apps/CogVideoX-Fun.git
42
+
43
+ # enter video_caption
44
+ cd CogVideoX-Fun/cogvideox/video_caption
45
+ ```
46
+
47
+ ### Data Preprocessing
48
+ #### Data Preparation
49
+ Place the downloaded videos into a folder under [datasets](./datasets/) (preferably without nested structures, as the video names are used as unique IDs in subsequent processes).
50
+ Taking Panda-70M as an example, the entire dataset directory structure is shown as follows:
51
+ ```
52
+ 📦 datasets/
53
+ ├── 📂 panda_70m/
54
+ │ ├── 📂 videos/
55
+ │ │ ├── 📂 data/
56
+ │ │ │ └── 📄 --C66yU3LjM_2.mp4
57
+ │ │ │ └── 📄 ...
58
+ ```
59
+
60
+ #### Video Splitting
61
+ CogVideoX-Fun utilizes [PySceneDetect](https://github.com/Breakthrough/PySceneDetect) to identify scene changes within the video
62
+ and performs video splitting via FFmpeg based on certain threshold values to ensure consistency of the video clip.
63
+ Video clips shorter than 3 seconds will be discarded, and those longer than 10 seconds will be splitted recursively.
64
+
65
+ The entire workflow of video splitting is in the [stage_1_video_splitting.sh](./scripts/stage_1_video_splitting.sh).
66
+ After running
67
+ ```shell
68
+ sh scripts/stage_1_video_splitting.sh
69
+ ```
70
+ the video clips are obtained in `cogvideox/video_caption/datasets/panda_70m/videos_clips/data/`.
71
+
72
+ #### Video Filtering
73
+ Based on the videos obtained in the previous step, CogVideoX-Fun provides a simple yet effective pipeline to filter out high-quality videos for recaptioning.
74
+ The overall process is as follows:
75
+
76
+ - Aesthetic filtering: Filter out videos with poor content (blurry, dim, etc.) by calculating the average aesthetic score of uniformly sampled 4 frames via [aesthetic-predictor-v2-5](https://github.com/discus0434/aesthetic-predictor-v2-5).
77
+ - Text filtering: Use [EasyOCR](https://github.com/JaidedAI/EasyOCR) to calculate the text area proportion of the middle frame to filter out videos with a large area of text.
78
+ - Motion filtering: Calculate interframe optical flow differences to filter out videos that move too slowly or too quickly.
79
+
80
+ The entire workflow of video filtering is in the [stage_2_video_filtering.sh](./scripts/stage_2_video_filtering.sh).
81
+ After running
82
+ ```shell
83
+ sh scripts/stage_2_video_filtering.sh
84
+ ```
85
+ the aesthetic score, text score, and motion score of videos will be saved in the corresponding meta files in the folder `cogvideox/video_caption/datasets/panda_70m/videos_clips/`.
86
+
87
+ > [!NOTE]
88
+ > The computation of the aesthetic score depends on the [google/siglip-so400m-patch14-384 model](https://huggingface.co/google/siglip-so400m-patch14-384).
89
+ Please run `HF_ENDPOINT=https://hf-mirror.com sh scripts/stage_2_video_filtering.sh` if you cannot access to huggingface.com.
90
+
91
+
92
+ #### Video Recaptioning
93
+ After obtaining the aboved high-quality filtered videos, CogVideoX-Fun utilizes [VILA1.5](https://github.com/NVlabs/VILA) to perform video recaptioning.
94
+ Subsequently, the recaptioning results are rewritten by LLMs to better meet with the requirements of video generation tasks.
95
+ Finally, an advanced VideoCLIPXL model is developed to filter out video-caption pairs with poor alignment, resulting in the final training dataset.
96
+
97
+ Please download the video caption model from [VILA1.5](https://huggingface.co/collections/Efficient-Large-Model/vila-on-pre-training-for-visual-language-models-65d8022a3a52cd9bcd62698e) of the appropriate size based on the GPU memory of your machine.
98
+ For A100 with 40G VRAM, you can download [VILA1.5-40b-AWQ](https://huggingface.co/Efficient-Large-Model/VILA1.5-40b-AWQ) by running
99
+ ```shell
100
+ # Add HF_ENDPOINT=https://hf-mirror.com before the command if you cannot access to huggingface.com
101
+ huggingface-cli download Efficient-Large-Model/VILA1.5-40b-AWQ --local-dir-use-symlinks False --local-dir /PATH/TO/VILA_MODEL
102
+ ```
103
+
104
+ Optionally, you can prepare local LLMs to rewrite the recaption results.
105
+ For example, you can download [Meta-Llama-3-8B-Instruct](https://huggingface.co/NousResearch/Meta-Llama-3-8B-Instruct) by running
106
+ ```shell
107
+ # Add HF_ENDPOINT=https://hf-mirror.com before the command if you cannot access to huggingface.com
108
+ huggingface-cli download NousResearch/Meta-Llama-3-8B-Instruct --local-dir-use-symlinks False --local-dir /PATH/TO/REWRITE_MODEL
109
+ ```
110
+
111
+ The entire workflow of video recaption is in the [stage_3_video_recaptioning.sh](./scripts/stage_3_video_recaptioning.sh).
112
+ After running
113
+ ```shell
114
+ VILA_MODEL_PATH=/PATH/TO/VILA_MODEL REWRITE_MODEL_PATH=/PATH/TO/REWRITE_MODEL sh scripts/stage_3_video_recaptioning.sh
115
+ ```
116
+ the final train file is obtained in `cogvideox/video_caption/datasets/panda_70m/videos_clips/meta_train_info.json`.
117
+
118
+
119
+ ### Beautiful Prompt (For CogVideoX-Fun Inference)
120
+ Beautiful Prompt aims to rewrite and beautify the user-uploaded prompt via LLMs, mapping it to the style of CogVideoX-Fun's training captions,
121
+ making it more suitable as the inference prompt and thus improving the quality of the generated videos.
122
+ We support batched inference with local LLMs or OpenAI compatible server based on [vLLM](https://github.com/vllm-project/vllm) for beautiful prompt.
123
+
124
+ #### Batched Inference
125
+ 1. Prepare original prompts in a jsonl file `cogvideox/video_caption/datasets/original_prompt.jsonl` with the following format:
126
+ ```json
127
+ {"prompt": "A stylish woman in a black leather jacket, red dress, and boots walks confidently down a damp Tokyo street."}
128
+ {"prompt": "An underwater world with realistic fish and other creatures of the sea."}
129
+ {"prompt": "a monarch butterfly perched on a tree trunk in the forest."}
130
+ {"prompt": "a child in a room with a bottle of wine and a lamp."}
131
+ {"prompt": "two men in suits walking down a hallway."}
132
+ ```
133
+
134
+ 2. Then you can perform beautiful prompt by running
135
+ ```shell
136
+ # Meta-Llama-3-8B-Instruct is sufficient for this task.
137
+ # Download it from https://huggingface.co/NousResearch/Meta-Llama-3-8B-Instruct or https://www.modelscope.cn/models/LLM-Research/Meta-Llama-3-8B-Instruct to /path/to/your_llm
138
+
139
+ python caption_rewrite.py \
140
+ --video_metadata_path datasets/original_prompt.jsonl \
141
+ --caption_column "prompt" \
142
+ --batch_size 1 \
143
+ --model_name /path/to/your_llm \
144
+ --prompt prompt/beautiful_prompt.txt \
145
+ --prefix '"detailed description": ' \
146
+ --saved_path datasets/beautiful_prompt.jsonl \
147
+ --saved_freq 1
148
+ ```
149
+
150
+ #### OpenAI Server
151
+ + You can request OpenAI compatible server to perform beautiful prompt by running
152
+ ```shell
153
+ OPENAI_API_KEY="your_openai_api_key" OPENAI_BASE_URL="your_openai_base_url" python beautiful_prompt.py \
154
+ --model "your_model_name" \
155
+ --prompt "your_prompt"
156
+ ```
157
+
158
+ + You can also deploy the OpenAI Compatible Server locally using vLLM. For example:
159
+ ```shell
160
+ # Meta-Llama-3-8B-Instruct is sufficient for this task.
161
+ # Download it from https://huggingface.co/NousResearch/Meta-Llama-3-8B-Instruct or https://www.modelscope.cn/models/LLM-Research/Meta-Llama-3-8B-Instruct to /path/to/your_llm
162
+
163
+ # deploy the OpenAI compatible server
164
+ python -m vllm.entrypoints.openai.api_server serve /path/to/your_llm --dtype auto --api-key "your_api_key"
165
+ ```
166
+
167
+ Then you can perform beautiful prompt by running
168
+ ```shell
169
+ python -m beautiful_prompt.py \
170
+ --model /path/to/your_llm \
171
+ --prompt "your_prompt" \
172
+ --base_url "http://localhost:8000/v1" \
173
+ --api_key "your_api_key"
174
+ ```
cogvideox/video_caption/README_zh-CN.md ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 数据预处理
2
+ [English](./README.md) | 简体中文
3
+
4
+ 该文件夹包含 CogVideoX-Fun 使用的数据集预处理(即视频切分、过滤和生成描述)和提示词美化的代码。整个过程支持分布式并行处理,能够处理大规模数据集。
5
+
6
+ 此外,我们和 [Data-Juicer](https://github.com/modelscope/data-juicer/blob/main/docs/DJ_SORA.md) 合作,能让你在 [Aliyun PAI-DLC](https://help.aliyun.com/zh/pai/user-guide/video-preprocessing/) 轻松进行视频数据的处理。
7
+
8
+ # 目录
9
+ - [数据预处理](#数据预处理)
10
+ - [目录](#目录)
11
+ - [快速开始](#快速开始)
12
+ - [安装](#安装)
13
+ - [数据集预处理](#数据集预处理)
14
+ - [数据准备](#数据准备)
15
+ - [视频切分](#视频切分)
16
+ - [视频过滤](#视频过滤)
17
+ - [视频描述](#视频描述)
18
+ - [提示词美化](#提示词美化)
19
+ - [批量推理](#批量推理)
20
+ - [OpenAI 服务器](#openai-服务器)
21
+
22
+
23
+ ## 快速开始
24
+ ### 安装
25
+ 推荐使用阿里云 DSW 和 Docker 来安装环境,请参考 [快速开始](../../README_zh-CN.md#1-云使用-aliyundswdocker). 你也可以参考 [Dockerfile](../../Dockerfile.ds) 中的镜像构建流程在本地安装对应的 conda 环境和其余依赖。
26
+
27
+ 为了提高推理速度和节省推理的显存,生成视频描述依赖于 [llm-awq](https://github.com/mit-han-lab/llm-awq)。因此,需要 RTX 3060 或者 A2 及以上的显卡 (CUDA Compute Capability >= 8.0)。
28
+
29
+ ```shell
30
+ # pull image
31
+ docker pull mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easycv/torch_cuda:cogvideox_fun
32
+
33
+ # enter image
34
+ docker run -it -p 7860:7860 --network host --gpus all --security-opt seccomp:unconfined --shm-size 200g mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easycv/torch_cuda:cogvideox_fun
35
+
36
+ # clone code
37
+ git clone https://github.com/aigc-apps/CogVideoX-Fun.git
38
+
39
+ # enter video_caption
40
+ cd CogVideoX-Fun/cogvideox/video_caption
41
+ ```
42
+
43
+ ### 数据集预处理
44
+ #### 数据准备
45
+ 将下载的视频准备到文件夹 [datasets](./datasets/)(最好不使用嵌套结构,因为视频名称在后续处理中用作唯一 ID)。以 Panda-70M 为例,完整的数据集目录结构如下所示:
46
+ ```
47
+ 📦 datasets/
48
+ ├── 📂 panda_70m/
49
+ │ ├── 📂 videos/
50
+ │ │ ├── 📂 data/
51
+ │ │ │ └── 📄 --C66yU3LjM_2.mp4
52
+ │ │ │ └── 📄 ...
53
+ ```
54
+
55
+ #### 视频切分
56
+ CogVideoX-Fun 使用 [PySceneDetect](https://github.com/Breakthrough/PySceneDetect) 来识别视频中的场景变化
57
+ 并根据某些阈值通过 FFmpeg 执行视频分割,以确保视频片段的一致性。
58
+ 短于 3 秒的视频片段将被丢弃,长于 10 秒的视频片段将被递归切分。
59
+
60
+ 视频切分的完整流程在 [stage_1_video_splitting.sh](./scripts/stage_1_video_splitting.sh)。执行
61
+ ```shell
62
+ sh scripts/stage_1_video_splitting.sh
63
+ ```
64
+ 后,切分后的视频位于 `cogvideox/video_caption/datasets/panda_70m/videos_clips/data/`。
65
+
66
+ #### 视频过滤
67
+ 基于上一步获得的视频,CogVideoX-Fun 提供了一个简单而有效的流程来过滤出高质量的视频。总体流程如下:
68
+
69
+ - 美学过滤:通过 [aesthetic-predictor-v2-5](https://github.com/discus0434/aesthetic-predictor-v2-5) 计算均匀采样的 4 帧视频的平均美学分数,从而筛选出内容不佳(模糊、昏暗等)的视频。
70
+ - 文本过滤:使用 [EasyOCR](https://github.com/JaidedAI/EasyOCR) 计算中间帧的文本区域比例,过滤掉含有大面积文本的视频。
71
+ - 运动过滤:计算帧间光流差,过滤掉移动太慢或太快的视频。
72
+
73
+ 视频过滤的完整流程在 [stage_2_video_filtering.sh](./scripts/stage_2_video_filtering.sh)。执行
74
+ ```shell
75
+ sh scripts/stage_2_video_filtering.sh
76
+ ```
77
+ 后,视频的美学得分、文本得分和运动得分对应的元文件保存在 `cogvideox/video_caption/datasets/panda_70m/videos_clips/`。
78
+
79
+ > [!NOTE]
80
+ > 美学得分的计算依赖于 [google/siglip-so400m-patch14-384 model](https://huggingface.co/google/siglip-so400m-patch14-384).
81
+ 请执行 `HF_ENDPOINT=https://hf-mirror.com sh scripts/stage_2_video_filtering.sh` 如果你无法访问 huggingface.com.
82
+
83
+ #### 视频描述
84
+ 在获得上述高质量的过滤视频后,CogVideoX-Fun 利用 [VILA1.5](https://github.com/NVlabs/VILA) 来生成视频描述。随后,使用 LLMs 对生成的视频描述进行重写,以更好地满足视频生成任务的要求。最后,使用自研的 VideoCLIPXL 模型来过滤掉描述和视频内容不一致的数据,从而得到最终的训练数据集。
85
+
86
+ 请根据机器的显存从 [VILA1.5](https://huggingface.co/collections/Efficient-Large-Model/vila-on-pre-training-for-visual-language-models-65d8022a3a52cd9bcd62698e) 下载合适大小的模型。对于 A100 40G,你可以执行下面的命令来下载 [VILA1.5-40b-AWQ](https://huggingface.co/Efficient-Large-Model/VILA1.5-40b-AWQ)
87
+ ```shell
88
+ # Add HF_ENDPOINT=https://hf-mirror.com before the command if you cannot access to huggingface.com
89
+ huggingface-cli download Efficient-Large-Model/VILA1.5-40b-AWQ --local-dir-use-symlinks False --local-dir /PATH/TO/VILA_MODEL
90
+ ```
91
+
92
+ 你可以选择性地准备 LLMs 来改写上述视频描述的结果。例如,你执行下面的命令来下载 [Meta-Llama-3-8B-Instruct](https://huggingface.co/NousResearch/Meta-Llama-3-8B-Instruct)
93
+ ```shell
94
+ # Add HF_ENDPOINT=https://hf-mirror.com before the command if you cannot access to huggingface.com
95
+ huggingface-cli download NousResearch/Meta-Llama-3-8B-Instruct --local-dir-use-symlinks False --local-dir /PATH/TO/REWRITE_MODEL
96
+ ```
97
+
98
+ 视频描述的完整流程在 [stage_3_video_recaptioning.sh](./scripts/stage_3_video_recaptioning.sh).
99
+ 执行
100
+ ```shell
101
+ VILA_MODEL_PATH=/PATH/TO/VILA_MODEL REWRITE_MODEL_PATH=/PATH/TO/REWRITE_MODEL sh scripts/stage_3_video_recaptioning.sh
102
+ ```
103
+ 后,最后的训练文件会保存在 `cogvideox/video_caption/datasets/panda_70m/videos_clips/meta_train_info.json`。
104
+
105
+ ### 提示词美化
106
+ 提示词美化旨在通过 LLMs 重写和美化用户上传的提示,将其映射为 CogVideoX-Fun 训练所使用的视频描述风格、
107
+ 使其更适合用作推理提示词,从而提高生成视频的质量。
108
+
109
+ 基于 [vLLM](https://github.com/vllm-project/vllm),我们支持使用本地 LLM 进行批量推理或请求 OpenAI 服务器的方式,以进行提示词美化。
110
+
111
+ #### 批量推理
112
+ 1. 将原始的提示词以下面的格式准备在文件 `cogvideox/video_caption/datasets/original_prompt.jsonl` 中:
113
+ ```json
114
+ {"prompt": "A stylish woman in a black leather jacket, red dress, and boots walks confidently down a damp Tokyo street."}
115
+ {"prompt": "An underwater world with realistic fish and other creatures of the sea."}
116
+ {"prompt": "a monarch butterfly perched on a tree trunk in the forest."}
117
+ {"prompt": "a child in a room with a bottle of wine and a lamp."}
118
+ {"prompt": "two men in suits walking down a hallway."}
119
+ ```
120
+
121
+ 2. 随后你可以通过执行以下的命令进行提示词美化
122
+ ```shell
123
+ # Meta-Llama-3-8B-Instruct is sufficient for this task.
124
+ # Download it from https://huggingface.co/NousResearch/Meta-Llama-3-8B-Instruct or https://www.modelscope.cn/models/LLM-Research/Meta-Llama-3-8B-Instruct to /path/to/your_llm
125
+
126
+ python caption_rewrite.py \
127
+ --video_metadata_path datasets/original_prompt.jsonl \
128
+ --caption_column "prompt" \
129
+ --batch_size 1 \
130
+ --model_name /path/to/your_llm \
131
+ --prompt prompt/beautiful_prompt.txt \
132
+ --prefix '"detailed description": ' \
133
+ --saved_path datasets/beautiful_prompt.jsonl \
134
+ --saved_freq 1
135
+ ```
136
+
137
+ #### OpenAI 服务器
138
+ + 你可以通过请求 OpenAI 服务器的方式来进行提示词美化
139
+ ```shell
140
+ OPENAI_API_KEY="your_openai_api_key" OPENAI_BASE_URL="your_openai_base_url" python beautiful_prompt.py \
141
+ --model "your_model_name" \
142
+ --prompt "your_prompt"
143
+ ```
144
+
145
+ + 你也可以执行以下命令,通过 vLLM 将本地 LLMs 部署成兼容 OpenAI 的服务器
146
+ ```shell
147
+ OPENAI_API_KEY="your_openai_api_key" OPENAI_BASE_URL="your_openai_base_url" python beautiful_prompt.py \
148
+ --model "your_model_name" \
149
+ --prompt "your_prompt"
150
+ ```
151
+
152
+ 然后再执行下面的命令来进行提示词美化
153
+ ```shell
154
+ python -m beautiful_prompt.py \
155
+ --model /path/to/your_llm \
156
+ --prompt "your_prompt" \
157
+ --base_url "http://localhost:8000/v1" \
158
+ --api_key "your_api_key"
159
+ ```
cogvideox/video_caption/beautiful_prompt.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This script (optional) can rewrite and beautify the user-uploaded prompt via LLMs, mapping it to the style of cogvideox's training captions,
3
+ making it more suitable as the inference prompt and thus improving the quality of the generated videos.
4
+
5
+ Usage:
6
+ + You can request OpenAI compatible server to perform beautiful prompt by running
7
+ ```shell
8
+ export OPENAI_API_KEY="your_openai_api_key" OPENAI_BASE_URL="your_openai_base_url" python beautiful_prompt.py \
9
+ --model "your_model_name" \
10
+ --prompt "your_prompt"
11
+ ```
12
+ + You can also deploy the OpenAI Compatible Server locally using vLLM. For example:
13
+ ```shell
14
+ # Meta-Llama-3-8B-Instruct is sufficient for this task.
15
+ # Download it from https://huggingface.co/NousResearch/Meta-Llama-3-8B-Instruct or https://www.modelscope.cn/models/LLM-Research/Meta-Llama-3-8B-Instruct to /path/to/your_llm
16
+
17
+ # deploy the OpenAI compatible server
18
+ python -m vllm.entrypoints.openai.api_server serve /path/to/your_llm --dtype auto --api-key "your_api_key"
19
+ ```
20
+
21
+ Then you can perform beautiful prompt by running
22
+ ```shell
23
+ python -m beautiful_prompt.py \
24
+ --model /path/to/your_llm \
25
+ --prompt "your_prompt" \
26
+ --base_url "http://localhost:8000/v1" \
27
+ --api_key "your_api_key"
28
+ ```
29
+ """
30
+ import argparse
31
+ import os
32
+
33
+ from openai import OpenAI
34
+
35
+ from cogvideox.video_caption.caption_rewrite import extract_output
36
+
37
+
38
+ def parse_args():
39
+ parser = argparse.ArgumentParser(description="Beautiful prompt.")
40
+ parser.add_argument("--model", type=str, required=True, help="The OpenAI model or the path to your local LLM.")
41
+ parser.add_argument("--prompt", type=str, required=True, help="The user-uploaded prompt.")
42
+ parser.add_argument(
43
+ "--template",
44
+ type=str,
45
+ default="cogvideox/video_caption/prompt/beautiful_prompt.txt",
46
+ help="A string or a txt file contains the template for beautiful prompt."
47
+ )
48
+ parser.add_argument(
49
+ "--max_retry_nums",
50
+ type=int,
51
+ default=5,
52
+ help="Maximum number of retries to obtain an output that meets the JSON format."
53
+ )
54
+ parser.add_argument(
55
+ "--base_url",
56
+ type=str,
57
+ default=None,
58
+ help="OpenAI API server url. If it is None, the OPENAI_BASE_URL from the environment variables will be used.",
59
+ )
60
+ parser.add_argument(
61
+ "--api_key",
62
+ type=str,
63
+ default=None,
64
+ help="OpenAI API key. If it is None, the OPENAI_API_KEY from the environment variables will be used.",
65
+ )
66
+
67
+ args = parser.parse_args()
68
+ return args
69
+
70
+
71
+ def main():
72
+ args = parse_args()
73
+
74
+ client = OpenAI(
75
+ base_url=os.getenv("OPENAI_BASE_URL", args.base_url),
76
+ api_key=os.environ.get("OPENAI_API_KEY", args.api_key),
77
+ )
78
+ if args.template.endswith(".txt") and os.path.exists(args.template):
79
+ with open(args.template, "r") as f:
80
+ args.template = "".join(f.readlines())
81
+ # print(f"Beautiful prompt template: {args.template}")
82
+
83
+ for _ in range(args.max_retry_nums):
84
+ completion = client.chat.completions.create(
85
+ model=args.model,
86
+ messages=[
87
+ # {"role": "system", "content": "You are a helpful assistant."},
88
+ {"role": "user", "content": args.template + "\n" + str(args.prompt)}
89
+ ],
90
+ temperature=0.7,
91
+ top_p=1,
92
+ max_tokens=1024,
93
+ )
94
+
95
+ output = completion.choices[0].message.content
96
+ output = extract_output(output, prefix='"detailed description": ')
97
+ if output is not None:
98
+ break
99
+ print(f"Beautiful prompt: {output}")
100
+
101
+
102
+ if __name__ == "__main__":
103
+ main()
cogvideox/video_caption/caption_rewrite.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import re
3
+ import os
4
+ from tqdm import tqdm
5
+
6
+ import pandas as pd
7
+ import torch
8
+ from natsort import index_natsorted
9
+ from vllm import LLM, SamplingParams
10
+ from transformers import AutoTokenizer
11
+
12
+ from utils.logger import logger
13
+
14
+
15
+ def extract_output(s, prefix='"rewritten description": '):
16
+ """Customize the function according to the prompt."""
17
+ # Since some LLMs struggles to output strictly formatted JSON strings as specified by the prompt,
18
+ # thus manually parse the output string `{"rewritten description": "your rewritten description here"}`.
19
+ match = re.search(r"{(.+?)}", s, re.DOTALL)
20
+ if not match:
21
+ logger.warning(f"{s} is not in the json format. Return None.")
22
+ return None
23
+ output = match.group(1).strip()
24
+ if output.startswith(prefix):
25
+ output = output[len(prefix) :]
26
+ if output[0] == '"' and output[-1] == '"':
27
+ return output[1:-1]
28
+ else:
29
+ logger.warning(f"{output} does not start and end with the double quote. Return None.")
30
+ return None
31
+ else:
32
+ logger.warning(f"{output} does not start with {prefix}. Return None.")
33
+ return None
34
+
35
+
36
+ def parse_args():
37
+ parser = argparse.ArgumentParser(description="Rewrite the video caption by LLMs.")
38
+ parser.add_argument(
39
+ "--video_metadata_path", type=str, required=True, help="The path to the video dataset metadata (csv/jsonl)."
40
+ )
41
+ parser.add_argument(
42
+ "--video_path_column",
43
+ type=str,
44
+ default=None,
45
+ help="The column contains the video path (an absolute path or a relative path w.r.t the video_folder).",
46
+ )
47
+ parser.add_argument(
48
+ "--caption_column",
49
+ type=str,
50
+ default="caption",
51
+ help="The column contains the video caption.",
52
+ )
53
+ parser.add_argument(
54
+ "--batch_size",
55
+ type=int,
56
+ default=128,
57
+ required=False,
58
+ help="The batch size for vllm inference. Adjust according to the number of GPUs to maximize inference throughput.",
59
+ )
60
+ parser.add_argument(
61
+ "--model_name",
62
+ type=str,
63
+ default="NousResearch/Meta-Llama-3-8B-Instruct",
64
+ )
65
+ parser.add_argument(
66
+ "--prompt",
67
+ type=str,
68
+ required=True,
69
+ help="A string or a txt file contains the prompt.",
70
+ )
71
+ parser.add_argument(
72
+ "--prefix",
73
+ type=str,
74
+ required=True,
75
+ help="The prefix to extract the output from LLMs.",
76
+ )
77
+ parser.add_argument("--saved_path", type=str, required=True, help="The save path to the output results (csv/jsonl).")
78
+ parser.add_argument("--saved_freq", type=int, default=1, help="The frequency to save the output results.")
79
+
80
+ args = parser.parse_args()
81
+ return args
82
+
83
+
84
+ def main():
85
+ args = parse_args()
86
+
87
+ if args.video_metadata_path.endswith(".csv"):
88
+ video_metadata_df = pd.read_csv(args.video_metadata_path)
89
+ elif args.video_metadata_path.endswith(".jsonl"):
90
+ video_metadata_df = pd.read_json(args.video_metadata_path, lines=True)
91
+ elif args.video_metadata_path.endswith(".json"):
92
+ video_metadata_df = pd.read_json(args.video_metadata_path)
93
+ else:
94
+ raise ValueError(f"The {args.video_metadata_path} must end with .csv, .jsonl or .json.")
95
+
96
+ saved_suffix = os.path.splitext(args.saved_path)[1]
97
+ if saved_suffix not in set([".csv", ".jsonl", ".json"]):
98
+ raise ValueError(f"The saved_path must end with .csv, .jsonl or .json.")
99
+
100
+ if os.path.exists(args.saved_path) and args.video_path_column is not None:
101
+ if args.saved_path.endswith(".csv"):
102
+ saved_metadata_df = pd.read_csv(args.saved_path)
103
+ elif args.saved_path.endswith(".jsonl"):
104
+ saved_metadata_df = pd.read_json(args.saved_path, lines=True)
105
+
106
+ # Filter out the unprocessed video-caption pairs by setting the indicator=True.
107
+ merged_df = video_metadata_df.merge(saved_metadata_df, on=args.video_path_column, how="outer", indicator=True)
108
+ video_metadata_df = merged_df[merged_df["_merge"] == "left_only"]
109
+ # Sorting to guarantee the same result for each process.
110
+ video_metadata_df = video_metadata_df.iloc[index_natsorted(video_metadata_df[args.video_path_column])].reset_index(
111
+ drop=True
112
+ )
113
+ logger.info(
114
+ f"Resume from {args.saved_path}: {len(saved_metadata_df)} processed and {len(video_metadata_df)} to be processed."
115
+ )
116
+
117
+ if args.prompt.endswith(".txt") and os.path.exists(args.prompt):
118
+ with open(args.prompt, "r") as f:
119
+ args.prompt = "".join(f.readlines())
120
+ logger.info(f"Prompt: {args.prompt}")
121
+
122
+ if args.video_path_column is not None:
123
+ video_path_list = video_metadata_df[args.video_path_column].tolist()
124
+ if args.caption_column in video_metadata_df.columns:
125
+ sampled_frame_caption_list = video_metadata_df[args.caption_column].tolist()
126
+ else:
127
+ # When two columns with the same name, the dataframe merge operation on will distinguish them by adding 'x' and 'y'.
128
+ sampled_frame_caption_list = video_metadata_df[args.caption_column + "_x"].tolist()
129
+
130
+ CUDA_VISIBLE_DEVICES = os.getenv("CUDA_VISIBLE_DEVICES", None)
131
+ tensor_parallel_size = torch.cuda.device_count() if CUDA_VISIBLE_DEVICES is None else len(CUDA_VISIBLE_DEVICES.split(","))
132
+ logger.info(f"Automatically set tensor_parallel_size={tensor_parallel_size} based on the available devices.")
133
+
134
+ llm = LLM(model=args.model_name, trust_remote_code=True, tensor_parallel_size=tensor_parallel_size)
135
+ if "Meta-Llama-3" in args.model_name:
136
+ if "Meta-Llama-3-70B" in args.model_name:
137
+ # Llama-3-70B should use the tokenizer from Llama-3-8B
138
+ # https://github.com/vllm-project/vllm/issues/4180#issuecomment-2068292942
139
+ tokenizer = AutoTokenizer.from_pretrained("NousResearch/Meta-Llama-3-8B-Instruct")
140
+ else:
141
+ tokenizer = AutoTokenizer.from_pretrained(args.model_name)
142
+ stop_token_ids = [tokenizer.eos_token_id, tokenizer.convert_tokens_to_ids("<|eot_id|>")]
143
+ sampling_params = SamplingParams(temperature=0.7, top_p=1, max_tokens=1024, stop_token_ids=stop_token_ids)
144
+ else:
145
+ tokenizer = AutoTokenizer.from_pretrained(args.model_name)
146
+ sampling_params = SamplingParams(temperature=0.7, top_p=1, max_tokens=1024)
147
+
148
+ result_dict = {args.caption_column: []}
149
+ if args.video_path_column is not None:
150
+ result_dict = {args.video_path_column: [], args.caption_column: []}
151
+
152
+ for i in tqdm(range(0, len(sampled_frame_caption_list), args.batch_size)):
153
+ if args.video_path_column is not None:
154
+ batch_video_path = video_path_list[i : i + args.batch_size]
155
+ batch_caption = sampled_frame_caption_list[i : i + args.batch_size]
156
+ batch_prompt = []
157
+ for caption in batch_caption:
158
+ # batch_prompt.append("user:" + args.prompt + str(caption) + "\n assistant:")
159
+ messages = [
160
+ {"role": "system", "content": "You are a helpful assistant."},
161
+ {"role": "user", "content": args.prompt + "\n" + str(caption)},
162
+ ]
163
+ text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
164
+ batch_prompt.append(text)
165
+
166
+ batch_output = llm.generate(batch_prompt, sampling_params)
167
+ batch_output = [output.outputs[0].text.rstrip() for output in batch_output]
168
+ batch_output = [extract_output(output, prefix=args.prefix) for output in batch_output]
169
+
170
+ # Filter out data that does not meet the output format.
171
+ batch_result = []
172
+ if args.video_path_column is not None:
173
+ for video_path, output in zip(batch_video_path, batch_output):
174
+ if output is not None:
175
+ batch_result.append((video_path, output))
176
+ batch_video_path, batch_output = zip(*batch_result)
177
+
178
+ result_dict[args.video_path_column].extend(batch_video_path)
179
+ else:
180
+ for output in batch_output:
181
+ if output is not None:
182
+ batch_result.append(output)
183
+
184
+ result_dict[args.caption_column].extend(batch_result)
185
+
186
+ # Save the metadata every args.saved_freq.
187
+ if i != 0 and ((i // args.batch_size) % args.saved_freq) == 0:
188
+ if len(result_dict[args.caption_column]) > 0:
189
+ result_df = pd.DataFrame(result_dict)
190
+ if args.saved_path.endswith(".csv"):
191
+ header = True if not os.path.exists(args.saved_path) else False
192
+ result_df.to_csv(args.saved_path, header=header, index=False, mode="a")
193
+ elif args.saved_path.endswith(".jsonl"):
194
+ result_df.to_json(args.saved_path, orient="records", lines=True, mode="a", force_ascii=False)
195
+ elif args.saved_path.endswith(".json"):
196
+ # Append is not supported.
197
+ if os.path.exists(args.saved_path):
198
+ saved_df = pd.read_json(args.saved_path, orient="records")
199
+ result_df = pd.concat([saved_df, result_df], ignore_index=True)
200
+ result_df.to_json(args.saved_path, orient="records", indent=4, force_ascii=False)
201
+ logger.info(f"Save result to {args.saved_path}.")
202
+
203
+ result_dict = {args.caption_column: []}
204
+ if args.video_path_column is not None:
205
+ result_dict = {args.video_path_column: [], args.caption_column: []}
206
+
207
+ if len(result_dict[args.caption_column]) > 0:
208
+ result_df = pd.DataFrame(result_dict)
209
+ if args.saved_path.endswith(".csv"):
210
+ header = True if not os.path.exists(args.saved_path) else False
211
+ result_df.to_csv(args.saved_path, header=header, index=False, mode="a")
212
+ elif args.saved_path.endswith(".jsonl"):
213
+ result_df.to_json(args.saved_path, orient="records", lines=True, mode="a")
214
+ elif args.saved_path.endswith(".json"):
215
+ # Append is not supported.
216
+ if os.path.exists(args.saved_path):
217
+ saved_df = pd.read_json(args.saved_path, orient="records")
218
+ result_df = pd.concat([saved_df, result_df], ignore_index=True)
219
+ result_df.to_json(args.saved_path, orient="records", indent=4, force_ascii=False)
220
+ logger.info(f"Save the final result to {args.saved_path}.")
221
+
222
+
223
+ if __name__ == "__main__":
224
+ main()
cogvideox/video_caption/compute_motion_score.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ast
2
+ import argparse
3
+ import gc
4
+ import os
5
+ from contextlib import contextmanager
6
+ from pathlib import Path
7
+
8
+ import cv2
9
+ import numpy as np
10
+ import pandas as pd
11
+ from joblib import Parallel, delayed
12
+ from natsort import natsorted
13
+ from tqdm import tqdm
14
+
15
+ from utils.logger import logger
16
+ from utils.filter import filter
17
+
18
+
19
+ @contextmanager
20
+ def VideoCapture(video_path):
21
+ cap = cv2.VideoCapture(video_path)
22
+ try:
23
+ yield cap
24
+ finally:
25
+ cap.release()
26
+ del cap
27
+ gc.collect()
28
+
29
+
30
+ def compute_motion_score(video_path):
31
+ video_motion_scores = []
32
+ sampling_fps = 2
33
+
34
+ try:
35
+ with VideoCapture(video_path) as cap:
36
+ fps = cap.get(cv2.CAP_PROP_FPS)
37
+ valid_fps = min(max(sampling_fps, 1), fps)
38
+ frame_interval = int(fps / valid_fps)
39
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
40
+
41
+ # if cannot get the second frame, use the last one
42
+ frame_interval = min(frame_interval, total_frames - 1)
43
+
44
+ prev_frame = None
45
+ frame_count = -1
46
+ while cap.isOpened():
47
+ ret, frame = cap.read()
48
+ frame_count += 1
49
+
50
+ if not ret:
51
+ break
52
+
53
+ # skip middle frames
54
+ if frame_count % frame_interval != 0:
55
+ continue
56
+
57
+ gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
58
+ if prev_frame is None:
59
+ prev_frame = gray_frame
60
+ continue
61
+
62
+ flow = cv2.calcOpticalFlowFarneback(
63
+ prev_frame,
64
+ gray_frame,
65
+ None,
66
+ pyr_scale=0.5,
67
+ levels=3,
68
+ winsize=15,
69
+ iterations=3,
70
+ poly_n=5,
71
+ poly_sigma=1.2,
72
+ flags=0,
73
+ )
74
+ mag, _ = cv2.cartToPolar(flow[..., 0], flow[..., 1])
75
+ frame_motion_score = np.mean(mag)
76
+ video_motion_scores.append(frame_motion_score)
77
+ prev_frame = gray_frame
78
+
79
+ video_meta_info = {
80
+ "video_path": Path(video_path).name,
81
+ "motion_score": round(float(np.mean(video_motion_scores)), 5),
82
+ }
83
+ return video_meta_info
84
+
85
+ except Exception as e:
86
+ print(f"Compute motion score for video {video_path} with error: {e}.")
87
+
88
+
89
+ def parse_args():
90
+ parser = argparse.ArgumentParser(description="Compute the motion score of the videos.")
91
+ parser.add_argument("--video_folder", type=str, default="", help="The video folder.")
92
+ parser.add_argument(
93
+ "--video_metadata_path", type=str, default=None, help="The path to the video dataset metadata (csv/jsonl)."
94
+ )
95
+ parser.add_argument(
96
+ "--video_path_column",
97
+ type=str,
98
+ default="video_path",
99
+ help="The column contains the video path (an absolute path or a relative path w.r.t the video_folder).",
100
+ )
101
+ parser.add_argument("--saved_path", type=str, required=True, help="The save path to the output results (csv/jsonl).")
102
+ parser.add_argument("--saved_freq", type=int, default=100, help="The frequency to save the output results.")
103
+ parser.add_argument("--n_jobs", type=int, default=1, help="The number of concurrent processes.")
104
+
105
+ parser.add_argument(
106
+ "--basic_metadata_path", type=str, default=None, help="The path to the basic metadata (csv/jsonl)."
107
+ )
108
+ parser.add_argument("--min_resolution", type=float, default=0, help="The resolution threshold.")
109
+ parser.add_argument("--min_duration", type=float, default=-1, help="The minimum duration.")
110
+ parser.add_argument("--max_duration", type=float, default=-1, help="The maximum duration.")
111
+ parser.add_argument(
112
+ "--asethetic_score_metadata_path", type=str, default=None, help="The path to the video quality metadata (csv/jsonl)."
113
+ )
114
+ parser.add_argument("--min_asethetic_score", type=float, default=4.0, help="The asethetic score threshold.")
115
+ parser.add_argument(
116
+ "--asethetic_score_siglip_metadata_path", type=str, default=None, help="The path to the video quality metadata (csv/jsonl)."
117
+ )
118
+ parser.add_argument("--min_asethetic_score_siglip", type=float, default=4.0, help="The asethetic score (SigLIP) threshold.")
119
+ parser.add_argument(
120
+ "--text_score_metadata_path", type=str, default=None, help="The path to the video text score metadata (csv/jsonl)."
121
+ )
122
+ parser.add_argument("--min_text_score", type=float, default=0.02, help="The text threshold.")
123
+
124
+ args = parser.parse_args()
125
+ return args
126
+
127
+
128
+ def main():
129
+ args = parse_args()
130
+
131
+ if args.video_metadata_path.endswith(".csv"):
132
+ video_metadata_df = pd.read_csv(args.video_metadata_path)
133
+ elif args.video_metadata_path.endswith(".jsonl"):
134
+ video_metadata_df = pd.read_json(args.video_metadata_path, lines=True)
135
+ else:
136
+ raise ValueError("The video_metadata_path must end with .csv or .jsonl.")
137
+ video_path_list = video_metadata_df[args.video_path_column].tolist()
138
+
139
+ if not (args.saved_path.endswith(".csv") or args.saved_path.endswith(".jsonl")):
140
+ raise ValueError("The saved_path must end with .csv or .jsonl.")
141
+
142
+ if os.path.exists(args.saved_path):
143
+ if args.saved_path.endswith(".csv"):
144
+ saved_metadata_df = pd.read_csv(args.saved_path)
145
+ elif args.saved_path.endswith(".jsonl"):
146
+ saved_metadata_df = pd.read_json(args.saved_path, lines=True)
147
+ saved_video_path_list = saved_metadata_df[args.video_path_column].tolist()
148
+ video_path_list = list(set(video_path_list).difference(set(saved_video_path_list)))
149
+ logger.info(f"Resume from {args.saved_path}: {len(saved_video_path_list)} processed and {len(video_path_list)} to be processed.")
150
+
151
+ video_path_list = filter(
152
+ video_path_list,
153
+ basic_metadata_path=args.basic_metadata_path,
154
+ min_resolution=args.min_resolution,
155
+ min_duration=args.min_duration,
156
+ max_duration=args.max_duration,
157
+ asethetic_score_metadata_path=args.asethetic_score_metadata_path,
158
+ min_asethetic_score=args.min_asethetic_score,
159
+ asethetic_score_siglip_metadata_path=args.asethetic_score_siglip_metadata_path,
160
+ min_asethetic_score_siglip=args.min_asethetic_score_siglip,
161
+ text_score_metadata_path=args.text_score_metadata_path,
162
+ min_text_score=args.min_text_score,
163
+ )
164
+ video_path_list = [os.path.join(args.video_folder, video_path) for video_path in video_path_list]
165
+ # Sorting to guarantee the same result for each process.
166
+ video_path_list = natsorted(video_path_list)
167
+
168
+ for i in tqdm(range(0, len(video_path_list), args.saved_freq)):
169
+ result_list = Parallel(n_jobs=args.n_jobs)(
170
+ delayed(compute_motion_score)(video_path) for video_path in tqdm(video_path_list[i: i + args.saved_freq])
171
+ )
172
+ result_list = [result for result in result_list if result is not None]
173
+ if len(result_list) == 0:
174
+ continue
175
+
176
+ result_df = pd.DataFrame(result_list)
177
+ if args.saved_path.endswith(".csv"):
178
+ header = False if os.path.exists(args.saved_path) else True
179
+ result_df.to_csv(args.saved_path, header=header, index=False, mode="a")
180
+ elif args.saved_path.endswith(".jsonl"):
181
+ result_df.to_json(args.saved_path, orient="records", lines=True, mode="a", force_ascii=False)
182
+ logger.info(f"Save result to {args.saved_path}.")
183
+
184
+
185
+ if __name__ == "__main__":
186
+ main()
cogvideox/video_caption/compute_text_score.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import easyocr
6
+ import numpy as np
7
+ import pandas as pd
8
+ from accelerate import PartialState
9
+ from accelerate.utils import gather_object
10
+ from natsort import natsorted
11
+ from tqdm import tqdm
12
+ from torchvision.datasets.utils import download_url
13
+
14
+ from utils.logger import logger
15
+ from utils.video_utils import extract_frames
16
+ from utils.filter import filter
17
+
18
+
19
+ def init_ocr_reader(root: str = "~/.cache/easyocr", device: str = "gpu"):
20
+ root = os.path.expanduser(root)
21
+ if not os.path.exists(root):
22
+ os.makedirs(root)
23
+ download_url(
24
+ "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/easyanimate/video_caption/easyocr/craft_mlt_25k.pth",
25
+ root,
26
+ filename="craft_mlt_25k.pth",
27
+ md5="2f8227d2def4037cdb3b34389dcf9ec1",
28
+ )
29
+ ocr_reader = easyocr.Reader(
30
+ lang_list=["en", "ch_sim"],
31
+ gpu=device,
32
+ recognizer=False,
33
+ verbose=False,
34
+ model_storage_directory=root,
35
+ )
36
+
37
+ return ocr_reader
38
+
39
+
40
+ def triangle_area(p1, p2, p3):
41
+ """Compute the triangle area according to its coordinates.
42
+ """
43
+ x1, y1 = p1
44
+ x2, y2 = p2
45
+ x3, y3 = p3
46
+ tri_area = 0.5 * np.abs(x1 * y2 + x2 * y3 + x3 * y1 - x2 * y1 - x3 * y2 - x1 * y3)
47
+ return tri_area
48
+
49
+
50
+ def compute_text_score(video_path, ocr_reader):
51
+ _, images = extract_frames(video_path, sample_method="mid")
52
+ images = [np.array(image) for image in images]
53
+
54
+ frame_ocr_area_ratios = []
55
+ for image in images:
56
+ # horizontal detected results and free-form detected
57
+ horizontal_list, free_list = ocr_reader.detect(np.asarray(image))
58
+ width, height = image.shape[0], image.shape[1]
59
+
60
+ total_area = width * height
61
+ # rectangles
62
+ rect_area = 0
63
+ for xmin, xmax, ymin, ymax in horizontal_list[0]:
64
+ if xmax < xmin or ymax < ymin:
65
+ continue
66
+ rect_area += (xmax - xmin) * (ymax - ymin)
67
+ # free-form
68
+ quad_area = 0
69
+ try:
70
+ for points in free_list[0]:
71
+ triangle1 = points[:3]
72
+ quad_area += triangle_area(*triangle1)
73
+ triangle2 = points[3:] + [points[0]]
74
+ quad_area += triangle_area(*triangle2)
75
+ except:
76
+ quad_area = 0
77
+ text_area = rect_area + quad_area
78
+
79
+ frame_ocr_area_ratios.append(text_area / total_area)
80
+
81
+ video_meta_info = {
82
+ "video_path": Path(video_path).name,
83
+ "text_score": round(np.mean(frame_ocr_area_ratios), 5),
84
+ }
85
+
86
+ return video_meta_info
87
+
88
+
89
+ def parse_args():
90
+ parser = argparse.ArgumentParser(description="Compute the text score of the middle frame in the videos.")
91
+ parser.add_argument("--video_folder", type=str, default="", help="The video folder.")
92
+ parser.add_argument(
93
+ "--video_metadata_path", type=str, default=None, help="The path to the video dataset metadata (csv/jsonl)."
94
+ )
95
+ parser.add_argument(
96
+ "--video_path_column",
97
+ type=str,
98
+ default="video_path",
99
+ help="The column contains the video path (an absolute path or a relative path w.r.t the video_folder).",
100
+ )
101
+ parser.add_argument("--saved_path", type=str, required=True, help="The save path to the output results (csv/jsonl).")
102
+ parser.add_argument("--saved_freq", type=int, default=100, help="The frequency to save the output results.")
103
+
104
+ parser.add_argument(
105
+ "--basic_metadata_path", type=str, default=None, help="The path to the basic metadata (csv/jsonl)."
106
+ )
107
+ parser.add_argument("--min_resolution", type=float, default=0, help="The resolution threshold.")
108
+ parser.add_argument("--min_duration", type=float, default=-1, help="The minimum duration.")
109
+ parser.add_argument("--max_duration", type=float, default=-1, help="The maximum duration.")
110
+ parser.add_argument(
111
+ "--asethetic_score_metadata_path", type=str, default=None, help="The path to the video quality metadata (csv/jsonl)."
112
+ )
113
+ parser.add_argument("--min_asethetic_score", type=float, default=4.0, help="The asethetic score threshold.")
114
+ parser.add_argument(
115
+ "--asethetic_score_siglip_metadata_path", type=str, default=None, help="The path to the video quality metadata (csv/jsonl)."
116
+ )
117
+ parser.add_argument("--min_asethetic_score_siglip", type=float, default=4.0, help="The asethetic score (SigLIP) threshold.")
118
+ parser.add_argument(
119
+ "--motion_score_metadata_path", type=str, default=None, help="The path to the video motion score metadata (csv/jsonl)."
120
+ )
121
+ parser.add_argument("--min_motion_score", type=float, default=2, help="The motion threshold.")
122
+
123
+ args = parser.parse_args()
124
+ return args
125
+
126
+
127
+ def main():
128
+ args = parse_args()
129
+
130
+ if args.video_metadata_path.endswith(".csv"):
131
+ video_metadata_df = pd.read_csv(args.video_metadata_path)
132
+ elif args.video_metadata_path.endswith(".jsonl"):
133
+ video_metadata_df = pd.read_json(args.video_metadata_path, lines=True)
134
+ else:
135
+ raise ValueError("The video_metadata_path must end with .csv or .jsonl.")
136
+ video_path_list = video_metadata_df[args.video_path_column].tolist()
137
+
138
+ if not (args.saved_path.endswith(".csv") or args.saved_path.endswith(".jsonl")):
139
+ raise ValueError("The saved_path must end with .csv or .jsonl.")
140
+
141
+ if os.path.exists(args.saved_path):
142
+ if args.saved_path.endswith(".csv"):
143
+ saved_metadata_df = pd.read_csv(args.saved_path)
144
+ elif args.saved_path.endswith(".jsonl"):
145
+ saved_metadata_df = pd.read_json(args.saved_path, lines=True)
146
+ saved_video_path_list = saved_metadata_df[args.video_path_column].tolist()
147
+ video_path_list = list(set(video_path_list).difference(set(saved_video_path_list)))
148
+ logger.info(f"Resume from {args.saved_path}: {len(saved_video_path_list)} processed and {len(video_path_list)} to be processed.")
149
+
150
+ video_path_list = filter(
151
+ video_path_list,
152
+ basic_metadata_path=args.basic_metadata_path,
153
+ min_resolution=args.min_resolution,
154
+ min_duration=args.min_duration,
155
+ max_duration=args.max_duration,
156
+ asethetic_score_metadata_path=args.asethetic_score_metadata_path,
157
+ min_asethetic_score=args.min_asethetic_score,
158
+ asethetic_score_siglip_metadata_path=args.asethetic_score_siglip_metadata_path,
159
+ min_asethetic_score_siglip=args.min_asethetic_score_siglip,
160
+ motion_score_metadata_path=args.motion_score_metadata_path,
161
+ min_motion_score=args.min_motion_score,
162
+ )
163
+ video_path_list = [os.path.join(args.video_folder, video_path) for video_path in video_path_list]
164
+ # Sorting to guarantee the same result for each process.
165
+ video_path_list = natsorted(video_path_list)
166
+
167
+ state = PartialState()
168
+ if state.is_main_process:
169
+ # Check if the model is downloaded in the main process.
170
+ ocr_reader = init_ocr_reader(device="cpu")
171
+ state.wait_for_everyone()
172
+ ocr_reader = init_ocr_reader(device=state.device)
173
+
174
+ index = len(video_path_list) - len(video_path_list) % state.num_processes
175
+ # Avoid the NCCL timeout in the final gather operation.
176
+ logger.info(f"Drop {len(video_path_list) % state.num_processes} videos to ensure each process handles the same number of videos.")
177
+ video_path_list = video_path_list[:index]
178
+ logger.info(f"{len(video_path_list)} videos are to be processed.")
179
+
180
+ result_list = []
181
+ with state.split_between_processes(video_path_list) as splitted_video_path_list:
182
+ for i, video_path in enumerate(tqdm(splitted_video_path_list)):
183
+ try:
184
+ video_meta_info = compute_text_score(video_path, ocr_reader)
185
+ result_list.append(video_meta_info)
186
+ except Exception as e:
187
+ logger.warning(f"Compute text score for video {video_path} with error: {e}.")
188
+ if i != 0 and i % args.saved_freq == 0:
189
+ state.wait_for_everyone()
190
+ gathered_result_list = gather_object(result_list)
191
+ if state.is_main_process and len(gathered_result_list) != 0:
192
+ result_df = pd.DataFrame(gathered_result_list)
193
+ if args.saved_path.endswith(".csv"):
194
+ header = False if os.path.exists(args.saved_path) else True
195
+ result_df.to_csv(args.saved_path, header=header, index=False, mode="a")
196
+ elif args.saved_path.endswith(".jsonl"):
197
+ result_df.to_json(args.saved_path, orient="records", lines=True, mode="a", force_ascii=False)
198
+ logger.info(f"Save result to {args.saved_path}.")
199
+ result_list = []
200
+
201
+ state.wait_for_everyone()
202
+ gathered_result_list = gather_object(result_list)
203
+ if state.is_main_process and len(gathered_result_list) != 0:
204
+ result_df = pd.DataFrame(gathered_result_list)
205
+ if args.saved_path.endswith(".csv"):
206
+ header = False if os.path.exists(args.saved_path) else True
207
+ result_df.to_csv(args.saved_path, header=header, index=False, mode="a")
208
+ elif args.saved_path.endswith(".jsonl"):
209
+ result_df.to_json(args.saved_path, orient="records", lines=True, mode="a", force_ascii=False)
210
+ logger.info(f"Save the final result to {args.saved_path}.")
211
+
212
+
213
+ if __name__ == "__main__":
214
+ main()
cogvideox/video_caption/compute_video_quality.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+
4
+ import pandas as pd
5
+ from accelerate import PartialState
6
+ from accelerate.utils import gather_object
7
+ from natsort import index_natsorted
8
+ from tqdm import tqdm
9
+ from torch.utils.data import DataLoader
10
+
11
+ import utils.image_evaluator as image_evaluator
12
+ import utils.video_evaluator as video_evaluator
13
+ from utils.logger import logger
14
+ from utils.video_dataset import VideoDataset, collate_fn
15
+
16
+
17
+ def parse_args():
18
+ parser = argparse.ArgumentParser(description="Compute scores of uniform sampled frames from videos.")
19
+ parser.add_argument(
20
+ "--video_metadata_path", type=str, default=None, help="The path to the video dataset metadata (csv/jsonl)."
21
+ )
22
+ parser.add_argument(
23
+ "--video_path_column",
24
+ type=str,
25
+ default="video_path",
26
+ help="The column contains the video path (an absolute path or a relative path w.r.t the video_folder).",
27
+ )
28
+ parser.add_argument("--video_folder", type=str, default="", help="The video folder.")
29
+ parser.add_argument(
30
+ "--caption_column",
31
+ type=str,
32
+ default=None,
33
+ help="The column contains the caption.",
34
+ )
35
+ parser.add_argument(
36
+ "--frame_sample_method",
37
+ type=str,
38
+ choices=["mid", "uniform", "image"],
39
+ default="uniform",
40
+ )
41
+ parser.add_argument(
42
+ "--num_sampled_frames",
43
+ type=int,
44
+ default=8,
45
+ help="num_sampled_frames",
46
+ )
47
+ parser.add_argument("--metrics", nargs="+", type=str, required=True, help="The evaluation metric(s) for generated images.")
48
+ parser.add_argument(
49
+ "--batch_size",
50
+ type=int,
51
+ default=10,
52
+ required=False,
53
+ help="The batch size for the video dataset.",
54
+ )
55
+ parser.add_argument(
56
+ "--num_workers",
57
+ type=int,
58
+ default=4,
59
+ required=False,
60
+ help="The number of workers for the video dataset.",
61
+ )
62
+ parser.add_argument("--saved_path", type=str, required=True, help="The save path to the output results (csv/jsonl).")
63
+ parser.add_argument("--saved_freq", type=int, default=1000, help="The frequency to save the output results.")
64
+
65
+ args = parser.parse_args()
66
+ return args
67
+
68
+
69
+ def main():
70
+ args = parse_args()
71
+
72
+ if args.video_metadata_path.endswith(".csv"):
73
+ video_metadata_df = pd.read_csv(args.video_metadata_path)
74
+ elif args.video_metadata_path.endswith(".jsonl"):
75
+ video_metadata_df = pd.read_json(args.video_metadata_path, lines=True)
76
+ else:
77
+ raise ValueError("The video_metadata_path must end with .csv or .jsonl.")
78
+
79
+ if not (args.saved_path.endswith(".csv") or args.saved_path.endswith(".jsonl")):
80
+ raise ValueError("The saved_path must end with .csv or .jsonl.")
81
+
82
+ if os.path.exists(args.saved_path):
83
+ if args.saved_path.endswith(".csv"):
84
+ saved_metadata_df = pd.read_csv(args.saved_path)
85
+ elif args.saved_path.endswith(".jsonl"):
86
+ saved_metadata_df = pd.read_json(args.saved_path, lines=True)
87
+
88
+ # Filter out the unprocessed video-caption pairs by setting the indicator=True.
89
+ merged_df = video_metadata_df.merge(saved_metadata_df, on="video_path", how="outer", indicator=True)
90
+ video_metadata_df = merged_df[merged_df["_merge"] == "left_only"]
91
+ # Sorting to guarantee the same result for each process.
92
+ video_metadata_df = video_metadata_df.iloc[index_natsorted(video_metadata_df["video_path"])].reset_index(drop=True)
93
+ if args.caption_column is None:
94
+ video_metadata_df = video_metadata_df[[args.video_path_column]]
95
+ else:
96
+ video_metadata_df = video_metadata_df[[args.video_path_column, args.caption_column + "_x"]]
97
+ video_metadata_df.rename(columns={args.caption_column + "_x": args.caption_column}, inplace=True)
98
+ logger.info(f"Resume from {args.saved_path}: {len(saved_metadata_df)} processed and {len(video_metadata_df)} to be processed.")
99
+
100
+ state = PartialState()
101
+ metric_fns = []
102
+ for metric in args.metrics:
103
+ if hasattr(image_evaluator, metric): # frame-wise
104
+ if state.is_main_process:
105
+ logger.info("Initializing frame-wise evaluator metrics...")
106
+ # Check if the model is downloaded in the main process.
107
+ getattr(image_evaluator, metric)(device="cpu")
108
+ state.wait_for_everyone()
109
+ metric_fns.append(getattr(image_evaluator, metric)(device=state.device))
110
+ else: # video-wise
111
+ if state.is_main_process:
112
+ logger.info("Initializing video-wise evaluator metrics...")
113
+ # Check if the model is downloaded in the main process.
114
+ getattr(video_evaluator, metric)(device="cpu")
115
+ state.wait_for_everyone()
116
+ metric_fns.append(getattr(video_evaluator, metric)(device=state.device))
117
+
118
+ result_dict = {args.video_path_column: [], "sample_frame_idx": []}
119
+ for metric in metric_fns:
120
+ result_dict[str(metric)] = []
121
+ if args.caption_column is not None:
122
+ result_dict[args.caption_column] = []
123
+
124
+ if args.frame_sample_method == "image":
125
+ logger.warning("Set args.num_sampled_frames to 1 since args.frame_sample_method is image.")
126
+ args.num_sampled_frames = 1
127
+
128
+ index = len(video_metadata_df) - len(video_metadata_df) % state.num_processes
129
+ # Avoid the NCCL timeout in the final gather operation.
130
+ logger.info(f"Drop {len(video_metadata_df) % state.num_processes} videos to ensure each process handles the same number of videos.")
131
+ video_metadata_df = video_metadata_df.iloc[:index]
132
+ logger.info(f"{len(video_metadata_df)} videos are to be processed.")
133
+
134
+ video_metadata_list = video_metadata_df.to_dict(orient='list')
135
+ with state.split_between_processes(video_metadata_list) as splitted_video_metadata:
136
+ video_dataset = VideoDataset(
137
+ dataset_inputs=splitted_video_metadata,
138
+ video_folder=args.video_folder,
139
+ text_column=args.caption_column,
140
+ sample_method=args.frame_sample_method,
141
+ num_sampled_frames=args.num_sampled_frames
142
+ )
143
+ video_loader = DataLoader(video_dataset, batch_size=args.batch_size, num_workers=args.num_workers, collate_fn=collate_fn)
144
+
145
+ for idx, batch in enumerate(tqdm(video_loader)):
146
+ if len(batch) > 0:
147
+ batch_video_path = batch["path"]
148
+ result_dict["sample_frame_idx"].extend(batch["sampled_frame_idx"])
149
+ batch_frame = batch["sampled_frame"] # [batch_size, num_sampled_frames, H, W, C]
150
+ batch_caption = None
151
+ if args.caption_column is not None:
152
+ batch_caption = batch["text"]
153
+ result_dict["caption"].extend(batch_caption)
154
+ # Compute the quality.
155
+ for i, metric in enumerate(args.metrics):
156
+ quality_scores = metric_fns[i](batch_frame, batch_caption)
157
+ if isinstance(quality_scores[0], list): # frame-wise
158
+ quality_scores = [
159
+ [round(score, 5) for score in inner_list]
160
+ for inner_list in quality_scores
161
+ ]
162
+ else: # video-wise
163
+ quality_scores = [round(score, 5) for score in quality_scores]
164
+ result_dict[str(metric_fns[i])].extend(quality_scores)
165
+
166
+ if args.video_folder == "":
167
+ saved_video_path_list = batch_video_path
168
+ else:
169
+ saved_video_path_list = [os.path.relpath(video_path, args.video_folder) for video_path in batch_video_path]
170
+ result_dict[args.video_path_column].extend(saved_video_path_list)
171
+
172
+ # Save the metadata in the main process every saved_freq.
173
+ if (idx != 0) and (idx % args.saved_freq == 0):
174
+ state.wait_for_everyone()
175
+ gathered_result_dict = {k: gather_object(v) for k, v in result_dict.items()}
176
+ if state.is_main_process and len(gathered_result_dict[args.video_path_column]) != 0:
177
+ result_df = pd.DataFrame(gathered_result_dict)
178
+ if args.saved_path.endswith(".csv"):
179
+ header = False if os.path.exists(args.saved_path) else True
180
+ result_df.to_csv(args.saved_path, header=header, index=False, mode="a")
181
+ elif args.saved_path.endswith(".jsonl"):
182
+ result_df.to_json(args.saved_path, orient="records", lines=True, mode="a", force_ascii=False)
183
+ logger.info(f"Save result to {args.saved_path}.")
184
+ for k in result_dict.keys():
185
+ result_dict[k] = []
186
+
187
+ # Wait for all processes to finish and gather the final result.
188
+ state.wait_for_everyone()
189
+ gathered_result_dict = {k: gather_object(v) for k, v in result_dict.items()}
190
+ # Save the metadata in the main process.
191
+ if state.is_main_process and len(gathered_result_dict[args.video_path_column]) != 0:
192
+ result_df = pd.DataFrame(gathered_result_dict)
193
+ if args.saved_path.endswith(".csv"):
194
+ header = False if os.path.exists(args.saved_path) else True
195
+ result_df.to_csv(args.saved_path, header=header, index=False, mode="a")
196
+ elif args.saved_path.endswith(".jsonl"):
197
+ result_df.to_json(args.saved_path, orient="records", lines=True, mode="a", force_ascii=False)
198
+ logger.info(f"Save the final result to {args.saved_path}.")
199
+
200
+ if __name__ == "__main__":
201
+ main()
cogvideox/video_caption/cutscene_detect.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ from copy import deepcopy
4
+ from pathlib import Path
5
+ from multiprocessing import Pool
6
+
7
+ import pandas as pd
8
+ from scenedetect import open_video, SceneManager
9
+ from scenedetect.detectors import ContentDetector
10
+ from tqdm import tqdm
11
+
12
+ from utils.logger import logger
13
+
14
+
15
+ def cutscene_detection_star(args):
16
+ return cutscene_detection(*args)
17
+
18
+
19
+ def cutscene_detection(video_path, saved_path, cutscene_threshold=27, min_scene_len=15):
20
+ try:
21
+ if os.path.exists(saved_path):
22
+ logger.info(f"{video_path} has been processed.")
23
+ return
24
+ # Use PyAV as the backend to avoid (to some exent) containing the last frame of the previous scene.
25
+ # https://github.com/Breakthrough/PySceneDetect/issues/279#issuecomment-2152596761.
26
+ video = open_video(video_path, backend="pyav")
27
+ frame_rate, frame_size = video.frame_rate, video.frame_size
28
+ duration = deepcopy(video.duration)
29
+
30
+ frame_points, frame_timecode = [], {}
31
+ scene_manager = SceneManager()
32
+ scene_manager.add_detector(
33
+ # [ContentDetector, ThresholdDetector, AdaptiveDetector]
34
+ ContentDetector(threshold=cutscene_threshold, min_scene_len=min_scene_len)
35
+ )
36
+ scene_manager.detect_scenes(video, show_progress=False)
37
+ scene_list = scene_manager.get_scene_list()
38
+ for scene in scene_list:
39
+ for frame_time_code in scene:
40
+ frame_index = frame_time_code.get_frames()
41
+ if frame_index not in frame_points:
42
+ frame_points.append(frame_index)
43
+ frame_timecode[frame_index] = frame_time_code
44
+
45
+ del video, scene_manager
46
+
47
+ frame_points = sorted(frame_points)
48
+ output_scene_list = []
49
+ for idx in range(len(frame_points) - 1):
50
+ output_scene_list.append((frame_timecode[frame_points[idx]], frame_timecode[frame_points[idx+1]]))
51
+
52
+ timecode_list = [(frame_timecode_tuple[0].get_timecode(), frame_timecode_tuple[1].get_timecode()) for frame_timecode_tuple in output_scene_list]
53
+ meta_scene = [{
54
+ "video_path": Path(video_path).name,
55
+ "timecode_list": timecode_list,
56
+ "fram_rate": frame_rate,
57
+ "frame_size": frame_size,
58
+ "duration": str(duration) # __repr__
59
+ }]
60
+ pd.DataFrame(meta_scene).to_json(saved_path, orient="records", lines=True)
61
+ except Exception as e:
62
+ logger.warning(f"Cutscene detection with {video_path} failed. Error is: {e}.")
63
+
64
+
65
+ if __name__ == "__main__":
66
+ parser = argparse.ArgumentParser(description="Cutscene Detection")
67
+ parser.add_argument(
68
+ "--video_metadata_path", type=str, required=True, help="The path to the video dataset metadata (csv/jsonl)."
69
+ )
70
+ parser.add_argument(
71
+ "--video_path_column",
72
+ type=str,
73
+ default="video_path",
74
+ help="The column contains the video path (an absolute path or a relative path w.r.t the video_folder).",
75
+ )
76
+ parser.add_argument("--video_folder", type=str, default="", help="The video folder.")
77
+ parser.add_argument("--saved_folder", type=str, required=True, help="The save path to the output results (csv/jsonl).")
78
+ parser.add_argument("--n_jobs", type=int, default=1, help="The number of processes.")
79
+
80
+ args = parser.parse_args()
81
+
82
+ metadata_df = pd.read_json(args.video_metadata_path, lines=True)
83
+ video_path_list = metadata_df[args.video_path_column].tolist()
84
+ video_path_list = [os.path.join(args.video_folder, video_path) for video_path in video_path_list]
85
+
86
+ if not os.path.exists(args.saved_folder):
87
+ os.makedirs(args.saved_folder, exist_ok=True)
88
+ # The glob can be slow when there are many small jsonl files.
89
+ saved_path_list = [os.path.join(args.saved_folder, Path(video_path).stem + ".jsonl") for video_path in video_path_list]
90
+ args_list = [
91
+ (video_path, saved_path)
92
+ for video_path, saved_path in zip(video_path_list, saved_path_list)
93
+ ]
94
+ # Since the length of the video is not uniform, the gather operation is not performed.
95
+ # We need to run easyanimate/video_caption/utils/gather_jsonl.py after the program finised.
96
+ with Pool(args.n_jobs) as pool:
97
+ results = list(tqdm(pool.imap(cutscene_detection_star, args_list), total=len(video_path_list)))
cogvideox/video_caption/filter_meta_train.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+
4
+ import pandas as pd
5
+ from natsort import natsorted
6
+
7
+ from utils.logger import logger
8
+ from utils.filter import filter
9
+
10
+
11
+ def parse_args():
12
+ parser = argparse.ArgumentParser()
13
+ parser.add_argument(
14
+ "--caption_metadata_path", type=str, default=None, help="The path to the video quality metadata (csv/jsonl)."
15
+ )
16
+ parser.add_argument(
17
+ "--video_path_column",
18
+ type=str,
19
+ default="video_path",
20
+ help="The column contains the video path (an absolute path or a relative path w.r.t the video_folder).",
21
+ )
22
+ parser.add_argument("--video_folder", type=str, default="", help="The video folder.")
23
+ parser.add_argument(
24
+ "--basic_metadata_path", type=str, default=None, help="The path to the basic metadata (csv/jsonl)."
25
+ )
26
+ parser.add_argument("--min_resolution", type=float, default=720*1280, help="The resolution threshold.")
27
+ parser.add_argument("--min_duration", type=float, default=-1, help="The minimum duration.")
28
+ parser.add_argument("--max_duration", type=float, default=-1, help="The maximum duration.")
29
+ parser.add_argument(
30
+ "--asethetic_score_metadata_path", type=str, default=None, help="The path to the video quality metadata (csv/jsonl)."
31
+ )
32
+ parser.add_argument("--min_asethetic_score", type=float, default=4.0, help="The asethetic score threshold.")
33
+ parser.add_argument(
34
+ "--asethetic_score_siglip_metadata_path", type=str, default=None, help="The path to the video quality (SigLIP) metadata (csv/jsonl)."
35
+ )
36
+ parser.add_argument("--min_asethetic_score_siglip", type=float, default=4.0, help="The asethetic score (SigLIP) threshold.")
37
+ parser.add_argument(
38
+ "--text_score_metadata_path", type=str, default=None, help="The path to the video text score metadata (csv/jsonl)."
39
+ )
40
+ parser.add_argument("--min_text_score", type=float, default=0.02, help="The text threshold.")
41
+ parser.add_argument(
42
+ "--motion_score_metadata_path", type=str, default=None, help="The path to the video motion score metadata (csv/jsonl)."
43
+ )
44
+ parser.add_argument("--min_motion_score", type=float, default=2, help="The motion threshold.")
45
+ parser.add_argument(
46
+ "--videoclipxl_score_metadata_path", type=str, default=None, help="The path to the video-caption VideoCLIPXL score metadata (csv/jsonl)."
47
+ )
48
+ parser.add_argument("--min_videoclipxl_score", type=float, default=0.20, help="The VideoCLIPXL score threshold.")
49
+ parser.add_argument("--saved_path", type=str, required=True)
50
+
51
+ args = parser.parse_args()
52
+ return args
53
+
54
+
55
+ def main():
56
+ args = parse_args()
57
+
58
+ raw_caption_df = pd.read_json(args.caption_metadata_path, lines=True)
59
+ video_path_list = raw_caption_df[args.video_path_column].to_list()
60
+ filtered_video_path_list = filter(
61
+ video_path_list,
62
+ basic_metadata_path=args.basic_metadata_path,
63
+ min_resolution=args.min_resolution,
64
+ min_duration=args.min_duration,
65
+ max_duration=args.max_duration,
66
+ asethetic_score_metadata_path=args.asethetic_score_metadata_path,
67
+ min_asethetic_score=args.min_asethetic_score,
68
+ asethetic_score_siglip_metadata_path=args.asethetic_score_siglip_metadata_path,
69
+ min_asethetic_score_siglip=args.min_asethetic_score_siglip,
70
+ text_score_metadata_path=args.text_score_metadata_path,
71
+ min_text_score=args.min_text_score,
72
+ motion_score_metadata_path=args.motion_score_metadata_path,
73
+ min_motion_score=args.min_motion_score,
74
+ videoclipxl_score_metadata_path=args.videoclipxl_score_metadata_path,
75
+ min_videoclipxl_score=args.min_videoclipxl_score,
76
+ video_path_column=args.video_path_column
77
+ )
78
+ filtered_video_path_list = natsorted(filtered_video_path_list)
79
+ filtered_caption_df = raw_caption_df[raw_caption_df[args.video_path_column].isin(filtered_video_path_list)]
80
+ train_df = filtered_caption_df.rename(columns={"video_path": "file_path", "caption": "text"})
81
+ train_df["file_path"] = train_df["file_path"].map(lambda x: os.path.join(args.video_folder, x))
82
+ train_df["type"] = "video"
83
+ train_df.to_json(args.saved_path, orient="records", force_ascii=False, indent=2)
84
+ logger.info(f"The final train file with {len(train_df)} videos are saved to {args.saved_path}.")
85
+
86
+
87
+ if __name__ == "__main__":
88
+ main()
cogvideox/video_caption/package_patches/easyocr_detection_patched.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Modified from https://github.com/JaidedAI/EasyOCR/blob/803b907/easyocr/detection.py.
2
+ 1. Disable DataParallel.
3
+ """
4
+ import torch
5
+ import torch.backends.cudnn as cudnn
6
+ from torch.autograd import Variable
7
+ from PIL import Image
8
+ from collections import OrderedDict
9
+
10
+ import cv2
11
+ import numpy as np
12
+ from .craft_utils import getDetBoxes, adjustResultCoordinates
13
+ from .imgproc import resize_aspect_ratio, normalizeMeanVariance
14
+ from .craft import CRAFT
15
+
16
+ def copyStateDict(state_dict):
17
+ if list(state_dict.keys())[0].startswith("module"):
18
+ start_idx = 1
19
+ else:
20
+ start_idx = 0
21
+ new_state_dict = OrderedDict()
22
+ for k, v in state_dict.items():
23
+ name = ".".join(k.split(".")[start_idx:])
24
+ new_state_dict[name] = v
25
+ return new_state_dict
26
+
27
+ def test_net(canvas_size, mag_ratio, net, image, text_threshold, link_threshold, low_text, poly, device, estimate_num_chars=False):
28
+ if isinstance(image, np.ndarray) and len(image.shape) == 4: # image is batch of np arrays
29
+ image_arrs = image
30
+ else: # image is single numpy array
31
+ image_arrs = [image]
32
+
33
+ img_resized_list = []
34
+ # resize
35
+ for img in image_arrs:
36
+ img_resized, target_ratio, size_heatmap = resize_aspect_ratio(img, canvas_size,
37
+ interpolation=cv2.INTER_LINEAR,
38
+ mag_ratio=mag_ratio)
39
+ img_resized_list.append(img_resized)
40
+ ratio_h = ratio_w = 1 / target_ratio
41
+ # preprocessing
42
+ x = [np.transpose(normalizeMeanVariance(n_img), (2, 0, 1))
43
+ for n_img in img_resized_list]
44
+ x = torch.from_numpy(np.array(x))
45
+ x = x.to(device)
46
+
47
+ # forward pass
48
+ with torch.no_grad():
49
+ y, feature = net(x)
50
+
51
+ boxes_list, polys_list = [], []
52
+ for out in y:
53
+ # make score and link map
54
+ score_text = out[:, :, 0].cpu().data.numpy()
55
+ score_link = out[:, :, 1].cpu().data.numpy()
56
+
57
+ # Post-processing
58
+ boxes, polys, mapper = getDetBoxes(
59
+ score_text, score_link, text_threshold, link_threshold, low_text, poly, estimate_num_chars)
60
+
61
+ # coordinate adjustment
62
+ boxes = adjustResultCoordinates(boxes, ratio_w, ratio_h)
63
+ polys = adjustResultCoordinates(polys, ratio_w, ratio_h)
64
+ if estimate_num_chars:
65
+ boxes = list(boxes)
66
+ polys = list(polys)
67
+ for k in range(len(polys)):
68
+ if estimate_num_chars:
69
+ boxes[k] = (boxes[k], mapper[k])
70
+ if polys[k] is None:
71
+ polys[k] = boxes[k]
72
+ boxes_list.append(boxes)
73
+ polys_list.append(polys)
74
+
75
+ return boxes_list, polys_list
76
+
77
+ def get_detector(trained_model, device='cpu', quantize=True, cudnn_benchmark=False):
78
+ net = CRAFT()
79
+
80
+ if device == 'cpu':
81
+ net.load_state_dict(copyStateDict(torch.load(trained_model, map_location=device)))
82
+ if quantize:
83
+ try:
84
+ torch.quantization.quantize_dynamic(net, dtype=torch.qint8, inplace=True)
85
+ except:
86
+ pass
87
+ else:
88
+ net.load_state_dict(copyStateDict(torch.load(trained_model, map_location=device)))
89
+ # net = torch.nn.DataParallel(net).to(device)
90
+ net = net.to(device)
91
+ cudnn.benchmark = cudnn_benchmark
92
+
93
+ net.eval()
94
+ return net
95
+
96
+ def get_textbox(detector, image, canvas_size, mag_ratio, text_threshold, link_threshold, low_text, poly, device, optimal_num_chars=None, **kwargs):
97
+ result = []
98
+ estimate_num_chars = optimal_num_chars is not None
99
+ bboxes_list, polys_list = test_net(canvas_size, mag_ratio, detector,
100
+ image, text_threshold,
101
+ link_threshold, low_text, poly,
102
+ device, estimate_num_chars)
103
+ if estimate_num_chars:
104
+ polys_list = [[p for p, _ in sorted(polys, key=lambda x: abs(optimal_num_chars - x[1]))]
105
+ for polys in polys_list]
106
+
107
+ for polys in polys_list:
108
+ single_img_result = []
109
+ for i, box in enumerate(polys):
110
+ poly = np.array(box).astype(np.int32).reshape((-1))
111
+ single_img_result.append(poly)
112
+ result.append(single_img_result)
113
+
114
+ return result
cogvideox/video_caption/package_patches/vila_siglip_encoder_patched.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from https://github.com/NVlabs/VILA/blob/1c88211/llava/model/multimodal_encoder/siglip_encoder.py
2
+ # 1. Support transformers >= 4.36.2.
3
+ import torch
4
+ import transformers
5
+ from packaging import version
6
+ from transformers import AutoConfig, AutoModel, PretrainedConfig
7
+
8
+ from llava.model.multimodal_encoder.vision_encoder import VisionTower, VisionTowerS2
9
+
10
+ if version.parse(transformers.__version__) > version.parse("4.36.2"):
11
+ from transformers import SiglipImageProcessor, SiglipVisionConfig, SiglipVisionModel
12
+ else:
13
+ from .siglip import SiglipImageProcessor, SiglipVisionConfig, SiglipVisionModel
14
+
15
+
16
+ class SiglipVisionTower(VisionTower):
17
+ def __init__(self, model_name_or_path: str, config: PretrainedConfig, state_dict=None):
18
+ super().__init__(model_name_or_path, config)
19
+ self.image_processor = SiglipImageProcessor.from_pretrained(model_name_or_path)
20
+ self.vision_tower = SiglipVisionModel.from_pretrained(
21
+ # TODO(ligeng): why pass config here leading to errors?
22
+ model_name_or_path, torch_dtype=eval(config.model_dtype), state_dict=state_dict
23
+ )
24
+ self.is_loaded = True
25
+
26
+
27
+ class SiglipVisionTowerS2(VisionTowerS2):
28
+ def __init__(self, model_name_or_path: str, config: PretrainedConfig):
29
+ super().__init__(model_name_or_path, config)
30
+ self.image_processor = SiglipImageProcessor.from_pretrained(model_name_or_path)
31
+ self.vision_tower = SiglipVisionModel.from_pretrained(
32
+ model_name_or_path, torch_dtype=eval(config.model_dtype)
33
+ )
34
+
35
+ # Make sure it crops/resizes the image to the largest scale in self.scales to maintain high-res information
36
+ self.image_processor.size['height'] = self.image_processor.size['width'] = self.scales[-1]
37
+
38
+ self.is_loaded = True
39
+
40
+ if version.parse(transformers.__version__) <= version.parse("4.36.2"):
41
+ AutoConfig.register("siglip_vision_model", SiglipVisionConfig)
42
+ AutoModel.register(SiglipVisionConfig, SiglipVisionModel)
cogvideox/video_caption/prompt/beautiful_prompt.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ I will upload some brief prompt words to be used for AI-generated videos. Please expand these brief prompt words into a more detailed description to enhance the quality of the generated videos. The detailed description should include the main subject (person, object, animal, or none) actions and their attributes or status sequence, the background (the objects, location, weather, and time), the view shot and camera movement.
2
+ The final detailed description must not exceed 200 words. Output with the following json format:
3
+ {"detailed description": "your detailed description here"}
4
+
5
+ Here is an example:
6
+ brief prompt words: "A stylish woman in a black leather jacket, red dress, and boots walks confidently down a damp Tokyo street."
7
+ {"detailed description": "A stylish woman walks down a Tokyo street filled with warm glowing neon and animated city signage. She wears a black leather jacket, a long red dress, and black boots, and carries a black purse. She wears sunglasses and red lipstick. She walks confidently and casually. The street is damp and reflective, creating a mirror effect of the colorful lights. Many pedestrians walk about."}
8
+
9
+ Here are the brief prompt words:
cogvideox/video_caption/prompt/rewrite.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Please rewrite the video description to be useful for AI to re-generate the video, according to the following requirements
2
+ 1. Do not start with something similar to 'The video/scene/frame shows' or "In this video/scene/frame".
3
+ 2. Remove the subjective content deviates from describing the visual content of the video. For instance, a sentence like "It gives a feeling of ease and tranquility and makes people feel comfortable" is considered subjective.
4
+ 3. Remove the non-existent description that does not in the visual content of the video, For instance, a sentence like "There is no visible detail that could be used to identify the individual beyond what is shown." is considered as the non-existent description.
5
+ 4. Here are some examples of good descriptions: 1) A stylish woman walks down a Tokyo street filled with warm glowing neon and animated city signage. She wears a black leather jacket, a long red dress, and black boots, and carries a black purse. She wears sunglasses and red lipstick. She walks confidently and casually. The street is damp and reflective, creating a mirror effect of the colorful lights. Many pedestrians walk about. 2) A large orange octopus is seen resting on the bottom of the ocean floor, blending in with the sandy and rocky terrain. Its tentacles are spread out around its body, and its eyes are closed. The octopus is unaware of a king crab that is crawling towards it from behind a rock, its claws raised and ready to attack. The crab is brown and spiny, with long legs and antennae. The scene is captured from a wide angle, showing the vastness and depth of the ocean. The water is clear and blue, with rays of sunlight filtering through. The shot is sharp and crisp, with a high dynamic range. The octopus and the crab are in focus, while the background is slightly blurred, creating a depth of field effect.
6
+ 5. Output with the following json format:
7
+ {"rewritten description": "your rewritten description here"}
8
+
9
+ Here is the video description:
cogvideox/video_caption/requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ pandas>=2.0.0
2
+ easyocr==1.7.1
3
+ git+https://github.com/openai/CLIP.git
4
+ natsort
5
+ joblib
6
+ scenedetect
7
+ av
8
+ # https://github.com/NVlabs/VILA/issues/78#issuecomment-2195568292
9
+ numpy<2.0.0
cogvideox/video_caption/scripts/stage_1_video_splitting.sh ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ VIDEO_FOLDER="datasets/panda_70m/videos/data/"
2
+ META_FILE_PATH="datasets/panda_70m/videos/meta_file_info.jsonl"
3
+ SCENE_FOLDER="datasets/panda_70m/videos/meta_scene_info/"
4
+ SCENE_SAVED_PATH="datasets/panda_70m/videos/meta_scene_info.jsonl"
5
+ OUTPUT_FOLDER="datasets/panda_70m/videos_clips/data/"
6
+ RESOLUTION_THRESHOLD=$((512*512))
7
+
8
+ # Set the duration range of video clips.
9
+ export MIN_SECONDS=3
10
+ export MAX_SECONDS=10
11
+
12
+ # Save all video names in a video folder as a meta file.
13
+ python -m utils.get_meta_file \
14
+ --video_folder $VIDEO_FOLDER \
15
+ --saved_path $META_FILE_PATH
16
+
17
+ # Perform scene detection on the video dataset.
18
+ # Adjust the n_jobs parameter based on the actual number of CPU cores in the machine.
19
+ python cutscene_detect.py \
20
+ --video_metadata_path $META_FILE_PATH \
21
+ --video_folder $VIDEO_FOLDER \
22
+ --saved_folder $SCENE_FOLDER \
23
+ --n_jobs 32
24
+
25
+ # Gather all scene jsonl files to a single scene jsonl file.
26
+ # Adjust the n_jobs parameter based on the actual I/O speed in the machine.
27
+ python -m utils.gather_jsonl \
28
+ --meta_folder $SCENE_FOLDER \
29
+ --meta_file_path $SCENE_SAVED_PATH \
30
+ --n_jobs 64
31
+
32
+ # Perform video splitting filtered by the RESOLUTION_THRESHOLD.
33
+ # It consumes more CPU computing resources compared to the above operations.
34
+ python video_splitting.py \
35
+ --video_metadata_path $SCENE_SAVED_PATH \
36
+ --video_folder $VIDEO_FOLDER \
37
+ --output_folder $OUTPUT_FOLDER \
38
+ --n_jobs 16 \
39
+ --resolution_threshold $RESOLUTION_THRESHOLD
cogvideox/video_caption/scripts/stage_2_video_filtering.sh ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ META_FILE_PATH="datasets/panda_70m/videos_clips/data/meta_file_info.jsonl"
2
+ VIDEO_FOLDER="datasets/panda_70m/videos_clips/data/"
3
+ VIDEO_QUALITY_SAVED_PATH="datasets/panda_70m/videos_clips/meta_quality_info_siglip.jsonl"
4
+ MIN_ASETHETIC_SCORE_SIGLIP=4.0
5
+ TEXT_SAVED_PATH="datasets/panda_70m/videos_clips/meta_text_info.jsonl"
6
+ MIN_TEXT_SCORE=0.02
7
+ MOTION_SAVED_PATH="datasets/panda_70m/videos_clips/meta_motion_info.jsonl"
8
+
9
+ python -m utils.get_meta_file \
10
+ --video_folder $VIDEO_FOLDER \
11
+ --saved_path $META_FILE_PATH
12
+
13
+ # Get the asethetic score (SigLIP) of all videos
14
+ accelerate launch compute_video_quality.py \
15
+ --video_metadata_path $META_FILE_PATH \
16
+ --video_folder $VIDEO_FOLDER \
17
+ --metrics "AestheticScoreSigLIP" \
18
+ --frame_sample_method uniform \
19
+ --num_sampled_frames 4 \
20
+ --saved_freq 10 \
21
+ --saved_path $VIDEO_QUALITY_SAVED_PATH \
22
+ --batch_size 4
23
+
24
+ # Get the text score of all videos filtered by the video quality score.
25
+ accelerate launch compute_text_score.py \
26
+ --video_metadata_path $META_FILE_PATH \
27
+ --video_folder $VIDEO_FOLDER \
28
+ --saved_freq 10 \
29
+ --saved_path $TEXT_SAVED_PATH \
30
+ --asethetic_score_siglip_metadata_path $VIDEO_QUALITY_SAVED_PATH \
31
+ --min_asethetic_score_siglip $MIN_ASETHETIC_SCORE_SIGLIP
32
+
33
+ # Get the motion score of all videos filtered by the video quality score and text score.
34
+ python compute_motion_score.py \
35
+ --video_metadata_path $META_FILE_PATH \
36
+ --video_folder $VIDEO_FOLDER \
37
+ --saved_freq 10 \
38
+ --saved_path $MOTION_SAVED_PATH \
39
+ --n_jobs 8 \
40
+ --text_score_metadata_path $TEXT_SAVED_PATH \
41
+ --min_text_score $MIN_TEXT_SCORE
cogvideox/video_caption/scripts/stage_3_video_recaptioning.sh ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ META_FILE_PATH="datasets/panda_70m/videos_clips/data/meta_file_info.jsonl"
2
+ VIDEO_FOLDER="datasets/panda_70m/videos_clips/data/"
3
+ MOTION_SAVED_PATH="datasets/panda_70m/videos_clips/meta_motion_info.jsonl"
4
+ MIN_MOTION_SCORE=2
5
+ VIDEO_CAPTION_SAVED_PATH="datasets/panda_70m/meta_caption_info_vila_8b.jsonl"
6
+ REWRITTEN_VIDEO_CAPTION_SAVED_PATH="datasets/panda_70m/meta_caption_info_vila_8b_rewritten.jsonl"
7
+ VIDEOCLIPXL_SCORE_SAVED_PATH="datasets/panda_70m/meta_caption_info_vila_8b_rewritten_videoclipxl.jsonl"
8
+ MIN_VIDEOCLIPXL_SCORE=0.20
9
+ TRAIN_SAVED_PATH="datasets/panda_70m/train_panda_70m.json"
10
+ # Manually download Efficient-Large-Model/Llama-3-VILA1.5-8b-AWQ to VILA_MODEL_PATH.
11
+ # Manually download meta-llama/Meta-Llama-3-8B-Instruct to REWRITE_MODEL_PATH.
12
+
13
+ # Use VILA1.5-AWQ to perform recaptioning.
14
+ accelerate launch vila_video_recaptioning.py \
15
+ --video_metadata_path ${META_FILE_PATH} \
16
+ --video_folder ${VIDEO_FOLDER} \
17
+ --model_path ${VILA_MODEL_PATH} \
18
+ --precision "W4A16" \
19
+ --saved_path $VIDEO_CAPTION_SAVED_PATH \
20
+ --saved_freq 1 \
21
+ --motion_score_metadata_path $MOTION_SAVED_PATH \
22
+ --min_motion_score $MIN_MOTION_SCORE
23
+
24
+ # Rewrite video captions (optional).
25
+ python caption_rewrite.py \
26
+ --video_metadata_path $VIDEO_CAPTION_SAVED_PATH \
27
+ --batch_size 4096 \
28
+ --model_name $REWRITE_MODEL_PATH \
29
+ --prompt prompt/rewrite.txt \
30
+ --prefix '"rewritten description": ' \
31
+ --saved_path $REWRITTEN_VIDEO_CAPTION_SAVED_PATH \
32
+ --saved_freq 1
33
+
34
+ # Compute caption-video alignment (optional).
35
+ accelerate launch compute_video_quality.py \
36
+ --video_metadata_path $REWRITTEN_VIDEO_CAPTION_SAVED_PATH \
37
+ --caption_column caption \
38
+ --video_folder $VIDEO_FOLDER \
39
+ --frame_sample_method uniform \
40
+ --num_sampled_frames 8 \
41
+ --metrics VideoCLIPXLScore \
42
+ --batch_size 4 \
43
+ --saved_path $VIDEOCLIPXL_SCORE_SAVED_PATH \
44
+ --saved_freq 10
45
+
46
+ # Get the final train file.
47
+ python filter_meta_train.py \
48
+ --caption_metadata_path $REWRITTEN_VIDEO_CAPTION_SAVED_PATH \
49
+ --video_folder=$VIDEO_FOLDER \
50
+ --videoclipxl_score_metadata_path $VIDEOCLIPXL_SCORE_SAVED_PATH \
51
+ --min_videoclipxl_score $MIN_VIDEOCLIPXL_SCORE \
52
+ --saved_path=$TRAIN_SAVED_PATH
cogvideox/video_caption/utils/filter.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ast
2
+ import os
3
+
4
+ import pandas as pd
5
+
6
+ from .logger import logger
7
+
8
+
9
+ def filter(
10
+ video_path_list,
11
+ basic_metadata_path=None,
12
+ min_resolution=0,
13
+ min_duration=-1,
14
+ max_duration=-1,
15
+ asethetic_score_metadata_path=None,
16
+ min_asethetic_score=4,
17
+ asethetic_score_siglip_metadata_path=None,
18
+ min_asethetic_score_siglip=4,
19
+ text_score_metadata_path=None,
20
+ min_text_score=0.02,
21
+ motion_score_metadata_path=None,
22
+ min_motion_score=2,
23
+ videoclipxl_score_metadata_path=None,
24
+ min_videoclipxl_score=0.20,
25
+ video_path_column="video_path",
26
+ ):
27
+ video_path_list = [os.path.basename(video_path) for video_path in video_path_list]
28
+
29
+ if basic_metadata_path is not None:
30
+ if basic_metadata_path.endswith(".csv"):
31
+ basic_df = pd.read_csv(basic_metadata_path)
32
+ elif basic_metadata_path.endswith(".jsonl"):
33
+ basic_df = pd.read_json(basic_metadata_path, lines=True)
34
+
35
+ basic_df["resolution"] = basic_df["frame_size"].apply(lambda x: x[0] * x[1])
36
+ filtered_basic_df = basic_df[basic_df["resolution"] < min_resolution]
37
+ filtered_video_path_list = filtered_basic_df[video_path_column].tolist()
38
+ filtered_video_path_list = [os.path.basename(video_path) for video_path in filtered_video_path_list]
39
+
40
+ video_path_list = list(set(video_path_list).difference(set(filtered_video_path_list)))
41
+ logger.info(
42
+ f"Load {basic_metadata_path} ({len(basic_df)}) and filter {len(filtered_video_path_list)} videos "
43
+ f"with resolution less than {min_resolution}."
44
+ )
45
+
46
+ if min_duration != -1:
47
+ filtered_basic_df = basic_df[basic_df["duration"] < min_duration]
48
+ filtered_video_path_list = filtered_basic_df[video_path_column].tolist()
49
+ filtered_video_path_list = [os.path.basename(video_path) for video_path in filtered_video_path_list]
50
+
51
+ video_path_list = list(set(video_path_list).difference(set(filtered_video_path_list)))
52
+ logger.info(
53
+ f"Load {basic_metadata_path} and filter {len(filtered_video_path_list)} videos "
54
+ f"with duration less than {min_duration}."
55
+ )
56
+
57
+ if max_duration != -1:
58
+ filtered_basic_df = basic_df[basic_df["duration"] > max_duration]
59
+ filtered_video_path_list = filtered_basic_df[video_path_column].tolist()
60
+ filtered_video_path_list = [os.path.basename(video_path) for video_path in filtered_video_path_list]
61
+
62
+ video_path_list = list(set(video_path_list).difference(set(filtered_video_path_list)))
63
+ logger.info(
64
+ f"Load {basic_metadata_path} and filter {len(filtered_video_path_list)} videos "
65
+ f"with duration greater than {max_duration}."
66
+ )
67
+
68
+ if asethetic_score_metadata_path is not None:
69
+ if asethetic_score_metadata_path.endswith(".csv"):
70
+ asethetic_score_df = pd.read_csv(asethetic_score_metadata_path)
71
+ elif asethetic_score_metadata_path.endswith(".jsonl"):
72
+ asethetic_score_df = pd.read_json(asethetic_score_metadata_path, lines=True)
73
+
74
+ # In pandas, csv will save lists as strings, whereas jsonl will not.
75
+ asethetic_score_df["aesthetic_score"] = asethetic_score_df["aesthetic_score"].apply(
76
+ lambda x: ast.literal_eval(x) if isinstance(x, str) else x
77
+ )
78
+ asethetic_score_df["aesthetic_score_mean"] = asethetic_score_df["aesthetic_score"].apply(lambda x: sum(x) / len(x))
79
+ filtered_asethetic_score_df = asethetic_score_df[asethetic_score_df["aesthetic_score_mean"] < min_asethetic_score]
80
+ filtered_video_path_list = filtered_asethetic_score_df[video_path_column].tolist()
81
+ filtered_video_path_list = [os.path.basename(video_path) for video_path in filtered_video_path_list]
82
+
83
+ video_path_list = list(set(video_path_list).difference(set(filtered_video_path_list)))
84
+ logger.info(
85
+ f"Load {asethetic_score_metadata_path} ({len(asethetic_score_df)}) and filter {len(filtered_video_path_list)} videos "
86
+ f"with aesthetic score less than {min_asethetic_score}."
87
+ )
88
+
89
+ if asethetic_score_siglip_metadata_path is not None:
90
+ if asethetic_score_siglip_metadata_path.endswith(".csv"):
91
+ asethetic_score_siglip_df = pd.read_csv(asethetic_score_siglip_metadata_path)
92
+ elif asethetic_score_siglip_metadata_path.endswith(".jsonl"):
93
+ asethetic_score_siglip_df = pd.read_json(asethetic_score_siglip_metadata_path, lines=True)
94
+
95
+ # In pandas, csv will save lists as strings, whereas jsonl will not.
96
+ asethetic_score_siglip_df["aesthetic_score_siglip"] = asethetic_score_siglip_df["aesthetic_score_siglip"].apply(
97
+ lambda x: ast.literal_eval(x) if isinstance(x, str) else x
98
+ )
99
+ asethetic_score_siglip_df["aesthetic_score_siglip_mean"] = asethetic_score_siglip_df["aesthetic_score_siglip"].apply(
100
+ lambda x: sum(x) / len(x)
101
+ )
102
+ filtered_asethetic_score_siglip_df = asethetic_score_siglip_df[
103
+ asethetic_score_siglip_df["aesthetic_score_siglip_mean"] < min_asethetic_score_siglip
104
+ ]
105
+ filtered_video_path_list = filtered_asethetic_score_siglip_df[video_path_column].tolist()
106
+ filtered_video_path_list = [os.path.basename(video_path) for video_path in filtered_video_path_list]
107
+
108
+ video_path_list = list(set(video_path_list).difference(set(filtered_video_path_list)))
109
+ logger.info(
110
+ f"Load {asethetic_score_siglip_metadata_path} ({len(asethetic_score_siglip_df)}) and filter {len(filtered_video_path_list)} videos "
111
+ f"with aesthetic score (SigLIP) less than {min_asethetic_score_siglip}."
112
+ )
113
+
114
+ if text_score_metadata_path is not None:
115
+ if text_score_metadata_path.endswith(".csv"):
116
+ text_score_df = pd.read_csv(text_score_metadata_path)
117
+ elif text_score_metadata_path.endswith(".jsonl"):
118
+ text_score_df = pd.read_json(text_score_metadata_path, lines=True)
119
+
120
+ filtered_text_score_df = text_score_df[text_score_df["text_score"] > min_text_score]
121
+ filtered_video_path_list = filtered_text_score_df[video_path_column].tolist()
122
+ filtered_video_path_list = [os.path.basename(video_path) for video_path in filtered_video_path_list]
123
+
124
+ video_path_list = list(set(video_path_list).difference(set(filtered_video_path_list)))
125
+ logger.info(
126
+ f"Load {text_score_metadata_path} ({len(text_score_df)}) and filter {len(filtered_video_path_list)} videos "
127
+ f"with text score greater than {min_text_score}."
128
+ )
129
+
130
+ if motion_score_metadata_path is not None:
131
+ if motion_score_metadata_path.endswith(".csv"):
132
+ motion_score_df = pd.read_csv(motion_score_metadata_path)
133
+ elif motion_score_metadata_path.endswith(".jsonl"):
134
+ motion_score_df = pd.read_json(motion_score_metadata_path, lines=True)
135
+
136
+ filtered_motion_score_df = motion_score_df[motion_score_df["motion_score"] < min_motion_score]
137
+ filtered_video_path_list = filtered_motion_score_df[video_path_column].tolist()
138
+ filtered_video_path_list = [os.path.basename(video_path) for video_path in filtered_video_path_list]
139
+
140
+ video_path_list = list(set(video_path_list).difference(set(filtered_video_path_list)))
141
+ logger.info(
142
+ f"Load {motion_score_metadata_path} ({len(motion_score_df)}) and filter {len(filtered_video_path_list)} videos "
143
+ f"with motion score smaller than {min_motion_score}."
144
+ )
145
+
146
+ if videoclipxl_score_metadata_path is not None:
147
+ if videoclipxl_score_metadata_path.endswith(".csv"):
148
+ videoclipxl_score_df = pd.read_csv(videoclipxl_score_metadata_path)
149
+ elif videoclipxl_score_metadata_path.endswith(".jsonl"):
150
+ videoclipxl_score_df = pd.read_json(videoclipxl_score_metadata_path, lines=True)
151
+
152
+ filtered_videoclipxl_score_df = videoclipxl_score_df[videoclipxl_score_df["videoclipxl_score"] < min_videoclipxl_score]
153
+ filtered_video_path_list = filtered_videoclipxl_score_df[video_path_column].tolist()
154
+ filtered_video_path_list = [os.path.basename(video_path) for video_path in filtered_video_path_list]
155
+
156
+ video_path_list = list(set(video_path_list).difference(set(filtered_video_path_list)))
157
+ logger.info(
158
+ f"Load {videoclipxl_score_metadata_path} ({len(videoclipxl_score_df)}) and "
159
+ f"filter {len(filtered_video_path_list)} videos with mixclip score smaller than {min_videoclipxl_score}."
160
+ )
161
+
162
+ return video_path_list
cogvideox/video_caption/utils/gather_jsonl.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import os
3
+ import glob
4
+ import json
5
+ from multiprocessing import Pool, Manager
6
+
7
+ import pandas as pd
8
+ from natsort import index_natsorted
9
+
10
+ from .logger import logger
11
+
12
+
13
+ def process_file(file_path, shared_list):
14
+ with open(file_path, "r") as f:
15
+ for line in f:
16
+ data = json.loads(line)
17
+ shared_list.append(data)
18
+
19
+
20
+ def parse_args():
21
+ parser = argparse.ArgumentParser(description="Gather all jsonl files in a folder (meta_folder) to a single jsonl file (meta_file_path).")
22
+ parser.add_argument("--meta_folder", type=str, required=True)
23
+ parser.add_argument("--meta_file_path", type=str, required=True)
24
+ parser.add_argument("--video_path_column", type=str, default="video_path")
25
+ parser.add_argument("--n_jobs", type=int, default=1)
26
+
27
+ args = parser.parse_args()
28
+ return args
29
+
30
+
31
+ def main():
32
+ args = parse_args()
33
+
34
+ jsonl_files = glob.glob(os.path.join(args.meta_folder, "*.jsonl"))
35
+
36
+ with Manager() as manager:
37
+ shared_list = manager.list()
38
+ with Pool(processes=args.n_jobs) as pool:
39
+ for file_path in jsonl_files:
40
+ pool.apply_async(process_file, args=(file_path, shared_list))
41
+ pool.close()
42
+ pool.join()
43
+
44
+ with open(args.meta_file_path, "w") as f:
45
+ for item in shared_list:
46
+ f.write(json.dumps(item) + '\n')
47
+
48
+ df = pd.read_json(args.meta_file_path, lines=True)
49
+ df = df.iloc[index_natsorted(df[args.video_path_column])].reset_index(drop=True)
50
+ logger.info(f"Save the gathered single jsonl file to {args.meta_file_path}.")
51
+ df.to_json(args.meta_file_path, orient="records", lines=True, force_ascii=False)
52
+
53
+
54
+ if __name__ == '__main__':
55
+ main()
cogvideox/video_caption/utils/get_meta_file.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ from pathlib import Path
3
+
4
+ import pandas as pd
5
+ from natsort import natsorted
6
+ from tqdm import tqdm
7
+
8
+ from .logger import logger
9
+
10
+
11
+ ALL_VIDEO_EXT = set(["mp4", "webm", "mkv", "avi", "flv", "mov"])
12
+ ALL_IMGAE_EXT = set(["png", "webp", "jpg", "jpeg", "bmp", "gif"])
13
+
14
+
15
+ def parse_args():
16
+ parser = argparse.ArgumentParser(description="Compute scores of uniform sampled frames from videos.")
17
+ parser.add_argument(
18
+ "--image_path_column",
19
+ type=str,
20
+ default="image_path",
21
+ help="The column contains the image path (an absolute path or a relative path w.r.t the image_folder).",
22
+ )
23
+ parser.add_argument("--image_folder", type=str, default=None, help="The video folder.")
24
+ parser.add_argument(
25
+ "--video_path_column",
26
+ type=str,
27
+ default="video_path",
28
+ help="The column contains the video path (an absolute path or a relative path w.r.t the video_folder).",
29
+ )
30
+ parser.add_argument("--video_folder", type=str, default=None, help="The video folder.")
31
+ parser.add_argument("--saved_path", type=str, required=True, help="The save path to the output results (csv/jsonl).")
32
+ parser.add_argument("--recursive", action="store_true", help="Whether to search sub-folders recursively.")
33
+
34
+ args = parser.parse_args()
35
+ return args
36
+
37
+
38
+ def main():
39
+ args = parse_args()
40
+
41
+ if args.video_folder is None and args.image_folder is None:
42
+ raise ValueError("Either video_folder or image_folder should be specified in the arguments.")
43
+ if args.video_folder is not None and args.image_folder is not None:
44
+ raise ValueError("Both video_folder and image_folder can not be specified in the arguments at the same time.")
45
+
46
+ # Use the path name instead of the file name as video_path/image_path (unique ID).
47
+ if args.video_folder is not None:
48
+ video_path_list = []
49
+ video_folder = Path(args.video_folder)
50
+ for ext in tqdm(list(ALL_VIDEO_EXT)):
51
+ if args.recursive:
52
+ video_path_list += [str(file.relative_to(video_folder)) for file in video_folder.rglob(f"*.{ext}")]
53
+ else:
54
+ video_path_list += [str(file.relative_to(video_folder)) for file in video_folder.glob(f"*.{ext}")]
55
+ video_path_list = natsorted(video_path_list)
56
+ meta_file_df = pd.DataFrame({args.video_path_column: video_path_list})
57
+
58
+ if args.image_folder is not None:
59
+ image_path_list = []
60
+ image_folder = Path(args.image_folder)
61
+ for ext in tqdm(list(ALL_IMGAE_EXT)):
62
+ if args.recursive:
63
+ image_path_list += [str(file.relative_to(image_folder)) for file in image_folder.rglob(f"*.{ext}")]
64
+ else:
65
+ image_path_list += [str(file.relative_to(image_folder)) for file in image_folder.glob(f"*.{ext}")]
66
+ image_path_list = natsorted(image_path_list)
67
+ meta_file_df = pd.DataFrame({args.image_path_column: image_path_list})
68
+
69
+ logger.info(f"{len(meta_file_df)} files in total. Save the result to {args.saved_path}.")
70
+ meta_file_df.to_json(args.saved_path, orient="records", lines=True)
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()
cogvideox/video_caption/utils/image_evaluator.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Union
3
+
4
+ import clip
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+ from PIL import Image
9
+ from torchvision.datasets.utils import download_url
10
+ from transformers import AutoModel, AutoProcessor
11
+
12
+ from .siglip_v2_5 import convert_v2_5_from_siglip
13
+
14
+ # All metrics.
15
+ __all__ = ["AestheticScore", "AestheticScoreSigLIP", "CLIPScore"]
16
+
17
+ _MODELS = {
18
+ "CLIP_ViT-L/14": "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/easyanimate/video_caption/clip/ViT-L-14.pt",
19
+ "Aesthetics_V2": "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/easyanimate/video_caption/clip/sac%2Blogos%2Bava1-l14-linearMSE.pth",
20
+ "aesthetic_predictor_v2_5": "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/easyanimate/video_caption/clip/aesthetic_predictor_v2_5.pth",
21
+ }
22
+ _MD5 = {
23
+ "CLIP_ViT-L/14": "096db1af569b284eb76b3881534822d9",
24
+ "Aesthetics_V2": "b1047fd767a00134b8fd6529bf19521a",
25
+ "aesthetic_predictor_v2_5": "c46eb8c29f714c9231dc630b8226842a",
26
+ }
27
+
28
+
29
+ def get_list_depth(lst):
30
+ if isinstance(lst, list):
31
+ return 1 + max(get_list_depth(item) for item in lst)
32
+ else:
33
+ return 0
34
+
35
+
36
+ def reshape_images(images: Union[list[list[Image.Image]], list[Image.Image]]):
37
+ # Check the input sanity.
38
+ depth = get_list_depth(images)
39
+ if depth == 1: # batch image input
40
+ if not isinstance(images[0], Image.Image):
41
+ raise ValueError("The item in 1D images should be Image.Image.")
42
+ num_sampled_frames = None
43
+ elif depth == 2: # batch video input
44
+ if not isinstance(images[0][0], Image.Image):
45
+ raise ValueError("The item in 2D images (videos) should be Image.Image.")
46
+ num_sampled_frames = len(images[0])
47
+ if not all(len(video_frames) == num_sampled_frames for video_frames in images):
48
+ raise ValueError("All item in 2D images should be with the same length.")
49
+ # [batch_size, num_sampled_frames, H, W, C] => [batch_size * num_sampled_frames, H, W, C].
50
+ reshaped_images = []
51
+ for video_frames in images:
52
+ reshaped_images.extend([frame for frame in video_frames])
53
+ images = reshaped_images
54
+ else:
55
+ raise ValueError("The input images should be in 1/2D list.")
56
+
57
+ return images, num_sampled_frames
58
+
59
+
60
+ def reshape_scores(scores: list[float], num_sampled_frames: int) -> list[float]:
61
+ if isinstance(scores, list):
62
+ if num_sampled_frames is not None: # Batch video input
63
+ batch_size = len(scores) // num_sampled_frames
64
+ scores = [
65
+ scores[i * num_sampled_frames:(i + 1) * num_sampled_frames]
66
+ for i in range(batch_size)
67
+ ]
68
+ return scores
69
+ else:
70
+ return [scores]
71
+
72
+
73
+ # if you changed the MLP architecture during training, change it also here:
74
+ class _MLP(nn.Module):
75
+ def __init__(self, input_size):
76
+ super().__init__()
77
+ self.input_size = input_size
78
+ self.layers = nn.Sequential(
79
+ nn.Linear(self.input_size, 1024),
80
+ # nn.ReLU(),
81
+ nn.Dropout(0.2),
82
+ nn.Linear(1024, 128),
83
+ # nn.ReLU(),
84
+ nn.Dropout(0.2),
85
+ nn.Linear(128, 64),
86
+ # nn.ReLU(),
87
+ nn.Dropout(0.1),
88
+ nn.Linear(64, 16),
89
+ # nn.ReLU(),
90
+ nn.Linear(16, 1),
91
+ )
92
+
93
+ def forward(self, x):
94
+ return self.layers(x)
95
+
96
+
97
+ class AestheticScore:
98
+ """Compute LAION Aesthetics Score V2 based on openai/clip. Note that the default
99
+ inference dtype with GPUs is fp16 in openai/clip.
100
+
101
+ Ref:
102
+ 1. https://github.com/christophschuhmann/improved-aesthetic-predictor/blob/main/simple_inference.py.
103
+ 2. https://github.com/openai/CLIP/issues/30.
104
+ """
105
+
106
+ def __init__(self, root: str = "~/.cache/clip", device: str = "cpu"):
107
+ # The CLIP model is loaded in the evaluation mode.
108
+ self.root = os.path.expanduser(root)
109
+ if not os.path.exists(self.root):
110
+ os.makedirs(self.root)
111
+ filename = "ViT-L-14.pt"
112
+ download_url(_MODELS["CLIP_ViT-L/14"], self.root, filename=filename, md5=_MD5["CLIP_ViT-L/14"])
113
+ self.clip_model, self.preprocess = clip.load(os.path.join(self.root, filename), device=device)
114
+ self.device = device
115
+ self._load_mlp()
116
+
117
+ def _load_mlp(self):
118
+ filename = "sac+logos+ava1-l14-linearMSE.pth"
119
+ download_url(_MODELS["Aesthetics_V2"], self.root, filename=filename, md5=_MD5["Aesthetics_V2"])
120
+ state_dict = torch.load(os.path.join(self.root, filename))
121
+ self.mlp = _MLP(768)
122
+ self.mlp.load_state_dict(state_dict)
123
+ self.mlp.to(self.device)
124
+ self.mlp.eval()
125
+
126
+ def __call__(self, images: Union[list[list[Image.Image]], list[Image.Image]], texts=None) -> list[float]:
127
+ images, num_sampled_frames = reshape_images(images)
128
+
129
+ with torch.no_grad():
130
+ images = torch.stack([self.preprocess(image) for image in images]).to(self.device)
131
+ image_embs = F.normalize(self.clip_model.encode_image(images))
132
+ scores = self.mlp(image_embs.float()) # torch.float16 -> torch.float32, [N, 1]
133
+
134
+ scores = scores.squeeze().tolist() # scalar or list
135
+ return reshape_scores(scores, num_sampled_frames)
136
+
137
+ def __repr__(self) -> str:
138
+ return "aesthetic_score"
139
+
140
+
141
+ class AestheticScoreSigLIP:
142
+ """Compute Aesthetics Score V2.5 based on google/siglip-so400m-patch14-384.
143
+
144
+ Ref:
145
+ 1. https://github.com/discus0434/aesthetic-predictor-v2-5.
146
+ 2. https://github.com/discus0434/aesthetic-predictor-v2-5/issues/2.
147
+ """
148
+
149
+ def __init__(
150
+ self,
151
+ root: str = "~/.cache/clip",
152
+ device: str = "cpu",
153
+ torch_dtype=torch.float16
154
+ ):
155
+ self.root = os.path.expanduser(root)
156
+ if not os.path.exists(self.root):
157
+ os.makedirs(self.root)
158
+ filename = "aesthetic_predictor_v2_5.pth"
159
+ download_url(_MODELS["aesthetic_predictor_v2_5"], self.root, filename=filename, md5=_MD5["aesthetic_predictor_v2_5"])
160
+ self.model, self.preprocessor = convert_v2_5_from_siglip(
161
+ predictor_name_or_path=os.path.join(self.root, filename),
162
+ low_cpu_mem_usage=True,
163
+ trust_remote_code=True,
164
+ )
165
+ self.model = self.model.to(device=device, dtype=torch_dtype)
166
+ self.device = device
167
+ self.torch_dtype = torch_dtype
168
+
169
+ def __call__(self, images: Union[list[list[Image.Image]], list[Image.Image]], texts=None) -> list[float]:
170
+ images, num_sampled_frames = reshape_images(images)
171
+
172
+ pixel_values = self.preprocessor(images, return_tensors="pt").pixel_values
173
+ pixel_values = pixel_values.to(self.device, self.torch_dtype)
174
+ with torch.no_grad():
175
+ scores = self.model(pixel_values).logits.squeeze().float().cpu().numpy()
176
+
177
+ scores = scores.squeeze().tolist() # scalar or list
178
+ return reshape_scores(scores, num_sampled_frames)
179
+
180
+ def __repr__(self) -> str:
181
+ return "aesthetic_score_siglip"
182
+
183
+
184
+ class CLIPScore:
185
+ """Compute CLIP scores for image-text pairs based on huggingface/transformers."""
186
+
187
+ def __init__(
188
+ self,
189
+ model_name_or_path: str = "openai/clip-vit-large-patch14",
190
+ torch_dtype=torch.float16,
191
+ device: str = "cpu",
192
+ ):
193
+ self.model = AutoModel.from_pretrained(model_name_or_path, torch_dtype=torch_dtype).eval().to(device)
194
+ self.processor = AutoProcessor.from_pretrained(model_name_or_path)
195
+ self.torch_dtype = torch_dtype
196
+ self.device = device
197
+
198
+ def __call__(self, images: Union[list[list[Image.Image]], list[Image.Image]], texts: list[str]) -> list[float]:
199
+ assert len(images) == len(texts)
200
+ images, num_sampled_frames = reshape_images(images)
201
+ # Expand texts in the batch video input case.
202
+ if num_sampled_frames is not None:
203
+ texts = [[text] * num_sampled_frames for text in texts]
204
+ texts = [item for sublist in texts for item in sublist]
205
+
206
+ image_inputs = self.processor(images=images, return_tensors="pt") # {"pixel_values": }
207
+ if self.torch_dtype == torch.float16:
208
+ image_inputs["pixel_values"] = image_inputs["pixel_values"].half()
209
+ text_inputs = self.processor(text=texts, return_tensors="pt", padding=True, truncation=True) # {"inputs_id": }
210
+ image_inputs, text_inputs = image_inputs.to(self.device), text_inputs.to(self.device)
211
+ with torch.no_grad():
212
+ image_embs = F.normalize(self.model.get_image_features(**image_inputs))
213
+ text_embs = F.normalize(self.model.get_text_features(**text_inputs))
214
+ scores = text_embs @ image_embs.T # [N, N]
215
+
216
+ scores = scores.squeeze().tolist() # scalar or list
217
+ return reshape_scores(scores, num_sampled_frames)
218
+
219
+ def __repr__(self) -> str:
220
+ return "clip_score"
221
+
222
+
223
+ if __name__ == "__main__":
224
+ from torch.utils.data import DataLoader
225
+ from tqdm import tqdm
226
+ from .video_dataset import VideoDataset, collate_fn
227
+
228
+ aesthetic_score = AestheticScore(device="cuda")
229
+ aesthetic_score_siglip = AestheticScoreSigLIP(device="cuda")
230
+ # clip_score = CLIPScore(device="cuda")
231
+
232
+ paths = ["your_image_path"] * 3
233
+ # texts = ["a joker", "a woman", "a man"]
234
+ images = [Image.open(p).convert("RGB") for p in paths]
235
+
236
+ print(aesthetic_score(images))
237
+ # print(clip_score(images, texts))
238
+
239
+ test_dataset = VideoDataset(
240
+ dataset_inputs={"video_path": ["your_video_path"] * 3},
241
+ sample_method="mid",
242
+ num_sampled_frames=2
243
+ )
244
+ test_loader = DataLoader(test_dataset, batch_size=1, num_workers=1, collate_fn=collate_fn)
245
+
246
+ for idx, batch in enumerate(tqdm(test_loader)):
247
+ batch_frame = batch["sampled_frame"]
248
+ print(aesthetic_score_siglip(batch_frame))
cogvideox/video_caption/utils/logger.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Borrowed from sd-webui-controlnet/scripts/logging.py
2
+ import copy
3
+ import logging
4
+ import sys
5
+
6
+
7
+ class ColoredFormatter(logging.Formatter):
8
+ COLORS = {
9
+ "DEBUG": "\033[0;36m", # CYAN
10
+ "INFO": "\033[0;32m", # GREEN
11
+ "WARNING": "\033[0;33m", # YELLOW
12
+ "ERROR": "\033[0;31m", # RED
13
+ "CRITICAL": "\033[0;37;41m", # WHITE ON RED
14
+ "RESET": "\033[0m", # RESET COLOR
15
+ }
16
+
17
+ def format(self, record):
18
+ colored_record = copy.copy(record)
19
+ levelname = colored_record.levelname
20
+ seq = self.COLORS.get(levelname, self.COLORS["RESET"])
21
+ colored_record.levelname = f"{seq}{levelname}{self.COLORS['RESET']}"
22
+ return super().format(colored_record)
23
+
24
+
25
+ # Create a new logger
26
+ logger = logging.getLogger("VideoCaption")
27
+ logger.propagate = False
28
+
29
+ # Add handler if we don't have one.
30
+ if not logger.handlers:
31
+ handler = logging.StreamHandler(sys.stdout)
32
+ handler.setFormatter(ColoredFormatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
33
+ logger.addHandler(handler)
34
+
35
+ # Configure logger
36
+ logger.setLevel("INFO")
cogvideox/video_caption/utils/longclip/README.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Long-CLIP
2
+ Codes in this directory are borrowed from https://github.com/beichenzbc/Long-CLIP/tree/4e6f5da/model.
3
+
4
+ We only modify the following code in [model_longclip.py](model_longclip.py) from
5
+ ```python
6
+ @property
7
+ def dtype(self):
8
+ return self.visual.conv1.weight.dtype
9
+ ```
10
+ to
11
+ ```python
12
+ @property
13
+ def dtype(self):
14
+ # Fix: the VideoCLIP-XL inference.
15
+ if hasattr(self, "visual"):
16
+ return self.visual.conv1.weight.dtype
17
+ else:
18
+ return self.token_embedding.weight.dtype
19
+ ```
cogvideox/video_caption/utils/longclip/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .longclip import *
cogvideox/video_caption/utils/longclip/bpe_simple_vocab_16e6.txt.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:924691ac288e54409236115652ad4aa250f48203de50a9e4722a6ecd48d6804a
3
+ size 1356917
cogvideox/video_caption/utils/longclip/longclip.py ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib
2
+ import os
3
+ import urllib
4
+ import warnings
5
+ from typing import Any, Union, List
6
+ from pkg_resources import packaging
7
+ from torch import nn
8
+ import torch
9
+ from PIL import Image
10
+ from torchvision.transforms import Compose, Resize, CenterCrop, ToTensor, Normalize
11
+ from tqdm import tqdm
12
+
13
+ from .model_longclip import build_model
14
+ from .simple_tokenizer import SimpleTokenizer as _Tokenizer
15
+
16
+ try:
17
+ from torchvision.transforms import InterpolationMode
18
+ BICUBIC = InterpolationMode.BICUBIC
19
+ except ImportError:
20
+ BICUBIC = Image.BICUBIC
21
+
22
+
23
+ if packaging.version.parse(torch.__version__) < packaging.version.parse("1.7.1"):
24
+ warnings.warn("PyTorch version 1.7.1 or higher is recommended")
25
+
26
+
27
+ __all__ = ["load", "tokenize"]
28
+ _tokenizer = _Tokenizer()
29
+
30
+
31
+ def _convert_image_to_rgb(image):
32
+ return image.convert("RGB")
33
+
34
+
35
+ def _transform(n_px):
36
+ return Compose([
37
+ Resize(n_px, interpolation=BICUBIC),
38
+ CenterCrop(n_px),
39
+ _convert_image_to_rgb,
40
+ ToTensor(),
41
+ Normalize((0.48145466, 0.4578275, 0.40821073), (0.26862954, 0.26130258, 0.27577711)),
42
+ ])
43
+
44
+
45
+
46
+ def load(name: str, device: Union[str, torch.device] = "cuda" if torch.cuda.is_available() else "cpu", download_root: str = None):
47
+ """Load a long CLIP model
48
+
49
+ Parameters
50
+ ----------
51
+ name : str
52
+ A model name listed by `clip.available_models()`, or the path to a model checkpoint containing the state_dict
53
+
54
+ device : Union[str, torch.device]
55
+ The device to put the loaded model
56
+
57
+ Returns
58
+ -------
59
+ model : torch.nn.Module
60
+ The CLIP model
61
+
62
+ preprocess : Callable[[PIL.Image], torch.Tensor]
63
+ A torchvision transform that converts a PIL image into a tensor that the returned model can take as its input
64
+ """
65
+
66
+ model_path = name
67
+
68
+ state_dict = torch.load(model_path, map_location="cpu")
69
+
70
+ model = build_model(state_dict or model.state_dict(), load_from_clip = False).to(device)
71
+
72
+ if str(device) == "cpu":
73
+ model.float()
74
+
75
+ return model, _transform(model.visual.input_resolution)
76
+
77
+
78
+
79
+ def _node_get(node: torch._C.Node, key: str):
80
+ """Gets attributes of a node which is polymorphic over return type.
81
+
82
+ From https://github.com/pytorch/pytorch/pull/82628
83
+ """
84
+ sel = node.kindOf(key)
85
+ return getattr(node, sel)(key)
86
+
87
+ def patch_device(module):
88
+ try:
89
+ graphs = [module.graph] if hasattr(module, "graph") else []
90
+ except RuntimeError:
91
+ graphs = []
92
+
93
+ if hasattr(module, "forward1"):
94
+ graphs.append(module.forward1.graph)
95
+
96
+ for graph in graphs:
97
+ for node in graph.findAllNodes("prim::Constant"):
98
+ if "value" in node.attributeNames() and str(_node_get(node, "value")).startswith("cuda"):
99
+ node.copyAttributes(device_node)
100
+
101
+ model.apply(patch_device)
102
+ patch_device(model.encode_image)
103
+ patch_device(model.encode_text)
104
+
105
+ # patch dtype to float32 on CPU
106
+ if str(device) == "cpu":
107
+ float_holder = torch.jit.trace(lambda: torch.ones([]).float(), example_inputs=[])
108
+ float_input = list(float_holder.graph.findNode("aten::to").inputs())[1]
109
+ float_node = float_input.node()
110
+
111
+ def patch_float(module):
112
+ try:
113
+ graphs = [module.graph] if hasattr(module, "graph") else []
114
+ except RuntimeError:
115
+ graphs = []
116
+
117
+ if hasattr(module, "forward1"):
118
+ graphs.append(module.forward1.graph)
119
+
120
+ for graph in graphs:
121
+ for node in graph.findAllNodes("aten::to"):
122
+ inputs = list(node.inputs())
123
+ for i in [1, 2]: # dtype can be the second or third argument to aten::to()
124
+ if _node_get(inputs[i].node(), "value") == 5:
125
+ inputs[i].node().copyAttributes(float_node)
126
+
127
+ model.apply(patch_float)
128
+ patch_float(model.encode_image)
129
+ patch_float(model.encode_text)
130
+
131
+ model.float()
132
+
133
+ return model, _transform(model.input_resolution.item())
134
+
135
+
136
+ def load_from_clip(name: str, device: Union[str, torch.device] = "cuda" if torch.cuda.is_available() else "cpu", jit: bool = False, download_root: str = None):
137
+ """Load from CLIP model for fine-tuning
138
+
139
+ Parameters
140
+ ----------
141
+ name : str
142
+ A model name listed by `clip.available_models()`, or the path to a model checkpoint containing the state_dict
143
+
144
+ device : Union[str, torch.device]
145
+ The device to put the loaded model
146
+
147
+ jit : bool
148
+ Whether to load the optimized JIT model or more hackable non-JIT model (default).
149
+
150
+ download_root: str
151
+ path to download the model files; by default, it uses "~/.cache/clip"
152
+
153
+ Returns
154
+ -------
155
+ model : torch.nn.Module
156
+ The CLIP model
157
+
158
+ preprocess : Callable[[PIL.Image], torch.Tensor]
159
+ A torchvision transform that converts a PIL image into a tensor that the returned model can take as its input
160
+ """
161
+
162
+ _MODELS = {
163
+ "RN50": "https://openaipublic.azureedge.net/clip/models/afeb0e10f9e5a86da6080e35cf09123aca3b358a0c3e3b6c78a7b63bc04b6762/RN50.pt",
164
+ "RN101": "https://openaipublic.azureedge.net/clip/models/8fa8567bab74a42d41c5915025a8e4538c3bdbe8804a470a72f30b0d94fab599/RN101.pt",
165
+ "RN50x4": "https://openaipublic.azureedge.net/clip/models/7e526bd135e493cef0776de27d5f42653e6b4c8bf9e0f653bb11773263205fdd/RN50x4.pt",
166
+ "RN50x16": "https://openaipublic.azureedge.net/clip/models/52378b407f34354e150460fe41077663dd5b39c54cd0bfd2b27167a4a06ec9aa/RN50x16.pt",
167
+ "RN50x64": "https://openaipublic.azureedge.net/clip/models/be1cfb55d75a9666199fb2206c106743da0f6468c9d327f3e0d0a543a9919d9c/RN50x64.pt",
168
+ "ViT-B/32": "https://openaipublic.azureedge.net/clip/models/40d365715913c9da98579312b702a82c18be219cc2a73407c4526f58eba950af/ViT-B-32.pt",
169
+ "ViT-B/16": "https://openaipublic.azureedge.net/clip/models/5806e77cd80f8b59890b7e101eabd078d9fb84e6937f9e85e4ecb61988df416f/ViT-B-16.pt",
170
+ "ViT-L/14": "https://openaipublic.azureedge.net/clip/models/b8cca3fd41ae0c99ba7e8951adf17d267cdb84cd88be6f7c2e0eca1737a03836/ViT-L-14.pt",
171
+ "ViT-L/14@336px": "https://openaipublic.azureedge.net/clip/models/3035c92b350959924f9f00213499208652fc7ea050643e8b385c2dac08641f02/ViT-L-14-336px.pt",
172
+ }
173
+
174
+ def available_models() -> List[str]:
175
+ """Returns the names of available CLIP models"""
176
+ return list(_MODELS.keys())
177
+
178
+ def _download(url: str, root: str):
179
+ os.makedirs(root, exist_ok=True)
180
+ filename = os.path.basename(url)
181
+
182
+ expected_sha256 = url.split("/")[-2]
183
+ download_target = os.path.join(root, filename)
184
+
185
+ if os.path.exists(download_target) and not os.path.isfile(download_target):
186
+ raise RuntimeError(f"{download_target} exists and is not a regular file")
187
+
188
+ if os.path.isfile(download_target):
189
+ if hashlib.sha256(open(download_target, "rb").read()).hexdigest() == expected_sha256:
190
+ return download_target
191
+ else:
192
+ warnings.warn(f"{download_target} exists, but the SHA256 checksum does not match; re-downloading the file")
193
+
194
+ with urllib.request.urlopen(url) as source, open(download_target, "wb") as output:
195
+ with tqdm(total=int(source.info().get("Content-Length")), ncols=80, unit='iB', unit_scale=True, unit_divisor=1024) as loop:
196
+ while True:
197
+ buffer = source.read(8192)
198
+ if not buffer:
199
+ break
200
+
201
+ output.write(buffer)
202
+ loop.update(len(buffer))
203
+
204
+ if hashlib.sha256(open(download_target, "rb").read()).hexdigest() != expected_sha256:
205
+ raise RuntimeError("Model has been downloaded but the SHA256 checksum does not not match")
206
+
207
+ return download_target
208
+
209
+ if name in _MODELS:
210
+ model_path = _download(_MODELS[name], download_root or os.path.expanduser("~/.cache/clip"))
211
+ elif os.path.isfile(name):
212
+ model_path = name
213
+ else:
214
+ raise RuntimeError(f"Model {name} not found; available models = {available_models()}")
215
+
216
+ with open(model_path, 'rb') as opened_file:
217
+ try:
218
+ # loading JIT archive
219
+ model = torch.jit.load(opened_file, map_location=device if jit else "cpu").eval()
220
+ state_dict = None
221
+ except RuntimeError:
222
+ # loading saved state dict
223
+ if jit:
224
+ warnings.warn(f"File {model_path} is not a JIT archive. Loading as a state dict instead")
225
+ jit = False
226
+ state_dict = torch.load(opened_file, map_location="cpu")
227
+
228
+ model = build_model(state_dict or model.state_dict(), load_from_clip = True).to(device)
229
+
230
+ positional_embedding_pre = model.positional_embedding.type(model.dtype)
231
+
232
+ length, dim = positional_embedding_pre.shape
233
+ keep_len = 20
234
+ posisitonal_embedding_new = torch.zeros([4*length-3*keep_len, dim], dtype=model.dtype)
235
+ for i in range(keep_len):
236
+ posisitonal_embedding_new[i] = positional_embedding_pre[i]
237
+ for i in range(length-1-keep_len):
238
+ posisitonal_embedding_new[4*i + keep_len] = positional_embedding_pre[i + keep_len]
239
+ posisitonal_embedding_new[4*i + 1 + keep_len] = 3*positional_embedding_pre[i + keep_len]/4 + 1*positional_embedding_pre[i+1+keep_len]/4
240
+ posisitonal_embedding_new[4*i + 2+keep_len] = 2*positional_embedding_pre[i+keep_len]/4 + 2*positional_embedding_pre[i+1+keep_len]/4
241
+ posisitonal_embedding_new[4*i + 3+keep_len] = 1*positional_embedding_pre[i+keep_len]/4 + 3*positional_embedding_pre[i+1+keep_len]/4
242
+
243
+ posisitonal_embedding_new[4*length -3*keep_len - 4] = positional_embedding_pre[length-1] + 0*(positional_embedding_pre[length-1] - positional_embedding_pre[length-2])/4
244
+ posisitonal_embedding_new[4*length -3*keep_len - 3] = positional_embedding_pre[length-1] + 1*(positional_embedding_pre[length-1] - positional_embedding_pre[length-2])/4
245
+ posisitonal_embedding_new[4*length -3*keep_len - 2] = positional_embedding_pre[length-1] + 2*(positional_embedding_pre[length-1] - positional_embedding_pre[length-2])/4
246
+ posisitonal_embedding_new[4*length -3*keep_len - 1] = positional_embedding_pre[length-1] + 3*(positional_embedding_pre[length-1] - positional_embedding_pre[length-2])/4
247
+
248
+ positional_embedding_res = posisitonal_embedding_new.clone()
249
+
250
+ model.positional_embedding = nn.Parameter(posisitonal_embedding_new, requires_grad=False)
251
+ model.positional_embedding_res = nn.Parameter(positional_embedding_res, requires_grad=True)
252
+
253
+ if str(device) == "cpu":
254
+ model.float()
255
+ return model, _transform(model.visual.input_resolution)
256
+
257
+ def _node_get(node: torch._C.Node, key: str):
258
+ """Gets attributes of a node which is polymorphic over return type.
259
+
260
+ From https://github.com/pytorch/pytorch/pull/82628
261
+ """
262
+ sel = node.kindOf(key)
263
+ return getattr(node, sel)(key)
264
+
265
+ def patch_device(module):
266
+ try:
267
+ graphs = [module.graph] if hasattr(module, "graph") else []
268
+ except RuntimeError:
269
+ graphs = []
270
+
271
+ if hasattr(module, "forward1"):
272
+ graphs.append(module.forward1.graph)
273
+
274
+ for graph in graphs:
275
+ for node in graph.findAllNodes("prim::Constant"):
276
+ if "value" in node.attributeNames() and str(_node_get(node, "value")).startswith("cuda"):
277
+ node.copyAttributes(device_node)
278
+
279
+ model.apply(patch_device)
280
+ patch_device(model.encode_image)
281
+ patch_device(model.encode_text)
282
+
283
+ # patch dtype to float32 on CPU
284
+ if str(device) == "cpu":
285
+ float_holder = torch.jit.trace(lambda: torch.ones([]).float(), example_inputs=[])
286
+ float_input = list(float_holder.graph.findNode("aten::to").inputs())[1]
287
+ float_node = float_input.node()
288
+
289
+ def patch_float(module):
290
+ try:
291
+ graphs = [module.graph] if hasattr(module, "graph") else []
292
+ except RuntimeError:
293
+ graphs = []
294
+
295
+ if hasattr(module, "forward1"):
296
+ graphs.append(module.forward1.graph)
297
+
298
+ for graph in graphs:
299
+ for node in graph.findAllNodes("aten::to"):
300
+ inputs = list(node.inputs())
301
+ for i in [1, 2]: # dtype can be the second or third argument to aten::to()
302
+ if _node_get(inputs[i].node(), "value") == 5:
303
+ inputs[i].node().copyAttributes(float_node)
304
+
305
+ model.apply(patch_float)
306
+ patch_float(model.encode_image)
307
+ patch_float(model.encode_text)
308
+
309
+ model.float()
310
+
311
+ return model, _transform(model.input_resolution.item())
312
+
313
+ def tokenize(texts: Union[str, List[str]], context_length: int = 77*4-60, truncate: bool = False) -> Union[torch.IntTensor, torch.LongTensor]:
314
+ """
315
+ Returns the tokenized representation of given input string(s)
316
+
317
+ Parameters
318
+ ----------
319
+ texts : Union[str, List[str]]
320
+ An input string or a list of input strings to tokenize
321
+
322
+ context_length : int
323
+ The context length to use; all CLIP models use 77 as the context length
324
+
325
+ truncate: bool
326
+ Whether to truncate the text in case its encoding is longer than the context length
327
+
328
+ Returns
329
+ -------
330
+ A two-dimensional tensor containing the resulting tokens, shape = [number of input strings, context_length].
331
+ We return LongTensor when torch version is <1.8.0, since older index_select requires indices to be long.
332
+ """
333
+ if isinstance(texts, str):
334
+ texts = [texts]
335
+
336
+ sot_token = _tokenizer.encoder["<|startoftext|>"]
337
+ eot_token = _tokenizer.encoder["<|endoftext|>"]
338
+ all_tokens = [[sot_token] + _tokenizer.encode(text) + [eot_token] for text in texts]
339
+ if packaging.version.parse(torch.__version__) < packaging.version.parse("1.8.0"):
340
+ result = torch.zeros(len(all_tokens), context_length, dtype=torch.long)
341
+ else:
342
+ result = torch.zeros(len(all_tokens), context_length, dtype=torch.int)
343
+
344
+ for i, tokens in enumerate(all_tokens):
345
+ if len(tokens) > context_length:
346
+ if truncate:
347
+ tokens = tokens[:context_length]
348
+ tokens[-1] = eot_token
349
+ else:
350
+ raise RuntimeError(f"Input {texts[i]} is too long for context length {context_length}")
351
+ result[i, :len(tokens)] = torch.tensor(tokens)
352
+
353
+ return result