Spaces:
Running on Zero
Running on Zero
Prism ZeroGPU demo
Browse files- .gitattributes +6 -0
- LICENSE +87 -0
- README.md +14 -7
- __pycache__/app.cpython-312.pyc +0 -0
- app.py +360 -0
- examples/case-1/prompt.txt +1 -0
- examples/case-1/reference_image.jpg +3 -0
- examples/case-2/prompt.txt +1 -0
- examples/case-2/reference_image.jpg +3 -0
- examples/case-3/prompt.txt +1 -0
- examples/case-3/reference_image.jpg +3 -0
- examples/case-4/prompt.txt +1 -0
- examples/case-4/reference_image.jpg +3 -0
- examples/case-5/prompt.txt +1 -0
- examples/case-5/reference_image.jpg +3 -0
- examples/case-6/prompt.txt +1 -0
- examples/case-6/reference_image.jpg +3 -0
- hymm/diffusion/__init__.py +0 -0
- hymm/diffusion/pipelines/__init__.py +0 -0
- hymm/diffusion/pipelines/mova_pipeline.py +485 -0
- hymm/diffusion/schedulers/__init__.py +1 -0
- hymm/diffusion/schedulers/flow_match_pair.py +277 -0
- hymm/models/__init__.py +0 -0
- hymm/models/modules/__init__.py +1 -0
- hymm/models/modules/block_sparse_attention/__init__.py +16 -0
- hymm/models/modules/block_sparse_attention/bias_rectification.py +452 -0
- hymm/models/modules/block_sparse_attention/bias_rectification_triton.py +363 -0
- hymm/models/modules/block_sparse_attention/bsa_interface.py +1503 -0
- hymm/models/modules/block_sparse_attention/common.py +111 -0
- hymm/models/modules/block_sparse_attention/dynamic_block_attention.py +335 -0
- hymm/models/modules/block_sparse_attention/dynamic_block_shape.py +406 -0
- hymm/models/modules/block_sparse_attention/flash_attn_bsa_varlen_mask.py +1014 -0
- hymm/models/modules/dac_vae.py +1103 -0
- hymm/models/modules/interactionv2.py +708 -0
- hymm/models/modules/mova.py +964 -0
- hymm/models/modules/wan_audio_dit.py +271 -0
- hymm/models/modules/wan_video_dit.py +1324 -0
- hymm/utils/__init__.py +1 -0
- hymm/utils/communications.py +413 -0
- hymm/utils/parallel_states.py +453 -0
- packages.txt +1 -0
- requirements.txt +14 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
examples/case-1/reference_image.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
examples/case-2/reference_image.jpg filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
examples/case-3/reference_image.jpg filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
examples/case-4/reference_image.jpg filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
examples/case-5/reference_image.jpg filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
examples/case-6/reference_image.jpg filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Tencent is pleased to support the community by making Prism available.
|
| 2 |
+
|
| 3 |
+
Copyright (C) 2026 Tencent. All rights reserved.
|
| 4 |
+
|
| 5 |
+
The open-source software and/or model(s) included in this distribution may have been modified by Tencent ("Tencent Modifications"). All Tencent Modifications are Copyright (C) Tencent.
|
| 6 |
+
|
| 7 |
+
Prism is licensed under MIT, except for the third-party components listed below, which remain licensed under their respective original terms. Prism does not impose any additional restrictions beyond those specified in the original licenses of these third-party components. Users are required to comply with all applicable terms and conditions of the original licenses and to ensure that the use of these third-party components conforms to all relevant laws and regulations.
|
| 8 |
+
|
| 9 |
+
For the avoidance of doubt, Prism refers solely to code, parameters and weights made publicly available by Tencent in accordance with MIT.
|
| 10 |
+
|
| 11 |
+
Terms of the MIT License:
|
| 12 |
+
--------------------------------------------------------------------
|
| 13 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
| 14 |
+
|
| 15 |
+
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
| 16 |
+
|
| 17 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
Dependencies and Licenses:
|
| 21 |
+
|
| 22 |
+
This open-source project builds upon the following open-source model(s) and/or software, each of which remains licensed under its original license(s). Certain component(s) may include modifications made by Tencent ("Tencent Modifications"), which are Copyright (C) Tencent.
|
| 23 |
+
|
| 24 |
+
In case you believe there have been errors in the attribution below, you may submit the concerns to us for review and correction.
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
Open Source Model(s)/Software Licensed under the Apache-2.0:
|
| 28 |
+
--------------------------------------------------------------------
|
| 29 |
+
1. MOVA-360p
|
| 30 |
+
Copyright (c) MOVA-360p original author and authors
|
| 31 |
+
Please note this model/software has been modified by Tencent in this distribution.
|
| 32 |
+
Terms of the Apache-2.0:
|
| 33 |
+
--------------------------------------------------------------------
|
| 34 |
+
Apache License
|
| 35 |
+
Version 2.0, January 2004
|
| 36 |
+
http://www.apache.org/licenses/
|
| 37 |
+
|
| 38 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 39 |
+
|
| 40 |
+
1. Definitions.
|
| 41 |
+
|
| 42 |
+
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
| 43 |
+
|
| 44 |
+
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
| 45 |
+
|
| 46 |
+
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
| 47 |
+
|
| 48 |
+
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
| 49 |
+
|
| 50 |
+
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
| 51 |
+
|
| 52 |
+
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
| 53 |
+
|
| 54 |
+
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
| 55 |
+
|
| 56 |
+
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
| 57 |
+
|
| 58 |
+
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
| 59 |
+
|
| 60 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
| 61 |
+
|
| 62 |
+
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
| 63 |
+
|
| 64 |
+
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
| 65 |
+
|
| 66 |
+
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
| 67 |
+
|
| 68 |
+
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
| 69 |
+
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
| 70 |
+
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
| 71 |
+
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
| 72 |
+
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
| 73 |
+
|
| 74 |
+
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
| 75 |
+
|
| 76 |
+
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
| 77 |
+
|
| 78 |
+
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
| 79 |
+
|
| 80 |
+
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
| 81 |
+
|
| 82 |
+
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
| 83 |
+
|
| 84 |
+
END OF TERMS AND CONDITIONS
|
| 85 |
+
|
| 86 |
+
==================================================
|
| 87 |
+
End of the Attribution Notice of this project.
|
README.md
CHANGED
|
@@ -1,13 +1,20 @@
|
|
| 1 |
---
|
| 2 |
-
title: Prism
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version:
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Prism Video Audio
|
| 3 |
+
emoji: 🎬
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: pink
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.50.0
|
| 8 |
+
python_version: "3.12"
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
+
license: mit
|
| 12 |
+
models:
|
| 13 |
+
- FrancisRing/Prism
|
| 14 |
+
startup_duration_timeout: 1h
|
| 15 |
+
short_description: Prism image+text to video with audio (ZeroGPU xlarge)
|
| 16 |
---
|
| 17 |
|
| 18 |
+
ZeroGPU demo of [FrancisRing/Prism](https://huggingface.co/FrancisRing/Prism)
|
| 19 |
+
(dynamic sparse attention, joint video + audio generation on the MOVA backbone).
|
| 20 |
+
Inference code is adapted from [Tencent-Hunyuan/Prism](https://github.com/Tencent-Hunyuan/Prism) (MIT).
|
__pycache__/app.cpython-312.pyc
ADDED
|
Binary file (21.9 kB). View file
|
|
|
app.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Prism (FrancisRing/Prism) joint video-audio generation on ZeroGPU (xlarge, 96GB)."""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
import time
|
| 6 |
+
import types
|
| 7 |
+
import random
|
| 8 |
+
import tempfile
|
| 9 |
+
import subprocess
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
# ---------------------------------------------------------------------------
|
| 14 |
+
# ZeroGPU does not support torch.compile -> turn every @torch.compile into a no-op
|
| 15 |
+
# (must happen before the model code is imported).
|
| 16 |
+
# ---------------------------------------------------------------------------
|
| 17 |
+
def _no_compile(fn=None, *args, **kwargs):
|
| 18 |
+
if callable(fn):
|
| 19 |
+
return fn
|
| 20 |
+
return lambda f: f
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
torch.compile = _no_compile
|
| 24 |
+
|
| 25 |
+
# The DAC audio VAE only needs `audiotools` for training/IO helpers. Stub it so we
|
| 26 |
+
# don't have to install descript-audiotools (it pins protobuf<3.20).
|
| 27 |
+
_at = types.ModuleType("audiotools")
|
| 28 |
+
_at_ml = types.ModuleType("audiotools.ml")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class _AudioSignal: # never used in the decode path
|
| 32 |
+
def __init__(self, *a, **k):
|
| 33 |
+
raise RuntimeError("audiotools.AudioSignal is not available in this Space")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
_at.AudioSignal = _AudioSignal
|
| 37 |
+
_at_ml.BaseModel = torch.nn.Module
|
| 38 |
+
_at.ml = _at_ml
|
| 39 |
+
sys.modules.setdefault("audiotools", _at)
|
| 40 |
+
sys.modules.setdefault("audiotools.ml", _at_ml)
|
| 41 |
+
|
| 42 |
+
import spaces # noqa: E402 (must be imported before CUDA is touched)
|
| 43 |
+
import numpy as np # noqa: E402
|
| 44 |
+
import gradio as gr # noqa: E402
|
| 45 |
+
from PIL import Image # noqa: E402
|
| 46 |
+
from huggingface_hub import snapshot_download # noqa: E402
|
| 47 |
+
from safetensors import safe_open # noqa: E402
|
| 48 |
+
from accelerate import init_empty_weights # noqa: E402
|
| 49 |
+
from accelerate.utils import set_module_tensor_to_device # noqa: E402
|
| 50 |
+
|
| 51 |
+
from hymm.models.modules.mova import MOVABridge # noqa: E402
|
| 52 |
+
from hymm.models.modules.wan_video_dit import WanModel # noqa: E402
|
| 53 |
+
from hymm.models.modules.wan_audio_dit import WanAudioModel # noqa: E402
|
| 54 |
+
from hymm.models.modules.interactionv2 import DualTowerConditionalBridge # noqa: E402
|
| 55 |
+
from hymm.models.modules.dac_vae import DAC # noqa: E402
|
| 56 |
+
from hymm.diffusion.pipelines.mova_pipeline import MOVAPipeline # noqa: E402
|
| 57 |
+
from hymm.diffusion.schedulers.flow_match_pair import FlowMatchPairScheduler # noqa: E402
|
| 58 |
+
from diffusers.models.autoencoders import AutoencoderKLWan # noqa: E402
|
| 59 |
+
from transformers import T5TokenizerFast, UMT5EncoderModel # noqa: E402
|
| 60 |
+
|
| 61 |
+
REPO_ID = "FrancisRing/Prism"
|
| 62 |
+
VARIANT = os.environ.get("PRISM_VARIANT", "preview_alpha") # or preview_beta (motion)
|
| 63 |
+
BASE = "pretrained_models/MOVA-360p"
|
| 64 |
+
DTYPE = torch.bfloat16
|
| 65 |
+
|
| 66 |
+
NEGATIVE_PROMPT = (
|
| 67 |
+
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,"
|
| 68 |
+
"整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
# ---------------------------------------------------------------------------
|
| 72 |
+
# Download only what inference needs: the Prism transformer checkpoint (it holds
|
| 73 |
+
# both video experts, the audio tower and the bridge) + text encoder / VAEs.
|
| 74 |
+
# The 57GB of base MOVA DiT weights are fully overridden, so they are skipped.
|
| 75 |
+
# ---------------------------------------------------------------------------
|
| 76 |
+
t0 = time.time()
|
| 77 |
+
root = snapshot_download(
|
| 78 |
+
REPO_ID,
|
| 79 |
+
allow_patterns=[
|
| 80 |
+
f"{VARIANT}/*",
|
| 81 |
+
f"{BASE}/model_index.json",
|
| 82 |
+
f"{BASE}/*/config.json",
|
| 83 |
+
f"{BASE}/text_encoder/*",
|
| 84 |
+
f"{BASE}/tokenizer/*",
|
| 85 |
+
f"{BASE}/scheduler/*",
|
| 86 |
+
f"{BASE}/video_vae/*",
|
| 87 |
+
f"{BASE}/audio_vae/*",
|
| 88 |
+
],
|
| 89 |
+
)
|
| 90 |
+
base = os.path.join(root, BASE)
|
| 91 |
+
print(f"[prism] download done in {time.time() - t0:.0f}s -> {root}")
|
| 92 |
+
|
| 93 |
+
# --- transformer: build on meta, then stream the checkpoint straight to cuda ---
|
| 94 |
+
t0 = time.time()
|
| 95 |
+
with init_empty_weights():
|
| 96 |
+
video_dit = WanModel.from_config(WanModel.load_config(base, subfolder="video_dit"))
|
| 97 |
+
video_dit_2 = WanModel.from_config(WanModel.load_config(base, subfolder="video_dit_2"))
|
| 98 |
+
audio_dit = WanAudioModel.from_config(WanAudioModel.load_config(base, subfolder="audio_dit"))
|
| 99 |
+
dual_bridge = DualTowerConditionalBridge.from_config(
|
| 100 |
+
DualTowerConditionalBridge.load_config(base, subfolder="dual_tower_bridge")
|
| 101 |
+
)
|
| 102 |
+
transformer = MOVABridge(
|
| 103 |
+
video_dit=video_dit,
|
| 104 |
+
video_dit_2=video_dit_2,
|
| 105 |
+
audio_dit=audio_dit,
|
| 106 |
+
dual_tower_bridge=dual_bridge,
|
| 107 |
+
boundary_ratio=0.9,
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
ckpt_file = os.path.join(root, VARIANT, "diffusion_pytorch_model.safetensors")
|
| 111 |
+
expected = set(transformer.state_dict().keys())
|
| 112 |
+
loaded = set()
|
| 113 |
+
with safe_open(ckpt_file, framework="pt", device="cpu") as f:
|
| 114 |
+
for k in f.keys():
|
| 115 |
+
if k not in expected:
|
| 116 |
+
print(f"[prism] unexpected key {k}")
|
| 117 |
+
continue
|
| 118 |
+
set_module_tensor_to_device(transformer, k, "cuda", value=f.get_tensor(k), dtype=DTYPE)
|
| 119 |
+
loaded.add(k)
|
| 120 |
+
missing = expected - loaded
|
| 121 |
+
if missing:
|
| 122 |
+
raise RuntimeError(f"[prism] {len(missing)} transformer keys missing, e.g. {sorted(missing)[:5]}")
|
| 123 |
+
transformer.to("cuda")
|
| 124 |
+
transformer.eval().requires_grad_(False)
|
| 125 |
+
print(f"[prism] transformer ready ({len(loaded)} tensors) in {time.time() - t0:.0f}s")
|
| 126 |
+
|
| 127 |
+
# --- frozen components ---
|
| 128 |
+
video_vae = AutoencoderKLWan.from_pretrained(base, subfolder="video_vae", torch_dtype=DTYPE).to("cuda").eval()
|
| 129 |
+
audio_vae = DAC.from_pretrained(base, subfolder="audio_vae", torch_dtype=DTYPE).to("cuda").eval()
|
| 130 |
+
text_encoder = UMT5EncoderModel.from_pretrained(base, subfolder="text_encoder", torch_dtype=DTYPE).to("cuda").eval()
|
| 131 |
+
tokenizer = T5TokenizerFast.from_pretrained(base, subfolder="tokenizer")
|
| 132 |
+
scheduler = FlowMatchPairScheduler.from_pretrained(base, subfolder="scheduler")
|
| 133 |
+
|
| 134 |
+
pipe = MOVAPipeline(
|
| 135 |
+
transformer=transformer,
|
| 136 |
+
video_vae=video_vae,
|
| 137 |
+
audio_vae=audio_vae,
|
| 138 |
+
text_encoder=text_encoder,
|
| 139 |
+
tokenizer=tokenizer,
|
| 140 |
+
scheduler=scheduler,
|
| 141 |
+
boundary_ratio=0.9,
|
| 142 |
+
device=torch.device("cuda"),
|
| 143 |
+
)
|
| 144 |
+
print("[prism] pipeline ready")
|
| 145 |
+
|
| 146 |
+
RESOLUTIONS = {
|
| 147 |
+
"480p (848×480)": (480, 848),
|
| 148 |
+
"720p (1280×720)": (720, 1280),
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def configure_sparse(sparsity, use_ivpq):
|
| 153 |
+
transformer.set_sparse_high_noise_only(False)
|
| 154 |
+
transformer.configure_bsa(
|
| 155 |
+
enable_bsa=True,
|
| 156 |
+
bsa_params={
|
| 157 |
+
"sparsity": float(sparsity),
|
| 158 |
+
"cdf_threshold": 0.20,
|
| 159 |
+
"chunk_3d_shape_q": [4, 4, 4],
|
| 160 |
+
"chunk_3d_shape_k": [4, 4, 4],
|
| 161 |
+
},
|
| 162 |
+
enable_bsa_v2a=False,
|
| 163 |
+
bsa_params_v2a=None,
|
| 164 |
+
)
|
| 165 |
+
for sa in transformer._video_self_attns():
|
| 166 |
+
sa.bsa_params["dynamic_block_lambda_a"] = 0.5
|
| 167 |
+
sa.bsa_params["dynamic_block_tau_128"] = 0.25
|
| 168 |
+
sa.bsa_params["dynamic_block_lambda_128"] = 1.0
|
| 169 |
+
transformer.configure_ivpq_dynamic_block(enable=bool(use_ivpq))
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def crop_and_resize(img, height, width):
|
| 173 |
+
w, h = img.size
|
| 174 |
+
target = width / height
|
| 175 |
+
if w / h > target:
|
| 176 |
+
nw = int(h * target)
|
| 177 |
+
img = img.crop(((w - nw) // 2, 0, (w - nw) // 2 + nw, h))
|
| 178 |
+
elif w / h < target:
|
| 179 |
+
nh = int(w / target)
|
| 180 |
+
img = img.crop((0, (h - nh) // 2, w, (h - nh) // 2 + nh))
|
| 181 |
+
return img.resize((width, height), Image.LANCZOS)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def target_size(image, resolution):
|
| 185 |
+
h, w = RESOLUTIONS[resolution]
|
| 186 |
+
if image is not None and image.height > image.width: # portrait input -> portrait video
|
| 187 |
+
h, w = w, h
|
| 188 |
+
return h, w
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def num_tokens(h, w, frames):
|
| 192 |
+
return ((frames - 1) // 4 + 1) * (h // 16) * (w // 16)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
# Rough GPU-time model (seconds per transformer forward), calibrated on the Space.
|
| 196 |
+
SEC_PER_TOKEN = float(os.environ.get("PRISM_SEC_PER_TOKEN", "1.0e-4"))
|
| 197 |
+
SEC_PER_TOKEN2 = float(os.environ.get("PRISM_SEC_PER_TOKEN2", "4.0e-9"))
|
| 198 |
+
MAX_DURATION = int(os.environ.get("PRISM_MAX_DURATION", "1500"))
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def estimate_seconds(image, resolution, frames, steps, cfg, sparsity):
|
| 202 |
+
h, w = target_size(image, resolution)
|
| 203 |
+
tok = num_tokens(h, w, int(frames))
|
| 204 |
+
per_forward = SEC_PER_TOKEN * tok + SEC_PER_TOKEN2 * tok * tok * (1.0 - float(sparsity) + 0.05)
|
| 205 |
+
passes = 1 if float(cfg) == 1.0 else 2
|
| 206 |
+
return int(60 + int(steps) * passes * per_forward)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def get_duration(image, prompt, audio_prompt, resolution, frames, steps, cfg, shift, audio_shift,
|
| 210 |
+
seed, sparsity, use_ivpq, *args, **kwargs):
|
| 211 |
+
return min(MAX_DURATION, int(estimate_seconds(image, resolution, frames, steps, cfg, sparsity) * 1.15))
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
@spaces.GPU(size="xlarge", duration=get_duration)
|
| 215 |
+
def _generate_gpu(image, prompt, audio_prompt, resolution, frames, steps, cfg, shift, audio_shift,
|
| 216 |
+
seed, sparsity, use_ivpq, progress=gr.Progress(track_tqdm=True)):
|
| 217 |
+
t_start = time.time()
|
| 218 |
+
h, w = target_size(image, resolution)
|
| 219 |
+
ref = crop_and_resize(image.convert("RGB"), h, w)
|
| 220 |
+
configure_sparse(sparsity, use_ivpq)
|
| 221 |
+
torch.manual_seed(int(seed))
|
| 222 |
+
video, audio = pipe(
|
| 223 |
+
prompt=prompt,
|
| 224 |
+
image=ref,
|
| 225 |
+
audio_prompt=audio_prompt or prompt,
|
| 226 |
+
negative_prompt=NEGATIVE_PROMPT,
|
| 227 |
+
seed=int(seed),
|
| 228 |
+
height=h,
|
| 229 |
+
width=w,
|
| 230 |
+
num_frames=int(frames),
|
| 231 |
+
video_fps=24.0,
|
| 232 |
+
num_inference_steps=int(steps),
|
| 233 |
+
visual_shift=float(shift),
|
| 234 |
+
audio_shift=float(audio_shift),
|
| 235 |
+
cfg_scale=float(cfg),
|
| 236 |
+
enable_vae_tiling=(h * w >= 720 * 1280),
|
| 237 |
+
)
|
| 238 |
+
frames_np = np.stack([np.asarray(fr) for fr in video[0]])
|
| 239 |
+
audio_np = audio[0].float().cpu().squeeze().numpy()
|
| 240 |
+
elapsed = time.time() - t_start
|
| 241 |
+
print(f"[prism] {h}x{w} {frames}f {steps} steps cfg={cfg} -> {elapsed:.1f}s on GPU "
|
| 242 |
+
f"(peak mem {torch.cuda.max_memory_allocated() / 2**30:.1f} GiB)")
|
| 243 |
+
return frames_np, audio_np, elapsed
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def save_video_with_audio(frames, audio_np, path, fps, sample_rate):
|
| 247 |
+
import wave
|
| 248 |
+
import imageio
|
| 249 |
+
|
| 250 |
+
with tempfile.TemporaryDirectory() as tmp:
|
| 251 |
+
tmp_video = os.path.join(tmp, "video.mp4")
|
| 252 |
+
tmp_audio = os.path.join(tmp, "audio.wav")
|
| 253 |
+
writer = imageio.get_writer(tmp_video, fps=fps, quality=9, macro_block_size=8)
|
| 254 |
+
for fr in frames:
|
| 255 |
+
writer.append_data(fr)
|
| 256 |
+
writer.close()
|
| 257 |
+
|
| 258 |
+
if audio_np.ndim == 1:
|
| 259 |
+
audio_np = audio_np[None, :]
|
| 260 |
+
audio_np = audio_np[:2]
|
| 261 |
+
pcm = (np.clip(audio_np, -1.0, 1.0) * 32767.0).astype(np.int16)
|
| 262 |
+
with wave.open(tmp_audio, "wb") as wf:
|
| 263 |
+
wf.setnchannels(pcm.shape[0])
|
| 264 |
+
wf.setsampwidth(2)
|
| 265 |
+
wf.setframerate(int(sample_rate))
|
| 266 |
+
wf.writeframes(pcm.T.reshape(-1).tobytes())
|
| 267 |
+
|
| 268 |
+
cmd = ["ffmpeg", "-y", "-i", tmp_video, "-i", tmp_audio, "-c:v", "copy", "-c:a", "aac",
|
| 269 |
+
"-b:a", "192k", "-movflags", "+faststart", "-shortest", path]
|
| 270 |
+
r = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| 271 |
+
if r.returncode != 0:
|
| 272 |
+
print("[prism] ffmpeg mux failed:", r.stderr.decode(errors="ignore")[-500:])
|
| 273 |
+
import shutil
|
| 274 |
+
shutil.copyfile(tmp_video, path)
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def generate(image, prompt, audio_prompt, resolution, frames, steps, cfg, shift, audio_shift,
|
| 278 |
+
seed, randomize_seed, sparsity, use_ivpq, progress=gr.Progress(track_tqdm=True)):
|
| 279 |
+
if image is None:
|
| 280 |
+
raise gr.Error("Please upload a reference image (Prism is an image+text → video+audio model).")
|
| 281 |
+
if not prompt or not prompt.strip():
|
| 282 |
+
raise gr.Error("Please enter a prompt.")
|
| 283 |
+
if randomize_seed:
|
| 284 |
+
seed = random.randint(0, 2**31 - 1)
|
| 285 |
+
frames = int(frames)
|
| 286 |
+
frames = frames - (frames - 1) % 4
|
| 287 |
+
frames_np, audio_np, elapsed = _generate_gpu(
|
| 288 |
+
image, prompt, audio_prompt, resolution, frames, steps, cfg, shift, audio_shift,
|
| 289 |
+
seed, sparsity, use_ivpq,
|
| 290 |
+
)
|
| 291 |
+
out = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False).name
|
| 292 |
+
save_video_with_audio(frames_np, audio_np, out, fps=24.0, sample_rate=pipe.audio_sample_rate)
|
| 293 |
+
return out, seed, f"GPU time: {elapsed:.0f}s (xlarge quota counts ×2)"
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def update_estimate(image, resolution, frames, steps, cfg, sparsity):
|
| 297 |
+
h, w = target_size(image, resolution)
|
| 298 |
+
est = estimate_seconds(image, resolution, int(frames), steps, cfg, sparsity)
|
| 299 |
+
return (f"Output {w}×{h}, {int(frames)} frames ({int(frames) / 24:.1f}s) · "
|
| 300 |
+
f"rough GPU estimate ≈ {est / 60:.1f} min → ≈ {2 * est / 60:.1f} min of daily ZeroGPU quota")
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
EXAMPLES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "examples")
|
| 304 |
+
examples = []
|
| 305 |
+
if os.path.isdir(EXAMPLES_DIR):
|
| 306 |
+
for name in sorted(os.listdir(EXAMPLES_DIR)):
|
| 307 |
+
d = os.path.join(EXAMPLES_DIR, name)
|
| 308 |
+
img, txt = os.path.join(d, "reference_image.jpg"), os.path.join(d, "prompt.txt")
|
| 309 |
+
if os.path.exists(img) and os.path.exists(txt):
|
| 310 |
+
with open(txt, encoding="utf-8") as fh:
|
| 311 |
+
examples.append([img, fh.read().strip()])
|
| 312 |
+
|
| 313 |
+
with gr.Blocks(title="Prism · joint video + audio") as demo:
|
| 314 |
+
gr.Markdown(
|
| 315 |
+
"# Prism: image + text → video with audio\n"
|
| 316 |
+
f"[Model](https://huggingface.co/{REPO_ID}) · [Code](https://github.com/Tencent-Hunyuan/Prism) · "
|
| 317 |
+
f"[Paper](https://arxiv.org/abs/2610.05416). Checkpoint: `{VARIANT}`. Runs on one ZeroGPU **xlarge** "
|
| 318 |
+
"(RTX Pro 6000, 96GB). xlarge uses quota at 2×, so keep clips short. "
|
| 319 |
+
"Sound can be tagged in the prompt with `<music>`, `<sfx>`, `<speech>`."
|
| 320 |
+
)
|
| 321 |
+
with gr.Row():
|
| 322 |
+
with gr.Column():
|
| 323 |
+
image = gr.Image(label="Reference image (first frame)", type="pil")
|
| 324 |
+
prompt = gr.Textbox(label="Prompt (video)", lines=6)
|
| 325 |
+
audio_prompt = gr.Textbox(label="Audio prompt (optional, defaults to the video prompt)", lines=2)
|
| 326 |
+
with gr.Row():
|
| 327 |
+
resolution = gr.Radio(list(RESOLUTIONS), value="720p (1280×720)", label="Resolution")
|
| 328 |
+
frames = gr.Slider(25, 121, value=49, step=4, label="Frames (24 fps)")
|
| 329 |
+
steps = gr.Slider(10, 50, value=30, step=1, label="Denoising steps")
|
| 330 |
+
estimate = gr.Markdown()
|
| 331 |
+
with gr.Accordion("Advanced", open=False):
|
| 332 |
+
cfg = gr.Slider(1.0, 10.0, value=5.0, step=0.5, label="CFG scale (1.0 = no CFG, ~2× faster)")
|
| 333 |
+
shift = gr.Slider(1.0, 17.0, value=7.0, step=0.5, label="Visual shift (720p: 7, 480p: 5)")
|
| 334 |
+
audio_shift = gr.Slider(1.0, 15.0, value=7.0, step=0.5, label="Audio shift")
|
| 335 |
+
sparsity = gr.Dropdown([0.75, 0.85, 0.93], value=0.75, label="Block-sparse attention sparsity")
|
| 336 |
+
use_ivpq = gr.Checkbox(value=True, label="Dynamic block shape (IVPQ)")
|
| 337 |
+
with gr.Row():
|
| 338 |
+
seed = gr.Number(value=42, precision=0, label="Seed")
|
| 339 |
+
randomize_seed = gr.Checkbox(value=False, label="Random seed")
|
| 340 |
+
run = gr.Button("Generate", variant="primary")
|
| 341 |
+
with gr.Column():
|
| 342 |
+
out_video = gr.Video(label="Result (with audio)")
|
| 343 |
+
used_seed = gr.Number(label="Seed used", interactive=False)
|
| 344 |
+
info = gr.Markdown()
|
| 345 |
+
|
| 346 |
+
est_inputs = [image, resolution, frames, steps, cfg, sparsity]
|
| 347 |
+
for c in est_inputs:
|
| 348 |
+
c.change(update_estimate, est_inputs, estimate)
|
| 349 |
+
demo.load(update_estimate, est_inputs, estimate)
|
| 350 |
+
|
| 351 |
+
run.click(
|
| 352 |
+
generate,
|
| 353 |
+
[image, prompt, audio_prompt, resolution, frames, steps, cfg, shift, audio_shift,
|
| 354 |
+
seed, randomize_seed, sparsity, use_ivpq],
|
| 355 |
+
[out_video, used_seed, info],
|
| 356 |
+
)
|
| 357 |
+
if examples:
|
| 358 |
+
gr.Examples(examples, [image, prompt])
|
| 359 |
+
|
| 360 |
+
demo.queue(max_size=20).launch()
|
examples/case-1/prompt.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
No background music, quiet recording environment, close-mic capture, no noticeable reverb. Close-up, eye-level view, softly lit with frontal lighting. The frame features an elderly white woman with short gray curly hair, blue eyes, deeply lined face, flushed cheeks, and red lipstick. She wears a black coat and a red scarf with a white floral pattern, a black microphone clipped to her collar. She sits in front of a brown wooden piece of furniture, with a pale yellow textured wall behind her, on which hangs a miniature model depicting a canal and Dutch-style buildings on both sides. Initially, the elderly white woman faces the camera, lips moving, speaking in a hoarse voice with a heavy expression<speech>weg haden ze nog gezegd, ze liepen weg.</speech>. Then, she briefly lowers her head, her gaze moving downward, mouth slightly open as if sighing. Immediately afterward, she raises her head to look at the camera again, continuing<speech>Nou, dan kwam de directie er bij, want de directeur moest oepen.</speech>, blinking slowly throughout, her expression solemn.
|
examples/case-1/reference_image.jpg
ADDED
|
Git LFS Details
|
examples/case-2/prompt.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Continuous <sfx>ambient store sounds, including people talking and footsteps</sfx> throughout. Medium close-up, eye-level view. Inside an indoor store with bright lighting. The main focus is a man and a woman conversing. On the left side of the frame is a young-to-middle-aged white woman wearing glasses, with brown wavy medium-short hair, wearing a black jacket and a dark red inner layer, holding a dark-colored wine bottle with a black label featuring gold English text <text>THE髭振酒</text>. On the right side is a middle-aged white man with a beard, receding hairline, wearing a gray plaid suit jacket and a light blue shirt, holding a green wine bottle with a white label featuring a minimalist black line pattern and text. Behind them are shelves filled with products, with multiple customers and staff walking and shopping in the background, and a row of track spotlights on the ceiling. Initially, the man in the gray suit holds up the green wine bottle, looking at the woman across from him. Then he dramatically widens his eyes, showing the bottle to her, enthusiastically saying <speech>And the second one that we have a wine.</speech>. The woman lowers her head, looking at her own dark-colored bottle, smiling. Immediately afterward, the woman in the black jacket looks up at the man, asking curiously <speech>They are also do a wine?</speech>. The man nods affirmatively, saying <speech>Yep.</speech>. The woman confirms, saying <speech>The messa.</speech>. The man then raises his left hand in a gesture of explanation, continuing to speak <speech>So when you don't know it, you're kind of afraid because maple.</speech>"}
|
examples/case-2/reference_image.jpg
ADDED
|
Git LFS Details
|
examples/case-3/prompt.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Continuous <music>solemn and expressive classical chamber music performance throughout, featuring two violins, one cello, one harpsichord, and one bassoon, with intertwined melodies and rich harmonies producing noticeable reverb in the stone-walled church space.</music>. High-angle wide shot, fixed frame: Gothic stone church interior. Soft, cool light from left arched window. White walls with carved memorials and shield emblems. Gray stone floor, red-and-black diamond tile path center. Eight musicians in semicircle. Center: young white male harpsichordist (black long-sleeve, facing away). Foreground left: middle-aged white female violinist (blonde, purple lace top, black dress). Beside her: balding middle-aged white male violinist. Foreground right: young white male cellist (brown short hair). Background: middle-aged white female violinist (blonde), balding middle-aged white male bassoonist, two standing male musicians. Each has black music stand. Microphones on floor. As the shot begins, all musicians start playing their instruments, gently swaying their bodies in rhythm with the music. <music>A solemn and expressive classical chamber music performance begins, featuring two violins, one cello, one harpsichord, and one bassoon, with intertwined melodies and rich harmonies producing noticeable reverb in the stone-walled church space.</music> The male and female violinists on the left and the cellist on the right are focusedly drawing their bows across the strings, fingers moving swiftly along the strings. The central harpsichordist's hands move rapidly across the keyboard while glancing sideways at the sheet music. The bassoonist in the background is playing his instrument. As the piece progresses, a cellist from the far right edge of the frame enters the shot and begins playing.
|
examples/case-3/reference_image.jpg
ADDED
|
Git LFS Details
|
examples/case-4/prompt.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<sfx>Constant noisy background sounds from the gym throughout, including equipment clanging, dumbbell plates hitting, and muffled conversations, with noticeable spatial reverb.</sfx>. Medium shot with a slight high-angle view, indoor gym setting, bright lighting. The main subject is a young white woman sitting on a black gym bench, with long golden ponytail, wearing white in-ear headphones, and a large black mandala tattoo on her left arm, though details like the medical giant logo beneath are somewhat blurred. She wears a white sports bra and olive-green ribbed leggings, and black-and-white Adidas sneakers. On the left side of the frame is a blue fitness equipment column with a warning label, an orange warning tag marked <text>WARNING</text>, and a white label below. The background is a white wall with blue brand text visible, partially showing <text>Nordic</text>, and <text>to</text> visible on the right. Other gym equipment and multiple members exercising are visible, including a man in a black hoodie and a woman in a light gray hoodie. Initially, the blonde woman keeps her upper body lifted with bent legs, arms hugging her knees close to her chest. Her mouth is slightly open, looking directly at the camera, beginning her explanation in a slightly hoarse but clear and strong voice in Swedish: <speech>So try to do or this can a try to move bengal。</speech>. Then, while maintaining the knee-hug position, she extends her right index finger forward, then brings it back, adjusting her left hand's position on her knee. Next, she moves her left hand to support her left hip and waist, while bringing her right knee to the side and tilting her body backward. Subsequently, she keeps her left leg bent for support, extends her right leg, and slowly moves it upward and downward in rhythm with the command, completing one snow angel-like motion. Throughout and after the movement, she continues speaking directly to the camera: <speech>Can she tell me about the room and lift it little from that? Are they just broke in them now we could。</speech>
|
examples/case-4/reference_image.jpg
ADDED
|
Git LFS Details
|
examples/case-5/prompt.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Continuous <sfx>sound of waves and water currents around the boat</sfx> throughout. Medium shot, eye-level view. During the day, in clear outdoor conditions, a middle-aged white man stands on a boat facing the camera. He wears a gray baseball cap and blue-tinted sunglasses, a gray short-sleeved T-shirt with a brand logo featuring <text>YETI</text> on the chest, mostly obscured by the large fish he holds. He cradles a massive dark brown grouper with a wet, shiny surface reflecting sunlight and a serrated dorsal fin. A white outboard motor is located behind his left, marked with black <text>EVINRUDE</text>, partially hidden by the fish's tail at times. On the right side of the frame, another man's profile is visible—bearded, wearing a hat, holding a fishing rod. The background shows a vast blue ocean under a sky with a few scattered clouds. Initially, the man with sunglasses beams a bright smile, enthusiastically saying <speech>I mean, so far we've caught Marlin, Cooper, I mean, a Wahoo,we got slam. Why stop now?</speech> to the camera. His expression is highly animated. Then, he briefly turns his head to the right to look at the man with the fishing rod beside him, before turning back, maintaining a joyful expression throughout. The camera gently shakes due to the boat's movement during the entire sequence.
|
examples/case-5/reference_image.jpg
ADDED
|
Git LFS Details
|
examples/case-6/prompt.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
No background music, with slight ambient outdoor noise. Eye-level medium close-up, centered composition. On a sunny day, a young white woman and a young South Asian man sit at a round black grid table under a black patio umbrella. The woman is on the left side of the frame, with long brown hair, wearing a pink sleeveless dress, and a thin black bracelet on her left wrist. The man is on the right side, with short black hair, wearing a blue short-sleeve Polo shirt with a black microphone clipped at the collar. A glass of water sits on the table in front of them. The scene is set on a wooden deck, with open water, distant apartment buildings, and green trees in the background, and large white clouds in the sky. Initially, the man in the blue Polo shirt looks at the woman across from him, raises both hands in gesture, and says in a gentle tone <speech>That's okay. We can order。</speech>. Then, the woman in the pink dress becomes serious, lowers her hands, and gently shakes her head, responding with a disappointed tone <speech>It's not about the wine. Nothing ever works out for me.</speech>. She then lifts her right hand and touches the glass on the table. The man leans forward slightly, listening intently. The woman then asks in a curious, probing tone <speech>Why do you say that?</speech>. The woman continues to look at him seriously and explains <speech>You never smelled at me when I smelled at you back at the start, so I decided to use hope this time.</speech>
|
examples/case-6/reference_image.jpg
ADDED
|
Git LFS Details
|
hymm/diffusion/__init__.py
ADDED
|
File without changes
|
hymm/diffusion/pipelines/__init__.py
ADDED
|
File without changes
|
hymm/diffusion/pipelines/mova_pipeline.py
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import gc
|
| 3 |
+
import html
|
| 4 |
+
import re
|
| 5 |
+
from typing import Any, List, Optional, Tuple, Union
|
| 6 |
+
|
| 7 |
+
import ftfy
|
| 8 |
+
import torch
|
| 9 |
+
import torch.distributed as dist
|
| 10 |
+
from diffusers.models.autoencoders import AutoencoderKLWan
|
| 11 |
+
from diffusers.utils.torch_utils import randn_tensor
|
| 12 |
+
from diffusers.video_processor import VideoProcessor
|
| 13 |
+
from diffusers.image_processor import PipelineImageInput
|
| 14 |
+
from tqdm import tqdm
|
| 15 |
+
from transformers import T5TokenizerFast, UMT5EncoderModel
|
| 16 |
+
|
| 17 |
+
from hymm.models.modules.mova import MOVABridge
|
| 18 |
+
from hymm.models.modules.dac_vae import DAC
|
| 19 |
+
from hymm.diffusion.schedulers.flow_match_pair import FlowMatchPairScheduler
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _basic_clean(text):
|
| 23 |
+
text = ftfy.fix_text(text)
|
| 24 |
+
text = html.unescape(html.unescape(text))
|
| 25 |
+
return text.strip()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _whitespace_clean(text):
|
| 29 |
+
text = re.sub(r"\s+", " ", text)
|
| 30 |
+
return text.strip()
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _prompt_clean(text):
|
| 34 |
+
return _whitespace_clean(_basic_clean(text))
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _retrieve_latents(encoder_output, generator=None, sample_mode="sample"):
|
| 38 |
+
if hasattr(encoder_output, "latent_dist") and sample_mode == "sample":
|
| 39 |
+
return encoder_output.latent_dist.sample(generator)
|
| 40 |
+
elif hasattr(encoder_output, "latent_dist") and sample_mode == "argmax":
|
| 41 |
+
return encoder_output.latent_dist.mode()
|
| 42 |
+
elif hasattr(encoder_output, "latents"):
|
| 43 |
+
return encoder_output.latents
|
| 44 |
+
else:
|
| 45 |
+
raise AttributeError("Could not access latents of provided encoder_output")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class MOVAPipeline:
|
| 49 |
+
"""
|
| 50 |
+
Pipeline that drives MOVA inference identically to the original
|
| 51 |
+
MOVA/mova/diffusion/pipelines/pipeline_mova.py.
|
| 52 |
+
|
| 53 |
+
Differences from the original:
|
| 54 |
+
- Does NOT inherit from DiffusionPipeline (no auto-offload magic).
|
| 55 |
+
- Uses MOVABridge as the transformer, which already holds video_dit(s),
|
| 56 |
+
audio_dit, and bridge.
|
| 57 |
+
- Compatible with the codebase's DeepSpeed / FSDP launcher.
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
def __init__(
|
| 61 |
+
self,
|
| 62 |
+
transformer: MOVABridge,
|
| 63 |
+
video_vae: AutoencoderKLWan,
|
| 64 |
+
audio_vae: DAC,
|
| 65 |
+
text_encoder: UMT5EncoderModel,
|
| 66 |
+
tokenizer: T5TokenizerFast,
|
| 67 |
+
scheduler: FlowMatchPairScheduler,
|
| 68 |
+
boundary_ratio: float = 0.9,
|
| 69 |
+
device=None,
|
| 70 |
+
):
|
| 71 |
+
self.transformer = transformer
|
| 72 |
+
self.video_vae = video_vae
|
| 73 |
+
self.audio_vae = audio_vae
|
| 74 |
+
self.text_encoder = text_encoder
|
| 75 |
+
self.tokenizer = tokenizer
|
| 76 |
+
self.scheduler = scheduler
|
| 77 |
+
self.boundary_ratio = boundary_ratio
|
| 78 |
+
|
| 79 |
+
self.vae_scale_factor_spatial = self.video_vae.config.scale_factor_spatial
|
| 80 |
+
self.vae_scale_factor_temporal = self.video_vae.config.scale_factor_temporal
|
| 81 |
+
self.video_processor = VideoProcessor(vae_scale_factor=self.vae_scale_factor_spatial)
|
| 82 |
+
|
| 83 |
+
self.audio_vae_scale_factor = int(self.audio_vae.hop_length)
|
| 84 |
+
self.audio_sample_rate = self.audio_vae.sample_rate
|
| 85 |
+
|
| 86 |
+
self._device = device
|
| 87 |
+
self._cpu_offload = False
|
| 88 |
+
|
| 89 |
+
@property
|
| 90 |
+
def device(self):
|
| 91 |
+
if self._device is not None:
|
| 92 |
+
return self._device
|
| 93 |
+
for p in self.transformer.parameters():
|
| 94 |
+
return p.device
|
| 95 |
+
return torch.device("cpu")
|
| 96 |
+
|
| 97 |
+
def to(self, device):
|
| 98 |
+
self._device = device
|
| 99 |
+
self.transformer.to(device)
|
| 100 |
+
self.video_vae.to(device)
|
| 101 |
+
self.audio_vae.to(device)
|
| 102 |
+
self.text_encoder.to(device)
|
| 103 |
+
return self
|
| 104 |
+
|
| 105 |
+
def enable_cpu_offload(self, gpu_device=None):
|
| 106 |
+
"""Enable sequential CPU offload to reduce peak GPU memory.
|
| 107 |
+
|
| 108 |
+
Only the active component lives on GPU at any time:
|
| 109 |
+
1. text_encoder on GPU -> encode prompts -> move to CPU
|
| 110 |
+
2. video_vae on GPU -> encode condition latents -> move to CPU
|
| 111 |
+
3. transformer on GPU -> denoising loop -> move to CPU
|
| 112 |
+
4. video_vae + audio_vae on GPU -> decode -> move to CPU
|
| 113 |
+
"""
|
| 114 |
+
self._cpu_offload = True
|
| 115 |
+
if gpu_device is not None:
|
| 116 |
+
self._device = gpu_device
|
| 117 |
+
|
| 118 |
+
def normalize_video_latents(self, latents):
|
| 119 |
+
mean = torch.tensor(
|
| 120 |
+
self.video_vae.config.latents_mean, device=latents.device, dtype=latents.dtype,
|
| 121 |
+
).view(1, self.video_vae.config.z_dim, 1, 1, 1)
|
| 122 |
+
inv_std = (1.0 / torch.tensor(
|
| 123 |
+
self.video_vae.config.latents_std, device=latents.device, dtype=latents.dtype,
|
| 124 |
+
)).view(1, self.video_vae.config.z_dim, 1, 1, 1)
|
| 125 |
+
return (latents - mean) * inv_std
|
| 126 |
+
|
| 127 |
+
def denormalize_video_latents(self, latents):
|
| 128 |
+
mean = torch.tensor(
|
| 129 |
+
self.video_vae.config.latents_mean, device=latents.device, dtype=latents.dtype,
|
| 130 |
+
).view(1, self.video_vae.config.z_dim, 1, 1, 1)
|
| 131 |
+
std = torch.tensor(
|
| 132 |
+
self.video_vae.config.latents_std, device=latents.device, dtype=latents.dtype,
|
| 133 |
+
).view(1, self.video_vae.config.z_dim, 1, 1, 1)
|
| 134 |
+
return latents * std + mean
|
| 135 |
+
|
| 136 |
+
def check_inputs(self, height, width, num_frames):
|
| 137 |
+
target_div = self.vae_scale_factor_spatial * 2
|
| 138 |
+
if height % target_div != 0 or width % target_div != 0:
|
| 139 |
+
raise ValueError(
|
| 140 |
+
f"`height` and `width` must be divisible by {target_div}, "
|
| 141 |
+
f"got {height} and {width}."
|
| 142 |
+
)
|
| 143 |
+
if num_frames % self.vae_scale_factor_temporal != 1:
|
| 144 |
+
raise ValueError(
|
| 145 |
+
f"`num_frames - 1` must be divisible by {self.vae_scale_factor_temporal}, "
|
| 146 |
+
f"got {num_frames - 1}."
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
def prepare_latents(
|
| 150 |
+
self,
|
| 151 |
+
image,
|
| 152 |
+
batch_size,
|
| 153 |
+
num_channels_latents,
|
| 154 |
+
height,
|
| 155 |
+
width,
|
| 156 |
+
num_frames,
|
| 157 |
+
dtype,
|
| 158 |
+
device,
|
| 159 |
+
generator=None,
|
| 160 |
+
latents=None,
|
| 161 |
+
last_image=None,
|
| 162 |
+
):
|
| 163 |
+
num_latent_frames = (num_frames - 1) // self.vae_scale_factor_temporal + 1
|
| 164 |
+
latent_height = height // self.vae_scale_factor_spatial
|
| 165 |
+
latent_width = width // self.vae_scale_factor_spatial
|
| 166 |
+
|
| 167 |
+
shape = (batch_size, num_channels_latents, num_latent_frames, latent_height, latent_width)
|
| 168 |
+
|
| 169 |
+
if latents is None:
|
| 170 |
+
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
| 171 |
+
else:
|
| 172 |
+
latents = latents.to(device=device, dtype=dtype)
|
| 173 |
+
|
| 174 |
+
image = image.unsqueeze(2)
|
| 175 |
+
|
| 176 |
+
if last_image is None:
|
| 177 |
+
video_condition = torch.cat(
|
| 178 |
+
[image, image.new_zeros(image.shape[0], image.shape[1], num_frames - 1, height, width)], dim=2,
|
| 179 |
+
)
|
| 180 |
+
else:
|
| 181 |
+
last_image = last_image.unsqueeze(2)
|
| 182 |
+
video_condition = torch.cat(
|
| 183 |
+
[image, image.new_zeros(image.shape[0], image.shape[1], num_frames - 2, height, width), last_image],
|
| 184 |
+
dim=2,
|
| 185 |
+
)
|
| 186 |
+
video_condition = video_condition.to(device=device, dtype=self.video_vae.dtype)
|
| 187 |
+
|
| 188 |
+
if isinstance(generator, list):
|
| 189 |
+
latent_condition = [
|
| 190 |
+
_retrieve_latents(self.video_vae.encode(video_condition), sample_mode="argmax")
|
| 191 |
+
for _ in generator
|
| 192 |
+
]
|
| 193 |
+
latent_condition = torch.cat(latent_condition)
|
| 194 |
+
else:
|
| 195 |
+
latent_condition = _retrieve_latents(
|
| 196 |
+
self.video_vae.encode(video_condition), sample_mode="argmax",
|
| 197 |
+
)
|
| 198 |
+
latent_condition = latent_condition.repeat(batch_size, 1, 1, 1, 1)
|
| 199 |
+
|
| 200 |
+
latent_condition = latent_condition.to(dtype)
|
| 201 |
+
latent_condition = self.normalize_video_latents(latent_condition)
|
| 202 |
+
|
| 203 |
+
mask_lat_size = torch.ones(batch_size, 1, num_frames, latent_height, latent_width)
|
| 204 |
+
if last_image is None:
|
| 205 |
+
mask_lat_size[:, :, list(range(1, num_frames))] = 0
|
| 206 |
+
else:
|
| 207 |
+
mask_lat_size[:, :, list(range(1, num_frames - 1))] = 0
|
| 208 |
+
first_frame_mask = mask_lat_size[:, :, 0:1]
|
| 209 |
+
first_frame_mask = torch.repeat_interleave(first_frame_mask, dim=2, repeats=self.vae_scale_factor_temporal)
|
| 210 |
+
mask_lat_size = torch.concat([first_frame_mask, mask_lat_size[:, :, 1:, :]], dim=2)
|
| 211 |
+
mask_lat_size = mask_lat_size.view(batch_size, -1, self.vae_scale_factor_temporal, latent_height, latent_width)
|
| 212 |
+
mask_lat_size = mask_lat_size.transpose(1, 2)
|
| 213 |
+
mask_lat_size = mask_lat_size.to(latent_condition.device)
|
| 214 |
+
|
| 215 |
+
return latents, torch.concat([mask_lat_size, latent_condition], dim=1)
|
| 216 |
+
|
| 217 |
+
def prepare_audio_latents(
|
| 218 |
+
self,
|
| 219 |
+
audio,
|
| 220 |
+
batch_size,
|
| 221 |
+
num_channels,
|
| 222 |
+
num_samples,
|
| 223 |
+
dtype,
|
| 224 |
+
device,
|
| 225 |
+
generator=None,
|
| 226 |
+
latents=None,
|
| 227 |
+
):
|
| 228 |
+
latent_t = (num_samples - 1) // self.audio_vae_scale_factor + 1
|
| 229 |
+
shape = (batch_size, num_channels, latent_t)
|
| 230 |
+
if latents is None:
|
| 231 |
+
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
| 232 |
+
else:
|
| 233 |
+
latents = latents.to(device=device, dtype=dtype)
|
| 234 |
+
return latents
|
| 235 |
+
|
| 236 |
+
def _get_t5_prompt_embeds(
|
| 237 |
+
self,
|
| 238 |
+
prompt,
|
| 239 |
+
num_videos_per_prompt=1,
|
| 240 |
+
max_sequence_length=512,
|
| 241 |
+
device=None,
|
| 242 |
+
dtype=None,
|
| 243 |
+
):
|
| 244 |
+
device = device or self.device
|
| 245 |
+
dtype = dtype or self.text_encoder.dtype
|
| 246 |
+
|
| 247 |
+
prompt = [prompt] if isinstance(prompt, str) else prompt
|
| 248 |
+
prompt = [_prompt_clean(u) for u in prompt]
|
| 249 |
+
batch_size = len(prompt)
|
| 250 |
+
|
| 251 |
+
text_inputs = self.tokenizer(
|
| 252 |
+
prompt,
|
| 253 |
+
padding="max_length",
|
| 254 |
+
max_length=max_sequence_length,
|
| 255 |
+
truncation=True,
|
| 256 |
+
add_special_tokens=True,
|
| 257 |
+
return_attention_mask=True,
|
| 258 |
+
return_tensors="pt",
|
| 259 |
+
)
|
| 260 |
+
text_input_ids, mask = text_inputs.input_ids, text_inputs.attention_mask
|
| 261 |
+
seq_lens = mask.gt(0).sum(dim=1).long()
|
| 262 |
+
|
| 263 |
+
prompt_embeds = self.text_encoder(
|
| 264 |
+
text_input_ids.to(device), mask.to(device),
|
| 265 |
+
).last_hidden_state
|
| 266 |
+
prompt_embeds = prompt_embeds.to(dtype=dtype, device=device)
|
| 267 |
+
prompt_embeds = [u[:v] for u, v in zip(prompt_embeds, seq_lens)]
|
| 268 |
+
prompt_embeds = torch.stack(
|
| 269 |
+
[torch.cat([u, u.new_zeros(max_sequence_length - u.size(0), u.size(1))]) for u in prompt_embeds],
|
| 270 |
+
dim=0,
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
_, seq_len, _ = prompt_embeds.shape
|
| 274 |
+
prompt_embeds = prompt_embeds.repeat(1, num_videos_per_prompt, 1)
|
| 275 |
+
prompt_embeds = prompt_embeds.view(batch_size * num_videos_per_prompt, seq_len, -1)
|
| 276 |
+
return prompt_embeds
|
| 277 |
+
|
| 278 |
+
@torch.no_grad()
|
| 279 |
+
def __call__(
|
| 280 |
+
self,
|
| 281 |
+
prompt,
|
| 282 |
+
image,
|
| 283 |
+
audio_prompt=None,
|
| 284 |
+
negative_prompt="",
|
| 285 |
+
seed=42,
|
| 286 |
+
height=360,
|
| 287 |
+
width=640,
|
| 288 |
+
num_frames=193,
|
| 289 |
+
video_fps=24.0,
|
| 290 |
+
num_inference_steps=50,
|
| 291 |
+
visual_shift=5.0,
|
| 292 |
+
audio_shift=5.0,
|
| 293 |
+
cfg_scale=5.0,
|
| 294 |
+
remove_video_dit=False,
|
| 295 |
+
enable_vae_tiling=False,
|
| 296 |
+
vae_tile_sample_min_size=None,
|
| 297 |
+
vae_tile_sample_stride=None,
|
| 298 |
+
):
|
| 299 |
+
"""
|
| 300 |
+
Run the full MOVA denoising loop.
|
| 301 |
+
This replicates the exact logic from MOVA's __call__.
|
| 302 |
+
|
| 303 |
+
VAE tiling (Phase 4 decode only, fully isolated / default OFF):
|
| 304 |
+
enable_vae_tiling: when True, the video VAE decodes the latents tile by
|
| 305 |
+
tile (spatial), drastically cutting peak decode VRAM so that high-res
|
| 306 |
+
(e.g. 2K) videos no longer OOM. When False the decode path is byte-for-
|
| 307 |
+
byte identical to the original (VAE tiling is never touched).
|
| 308 |
+
vae_tile_sample_min_size: tile size (pixels, applied to both H and W).
|
| 309 |
+
None -> diffusers/Wan default (256), same as decode_wan_vae.py.
|
| 310 |
+
vae_tile_sample_stride: stride between tiles (pixels, H and W). overlap =
|
| 311 |
+
tile_size - stride. None -> diffusers/Wan default (192), i.e. 25% overlap,
|
| 312 |
+
same as decode_wan_vae.py.
|
| 313 |
+
|
| 314 |
+
Note on FSDP + SP: only the transformer is FSDP-sharded; the video VAE is a
|
| 315 |
+
full replica on every rank and the post-denoise latents are identical across
|
| 316 |
+
all SP ranks. VAE tiling is deterministic, so every rank produces the SAME
|
| 317 |
+
decoded video whether or not tiling is enabled.
|
| 318 |
+
"""
|
| 319 |
+
self.check_inputs(height, width, num_frames)
|
| 320 |
+
|
| 321 |
+
audio_num_samples = int(self.audio_sample_rate * num_frames / video_fps)
|
| 322 |
+
device = self.device
|
| 323 |
+
offload = self._cpu_offload
|
| 324 |
+
|
| 325 |
+
# Deterministic noise generator (independent of global random state)
|
| 326 |
+
generator = torch.Generator(device=device)
|
| 327 |
+
generator.manual_seed(seed)
|
| 328 |
+
|
| 329 |
+
# ------------------------------------------------------------------
|
| 330 |
+
# Phase 1: Text encoding
|
| 331 |
+
# ------------------------------------------------------------------
|
| 332 |
+
if offload:
|
| 333 |
+
self.text_encoder.to(device)
|
| 334 |
+
|
| 335 |
+
self.scheduler.set_timesteps(num_inference_steps, shift=visual_shift, device=device)
|
| 336 |
+
self.scheduler.set_pair_postprocess_by_name(
|
| 337 |
+
"dual_sigma_shift",
|
| 338 |
+
visual_shift=float(visual_shift),
|
| 339 |
+
audio_shift=float(audio_shift),
|
| 340 |
+
)
|
| 341 |
+
paired_timesteps = self.scheduler.get_pairs()
|
| 342 |
+
|
| 343 |
+
prompt_embeds = self._get_t5_prompt_embeds(prompt)
|
| 344 |
+
audio_prompt_embeds = self._get_t5_prompt_embeds(audio_prompt) if audio_prompt is not None else None
|
| 345 |
+
negative_prompt_embeds = self._get_t5_prompt_embeds(negative_prompt)
|
| 346 |
+
|
| 347 |
+
if offload:
|
| 348 |
+
self.text_encoder.to("cpu")
|
| 349 |
+
torch.cuda.empty_cache()
|
| 350 |
+
|
| 351 |
+
# ------------------------------------------------------------------
|
| 352 |
+
# Phase 2: VAE encode (prepare latents)
|
| 353 |
+
# ------------------------------------------------------------------
|
| 354 |
+
if offload:
|
| 355 |
+
self.video_vae.to(device)
|
| 356 |
+
|
| 357 |
+
num_channels_latents = self.video_vae.config.z_dim
|
| 358 |
+
image = self.video_processor.preprocess(image, height=height, width=width).to(device, dtype=torch.float32)
|
| 359 |
+
|
| 360 |
+
latents, condition = self.prepare_latents(
|
| 361 |
+
image, 1, num_channels_latents, height, width, num_frames,
|
| 362 |
+
torch.float32, device, generator=generator, latents=None, last_image=None,
|
| 363 |
+
)
|
| 364 |
+
audio_latents = self.prepare_audio_latents(
|
| 365 |
+
None, 1, self.audio_vae.latent_dim, audio_num_samples,
|
| 366 |
+
torch.float32, device, generator=generator, latents=None,
|
| 367 |
+
)
|
| 368 |
+
|
| 369 |
+
if offload:
|
| 370 |
+
self.video_vae.to("cpu")
|
| 371 |
+
torch.cuda.empty_cache()
|
| 372 |
+
|
| 373 |
+
# ------------------------------------------------------------------
|
| 374 |
+
# Phase 3: Denoising loop (transformer on GPU)
|
| 375 |
+
# ------------------------------------------------------------------
|
| 376 |
+
if offload:
|
| 377 |
+
self.transformer.to(device)
|
| 378 |
+
|
| 379 |
+
cur_use_dit_2 = False
|
| 380 |
+
total_steps = paired_timesteps.shape[0]
|
| 381 |
+
switched = False
|
| 382 |
+
boundary_timestep = self.boundary_ratio * self.scheduler.config.num_train_timesteps
|
| 383 |
+
|
| 384 |
+
is_main = not dist.is_initialized() or dist.get_rank() == 0
|
| 385 |
+
|
| 386 |
+
for idx_step in tqdm(range(total_steps), disable=not is_main):
|
| 387 |
+
timestep, audio_timestep = paired_timesteps[idx_step]
|
| 388 |
+
|
| 389 |
+
if not switched and timestep.item() < boundary_timestep:
|
| 390 |
+
cur_use_dit_2 = True
|
| 391 |
+
if remove_video_dit:
|
| 392 |
+
self.transformer.video_dit = None
|
| 393 |
+
gc.collect()
|
| 394 |
+
switched = True
|
| 395 |
+
|
| 396 |
+
latent_model_input = torch.cat([latents, condition], dim=1)
|
| 397 |
+
timestep_t = timestep.unsqueeze(0).to(device=device, dtype=torch.float32)
|
| 398 |
+
audio_timestep_t = audio_timestep.unsqueeze(0).to(device=device, dtype=torch.float32)
|
| 399 |
+
|
| 400 |
+
noise_pred_posi = self.transformer(
|
| 401 |
+
visual_latents=latent_model_input,
|
| 402 |
+
audio_latents=audio_latents,
|
| 403 |
+
context=prompt_embeds,
|
| 404 |
+
timestep=timestep_t,
|
| 405 |
+
audio_context=audio_prompt_embeds,
|
| 406 |
+
audio_timestep=audio_timestep_t,
|
| 407 |
+
video_fps=video_fps,
|
| 408 |
+
num_train_timesteps=self.scheduler.config.num_train_timesteps,
|
| 409 |
+
use_video_dit_2=cur_use_dit_2,
|
| 410 |
+
)
|
| 411 |
+
|
| 412 |
+
if cfg_scale == 1.0:
|
| 413 |
+
visual_noise_pred = noise_pred_posi[0].float()
|
| 414 |
+
audio_noise_pred = noise_pred_posi[1].float()
|
| 415 |
+
else:
|
| 416 |
+
noise_pred_nega = self.transformer(
|
| 417 |
+
visual_latents=latent_model_input,
|
| 418 |
+
audio_latents=audio_latents,
|
| 419 |
+
context=negative_prompt_embeds,
|
| 420 |
+
timestep=timestep_t,
|
| 421 |
+
audio_context=None,
|
| 422 |
+
audio_timestep=audio_timestep_t,
|
| 423 |
+
video_fps=video_fps,
|
| 424 |
+
num_train_timesteps=self.scheduler.config.num_train_timesteps,
|
| 425 |
+
use_video_dit_2=cur_use_dit_2,
|
| 426 |
+
)
|
| 427 |
+
visual_noise_pred_nega = noise_pred_nega[0].float()
|
| 428 |
+
audio_noise_pred_nega = noise_pred_nega[1].float()
|
| 429 |
+
visual_noise_pred_posi = noise_pred_posi[0].float()
|
| 430 |
+
audio_noise_pred_posi = noise_pred_posi[1].float()
|
| 431 |
+
visual_noise_pred = visual_noise_pred_nega + cfg_scale * (visual_noise_pred_posi - visual_noise_pred_nega)
|
| 432 |
+
audio_noise_pred = audio_noise_pred_nega + cfg_scale * (audio_noise_pred_posi - audio_noise_pred_nega)
|
| 433 |
+
|
| 434 |
+
next_timestep = paired_timesteps[idx_step + 1, 0] if idx_step + 1 < total_steps else None
|
| 435 |
+
next_audio_timestep = paired_timesteps[idx_step + 1, 1] if idx_step + 1 < total_steps else None
|
| 436 |
+
latents = self.scheduler.step_from_to(
|
| 437 |
+
visual_noise_pred, timestep, next_timestep, latents,
|
| 438 |
+
)
|
| 439 |
+
audio_latents = self.scheduler.step_from_to(
|
| 440 |
+
audio_noise_pred, audio_timestep, next_audio_timestep, audio_latents,
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
if offload:
|
| 444 |
+
self.transformer.to("cpu")
|
| 445 |
+
torch.cuda.empty_cache()
|
| 446 |
+
|
| 447 |
+
# ------------------------------------------------------------------
|
| 448 |
+
# Phase 4: VAE decode
|
| 449 |
+
# ------------------------------------------------------------------
|
| 450 |
+
if offload:
|
| 451 |
+
self.video_vae.to(device)
|
| 452 |
+
|
| 453 |
+
# Optional tiled VAE decode (default OFF -> original path untouched). When
|
| 454 |
+
# enabled, decode the latents tile-by-tile spatially to cut peak decode VRAM
|
| 455 |
+
# for high-res (e.g. 2K) videos. Mirrors decode_wan_vae.py: min_size/stride
|
| 456 |
+
# map to the AutoencoderKLWan (height & width) tiling knobs; None keeps the
|
| 457 |
+
# diffusers defaults (256 / 192).
|
| 458 |
+
if enable_vae_tiling:
|
| 459 |
+
tiling_kwargs = {}
|
| 460 |
+
if vae_tile_sample_min_size is not None:
|
| 461 |
+
tiling_kwargs["tile_sample_min_height"] = vae_tile_sample_min_size
|
| 462 |
+
tiling_kwargs["tile_sample_min_width"] = vae_tile_sample_min_size
|
| 463 |
+
if vae_tile_sample_stride is not None:
|
| 464 |
+
tiling_kwargs["tile_sample_stride_height"] = vae_tile_sample_stride
|
| 465 |
+
tiling_kwargs["tile_sample_stride_width"] = vae_tile_sample_stride
|
| 466 |
+
self.video_vae.enable_tiling(**tiling_kwargs)
|
| 467 |
+
|
| 468 |
+
video_latents = self.denormalize_video_latents(latents)
|
| 469 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 470 |
+
video = self.video_vae.decode(video_latents).sample
|
| 471 |
+
video = self.video_processor.postprocess_video(video, output_type="pil")
|
| 472 |
+
|
| 473 |
+
if offload:
|
| 474 |
+
self.video_vae.to("cpu")
|
| 475 |
+
self.audio_vae.to(device)
|
| 476 |
+
torch.cuda.empty_cache()
|
| 477 |
+
|
| 478 |
+
with torch.autocast("cuda", dtype=torch.float32):
|
| 479 |
+
audio = self.audio_vae.decode(audio_latents)
|
| 480 |
+
|
| 481 |
+
if offload:
|
| 482 |
+
self.audio_vae.to("cpu")
|
| 483 |
+
torch.cuda.empty_cache()
|
| 484 |
+
|
| 485 |
+
return video, audio
|
hymm/diffusion/schedulers/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .flow_match_pair import FlowMatchPairScheduler
|
hymm/diffusion/schedulers/flow_match_pair.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 5 |
+
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class FlowMatchSchedulerBase:
|
| 9 |
+
def __init__(
|
| 10 |
+
self,
|
| 11 |
+
num_inference_steps=100,
|
| 12 |
+
num_train_timesteps=1000,
|
| 13 |
+
shift=3.0,
|
| 14 |
+
sigma_max=1.0,
|
| 15 |
+
sigma_min=0.003 / 1.002,
|
| 16 |
+
inverse_timesteps=False,
|
| 17 |
+
extra_one_step=False,
|
| 18 |
+
reverse_sigmas=False,
|
| 19 |
+
exponential_shift=False,
|
| 20 |
+
exponential_shift_mu=None,
|
| 21 |
+
shift_terminal=None,
|
| 22 |
+
):
|
| 23 |
+
self.num_train_timesteps = num_train_timesteps
|
| 24 |
+
self.shift = shift
|
| 25 |
+
self.sigma_max = sigma_max
|
| 26 |
+
self.sigma_min = sigma_min
|
| 27 |
+
self.inverse_timesteps = inverse_timesteps
|
| 28 |
+
self.extra_one_step = extra_one_step
|
| 29 |
+
self.reverse_sigmas = reverse_sigmas
|
| 30 |
+
self.exponential_shift = exponential_shift
|
| 31 |
+
self.exponential_shift_mu = exponential_shift_mu
|
| 32 |
+
self.shift_terminal = shift_terminal
|
| 33 |
+
self.train_timesteps = None
|
| 34 |
+
self.train_sigmas = None
|
| 35 |
+
self.set_timesteps(num_train_timesteps)
|
| 36 |
+
self.set_timesteps(num_inference_steps)
|
| 37 |
+
|
| 38 |
+
def set_timesteps(self, num_inference_steps=100, denoising_strength=1.0, training=False, shift=None, dynamic_shift_len=None, device=None):
|
| 39 |
+
if shift is not None:
|
| 40 |
+
self.shift = shift
|
| 41 |
+
sigma_start = self.sigma_min + (self.sigma_max - self.sigma_min) * denoising_strength
|
| 42 |
+
if self.extra_one_step:
|
| 43 |
+
self.sigmas = torch.linspace(sigma_start, self.sigma_min, num_inference_steps + 1)[:-1]
|
| 44 |
+
else:
|
| 45 |
+
self.sigmas = torch.linspace(sigma_start, self.sigma_min, num_inference_steps)
|
| 46 |
+
if self.inverse_timesteps:
|
| 47 |
+
self.sigmas = torch.flip(self.sigmas, dims=[0])
|
| 48 |
+
if self.exponential_shift:
|
| 49 |
+
mu = self.calculate_shift(dynamic_shift_len) if dynamic_shift_len is not None else self.exponential_shift_mu
|
| 50 |
+
self.sigmas = math.exp(mu) / (math.exp(mu) + (1 / self.sigmas - 1))
|
| 51 |
+
else:
|
| 52 |
+
self.sigmas = self.shift * self.sigmas / (1 + (self.shift - 1) * self.sigmas)
|
| 53 |
+
if self.shift_terminal is not None:
|
| 54 |
+
one_minus_z = 1 - self.sigmas
|
| 55 |
+
scale_factor = one_minus_z[-1] / (1 - self.shift_terminal)
|
| 56 |
+
self.sigmas = 1 - (one_minus_z / scale_factor)
|
| 57 |
+
if self.reverse_sigmas:
|
| 58 |
+
self.sigmas = 1 - self.sigmas
|
| 59 |
+
self.timesteps = self.sigmas * self.num_train_timesteps
|
| 60 |
+
if self.train_timesteps is None:
|
| 61 |
+
self.train_timesteps = self.timesteps
|
| 62 |
+
self.train_sigmas = self.sigmas
|
| 63 |
+
if training:
|
| 64 |
+
x = self.timesteps
|
| 65 |
+
y = torch.exp(-2 * ((x - num_inference_steps / 2) / num_inference_steps) ** 2)
|
| 66 |
+
y_shifted = y - y.min()
|
| 67 |
+
bsmntw_weighing = y_shifted * (num_inference_steps / y_shifted.sum())
|
| 68 |
+
self.linear_timesteps_weights = bsmntw_weighing
|
| 69 |
+
self.training = True
|
| 70 |
+
else:
|
| 71 |
+
self.training = False
|
| 72 |
+
|
| 73 |
+
def step(self, model_output, timestep, sample, to_final=False, **kwargs):
|
| 74 |
+
if isinstance(timestep, torch.Tensor):
|
| 75 |
+
timestep = timestep.cpu()
|
| 76 |
+
timestep_id = torch.argmin((self.timesteps - timestep).abs())
|
| 77 |
+
sigma = self.sigmas[timestep_id]
|
| 78 |
+
if to_final or timestep_id + 1 >= len(self.timesteps):
|
| 79 |
+
sigma_ = 1 if (self.inverse_timesteps or self.reverse_sigmas) else 0
|
| 80 |
+
else:
|
| 81 |
+
sigma_ = self.sigmas[timestep_id + 1]
|
| 82 |
+
prev_sample = sample + model_output * (sigma_ - sigma)
|
| 83 |
+
return prev_sample
|
| 84 |
+
|
| 85 |
+
def add_noise(self, original_samples, noise, timestep):
|
| 86 |
+
if isinstance(timestep, torch.Tensor):
|
| 87 |
+
timestep = timestep.cpu()
|
| 88 |
+
timestep_id = torch.argmin((self.timesteps - timestep).abs())
|
| 89 |
+
sigma = self.sigmas[timestep_id]
|
| 90 |
+
sample = (1 - sigma) * original_samples + sigma * noise
|
| 91 |
+
return sample
|
| 92 |
+
|
| 93 |
+
def training_target(self, sample, noise, timestep):
|
| 94 |
+
return noise - sample
|
| 95 |
+
|
| 96 |
+
def calculate_shift(
|
| 97 |
+
self,
|
| 98 |
+
image_seq_len,
|
| 99 |
+
base_seq_len: int = 256,
|
| 100 |
+
max_seq_len: int = 8192,
|
| 101 |
+
base_shift: float = 0.5,
|
| 102 |
+
max_shift: float = 0.9,
|
| 103 |
+
):
|
| 104 |
+
m = (max_shift - base_shift) / (max_seq_len - base_seq_len)
|
| 105 |
+
b = base_shift - m * base_seq_len
|
| 106 |
+
mu = image_seq_len * m + b
|
| 107 |
+
return mu
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
class FlowMatchPairScheduler(FlowMatchSchedulerBase, SchedulerMixin, ConfigMixin):
|
| 111 |
+
"""
|
| 112 |
+
Paired timestep scheduler for dual-tower (video + audio) diffusion.
|
| 113 |
+
Returns tensor with shape [num_timesteps, 2], each row is (visual_t, audio_t).
|
| 114 |
+
"""
|
| 115 |
+
|
| 116 |
+
@register_to_config
|
| 117 |
+
def __init__(
|
| 118 |
+
self,
|
| 119 |
+
num_inference_steps=100,
|
| 120 |
+
num_train_timesteps=1000,
|
| 121 |
+
shift=3.0,
|
| 122 |
+
sigma_max=1.0,
|
| 123 |
+
sigma_min=0.003 / 1.002,
|
| 124 |
+
inverse_timesteps=False,
|
| 125 |
+
extra_one_step=False,
|
| 126 |
+
reverse_sigmas=False,
|
| 127 |
+
exponential_shift=False,
|
| 128 |
+
exponential_shift_mu=None,
|
| 129 |
+
shift_terminal=None,
|
| 130 |
+
):
|
| 131 |
+
self._pair_postprocess_fn = None
|
| 132 |
+
self._pair_postprocess_requires_source = False
|
| 133 |
+
self.pair_timesteps = None
|
| 134 |
+
self.pair_sigmas = None
|
| 135 |
+
self.timesteps = None
|
| 136 |
+
self.sigmas = None
|
| 137 |
+
super().__init__(
|
| 138 |
+
num_inference_steps=num_inference_steps,
|
| 139 |
+
num_train_timesteps=num_train_timesteps,
|
| 140 |
+
shift=shift,
|
| 141 |
+
sigma_max=sigma_max,
|
| 142 |
+
sigma_min=sigma_min,
|
| 143 |
+
inverse_timesteps=inverse_timesteps,
|
| 144 |
+
extra_one_step=extra_one_step,
|
| 145 |
+
reverse_sigmas=reverse_sigmas,
|
| 146 |
+
exponential_shift=exponential_shift,
|
| 147 |
+
exponential_shift_mu=exponential_shift_mu,
|
| 148 |
+
shift_terminal=shift_terminal,
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
def set_pair_postprocess(self, fn):
|
| 152 |
+
if fn is not None and not callable(fn):
|
| 153 |
+
raise TypeError("pair_postprocess must be a callable object or None")
|
| 154 |
+
self._pair_postprocess_fn = fn
|
| 155 |
+
self._pair_postprocess_requires_source = False if fn is None else bool(getattr(fn, "_requires_source", False))
|
| 156 |
+
if self.timesteps is None or self.sigmas is None:
|
| 157 |
+
raise RuntimeError("Scheduler not initialized, please call set_timesteps() first")
|
| 158 |
+
self._refresh_pair_cache()
|
| 159 |
+
|
| 160 |
+
def set_pair_postprocess_by_name(self, name, **kwargs):
|
| 161 |
+
if name is None or str(name).lower() in ("none", "off", "false", "no"):
|
| 162 |
+
self.set_pair_postprocess(None)
|
| 163 |
+
return
|
| 164 |
+
if name == "dual_sigma_shift":
|
| 165 |
+
visual_shift = float(kwargs.get("visual_shift", self.shift))
|
| 166 |
+
audio_shift = float(kwargs.get("audio_shift", self.shift))
|
| 167 |
+
visual_denoising_strength = float(kwargs.get("visual_denoising_strength", 1.0))
|
| 168 |
+
audio_denoising_strength = float(kwargs.get("audio_denoising_strength", 1.0))
|
| 169 |
+
visual_mu = kwargs.get("visual_exponential_shift_mu", self.exponential_shift_mu)
|
| 170 |
+
audio_mu = kwargs.get("audio_exponential_shift_mu", self.exponential_shift_mu)
|
| 171 |
+
|
| 172 |
+
def _dual_sigma_shift(pairs, *, source):
|
| 173 |
+
num_steps = pairs.shape[0]
|
| 174 |
+
device = pairs.device
|
| 175 |
+
dtype = pairs.dtype
|
| 176 |
+
|
| 177 |
+
def _build_column(shift_value, denoising_strength, mu_override):
|
| 178 |
+
sigma_start = self.sigma_min + (self.sigma_max - self.sigma_min) * denoising_strength
|
| 179 |
+
if self.extra_one_step:
|
| 180 |
+
base = torch.linspace(sigma_start, self.sigma_min, num_steps + 1, device=device, dtype=dtype)[:-1]
|
| 181 |
+
else:
|
| 182 |
+
base = torch.linspace(sigma_start, self.sigma_min, num_steps, device=device, dtype=dtype)
|
| 183 |
+
if self.inverse_timesteps:
|
| 184 |
+
base = torch.flip(base, dims=[0])
|
| 185 |
+
if self.exponential_shift:
|
| 186 |
+
mu_value = mu_override
|
| 187 |
+
if mu_value is None:
|
| 188 |
+
raise RuntimeError("exponential_shift is enabled but exponential_shift_mu is not provided")
|
| 189 |
+
exp_mu = math.exp(float(mu_value))
|
| 190 |
+
base = exp_mu / (exp_mu + (1 / base - 1))
|
| 191 |
+
else:
|
| 192 |
+
base = shift_value * base / (1 + (shift_value - 1) * base)
|
| 193 |
+
if self.shift_terminal is not None:
|
| 194 |
+
one_minus_z = 1 - base
|
| 195 |
+
scale_factor = one_minus_z[-1] / (1 - self.shift_terminal)
|
| 196 |
+
base = 1 - (one_minus_z / scale_factor)
|
| 197 |
+
if self.reverse_sigmas:
|
| 198 |
+
base = 1 - base
|
| 199 |
+
if source == "timesteps":
|
| 200 |
+
return base * self.num_train_timesteps
|
| 201 |
+
return base
|
| 202 |
+
|
| 203 |
+
visual_column = _build_column(visual_shift, visual_denoising_strength, visual_mu)
|
| 204 |
+
audio_column = _build_column(audio_shift, audio_denoising_strength, audio_mu)
|
| 205 |
+
return torch.stack([visual_column, audio_column], dim=1)
|
| 206 |
+
|
| 207 |
+
_dual_sigma_shift._requires_source = True
|
| 208 |
+
self.set_pair_postprocess(_dual_sigma_shift)
|
| 209 |
+
return
|
| 210 |
+
raise ValueError(f"Unsupported pair postprocessing name: {name}")
|
| 211 |
+
|
| 212 |
+
def _make_pairs_from_vector(self, vec):
|
| 213 |
+
return torch.stack([vec, vec], dim=1)
|
| 214 |
+
|
| 215 |
+
def get_pairs(self, source="timesteps"):
|
| 216 |
+
if source == "timesteps":
|
| 217 |
+
pairs = self.pair_timesteps
|
| 218 |
+
elif source == "sigmas":
|
| 219 |
+
pairs = self.pair_sigmas
|
| 220 |
+
else:
|
| 221 |
+
raise ValueError("source only supports 'timesteps' or 'sigmas'")
|
| 222 |
+
if pairs is None:
|
| 223 |
+
raise RuntimeError("Scheduler not initialized, please call set_timesteps() first")
|
| 224 |
+
return pairs
|
| 225 |
+
|
| 226 |
+
@property
|
| 227 |
+
def visual_timesteps(self):
|
| 228 |
+
if self.pair_timesteps is None:
|
| 229 |
+
raise RuntimeError("Scheduler not initialized")
|
| 230 |
+
return self.pair_timesteps[:, 0]
|
| 231 |
+
|
| 232 |
+
@property
|
| 233 |
+
def audio_timesteps(self):
|
| 234 |
+
if self.pair_timesteps is None:
|
| 235 |
+
raise RuntimeError("Scheduler not initialized")
|
| 236 |
+
return self.pair_timesteps[:, 1]
|
| 237 |
+
|
| 238 |
+
def set_timesteps(self, *args, **kwargs):
|
| 239 |
+
super().set_timesteps(*args, **kwargs)
|
| 240 |
+
self._refresh_pair_cache()
|
| 241 |
+
|
| 242 |
+
def timestep_to_sigma(self, timestep):
|
| 243 |
+
t_value = float(timestep)
|
| 244 |
+
t_cpu = torch.tensor(t_value)
|
| 245 |
+
idx = torch.argmin((self.train_timesteps - t_cpu).abs())
|
| 246 |
+
return self.train_sigmas[idx]
|
| 247 |
+
|
| 248 |
+
def step_from_to(self, model_output, timestep_from, timestep_to, sample):
|
| 249 |
+
sigma_from = self.timestep_to_sigma(timestep_from)
|
| 250 |
+
if timestep_to is None:
|
| 251 |
+
sigma_to = torch.tensor(
|
| 252 |
+
1.0 if (self.inverse_timesteps or self.reverse_sigmas) else 0.0,
|
| 253 |
+
device=sigma_from.device, dtype=sigma_from.dtype,
|
| 254 |
+
)
|
| 255 |
+
else:
|
| 256 |
+
sigma_to = self.timestep_to_sigma(timestep_to)
|
| 257 |
+
prev_sample = sample + model_output * (sigma_to - sigma_from)
|
| 258 |
+
return prev_sample
|
| 259 |
+
|
| 260 |
+
def _refresh_pair_cache(self):
|
| 261 |
+
if self.timesteps is None or self.sigmas is None:
|
| 262 |
+
return
|
| 263 |
+
|
| 264 |
+
def _apply_postprocess(pairs, source):
|
| 265 |
+
if self._pair_postprocess_fn is None:
|
| 266 |
+
return pairs
|
| 267 |
+
if self._pair_postprocess_requires_source:
|
| 268 |
+
modified = self._pair_postprocess_fn(pairs, source=source)
|
| 269 |
+
else:
|
| 270 |
+
modified = self._pair_postprocess_fn(pairs)
|
| 271 |
+
return modified
|
| 272 |
+
|
| 273 |
+
base_pairs_timesteps = self._make_pairs_from_vector(self.timesteps)
|
| 274 |
+
base_pairs_sigmas = self._make_pairs_from_vector(self.sigmas)
|
| 275 |
+
|
| 276 |
+
self.pair_timesteps = _apply_postprocess(base_pairs_timesteps, "timesteps")
|
| 277 |
+
self.pair_sigmas = _apply_postprocess(base_pairs_sigmas, "sigmas")
|
hymm/models/__init__.py
ADDED
|
File without changes
|
hymm/models/modules/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .mova import MOVABridge, FusedMOVABlock
|
hymm/models/modules/block_sparse_attention/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .bsa_interface import (
|
| 2 |
+
flash_attn_bsa_3d,
|
| 3 |
+
flash_attn_bsa_cross,
|
| 4 |
+
flash_attn_bsa_3d_audio_guided,
|
| 5 |
+
flash_attn_bsa_3d_variance_guided,
|
| 6 |
+
compute_audio_spatial_concentration_gate,
|
| 7 |
+
compute_timestep_reliability_gate,
|
| 8 |
+
compute_block_audio_saliency,
|
| 9 |
+
audio_weighted_mean_pooling_compression,
|
| 10 |
+
compute_channel_variance_density,
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
from .bias_rectification import (
|
| 14 |
+
bsa_taylor_sparse_attn,
|
| 15 |
+
bsa_rectified_sparse_attn,
|
| 16 |
+
)
|
hymm/models/modules/block_sparse_attention/bias_rectification.py
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
from typing import Optional, Tuple
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.distributed as dist
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
|
| 9 |
+
from .bsa_interface import (
|
| 10 |
+
mean_pooling_compression,
|
| 11 |
+
masked_mean_pooling_compression,
|
| 12 |
+
cal_score,
|
| 13 |
+
get_select_indices_topk_from_score,
|
| 14 |
+
get_select_indices_cdf_from_score,
|
| 15 |
+
get_select_indices_cdf_topk_from_score,
|
| 16 |
+
create_mask_from_indices_varlen,
|
| 17 |
+
attn_fwd_bsa_varlen_triton,
|
| 18 |
+
attn_bwd_bsa_varlen_triton,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# =====================================================================
|
| 23 |
+
# Pure BSA Triton wrapper — only this is an autograd Function.
|
| 24 |
+
# Everything else (pooling, scoring, correction) lives outside in
|
| 25 |
+
# autograd-friendly PyTorch ops so gradients flow correctly.
|
| 26 |
+
# =====================================================================
|
| 27 |
+
class _bsa_kernel_only(torch.autograd.Function):
|
| 28 |
+
"""Forward + backward through `attn_fwd_bsa_varlen_triton` only.
|
| 29 |
+
|
| 30 |
+
Returns (o, lse_natural). lse is the log-sum-exp of the *attended* keys
|
| 31 |
+
in natural log units (= LN2 * the kernel's log2 lse).
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
@staticmethod
|
| 35 |
+
def forward(ctx, q, k, v, sm_scale, block_indices, block_indices_lens,
|
| 36 |
+
chunk_size_q, chunk_size_k, sparsity, kv_valid_mask):
|
| 37 |
+
o, lse_natural = attn_fwd_bsa_varlen_triton(
|
| 38 |
+
q, k, v, sm_scale, block_indices, block_indices_lens,
|
| 39 |
+
chunk_size_q, chunk_size_k, sparsity,
|
| 40 |
+
kv_valid_mask=kv_valid_mask,
|
| 41 |
+
)
|
| 42 |
+
ctx.save_for_backward(q, k, v, o, lse_natural, block_indices, block_indices_lens)
|
| 43 |
+
ctx.sm_scale = sm_scale
|
| 44 |
+
ctx.chunk_size_q = chunk_size_q
|
| 45 |
+
ctx.chunk_size_k = chunk_size_k
|
| 46 |
+
ctx.sparsity = sparsity
|
| 47 |
+
ctx.kv_valid_mask = kv_valid_mask
|
| 48 |
+
return o, lse_natural
|
| 49 |
+
|
| 50 |
+
@staticmethod
|
| 51 |
+
def backward(ctx, do, dlse_unused):
|
| 52 |
+
q, k, v, o, lse, block_indices, block_indices_lens = ctx.saved_tensors
|
| 53 |
+
dq = torch.empty_like(q)
|
| 54 |
+
dk = torch.empty_like(k)
|
| 55 |
+
dv = torch.empty_like(v)
|
| 56 |
+
do = do.contiguous()
|
| 57 |
+
attn_bwd_bsa_varlen_triton(
|
| 58 |
+
do, q, k, v, o, dq, dk, dv,
|
| 59 |
+
ctx.sm_scale, lse,
|
| 60 |
+
block_indices, block_indices_lens,
|
| 61 |
+
ctx.chunk_size_q, ctx.chunk_size_k, ctx.sparsity,
|
| 62 |
+
kv_valid_mask=ctx.kv_valid_mask,
|
| 63 |
+
)
|
| 64 |
+
# 10 inputs: q, k, v, sm_scale, block_indices, block_indices_lens,
|
| 65 |
+
# chunk_size_q, chunk_size_k, sparsity, kv_valid_mask
|
| 66 |
+
return dq, dk, dv, None, None, None, None, None, None, None
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def _bsa_kernel(q, k, v, sm_scale, block_indices, block_indices_lens,
|
| 70 |
+
chunk_size_q, chunk_size_k, sparsity, kv_valid_mask):
|
| 71 |
+
"""Wrapper that exposes (o, lse) but always detaches lse.
|
| 72 |
+
|
| 73 |
+
`lse` is an attention normalisation statistic used downstream as a
|
| 74 |
+
coefficient in the Taylor merge weights. We treat it as a fixed
|
| 75 |
+
quantity (FlashAttention convention) and explicitly detach it so the
|
| 76 |
+
autograd engine does not try to backpropagate through a path we
|
| 77 |
+
intentionally do not implement in `_bsa_kernel_only.backward`.
|
| 78 |
+
"""
|
| 79 |
+
o, lse = _bsa_kernel_only.apply(
|
| 80 |
+
q, k, v, sm_scale, block_indices, block_indices_lens,
|
| 81 |
+
chunk_size_q, chunk_size_k, sparsity, kv_valid_mask,
|
| 82 |
+
)
|
| 83 |
+
return o, lse.detach()
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# =====================================================================
|
| 87 |
+
# Shared helpers (block-level pooling, selection, validity)
|
| 88 |
+
# =====================================================================
|
| 89 |
+
def _pool_qk(q, k, chunk_size_q, chunk_size_k, kv_valid_mask, q_valid_mask):
|
| 90 |
+
"""Pool Q and K to block representatives, masking padded tokens.
|
| 91 |
+
|
| 92 |
+
Self-attn case (Q/K share grid): both use kv_valid_mask.
|
| 93 |
+
Cross-attn case: each side uses its own valid_mask.
|
| 94 |
+
|
| 95 |
+
All ops are autograd-tracked (gradients to q / k flow through pooling).
|
| 96 |
+
"""
|
| 97 |
+
if q_valid_mask is not None:
|
| 98 |
+
q_cmp = masked_mean_pooling_compression(q, chunk_size_q, q_valid_mask)
|
| 99 |
+
elif kv_valid_mask is not None and q.shape[2] == k.shape[2]:
|
| 100 |
+
# self-attention: Q shares K's valid mask
|
| 101 |
+
q_cmp = masked_mean_pooling_compression(q, chunk_size_q, kv_valid_mask)
|
| 102 |
+
else:
|
| 103 |
+
q_cmp = mean_pooling_compression(q, chunk_size_q)
|
| 104 |
+
if kv_valid_mask is not None:
|
| 105 |
+
k_cmp = masked_mean_pooling_compression(k, chunk_size_k, kv_valid_mask)
|
| 106 |
+
else:
|
| 107 |
+
k_cmp = mean_pooling_compression(k, chunk_size_k)
|
| 108 |
+
return q_cmp, k_cmp
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _pool_v(v, chunk_size_k, kv_valid_mask):
|
| 112 |
+
if kv_valid_mask is not None:
|
| 113 |
+
return masked_mean_pooling_compression(v, chunk_size_k, kv_valid_mask)
|
| 114 |
+
return mean_pooling_compression(v, chunk_size_k)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def _select_blocks(selection_score, sparsity, cdf_threshold, sm_scale):
|
| 118 |
+
"""topk / top-p / hybrid selection (non-differentiable)."""
|
| 119 |
+
if sparsity is not None and cdf_threshold is None:
|
| 120 |
+
return get_select_indices_topk_from_score(selection_score, sparsity)
|
| 121 |
+
if sparsity is None and cdf_threshold is not None:
|
| 122 |
+
return get_select_indices_cdf_from_score(selection_score, cdf_threshold, sm_scale)
|
| 123 |
+
if sparsity is not None and cdf_threshold is not None:
|
| 124 |
+
return get_select_indices_cdf_topk_from_score(selection_score, sparsity, cdf_threshold, sm_scale)
|
| 125 |
+
raise ValueError("Either sparsity or cdf_threshold must be provided")
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def _compute_block_valid_mask(kv_valid_mask: torch.Tensor, chunk_size: int) -> torch.Tensor:
|
| 129 |
+
"""A K block is valid if any token within it is valid."""
|
| 130 |
+
S = kv_valid_mask.shape[0]
|
| 131 |
+
num_blocks = S // chunk_size
|
| 132 |
+
return kv_valid_mask.view(num_blocks, chunk_size).any(dim=-1)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def _compute_block_valid_token_counts(kv_valid_mask: torch.Tensor, chunk_size: int) -> torch.Tensor:
|
| 136 |
+
S = kv_valid_mask.shape[0]
|
| 137 |
+
num_blocks = S // chunk_size
|
| 138 |
+
return kv_valid_mask.view(num_blocks, chunk_size).sum(dim=-1).float()
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def _masked_softmax_along_last(score: torch.Tensor, sm_scale: float,
|
| 142 |
+
block_valid: Optional[torch.Tensor]) -> torch.Tensor:
|
| 143 |
+
"""softmax(score * sm_scale, dim=-1) with -inf at padded K blocks."""
|
| 144 |
+
if block_valid is None:
|
| 145 |
+
return torch.softmax(score * sm_scale, dim=-1)
|
| 146 |
+
masked = score.masked_fill(~block_valid.view(1, 1, 1, -1), float('-inf'))
|
| 147 |
+
return torch.softmax(masked * sm_scale, dim=-1)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def _masked_var_along_last(probs: torch.Tensor,
|
| 151 |
+
block_valid: Optional[torch.Tensor]) -> torch.Tensor:
|
| 152 |
+
"""Variance of softmax distribution restricted to valid K blocks.
|
| 153 |
+
|
| 154 |
+
Without masking, padded blocks contribute zero (since A_pool=0 there) but
|
| 155 |
+
inflate the denominator → biases variance downward → biases the LIVEditor
|
| 156 |
+
flat/sharp split. We compute Var[p] only over valid entries.
|
| 157 |
+
"""
|
| 158 |
+
if block_valid is None:
|
| 159 |
+
return probs.var(dim=-1, unbiased=False)
|
| 160 |
+
bv = block_valid.view(1, 1, 1, -1).to(probs.dtype)
|
| 161 |
+
n_valid = bv.sum(dim=-1).clamp(min=1.0)
|
| 162 |
+
p_masked = probs * bv
|
| 163 |
+
mean = p_masked.sum(dim=-1, keepdim=True) / n_valid
|
| 164 |
+
var = ((p_masked - mean) ** 2 * bv).sum(dim=-1) / n_valid
|
| 165 |
+
return var
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def _global_quantile_across_sp(values: torch.Tensor, q: float) -> torch.Tensor:
|
| 169 |
+
|
| 170 |
+
# Imported at call scope but NOT guarded by try/except: swallowing an import
|
| 171 |
+
# error here would make one rank skip the all_gather below while its peers
|
| 172 |
+
# still enter it, which is an unrecoverable NCCL hang rather than a crash.
|
| 173 |
+
from hymm.utils.parallel_states import nccl_info, get_sequence_parallel_state
|
| 174 |
+
|
| 175 |
+
if (not get_sequence_parallel_state()) or nccl_info.sp_size <= 1:
|
| 176 |
+
return torch.quantile(values.float().reshape(-1), q)
|
| 177 |
+
|
| 178 |
+
sp_group = nccl_info.sp_group
|
| 179 |
+
flat = values.float().reshape(-1).contiguous()
|
| 180 |
+
|
| 181 |
+
# All ranks in SP have the same flat shape because Q/K/V are split by HEAD,
|
| 182 |
+
# not by token. So we can use a tensor all-gather.
|
| 183 |
+
world_size = nccl_info.sp_size
|
| 184 |
+
gather_list = [torch.empty_like(flat) for _ in range(world_size)]
|
| 185 |
+
dist.all_gather(gather_list, flat, group=sp_group)
|
| 186 |
+
global_flat = torch.cat(gather_list, dim=0)
|
| 187 |
+
return torch.quantile(global_flat, q)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def _taylor_merge_pytorch(
|
| 191 |
+
q: torch.Tensor,
|
| 192 |
+
k_cmp: torch.Tensor,
|
| 193 |
+
v_cmp: torch.Tensor,
|
| 194 |
+
o_spa: torch.Tensor,
|
| 195 |
+
lse_spa: torch.Tensor,
|
| 196 |
+
nonsel_mask: torch.Tensor,
|
| 197 |
+
is_flat: torch.Tensor,
|
| 198 |
+
block_token_counts: torch.Tensor,
|
| 199 |
+
chunk_size_q: int,
|
| 200 |
+
sm_scale: float,
|
| 201 |
+
) -> torch.Tensor:
|
| 202 |
+
|
| 203 |
+
B, H, S_q, D = o_spa.shape
|
| 204 |
+
N_q, N_k = nonsel_mask.shape[-2], nonsel_mask.shape[-1]
|
| 205 |
+
|
| 206 |
+
nonsel_per_tok = (
|
| 207 |
+
nonsel_mask.unsqueeze(3)
|
| 208 |
+
.expand(B, H, N_q, chunk_size_q, N_k)
|
| 209 |
+
.contiguous()
|
| 210 |
+
.reshape(B, H, S_q, N_k)
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
raw_scores = torch.matmul(q, k_cmp.transpose(-1, -2)) * sm_scale # [B, H, S_q, N_k]
|
| 214 |
+
log_counts = torch.log(block_token_counts.float().clamp(min=1.0)).view(1, 1, 1, N_k)
|
| 215 |
+
scores_full = raw_scores + log_counts.to(raw_scores.dtype)
|
| 216 |
+
|
| 217 |
+
# ---- LSE: -inf-masked path; logsumexp handles all-(-inf) rows safely ----
|
| 218 |
+
scores_inf = scores_full.masked_fill(~nonsel_per_tok, float('-inf'))
|
| 219 |
+
lse_taylor = torch.logsumexp(scores_inf, dim=-1) # [B, H, S_q]
|
| 220 |
+
has_taylor = torch.isfinite(lse_taylor) # bool
|
| 221 |
+
|
| 222 |
+
# ---- Softmax: use a finite sentinel to avoid NaN; gate rows w/o nonsel ----
|
| 223 |
+
LARGE_NEG = -1.0e30
|
| 224 |
+
scores_safe = scores_full.masked_fill(~nonsel_per_tok, LARGE_NEG)
|
| 225 |
+
weights = torch.softmax(scores_safe, dim=-1) # finite everywhere
|
| 226 |
+
# For rows with NO valid nonsel, every entry is LARGE_NEG → softmax = uniform
|
| 227 |
+
# → zero them out so o_taylor = 0 there.
|
| 228 |
+
weights = weights * has_taylor.unsqueeze(-1).to(weights.dtype)
|
| 229 |
+
o_taylor = torch.matmul(weights, v_cmp)
|
| 230 |
+
|
| 231 |
+
# ---- Stable lse merge ----
|
| 232 |
+
lse_max = torch.maximum(lse_spa, lse_taylor)
|
| 233 |
+
finite = torch.isfinite(lse_max)
|
| 234 |
+
|
| 235 |
+
# diff is undefined when both lses are -inf (rare; lse_spa is always finite
|
| 236 |
+
# in practice because BSA selects ≥1 block). Replace those NaNs by 0 inside
|
| 237 |
+
# the discarded `where` branch so backward never sees NaN.
|
| 238 |
+
diff = torch.minimum(lse_spa, lse_taylor) - lse_max
|
| 239 |
+
diff = torch.where(finite, diff, torch.zeros_like(diff))
|
| 240 |
+
lse_total = torch.where(finite, lse_max + torch.log1p(torch.exp(diff)), lse_max)
|
| 241 |
+
|
| 242 |
+
w_spa = torch.where(finite, torch.exp(lse_spa - lse_total), torch.ones_like(lse_total))
|
| 243 |
+
w_taylor = torch.where(
|
| 244 |
+
finite & has_taylor,
|
| 245 |
+
torch.exp(lse_taylor - lse_total),
|
| 246 |
+
torch.zeros_like(lse_total),
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
merged = w_spa.unsqueeze(-1) * o_spa + w_taylor.unsqueeze(-1) * o_taylor
|
| 250 |
+
|
| 251 |
+
# Sharp queries (block-level boolean) keep o_spa unchanged.
|
| 252 |
+
flat_tok = (
|
| 253 |
+
is_flat.unsqueeze(-1)
|
| 254 |
+
.expand(B, H, N_q, chunk_size_q)
|
| 255 |
+
.contiguous()
|
| 256 |
+
.reshape(B, H, S_q)
|
| 257 |
+
)
|
| 258 |
+
return torch.where(flat_tok.unsqueeze(-1), merged, o_spa).to(o_spa.dtype).contiguous()
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def bsa_taylor_sparse_attn(
|
| 262 |
+
q: torch.Tensor, # [B, H, S_q, D] in 3d-block layout
|
| 263 |
+
k: torch.Tensor, # [B, H, S_k, D]
|
| 264 |
+
v: torch.Tensor, # [B, H, S_k, D]
|
| 265 |
+
chunk_size_q: int,
|
| 266 |
+
chunk_size_k: int,
|
| 267 |
+
sparsity: Optional[float],
|
| 268 |
+
cdf_threshold: Optional[float],
|
| 269 |
+
sm_scale: float,
|
| 270 |
+
*,
|
| 271 |
+
alpha_f: float = 0.5,
|
| 272 |
+
kv_valid_mask: Optional[torch.Tensor] = None,
|
| 273 |
+
q_valid_mask: Optional[torch.Tensor] = None,
|
| 274 |
+
score_modifier: Optional[torch.Tensor] = None,
|
| 275 |
+
use_triton_inference: bool = True,
|
| 276 |
+
) -> torch.Tensor:
|
| 277 |
+
# --- (a) pool Q and K (autograd-tracked) ---
|
| 278 |
+
q_cmp, k_cmp = _pool_qk(q, k, chunk_size_q, chunk_size_k, kv_valid_mask, q_valid_mask)
|
| 279 |
+
|
| 280 |
+
# --- (b) block scoring + selection (selection itself is non-differentiable) ---
|
| 281 |
+
raw_score = cal_score(q_cmp, k_cmp) # [B, H, N_q, N_k]
|
| 282 |
+
with torch.no_grad():
|
| 283 |
+
sel_score = raw_score * score_modifier if score_modifier is not None else raw_score
|
| 284 |
+
block_indices, block_indices_lens = _select_blocks(sel_score, sparsity, cdf_threshold, sm_scale)
|
| 285 |
+
|
| 286 |
+
# --- (c) BSA Triton kernel; this is the only autograd Function call ---
|
| 287 |
+
o_spa, lse_spa = _bsa_kernel(
|
| 288 |
+
q, k, v, sm_scale, block_indices, block_indices_lens,
|
| 289 |
+
chunk_size_q, chunk_size_k, sparsity, kv_valid_mask,
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
# --- (d) Taylor side products (block_valid, A_pool, M_n, is_flat) ---
|
| 293 |
+
block_valid = _compute_block_valid_mask(kv_valid_mask, chunk_size_k) if kv_valid_mask is not None else None
|
| 294 |
+
|
| 295 |
+
with torch.no_grad():
|
| 296 |
+
sel_mask = create_mask_from_indices_varlen(block_indices, raw_score.shape[-1])
|
| 297 |
+
nonsel_mask = ~sel_mask
|
| 298 |
+
if block_valid is not None:
|
| 299 |
+
nonsel_mask = nonsel_mask & block_valid.view(1, 1, 1, -1)
|
| 300 |
+
|
| 301 |
+
# Block-level true probability distribution (no score_modifier, no scale boost)
|
| 302 |
+
A_pool_for_class = _masked_softmax_along_last(raw_score.detach(), sm_scale, block_valid)
|
| 303 |
+
M_n = _masked_var_along_last(A_pool_for_class, block_valid) # [B, H, N_q]
|
| 304 |
+
tau_M = _global_quantile_across_sp(M_n, alpha_f)
|
| 305 |
+
is_flat = M_n < tau_M
|
| 306 |
+
|
| 307 |
+
if kv_valid_mask is not None:
|
| 308 |
+
btc = _compute_block_valid_token_counts(kv_valid_mask, chunk_size_k)
|
| 309 |
+
else:
|
| 310 |
+
btc = torch.full(
|
| 311 |
+
(k.shape[2] // chunk_size_k,), float(chunk_size_k),
|
| 312 |
+
device=q.device, dtype=torch.float32,
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
# --- (e) v_cmp (autograd-tracked: gradients to V flow through this) ---
|
| 316 |
+
v_cmp = _pool_v(v, chunk_size_k, kv_valid_mask)
|
| 317 |
+
|
| 318 |
+
# --- (f) merge (autograd-tracked) ---
|
| 319 |
+
use_triton = (
|
| 320 |
+
use_triton_inference
|
| 321 |
+
and not torch.is_grad_enabled()
|
| 322 |
+
and q.is_cuda
|
| 323 |
+
)
|
| 324 |
+
if use_triton:
|
| 325 |
+
try:
|
| 326 |
+
from .bias_rectification_triton import taylor_merge_triton
|
| 327 |
+
return taylor_merge_triton(
|
| 328 |
+
q, k_cmp, v_cmp, o_spa, lse_spa, nonsel_mask, is_flat, btc,
|
| 329 |
+
chunk_size_q, sm_scale,
|
| 330 |
+
)
|
| 331 |
+
except Exception as e:
|
| 332 |
+
# Fall through to PyTorch path on any kernel error.
|
| 333 |
+
print(f"[bias_rectification] taylor_merge_triton fallback: {e}")
|
| 334 |
+
|
| 335 |
+
return _taylor_merge_pytorch(
|
| 336 |
+
q, k_cmp, v_cmp, o_spa, lse_spa, nonsel_mask, is_flat, btc,
|
| 337 |
+
chunk_size_q, sm_scale,
|
| 338 |
+
)
|
| 339 |
+
|
| 340 |
+
def _rectified_correction_pytorch(
|
| 341 |
+
o_spa: torch.Tensor,
|
| 342 |
+
R_n: torch.Tensor,
|
| 343 |
+
A_pool: torch.Tensor,
|
| 344 |
+
rectify_mask: torch.Tensor,
|
| 345 |
+
v_cmp: torch.Tensor,
|
| 346 |
+
chunk_size_q: int,
|
| 347 |
+
) -> torch.Tensor:
|
| 348 |
+
"""O' = R_n * O_spa + (A_pool * rectify_mask) @ V_cmp, autograd-tracked.
|
| 349 |
+
|
| 350 |
+
Memory-contiguous via expand+contiguous+reshape: each broadcast tensor is
|
| 351 |
+
materialised exactly once, never produces a non-contiguous view that the
|
| 352 |
+
next op would silently re-materialise.
|
| 353 |
+
"""
|
| 354 |
+
B, H, S, D = o_spa.shape
|
| 355 |
+
N_q = R_n.shape[-1]
|
| 356 |
+
|
| 357 |
+
# Broadcast R_n [B, H, N_q] → [B, H, S, 1]: one materialisation, contiguous.
|
| 358 |
+
R_tok = (
|
| 359 |
+
R_n.unsqueeze(-1).unsqueeze(-1)
|
| 360 |
+
.expand(B, H, N_q, chunk_size_q, 1)
|
| 361 |
+
.contiguous()
|
| 362 |
+
.reshape(B, H, S, 1)
|
| 363 |
+
)
|
| 364 |
+
o_cri = R_tok * o_spa
|
| 365 |
+
|
| 366 |
+
# Non-critical contribution: weighted sum of pooled V over rectifiable blocks.
|
| 367 |
+
A_rect = A_pool * rectify_mask.to(A_pool.dtype)
|
| 368 |
+
o_ncri_block = torch.matmul(A_rect, v_cmp) # [B, H, N_q, D]
|
| 369 |
+
|
| 370 |
+
# Broadcast block-level output to per-token, again contiguous.
|
| 371 |
+
o_ncri = (
|
| 372 |
+
o_ncri_block.unsqueeze(3)
|
| 373 |
+
.expand(B, H, N_q, chunk_size_q, D)
|
| 374 |
+
.contiguous()
|
| 375 |
+
.reshape(B, H, S, D)
|
| 376 |
+
)
|
| 377 |
+
return (o_cri + o_ncri).to(o_spa.dtype).contiguous()
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def bsa_rectified_sparse_attn(
|
| 381 |
+
q: torch.Tensor,
|
| 382 |
+
k: torch.Tensor,
|
| 383 |
+
v: torch.Tensor,
|
| 384 |
+
chunk_size_q: int,
|
| 385 |
+
chunk_size_k: int,
|
| 386 |
+
sparsity: Optional[float],
|
| 387 |
+
cdf_threshold: Optional[float],
|
| 388 |
+
sm_scale: float,
|
| 389 |
+
*,
|
| 390 |
+
kv_valid_mask: Optional[torch.Tensor] = None,
|
| 391 |
+
q_valid_mask: Optional[torch.Tensor] = None,
|
| 392 |
+
score_modifier: Optional[torch.Tensor] = None,
|
| 393 |
+
use_triton_inference: bool = True,
|
| 394 |
+
) -> torch.Tensor:
|
| 395 |
+
# --- (a) pooling (autograd-tracked) ---
|
| 396 |
+
q_cmp, k_cmp = _pool_qk(q, k, chunk_size_q, chunk_size_k, kv_valid_mask, q_valid_mask)
|
| 397 |
+
|
| 398 |
+
# --- (b) raw scoring (autograd-tracked) ---
|
| 399 |
+
raw_score = cal_score(q_cmp, k_cmp)
|
| 400 |
+
|
| 401 |
+
# --- (c) selection (no grad) ---
|
| 402 |
+
with torch.no_grad():
|
| 403 |
+
sel_score = raw_score * score_modifier if score_modifier is not None else raw_score
|
| 404 |
+
block_indices, block_indices_lens = _select_blocks(sel_score, sparsity, cdf_threshold, sm_scale)
|
| 405 |
+
|
| 406 |
+
# --- (d) BSA Triton (autograd via _bsa_kernel_only) ---
|
| 407 |
+
o_spa, _ = _bsa_kernel(
|
| 408 |
+
q, k, v, sm_scale, block_indices, block_indices_lens,
|
| 409 |
+
chunk_size_q, chunk_size_k, sparsity, kv_valid_mask,
|
| 410 |
+
)
|
| 411 |
+
|
| 412 |
+
# --- (e) sel_mask / rectify_mask (no grad; bool tensors, contiguous) ---
|
| 413 |
+
block_valid = _compute_block_valid_mask(kv_valid_mask, chunk_size_k) if kv_valid_mask is not None else None
|
| 414 |
+
with torch.no_grad():
|
| 415 |
+
sel_mask = create_mask_from_indices_varlen(block_indices, raw_score.shape[-1])
|
| 416 |
+
rectify_mask = ~sel_mask # all non-selected blocks
|
| 417 |
+
if block_valid is not None:
|
| 418 |
+
rectify_mask = rectify_mask & block_valid.view(1, 1, 1, -1)
|
| 419 |
+
rectify_mask = rectify_mask.contiguous()
|
| 420 |
+
|
| 421 |
+
# --- (f) A_pool & R_n (autograd-tracked: ∂L/∂q,∂L/∂k flow through these) ---
|
| 422 |
+
A_pool = _masked_softmax_along_last(raw_score, sm_scale, block_valid)
|
| 423 |
+
R_n = (1.0 - (A_pool * rectify_mask.to(A_pool.dtype)).sum(dim=-1)).clamp(min=1e-6)
|
| 424 |
+
|
| 425 |
+
# --- (g) v_cmp (autograd-tracked: ∂L/∂v flows through pooling) ---
|
| 426 |
+
v_cmp = _pool_v(v, chunk_size_k, kv_valid_mask)
|
| 427 |
+
|
| 428 |
+
# --- (h) correction (fused Triton kernel for inference, PyTorch for training) ---
|
| 429 |
+
use_triton = (
|
| 430 |
+
use_triton_inference
|
| 431 |
+
and not torch.is_grad_enabled()
|
| 432 |
+
and q.is_cuda
|
| 433 |
+
)
|
| 434 |
+
if use_triton:
|
| 435 |
+
try:
|
| 436 |
+
from .bias_rectification_triton import rectified_correction_triton
|
| 437 |
+
return rectified_correction_triton(
|
| 438 |
+
o_spa, R_n.detach(), A_pool.detach(), rectify_mask, v_cmp.detach(),
|
| 439 |
+
chunk_size_q,
|
| 440 |
+
)
|
| 441 |
+
except Exception as e:
|
| 442 |
+
print(f"[bias_rectification] rectified_correction_triton fallback: {e}")
|
| 443 |
+
|
| 444 |
+
return _rectified_correction_pytorch(
|
| 445 |
+
o_spa, R_n, A_pool, rectify_mask, v_cmp, chunk_size_q,
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
__all__ = [
|
| 450 |
+
"bsa_taylor_sparse_attn",
|
| 451 |
+
"bsa_rectified_sparse_attn",
|
| 452 |
+
]
|
hymm/models/modules/block_sparse_attention/bias_rectification_triton.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import triton
|
| 3 |
+
import triton.language as tl
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
# =====================================================================
|
| 7 |
+
# Rectified-SpaAttn: fused (R_n * o_spa + (A_pool * rectify_mask) @ v_cmp)
|
| 8 |
+
# =====================================================================
|
| 9 |
+
@triton.jit
|
| 10 |
+
def _rectified_correction_fwd_kernel(
|
| 11 |
+
O_spa, R_n, A_pool, rectify_mask, V_cmp, Out,
|
| 12 |
+
stride_oz, stride_oh, stride_om, stride_od,
|
| 13 |
+
stride_rz, stride_rh, stride_rn, # R_n: [B, H, N_q]
|
| 14 |
+
stride_az, stride_ah, stride_an, stride_ak, # A_pool: [B, H, N_q, N_k]
|
| 15 |
+
stride_mz, stride_mh, stride_mn, stride_mk, # rectify_mask: [B, H, N_q, N_k]
|
| 16 |
+
stride_vz, stride_vh, stride_vk, stride_vd, # V_cmp: [B, H, N_k, D]
|
| 17 |
+
stride_cz, stride_ch, stride_cm, stride_cd, # Out: [B, H, S_q, D]
|
| 18 |
+
H, N_q, N_k,
|
| 19 |
+
CHUNK_SIZE_Q: tl.constexpr, # tokens per Q block
|
| 20 |
+
BLOCK_M: tl.constexpr, # tokens per program (<= CHUNK_SIZE_Q)
|
| 21 |
+
BLOCK_N: tl.constexpr, # K blocks per inner step
|
| 22 |
+
HEAD_DIM: tl.constexpr,
|
| 23 |
+
):
|
| 24 |
+
"""Compute Out[b, h, s, :] = R_n[b, h, n_q(s)] * O_spa[b, h, s, :]
|
| 25 |
+
+ sum_{j} A_pool[b, h, n_q(s), j]
|
| 26 |
+
* rectify_mask[b, h, n_q(s), j]
|
| 27 |
+
* V_cmp[b, h, j, :]
|
| 28 |
+
where n_q(s) = s // CHUNK_SIZE_Q.
|
| 29 |
+
|
| 30 |
+
Each program handles BLOCK_M consecutive query tokens (within one Q block,
|
| 31 |
+
BLOCK_M <= CHUNK_SIZE_Q so every token in the program shares the same n_q).
|
| 32 |
+
"""
|
| 33 |
+
pid_m = tl.program_id(0)
|
| 34 |
+
pid_zh = tl.program_id(1)
|
| 35 |
+
off_z = pid_zh // H
|
| 36 |
+
off_h = pid_zh % H
|
| 37 |
+
|
| 38 |
+
# All BLOCK_M rows belong to the same Q block (BLOCK_M divides CHUNK_SIZE_Q).
|
| 39 |
+
n_q = (pid_m * BLOCK_M) // CHUNK_SIZE_Q
|
| 40 |
+
|
| 41 |
+
offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
| 42 |
+
offs_d = tl.arange(0, HEAD_DIM)
|
| 43 |
+
|
| 44 |
+
# -------- Load R_n and the BSA output --------
|
| 45 |
+
r_ptr = R_n + off_z * stride_rz + off_h * stride_rh + n_q * stride_rn
|
| 46 |
+
r = tl.load(r_ptr).to(tl.float32)
|
| 47 |
+
|
| 48 |
+
o_spa_ptrs = (
|
| 49 |
+
O_spa
|
| 50 |
+
+ off_z * stride_oz + off_h * stride_oh
|
| 51 |
+
+ offs_m[:, None] * stride_om + offs_d[None, :] * stride_od
|
| 52 |
+
)
|
| 53 |
+
out_ptrs = (
|
| 54 |
+
Out
|
| 55 |
+
+ off_z * stride_cz + off_h * stride_ch
|
| 56 |
+
+ offs_m[:, None] * stride_cm + offs_d[None, :] * stride_cd
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
s_q_total = pid_m * BLOCK_M + BLOCK_M # valid rows are those < S_q_total in caller
|
| 60 |
+
# We don't know S_q here; rely on caller to guarantee S_q is a multiple of CHUNK_SIZE_Q
|
| 61 |
+
# via padding. But we still do a row mask to be safe for the last program.
|
| 62 |
+
row_mask = offs_m[:, None] < (n_q + 1) * CHUNK_SIZE_Q
|
| 63 |
+
|
| 64 |
+
o_spa = tl.load(o_spa_ptrs, mask=row_mask, other=0.0).to(tl.float32)
|
| 65 |
+
acc = r * o_spa # broadcast scalar * [BLOCK_M, HEAD_DIM]
|
| 66 |
+
|
| 67 |
+
# -------- Accumulate non-critical contribution: sum_j A * mask * V --------
|
| 68 |
+
a_base = (
|
| 69 |
+
A_pool
|
| 70 |
+
+ off_z * stride_az + off_h * stride_ah + n_q * stride_an
|
| 71 |
+
)
|
| 72 |
+
m_base = (
|
| 73 |
+
rectify_mask
|
| 74 |
+
+ off_z * stride_mz + off_h * stride_mh + n_q * stride_mn
|
| 75 |
+
)
|
| 76 |
+
v_base = V_cmp + off_z * stride_vz + off_h * stride_vh
|
| 77 |
+
|
| 78 |
+
for j_start in range(0, N_k, BLOCK_N):
|
| 79 |
+
offs_n = j_start + tl.arange(0, BLOCK_N)
|
| 80 |
+
n_mask = offs_n < N_k
|
| 81 |
+
|
| 82 |
+
a = tl.load(a_base + offs_n * stride_ak, mask=n_mask, other=0.0).to(tl.float32)
|
| 83 |
+
m = tl.load(m_base + offs_n * stride_mk, mask=n_mask, other=0).to(tl.float32)
|
| 84 |
+
coef = a * m # [BLOCK_N], zero where mask is False
|
| 85 |
+
|
| 86 |
+
v_ptrs = v_base + offs_n[:, None] * stride_vk + offs_d[None, :] * stride_vd
|
| 87 |
+
v = tl.load(v_ptrs, mask=n_mask[:, None], other=0.0).to(tl.float32)
|
| 88 |
+
|
| 89 |
+
# acc += coef[:, None] * v summed over BLOCK_N (one Q row uses all K blocks)
|
| 90 |
+
# Contribution is the same for all Q tokens in this block, so we add a row-wise
|
| 91 |
+
# contribution: acc[i, :] += sum_j coef[j] * v[j, :]
|
| 92 |
+
contrib = tl.sum(coef[:, None] * v, axis=0) # [HEAD_DIM]
|
| 93 |
+
acc += contrib[None, :]
|
| 94 |
+
|
| 95 |
+
tl.store(out_ptrs, acc.to(Out.type.element_ty), mask=row_mask)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def rectified_correction_triton(
|
| 99 |
+
o_spa: torch.Tensor, # [B, H, S_q, D]
|
| 100 |
+
R_n: torch.Tensor, # [B, H, N_q]
|
| 101 |
+
A_pool: torch.Tensor, # [B, H, N_q, N_k] (probabilities, fp32)
|
| 102 |
+
rectify_mask: torch.Tensor, # [B, H, N_q, N_k] (bool)
|
| 103 |
+
v_cmp: torch.Tensor, # [B, H, N_k, D]
|
| 104 |
+
chunk_size_q: int,
|
| 105 |
+
) -> torch.Tensor:
|
| 106 |
+
"""Fused Rectified-SpaAttn correction without materialising the
|
| 107 |
+
[B, H, S_q, D] o_ncri intermediate.
|
| 108 |
+
|
| 109 |
+
Output: out = R_n_token_level * o_spa + (A_pool * rectify_mask) @ v_cmp,
|
| 110 |
+
where R_n is broadcast from N_q to S_q via the chunk index.
|
| 111 |
+
"""
|
| 112 |
+
B, H, S_q, D = o_spa.shape
|
| 113 |
+
N_q = R_n.shape[-1]
|
| 114 |
+
N_k = A_pool.shape[-1]
|
| 115 |
+
assert S_q == N_q * chunk_size_q, f"S_q={S_q} must equal N_q*chunk_size_q={N_q*chunk_size_q}"
|
| 116 |
+
|
| 117 |
+
# Choose BLOCK_M = chunk_size_q for chunk_size_q <= 64; else use 64.
|
| 118 |
+
if chunk_size_q <= 64:
|
| 119 |
+
BLOCK_M = chunk_size_q
|
| 120 |
+
else:
|
| 121 |
+
# When chunk_size_q is large, split each Q block into multiple programs.
|
| 122 |
+
BLOCK_M = 64
|
| 123 |
+
assert chunk_size_q % BLOCK_M == 0, (
|
| 124 |
+
f"chunk_size_q={chunk_size_q} must be divisible by BLOCK_M={BLOCK_M}")
|
| 125 |
+
|
| 126 |
+
BLOCK_N = 64 if N_k >= 64 else max(1, triton.next_power_of_2(N_k))
|
| 127 |
+
|
| 128 |
+
o_spa = o_spa.contiguous()
|
| 129 |
+
A_pool = A_pool.contiguous()
|
| 130 |
+
rectify_mask = rectify_mask.contiguous()
|
| 131 |
+
v_cmp = v_cmp.contiguous()
|
| 132 |
+
R_n = R_n.contiguous()
|
| 133 |
+
out = torch.empty_like(o_spa)
|
| 134 |
+
|
| 135 |
+
grid = (triton.cdiv(S_q, BLOCK_M), B * H, 1)
|
| 136 |
+
|
| 137 |
+
_rectified_correction_fwd_kernel[grid](
|
| 138 |
+
o_spa, R_n, A_pool, rectify_mask, v_cmp, out,
|
| 139 |
+
o_spa.stride(0), o_spa.stride(1), o_spa.stride(2), o_spa.stride(3),
|
| 140 |
+
R_n.stride(0), R_n.stride(1), R_n.stride(2),
|
| 141 |
+
A_pool.stride(0), A_pool.stride(1), A_pool.stride(2), A_pool.stride(3),
|
| 142 |
+
rectify_mask.stride(0), rectify_mask.stride(1), rectify_mask.stride(2), rectify_mask.stride(3),
|
| 143 |
+
v_cmp.stride(0), v_cmp.stride(1), v_cmp.stride(2), v_cmp.stride(3),
|
| 144 |
+
out.stride(0), out.stride(1), out.stride(2), out.stride(3),
|
| 145 |
+
H, N_q, N_k,
|
| 146 |
+
CHUNK_SIZE_Q=chunk_size_q,
|
| 147 |
+
BLOCK_M=BLOCK_M,
|
| 148 |
+
BLOCK_N=BLOCK_N,
|
| 149 |
+
HEAD_DIM=D,
|
| 150 |
+
num_warps=4,
|
| 151 |
+
num_stages=2,
|
| 152 |
+
)
|
| 153 |
+
return out
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
@triton.jit
|
| 157 |
+
def _taylor_merge_fwd_kernel(
|
| 158 |
+
Q, K_cmp, V_cmp,
|
| 159 |
+
O_spa, LSE_spa,
|
| 160 |
+
Out,
|
| 161 |
+
block_token_counts, # [N_k] (fp32, log already taken outside)
|
| 162 |
+
nonsel_mask, # [B, H, N_q, N_k] bool
|
| 163 |
+
is_flat, # [B, H, N_q] bool
|
| 164 |
+
sm_scale_log2, # scalar, in log2
|
| 165 |
+
sm_scale, # scalar, natural
|
| 166 |
+
stride_qz, stride_qh, stride_qm, stride_qd,
|
| 167 |
+
stride_kz, stride_kh, stride_kk, stride_kd,
|
| 168 |
+
stride_vz, stride_vh, stride_vk, stride_vd,
|
| 169 |
+
stride_oz, stride_oh, stride_om, stride_od,
|
| 170 |
+
stride_lz, stride_lh, stride_lm, # LSE_spa [B, H, S_q]
|
| 171 |
+
stride_cz, stride_ch, stride_cm, stride_cd, # Out
|
| 172 |
+
stride_btc, # block_token_counts [N_k]
|
| 173 |
+
stride_mz, stride_mh, stride_mn, stride_mk,
|
| 174 |
+
stride_fz, stride_fh, stride_fn,
|
| 175 |
+
H, N_q, N_k,
|
| 176 |
+
CHUNK_SIZE_Q: tl.constexpr,
|
| 177 |
+
BLOCK_M: tl.constexpr,
|
| 178 |
+
BLOCK_N: tl.constexpr,
|
| 179 |
+
HEAD_DIM: tl.constexpr,
|
| 180 |
+
):
|
| 181 |
+
"""For each query token i in a Q block n_q:
|
| 182 |
+
z_taylor[j] = q_i · K_cmp[j] * sm_scale + log_count[j] (only for j in nonsel)
|
| 183 |
+
lse_taylor = logsumexp_j z_taylor (over nonsel j)
|
| 184 |
+
o_taylor = sum_j softmax(z_taylor)[j] * V_cmp[j]
|
| 185 |
+
Then if is_flat[n_q]:
|
| 186 |
+
w_spa = exp(lse_spa - lse_total)
|
| 187 |
+
w_taylor = exp(lse_taylor - lse_total)
|
| 188 |
+
out = w_spa * o_spa + w_taylor * o_taylor
|
| 189 |
+
else:
|
| 190 |
+
out = o_spa
|
| 191 |
+
"""
|
| 192 |
+
pid_m = tl.program_id(0)
|
| 193 |
+
pid_zh = tl.program_id(1)
|
| 194 |
+
off_z = pid_zh // H
|
| 195 |
+
off_h = pid_zh % H
|
| 196 |
+
|
| 197 |
+
n_q = (pid_m * BLOCK_M) // CHUNK_SIZE_Q
|
| 198 |
+
offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
| 199 |
+
offs_d = tl.arange(0, HEAD_DIM)
|
| 200 |
+
|
| 201 |
+
flat_ptr = is_flat + off_z * stride_fz + off_h * stride_fh + n_q * stride_fn
|
| 202 |
+
flat_val = tl.load(flat_ptr).to(tl.int32)
|
| 203 |
+
|
| 204 |
+
# Load Q rows for this program
|
| 205 |
+
q_ptrs = (
|
| 206 |
+
Q + off_z * stride_qz + off_h * stride_qh
|
| 207 |
+
+ offs_m[:, None] * stride_qm + offs_d[None, :] * stride_qd
|
| 208 |
+
)
|
| 209 |
+
q = tl.load(q_ptrs).to(tl.float32)
|
| 210 |
+
|
| 211 |
+
# Always read o_spa
|
| 212 |
+
o_spa_ptrs = (
|
| 213 |
+
O_spa + off_z * stride_oz + off_h * stride_oh
|
| 214 |
+
+ offs_m[:, None] * stride_om + offs_d[None, :] * stride_od
|
| 215 |
+
)
|
| 216 |
+
o_spa = tl.load(o_spa_ptrs).to(tl.float32)
|
| 217 |
+
|
| 218 |
+
out_ptrs = (
|
| 219 |
+
Out + off_z * stride_cz + off_h * stride_ch
|
| 220 |
+
+ offs_m[:, None] * stride_cm + offs_d[None, :] * stride_cd
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
if flat_val == 0:
|
| 224 |
+
# Sharp queries: keep o_spa unchanged.
|
| 225 |
+
tl.store(out_ptrs, o_spa.to(Out.type.element_ty))
|
| 226 |
+
return
|
| 227 |
+
|
| 228 |
+
# --- Streaming softmax over nonsel K blocks for Taylor ---
|
| 229 |
+
m_i = tl.zeros([BLOCK_M], dtype=tl.float32) - float("inf")
|
| 230 |
+
l_i = tl.zeros([BLOCK_M], dtype=tl.float32)
|
| 231 |
+
acc = tl.zeros([BLOCK_M, HEAD_DIM], dtype=tl.float32)
|
| 232 |
+
|
| 233 |
+
mask_base = nonsel_mask + off_z * stride_mz + off_h * stride_mh + n_q * stride_mn
|
| 234 |
+
k_base = K_cmp + off_z * stride_kz + off_h * stride_kh
|
| 235 |
+
v_base = V_cmp + off_z * stride_vz + off_h * stride_vh
|
| 236 |
+
|
| 237 |
+
for j_start in range(0, N_k, BLOCK_N):
|
| 238 |
+
offs_n = j_start + tl.arange(0, BLOCK_N)
|
| 239 |
+
n_mask = offs_n < N_k
|
| 240 |
+
|
| 241 |
+
# Load K_cmp [BLOCK_N, HEAD_DIM]
|
| 242 |
+
k_ptrs = k_base + offs_n[:, None] * stride_kk + offs_d[None, :] * stride_kd
|
| 243 |
+
k_cmp_b = tl.load(k_ptrs, mask=n_mask[:, None], other=0.0).to(tl.float32)
|
| 244 |
+
|
| 245 |
+
# Score q @ k_cmp^T : [BLOCK_M, BLOCK_N]
|
| 246 |
+
qk = tl.dot(q, tl.trans(k_cmp_b)) * sm_scale
|
| 247 |
+
|
| 248 |
+
# Add log(block_token_count[j]) to make Taylor weights commensurate.
|
| 249 |
+
log_cnt = tl.load(block_token_counts + offs_n * stride_btc, mask=n_mask, other=0.0)
|
| 250 |
+
qk = qk + log_cnt[None, :]
|
| 251 |
+
|
| 252 |
+
# Mask out non-eligible blocks (selected, padded, etc.)
|
| 253 |
+
nm = tl.load(mask_base + offs_n * stride_mk, mask=n_mask, other=0).to(tl.int32)
|
| 254 |
+
valid = (nm != 0) & n_mask
|
| 255 |
+
qk = tl.where(valid[None, :], qk, float("-inf"))
|
| 256 |
+
|
| 257 |
+
# Streaming online softmax update
|
| 258 |
+
m_ij = tl.maximum(m_i, tl.max(qk, axis=1))
|
| 259 |
+
alpha = tl.math.exp2((m_i - m_ij) * 1.4426950408889634)
|
| 260 |
+
# exp in natural log: convert qk (already natural) to natural exp
|
| 261 |
+
# We want p = exp(qk - m_ij). Use tl.math.exp.
|
| 262 |
+
p = tl.math.exp(qk - m_ij[:, None])
|
| 263 |
+
p = tl.where(valid[None, :], p, 0.0)
|
| 264 |
+
|
| 265 |
+
# Load V_cmp [BLOCK_N, HEAD_DIM]
|
| 266 |
+
v_ptrs = v_base + offs_n[:, None] * stride_vk + offs_d[None, :] * stride_vd
|
| 267 |
+
v_cmp_b = tl.load(v_ptrs, mask=n_mask[:, None], other=0.0).to(tl.float32)
|
| 268 |
+
|
| 269 |
+
l_i = l_i * alpha + tl.sum(p, axis=1)
|
| 270 |
+
acc = acc * alpha[:, None] + tl.dot(p, v_cmp_b)
|
| 271 |
+
m_i = m_ij
|
| 272 |
+
|
| 273 |
+
# lse_taylor = m_i + log(l_i); guard l_i = 0 (no nonsel blocks → o_taylor = 0, lse = -inf)
|
| 274 |
+
l_safe = tl.where(l_i > 0.0, l_i, 1.0)
|
| 275 |
+
lse_taylor = tl.where(l_i > 0.0, m_i + tl.log(l_safe), tl.full([BLOCK_M], -float("inf"), tl.float32))
|
| 276 |
+
o_taylor = tl.where(l_i[:, None] > 0.0, acc / l_safe[:, None], tl.zeros([BLOCK_M, HEAD_DIM], dtype=tl.float32))
|
| 277 |
+
|
| 278 |
+
# Load lse_spa for these tokens
|
| 279 |
+
lse_spa_ptrs = LSE_spa + off_z * stride_lz + off_h * stride_lh + offs_m * stride_lm
|
| 280 |
+
lse_spa = tl.load(lse_spa_ptrs)
|
| 281 |
+
|
| 282 |
+
# Stable lse merge: lse_total = max + log(exp(lse_a - max) + exp(lse_b - max))
|
| 283 |
+
lse_max = tl.maximum(lse_spa, lse_taylor)
|
| 284 |
+
finite = (lse_max > -float("inf"))
|
| 285 |
+
a_ext = tl.where(finite, lse_spa - lse_max, 0.0)
|
| 286 |
+
b_ext = tl.where(finite, lse_taylor - lse_max, 0.0)
|
| 287 |
+
lse_total = tl.where(finite, lse_max + tl.log(tl.math.exp(a_ext) + tl.math.exp(b_ext)), lse_max)
|
| 288 |
+
|
| 289 |
+
w_spa = tl.where(finite, tl.math.exp(lse_spa - lse_total), 1.0)
|
| 290 |
+
w_taylor = tl.where(finite, tl.math.exp(lse_taylor - lse_total), 0.0)
|
| 291 |
+
|
| 292 |
+
out = w_spa[:, None] * o_spa + w_taylor[:, None] * o_taylor
|
| 293 |
+
tl.store(out_ptrs, out.to(Out.type.element_ty))
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def taylor_merge_triton(
|
| 297 |
+
q: torch.Tensor, # [B, H, S_q, D] float (bf16/fp16/fp32)
|
| 298 |
+
k_cmp: torch.Tensor, # [B, H, N_k, D]
|
| 299 |
+
v_cmp: torch.Tensor, # [B, H, N_k, D]
|
| 300 |
+
o_spa: torch.Tensor, # [B, H, S_q, D]
|
| 301 |
+
lse_spa: torch.Tensor, # [B, H, S_q] natural-log LSE from BSA
|
| 302 |
+
nonsel_mask: torch.Tensor, # [B, H, N_q, N_k] bool
|
| 303 |
+
is_flat: torch.Tensor, # [B, H, N_q] bool
|
| 304 |
+
block_token_counts: torch.Tensor, # [N_k] fp32 — number of valid tokens per K block
|
| 305 |
+
chunk_size_q: int,
|
| 306 |
+
sm_scale: float,
|
| 307 |
+
) -> torch.Tensor:
|
| 308 |
+
"""Fused Taylor merge.
|
| 309 |
+
|
| 310 |
+
Materialises NONE of the [B, H, S_q, N_k] taylor_scores tensor.
|
| 311 |
+
Sharp queries (is_flat=False) skip the streaming softmax entirely.
|
| 312 |
+
"""
|
| 313 |
+
B, H, S_q, D = o_spa.shape
|
| 314 |
+
N_q = nonsel_mask.shape[-2]
|
| 315 |
+
N_k = nonsel_mask.shape[-1]
|
| 316 |
+
assert S_q == N_q * chunk_size_q
|
| 317 |
+
|
| 318 |
+
if chunk_size_q <= 64:
|
| 319 |
+
BLOCK_M = chunk_size_q
|
| 320 |
+
else:
|
| 321 |
+
BLOCK_M = 64
|
| 322 |
+
assert chunk_size_q % BLOCK_M == 0
|
| 323 |
+
|
| 324 |
+
BLOCK_N = 64 if N_k >= 64 else max(1, triton.next_power_of_2(N_k))
|
| 325 |
+
|
| 326 |
+
# Take log of token counts once; pad blocks (count=0) → log(1) = 0, never reached
|
| 327 |
+
# because they're already excluded via nonsel_mask & block_valid in the caller.
|
| 328 |
+
log_counts = torch.log(block_token_counts.float().clamp(min=1.0)).contiguous()
|
| 329 |
+
|
| 330 |
+
q = q.contiguous()
|
| 331 |
+
k_cmp = k_cmp.contiguous()
|
| 332 |
+
v_cmp = v_cmp.contiguous()
|
| 333 |
+
o_spa = o_spa.contiguous()
|
| 334 |
+
lse_spa = lse_spa.contiguous()
|
| 335 |
+
nonsel_mask = nonsel_mask.contiguous()
|
| 336 |
+
is_flat = is_flat.contiguous()
|
| 337 |
+
out = torch.empty_like(o_spa)
|
| 338 |
+
|
| 339 |
+
grid = (triton.cdiv(S_q, BLOCK_M), B * H, 1)
|
| 340 |
+
|
| 341 |
+
_taylor_merge_fwd_kernel[grid](
|
| 342 |
+
q, k_cmp, v_cmp,
|
| 343 |
+
o_spa, lse_spa, out,
|
| 344 |
+
log_counts, nonsel_mask, is_flat,
|
| 345 |
+
sm_scale * 1.4426950408889634, sm_scale,
|
| 346 |
+
q.stride(0), q.stride(1), q.stride(2), q.stride(3),
|
| 347 |
+
k_cmp.stride(0), k_cmp.stride(1), k_cmp.stride(2), k_cmp.stride(3),
|
| 348 |
+
v_cmp.stride(0), v_cmp.stride(1), v_cmp.stride(2), v_cmp.stride(3),
|
| 349 |
+
o_spa.stride(0), o_spa.stride(1), o_spa.stride(2), o_spa.stride(3),
|
| 350 |
+
lse_spa.stride(0), lse_spa.stride(1), lse_spa.stride(2),
|
| 351 |
+
out.stride(0), out.stride(1), out.stride(2), out.stride(3),
|
| 352 |
+
log_counts.stride(0),
|
| 353 |
+
nonsel_mask.stride(0), nonsel_mask.stride(1), nonsel_mask.stride(2), nonsel_mask.stride(3),
|
| 354 |
+
is_flat.stride(0), is_flat.stride(1), is_flat.stride(2),
|
| 355 |
+
H, N_q, N_k,
|
| 356 |
+
CHUNK_SIZE_Q=chunk_size_q,
|
| 357 |
+
BLOCK_M=BLOCK_M,
|
| 358 |
+
BLOCK_N=BLOCK_N,
|
| 359 |
+
HEAD_DIM=D,
|
| 360 |
+
num_warps=4,
|
| 361 |
+
num_stages=2,
|
| 362 |
+
)
|
| 363 |
+
return out
|
hymm/models/modules/block_sparse_attention/bsa_interface.py
ADDED
|
@@ -0,0 +1,1503 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
import triton
|
| 5 |
+
import triton.language as tl
|
| 6 |
+
import math
|
| 7 |
+
|
| 8 |
+
from .common import _attn_fwd_gating, _attn_bwd_preprocess, configs_gating_preset
|
| 9 |
+
from .flash_attn_bsa_varlen_mask import (
|
| 10 |
+
_attn_fwd_bsa_varlen, _attn_fwd_bsa_varlen_align, _attn_bwd_dkdv_bsa_varlen_wrapper, _attn_bwd_dq_bsa_varlen_wrapper, _attn_bwd_dq_bsa_varlen_align_wrapper,
|
| 11 |
+
configs_fwd_bsa_varlen_preset, configs_fwd_bsa_varlen_align_preset, configs_bwd_dkdv_bsa_varlen_preset, configs_bwd_dq_bsa_varlen_preset, configs_bwd_dq_bsa_varlen_align_preset
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
torch._dynamo.config.cache_size_limit = 32
|
| 15 |
+
|
| 16 |
+
def is_cuda():
|
| 17 |
+
return triton.runtime.driver.active.get_current_target().backend == "cuda"
|
| 18 |
+
|
| 19 |
+
def supports_tma():
|
| 20 |
+
return is_cuda() and torch.cuda.get_device_capability()[0] >= 9
|
| 21 |
+
|
| 22 |
+
HAS_TMA_DESC = "nv_tma_desc_type" in dir(tl)
|
| 23 |
+
|
| 24 |
+
if HAS_TMA_DESC:
|
| 25 |
+
print("TMA benchmarks will be running with experimental grid constant TMA descriptor.", )
|
| 26 |
+
else:
|
| 27 |
+
print("TMA benchmarks will be running without grid constant TMA descriptor.", )
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# TmaAutoTuneHelper used in htyu's PR #5622
|
| 31 |
+
class TmaAutoTuneHelper:
|
| 32 |
+
|
| 33 |
+
# duck typing wrapper to implement the same interface as TmaDescKernelParam in Triton PR #4498
|
| 34 |
+
class KernelParamWrapper:
|
| 35 |
+
|
| 36 |
+
def __init__(self, desc):
|
| 37 |
+
self.desc = desc
|
| 38 |
+
|
| 39 |
+
def tma_desc_cpu_ptr(self):
|
| 40 |
+
return self.desc.data_ptr()
|
| 41 |
+
|
| 42 |
+
TMA_SIZE = 128
|
| 43 |
+
|
| 44 |
+
def __init__(self):
|
| 45 |
+
self.fill_1d_tma_descriptor_inner = (triton.runtime.driver.active.utils.fill_1d_tma_descriptor)
|
| 46 |
+
self.fill_2d_tma_descriptor_inner = (triton.runtime.driver.active.utils.fill_2d_tma_descriptor)
|
| 47 |
+
if HAS_TMA_DESC:
|
| 48 |
+
self.descriptors = {}
|
| 49 |
+
else:
|
| 50 |
+
self.cuda_descriptors = {}
|
| 51 |
+
|
| 52 |
+
# Call this method outside of the lambda function for grid size
|
| 53 |
+
def init_tma_descriptor(self, name):
|
| 54 |
+
if HAS_TMA_DESC:
|
| 55 |
+
self.descriptors[name] = torch.empty(TmaAutoTuneHelper.TMA_SIZE, device="cpu", dtype=torch.int8)
|
| 56 |
+
else:
|
| 57 |
+
self.cuda_descriptors[name] = torch.empty(TmaAutoTuneHelper.TMA_SIZE, device="cuda", dtype=torch.int8)
|
| 58 |
+
|
| 59 |
+
# Call this method inside the lambda function for grid size
|
| 60 |
+
def fill_1d_tma_descriptor(self, name, ptr, dim, block_dim, element_size):
|
| 61 |
+
if HAS_TMA_DESC:
|
| 62 |
+
desc_x = self.descriptors[name]
|
| 63 |
+
assert desc_x.data_ptr() % 64 == 0
|
| 64 |
+
self.fill_1d_tma_descriptor_inner(ptr, dim, block_dim, element_size, desc_x.data_ptr())
|
| 65 |
+
else:
|
| 66 |
+
desc_x = self.cuda_descriptors[name]
|
| 67 |
+
buf_x = torch.empty_like(desc_x, device="cpu", pin_memory=True)
|
| 68 |
+
self.fill_1d_tma_descriptor_inner(ptr, dim, block_dim, element_size, buf_x.data_ptr())
|
| 69 |
+
desc_x.copy_(buf_x, non_blocking=True)
|
| 70 |
+
|
| 71 |
+
# Call this method inside the lambda function for grid size
|
| 72 |
+
def fill_2d_tma_descriptor(self, name, ptr, dim1, dim0, block_dim1, block_dim0, element_size):
|
| 73 |
+
if HAS_TMA_DESC:
|
| 74 |
+
desc_x = self.descriptors[name]
|
| 75 |
+
assert desc_x.data_ptr() % 64 == 0
|
| 76 |
+
self.fill_2d_tma_descriptor_inner(ptr, dim1, dim0, block_dim1, block_dim0, element_size, desc_x.data_ptr())
|
| 77 |
+
else:
|
| 78 |
+
desc_x = self.cuda_descriptors[name]
|
| 79 |
+
buf_x = torch.empty_like(desc_x, device="cpu", pin_memory=True)
|
| 80 |
+
self.fill_2d_tma_descriptor_inner(ptr, dim1, dim0, block_dim1, block_dim0, element_size, buf_x.data_ptr())
|
| 81 |
+
desc_x.copy_(buf_x, non_blocking=True)
|
| 82 |
+
|
| 83 |
+
def get_tma_descriptor_kernel_param(self, name):
|
| 84 |
+
if HAS_TMA_DESC:
|
| 85 |
+
assert self.descriptors[name] is not None
|
| 86 |
+
return self.KernelParamWrapper(self.descriptors[name])
|
| 87 |
+
else:
|
| 88 |
+
assert self.cuda_descriptors[name] is not None
|
| 89 |
+
return self.cuda_descriptors[name]
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
@triton.jit
|
| 93 |
+
def create_mask_from_indices_kernel(
|
| 94 |
+
block_indices,
|
| 95 |
+
block_mask,
|
| 96 |
+
stride_bz, stride_bh, stride_bm, stride_bs,
|
| 97 |
+
stride_mz, stride_mh, stride_mm, stride_mn,
|
| 98 |
+
H,
|
| 99 |
+
):
|
| 100 |
+
i_zh, i_m, i_s = tl.program_id(0), tl.program_id(1), tl.program_id(2)
|
| 101 |
+
i_z, i_h = i_zh // H, i_zh % H
|
| 102 |
+
|
| 103 |
+
off_b = i_z.to(tl.int64) * stride_bz + i_h.to(tl.int64) * stride_bh + i_m.to(tl.int64) * stride_bm + i_s.to(tl.int64) * stride_bs
|
| 104 |
+
|
| 105 |
+
b_i = tl.load(block_indices + off_b)
|
| 106 |
+
|
| 107 |
+
off_m = i_z.to(tl.int64) * stride_mz + i_h.to(tl.int64) * stride_mh + i_m.to(tl.int64) * stride_mm + b_i.to(tl.int64) * stride_mn
|
| 108 |
+
|
| 109 |
+
b_m = 1
|
| 110 |
+
tl.store(block_mask + off_m, b_m.to(block_mask.dtype.element_ty))
|
| 111 |
+
|
| 112 |
+
def create_mask_from_indices_triton(
|
| 113 |
+
block_indices,
|
| 114 |
+
N_cols
|
| 115 |
+
):
|
| 116 |
+
B, H, N_rows, S = block_indices.shape
|
| 117 |
+
block_mask = torch.zeros((B, H, N_rows, N_cols), dtype=torch.bool, device=block_indices.device)
|
| 118 |
+
create_mask_from_indices_kernel[(B * H, N_rows, S)](
|
| 119 |
+
block_indices,
|
| 120 |
+
block_mask,
|
| 121 |
+
block_indices.stride(0), block_indices.stride(1), block_indices.stride(2), block_indices.stride(3),
|
| 122 |
+
block_mask.stride(0), block_mask.stride(1), block_mask.stride(2), block_mask.stride(3),
|
| 123 |
+
H,
|
| 124 |
+
)
|
| 125 |
+
return block_mask
|
| 126 |
+
|
| 127 |
+
@torch.compile
|
| 128 |
+
def create_mask_from_indices_varlen(block_indices, N_cols_mask):
|
| 129 |
+
|
| 130 |
+
B, H, M, _ = block_indices.shape
|
| 131 |
+
device = block_indices.device
|
| 132 |
+
|
| 133 |
+
mask = torch.zeros((B, H, M, N_cols_mask), dtype=torch.bool, device=device)
|
| 134 |
+
|
| 135 |
+
valid = block_indices < N_cols_mask
|
| 136 |
+
|
| 137 |
+
b_idx = torch.arange(B, device=device)[:, None, None, None].expand_as(block_indices)
|
| 138 |
+
h_idx = torch.arange(H, device=device)[None, :, None, None].expand_as(block_indices)
|
| 139 |
+
m_idx = torch.arange(M, device=device)[None, None, :, None].expand_as(block_indices)
|
| 140 |
+
|
| 141 |
+
valid_coords = (b_idx[valid], h_idx[valid], m_idx[valid], block_indices[valid])
|
| 142 |
+
|
| 143 |
+
mask[valid_coords] = True
|
| 144 |
+
|
| 145 |
+
return mask
|
| 146 |
+
|
| 147 |
+
@torch.compile
|
| 148 |
+
def create_indices_k_from_indices_q_varlen(
|
| 149 |
+
block_indices,
|
| 150 |
+
N_cols_mask # indicate the number of the last dimension of the bool mask, since this information cannot be determined by block_indices, which may contain invalid elements
|
| 151 |
+
):
|
| 152 |
+
block_mask_qk = create_mask_from_indices_varlen(block_indices, N_cols_mask)
|
| 153 |
+
B, H, M, N = block_mask_qk.shape
|
| 154 |
+
block_mask_kq = block_mask_qk.permute(0, 1, 3, 2)
|
| 155 |
+
indices = torch.arange(M, device=block_indices.device).view(1, 1, 1, -1).expand_as(block_mask_kq)
|
| 156 |
+
block_indices_k = torch.where(block_mask_kq, indices, M)
|
| 157 |
+
block_indices_k, _ = torch.sort(block_indices_k, dim=-1)
|
| 158 |
+
|
| 159 |
+
block_indices_k_lens = (block_indices_k < M).sum(dim=-1)
|
| 160 |
+
|
| 161 |
+
return block_indices_k, block_indices_k_lens
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
@torch.compile
|
| 165 |
+
def mean_pooling_compression(
|
| 166 |
+
x: torch.Tensor,
|
| 167 |
+
block_size: int
|
| 168 |
+
) -> torch.Tensor:
|
| 169 |
+
B, H, S = x.shape[:3]
|
| 170 |
+
num_block = math.ceil(S / block_size)
|
| 171 |
+
if S % block_size != 0:
|
| 172 |
+
x = F.pad(x, (0, 0, 0, num_block * block_size - S))
|
| 173 |
+
x_cmp = x.view(B, H, num_block, block_size, -1).mean(dim=3)
|
| 174 |
+
return x_cmp
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
@torch.compile
|
| 178 |
+
def masked_mean_pooling_compression(
|
| 179 |
+
x: torch.Tensor,
|
| 180 |
+
block_size: int,
|
| 181 |
+
valid_mask: torch.Tensor,
|
| 182 |
+
) -> torch.Tensor:
|
| 183 |
+
"""Mean pooling that ignores padded positions marked False in valid_mask.
|
| 184 |
+
|
| 185 |
+
Handles both cases:
|
| 186 |
+
- S divisible by block_size (after 3D padding + block rearrangement)
|
| 187 |
+
- S not divisible by block_size (pads the tail and extends valid_mask)
|
| 188 |
+
"""
|
| 189 |
+
B, H, S, D = x.shape
|
| 190 |
+
num_block = math.ceil(S / block_size)
|
| 191 |
+
if S % block_size != 0:
|
| 192 |
+
pad_len = num_block * block_size - S
|
| 193 |
+
x = F.pad(x, (0, 0, 0, pad_len))
|
| 194 |
+
valid_mask = F.pad(valid_mask, (0, pad_len), value=False)
|
| 195 |
+
|
| 196 |
+
x_blocks = x.view(B, H, num_block, block_size, D)
|
| 197 |
+
mask_blocks = valid_mask.view(num_block, block_size)
|
| 198 |
+
mask_f = mask_blocks.float().unsqueeze(0).unsqueeze(0).unsqueeze(-1)
|
| 199 |
+
counts = mask_f.sum(dim=3, keepdim=True).clamp(min=1.0)
|
| 200 |
+
x_cmp = (x_blocks * mask_f).sum(dim=3) / counts.squeeze(3)
|
| 201 |
+
return x_cmp
|
| 202 |
+
|
| 203 |
+
@torch.compile
|
| 204 |
+
def cal_score(q, k):
|
| 205 |
+
k_transposed = k.transpose(-1, -2) # [b, h, d, s_k]
|
| 206 |
+
score = torch.matmul(q, k_transposed) # [b, h, s_q, s_k]
|
| 207 |
+
return score
|
| 208 |
+
|
| 209 |
+
def cal_score_triton(q, k):
|
| 210 |
+
B, H, s_q, D = q.shape
|
| 211 |
+
s_k = k.shape[2]
|
| 212 |
+
|
| 213 |
+
score = torch.empty(B, H, s_q, s_k, device=q.device, dtype=q.dtype)
|
| 214 |
+
|
| 215 |
+
kernel_config = {} if os.environ.get('TRITON_AUTOTUNE_ENBALE', '0') == '1' else configs_gating_preset['default']
|
| 216 |
+
|
| 217 |
+
grid = lambda args: (triton.cdiv(s_q, args["BLOCK_M"]), B * H, 1)
|
| 218 |
+
_attn_fwd_gating[grid](
|
| 219 |
+
q, k, score,
|
| 220 |
+
q.stride(0), q.stride(1), q.stride(2), q.stride(3),
|
| 221 |
+
k.stride(0), k.stride(1), k.stride(2), k.stride(3),
|
| 222 |
+
score.stride(0), score.stride(1), score.stride(2), score.stride(3),
|
| 223 |
+
H, s_q, s_k,
|
| 224 |
+
HEAD_DIM=D,
|
| 225 |
+
**kernel_config
|
| 226 |
+
)
|
| 227 |
+
return score
|
| 228 |
+
|
| 229 |
+
@torch.compile
|
| 230 |
+
def get_select_indices_topk(q, k, sparsity):
|
| 231 |
+
score = cal_score(q, k)
|
| 232 |
+
block_indices, block_indices_lens = get_select_indices_topk_from_score(score, sparsity)
|
| 233 |
+
return block_indices, block_indices_lens
|
| 234 |
+
|
| 235 |
+
@torch.compile
|
| 236 |
+
def get_select_indices_topk_from_score(score, sparsity):
|
| 237 |
+
num_selected = int((1 - sparsity) * score.shape[-1])
|
| 238 |
+
block_indices = torch.topk(score, num_selected)[1]
|
| 239 |
+
block_indices, _ = torch.sort(block_indices, dim=-1)
|
| 240 |
+
|
| 241 |
+
block_indices_lens = torch.full(
|
| 242 |
+
(block_indices.shape[0], block_indices.shape[1], block_indices.shape[2]),
|
| 243 |
+
num_selected,
|
| 244 |
+
dtype=torch.int32,
|
| 245 |
+
device=block_indices.device
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
return block_indices, block_indices_lens
|
| 249 |
+
|
| 250 |
+
@torch.compile
|
| 251 |
+
def get_select_indices_cdf(q, k, cdf_threshold):
|
| 252 |
+
score = cal_score(q, k)
|
| 253 |
+
head_dim = q.shape[-1]
|
| 254 |
+
block_indices, block_indices_lens = get_select_indices_cdf_from_score(score, cdf_threshold, 1 / head_dim**0.5)
|
| 255 |
+
return block_indices, block_indices_lens
|
| 256 |
+
|
| 257 |
+
@torch.compile
|
| 258 |
+
def get_select_indices_cdf_from_score(score, cdf_threshold, sm_scale):
|
| 259 |
+
weights = torch.softmax(score * sm_scale, dim=-1)
|
| 260 |
+
|
| 261 |
+
B, H, Sq, Sk = weights.shape
|
| 262 |
+
upper_bound = min(Sk, int(cdf_threshold * Sk) + 1)
|
| 263 |
+
topk_vals, topk_idx = torch.topk(weights, k=upper_bound, dim=-1, largest=True, sorted=True)
|
| 264 |
+
cdf = torch.cumsum(topk_vals, dim=-1)
|
| 265 |
+
# Standard nucleus (top-p): pick the smallest set whose cumulative prob >= threshold,
|
| 266 |
+
# i.e. INCLUDE the block that crosses the threshold. (cdf < p).sum() counts the prefix
|
| 267 |
+
# blocks still strictly below p; +1 adds the crossing block.
|
| 268 |
+
num_selected = (cdf < cdf_threshold).to(torch.int32).sum(dim=-1, keepdim=True) + 1
|
| 269 |
+
num_selected = num_selected.clamp(min=1, max=Sk)
|
| 270 |
+
|
| 271 |
+
block_indices = topk_idx.contiguous()
|
| 272 |
+
pos = torch.arange(upper_bound, device=block_indices.device)
|
| 273 |
+
block_indices = block_indices.masked_fill(pos >= num_selected, Sk)
|
| 274 |
+
block_indices, _ = torch.sort(block_indices, dim=-1)
|
| 275 |
+
return block_indices, num_selected.squeeze(-1).to(torch.int32)
|
| 276 |
+
|
| 277 |
+
@torch.compile
|
| 278 |
+
def get_select_indices_cdf_topk(q, k, sparsity, cdf_threshold):
|
| 279 |
+
score = cal_score(q, k)
|
| 280 |
+
head_dim = q.shape[-1]
|
| 281 |
+
block_indices, block_indices_lens = get_select_indices_cdf_topk_from_score(score, sparsity, cdf_threshold, 1 / head_dim**0.5)
|
| 282 |
+
return block_indices, block_indices_lens
|
| 283 |
+
|
| 284 |
+
@torch.compile
|
| 285 |
+
def get_select_indices_cdf_topk_from_score(score, sparsity, cdf_threshold, sm_scale):
|
| 286 |
+
weights = torch.softmax(score * sm_scale, dim=-1)
|
| 287 |
+
|
| 288 |
+
B, H, Sq, Sk = weights.shape
|
| 289 |
+
num_selected_topk = max(1, int((1 - sparsity) * Sk))
|
| 290 |
+
upper_bound = min(Sk, max(num_selected_topk, int(cdf_threshold * Sk) + 1))
|
| 291 |
+
|
| 292 |
+
topk_vals, topk_idx = torch.topk(weights, k=upper_bound, dim=-1, largest=True, sorted=True)
|
| 293 |
+
cdf = torch.cumsum(topk_vals, dim=-1)
|
| 294 |
+
# Standard nucleus (top-p): pick the smallest set whose cumulative prob >= threshold,
|
| 295 |
+
# i.e. INCLUDE the block that crosses the threshold. (cdf < p).sum() counts the prefix
|
| 296 |
+
# blocks still strictly below p; +1 adds the crossing block.
|
| 297 |
+
num_selected = (cdf < cdf_threshold).to(torch.int32).sum(dim=-1, keepdim=True) + 1
|
| 298 |
+
num_selected = num_selected.clamp(min=num_selected_topk, max=Sk)
|
| 299 |
+
|
| 300 |
+
block_indices = topk_idx.contiguous()
|
| 301 |
+
pos = torch.arange(upper_bound, device=block_indices.device)
|
| 302 |
+
block_indices = block_indices.masked_fill(pos >= num_selected, Sk)
|
| 303 |
+
block_indices, _ = torch.sort(block_indices, dim=-1)
|
| 304 |
+
return block_indices, num_selected.squeeze(-1).to(torch.int32)
|
| 305 |
+
|
| 306 |
+
def get_select_indices(q, k, sparsity, cdf_threshold):
|
| 307 |
+
if sparsity is not None and cdf_threshold is None:
|
| 308 |
+
block_indices, block_indices_lens = get_select_indices_topk(q, k, sparsity)
|
| 309 |
+
elif sparsity is None and cdf_threshold is not None:
|
| 310 |
+
block_indices, block_indices_lens = get_select_indices_cdf(q, k, cdf_threshold)
|
| 311 |
+
elif sparsity is not None and cdf_threshold is not None:
|
| 312 |
+
block_indices, block_indices_lens = get_select_indices_cdf_topk(q, k, sparsity, cdf_threshold)
|
| 313 |
+
else:
|
| 314 |
+
raise ValueError
|
| 315 |
+
return block_indices, block_indices_lens
|
| 316 |
+
|
| 317 |
+
def get_select_indices_from_score(score, sparsity, cdf_threshold):
|
| 318 |
+
if sparsity is not None and cdf_threshold is None:
|
| 319 |
+
block_indices, block_indices_lens = get_select_indices_topk_from_score(score, sparsity)
|
| 320 |
+
elif sparsity is None and cdf_threshold is not None:
|
| 321 |
+
block_indices, block_indices_lens = get_select_indices_cdf_from_score(score, cdf_threshold)
|
| 322 |
+
elif sparsity is not None and cdf_threshold is not None:
|
| 323 |
+
block_indices, block_indices_lens = get_select_indices_cdf_topk_from_score(score, sparsity, cdf_threshold)
|
| 324 |
+
else:
|
| 325 |
+
raise ValueError
|
| 326 |
+
return block_indices, block_indices_lens
|
| 327 |
+
|
| 328 |
+
def attn_fwd_bsa_varlen_triton(
|
| 329 |
+
q,
|
| 330 |
+
k,
|
| 331 |
+
v,
|
| 332 |
+
sm_scale,
|
| 333 |
+
block_indices,
|
| 334 |
+
block_indices_lens,
|
| 335 |
+
chunk_size_q,
|
| 336 |
+
chunk_size_k,
|
| 337 |
+
sparsity,
|
| 338 |
+
kv_valid_mask=None,
|
| 339 |
+
):
|
| 340 |
+
|
| 341 |
+
B, H, Seq, D = q.shape
|
| 342 |
+
|
| 343 |
+
o = torch.empty_like(q)
|
| 344 |
+
M = torch.empty((q.shape[0], q.shape[1], q.shape[2]), device=q.device, dtype=torch.float32)
|
| 345 |
+
|
| 346 |
+
grid = lambda args: (triton.cdiv(q.shape[2], args["BLOCK_M"]), q.shape[0] * q.shape[1], 1)
|
| 347 |
+
|
| 348 |
+
config_key = 'BLOCK_N_LG=64' if chunk_size_k == 64 else 'default'
|
| 349 |
+
if chunk_size_k > 128:
|
| 350 |
+
fwd_func = _attn_fwd_bsa_varlen
|
| 351 |
+
kernel_config = {} if os.environ.get('TRITON_AUTOTUNE_ENBALE', '0') == '1' else configs_fwd_bsa_varlen_preset[config_key]
|
| 352 |
+
else:
|
| 353 |
+
fwd_func = _attn_fwd_bsa_varlen_align
|
| 354 |
+
kernel_config = {} if os.environ.get('TRITON_AUTOTUNE_ENBALE', '0') == '1' else configs_fwd_bsa_varlen_align_preset[config_key]
|
| 355 |
+
|
| 356 |
+
block_indices = block_indices.contiguous()
|
| 357 |
+
block_indices_lens = block_indices_lens.contiguous()
|
| 358 |
+
|
| 359 |
+
has_kv_mask = 1 if kv_valid_mask is not None else 0
|
| 360 |
+
_kv_mask = kv_valid_mask if kv_valid_mask is not None else torch.empty(0, dtype=torch.bool, device=q.device)
|
| 361 |
+
|
| 362 |
+
fwd_func[grid](
|
| 363 |
+
q, k, v, sm_scale, M, o,
|
| 364 |
+
block_indices,
|
| 365 |
+
block_indices_lens,
|
| 366 |
+
_kv_mask,
|
| 367 |
+
q.stride(0), q.stride(1), q.stride(2), q.stride(3),
|
| 368 |
+
k.stride(0), k.stride(1), k.stride(2), k.stride(3),
|
| 369 |
+
v.stride(0), v.stride(1), v.stride(2), v.stride(3),
|
| 370 |
+
o.stride(0), o.stride(1), o.stride(2), o.stride(3),
|
| 371 |
+
block_indices.stride(0), block_indices.stride(1), block_indices.stride(2), block_indices.stride(3),
|
| 372 |
+
block_indices_lens.stride(0), block_indices_lens.stride(1), block_indices_lens.stride(2),
|
| 373 |
+
H, Seq,
|
| 374 |
+
D,
|
| 375 |
+
BLOCK_M=chunk_size_q,
|
| 376 |
+
BLOCK_N_LG=chunk_size_k,
|
| 377 |
+
SPARSITY=sparsity,
|
| 378 |
+
HAS_KV_MASK=has_kv_mask,
|
| 379 |
+
**kernel_config
|
| 380 |
+
)
|
| 381 |
+
|
| 382 |
+
LN2 = 0.6931471824645996
|
| 383 |
+
lse = M * LN2
|
| 384 |
+
|
| 385 |
+
return o, lse
|
| 386 |
+
|
| 387 |
+
def attn_bwd_bsa_varlen_triton(
|
| 388 |
+
do,
|
| 389 |
+
q,
|
| 390 |
+
k,
|
| 391 |
+
v,
|
| 392 |
+
o,
|
| 393 |
+
dq,
|
| 394 |
+
dk,
|
| 395 |
+
dv,
|
| 396 |
+
sm_scale,
|
| 397 |
+
M,
|
| 398 |
+
block_indices,
|
| 399 |
+
block_indices_lens,
|
| 400 |
+
chunk_size_q,
|
| 401 |
+
chunk_size_k,
|
| 402 |
+
sparsity,
|
| 403 |
+
kv_valid_mask=None,
|
| 404 |
+
):
|
| 405 |
+
RCP_LN2 = 1.4426950408889634
|
| 406 |
+
M = M * RCP_LN2 # ln -> log2
|
| 407 |
+
|
| 408 |
+
do = do.contiguous()
|
| 409 |
+
# assert q.stride() == k.stride() == v.stride() == o.stride() == do.stride()
|
| 410 |
+
|
| 411 |
+
BATCH, N_HEAD, N_CTX, HEAD_DIM = q.shape
|
| 412 |
+
N_CTX_KV = k.shape[-2]
|
| 413 |
+
|
| 414 |
+
RCP_LN2 = 1.4426950408889634 # = 1.0 / ln(2) # reciprocal
|
| 415 |
+
arg_k = k
|
| 416 |
+
arg_k = arg_k * (sm_scale * RCP_LN2)
|
| 417 |
+
|
| 418 |
+
if min(chunk_size_q, chunk_size_k) >= 128:
|
| 419 |
+
PRE_BLOCK = 128
|
| 420 |
+
else:
|
| 421 |
+
PRE_BLOCK = min(chunk_size_q, chunk_size_k)
|
| 422 |
+
|
| 423 |
+
assert N_CTX % PRE_BLOCK == 0
|
| 424 |
+
pre_grid = (N_CTX // PRE_BLOCK, BATCH * N_HEAD)
|
| 425 |
+
delta = torch.empty_like(M)
|
| 426 |
+
_attn_bwd_preprocess[pre_grid](
|
| 427 |
+
o, do,
|
| 428 |
+
delta,
|
| 429 |
+
N_CTX,
|
| 430 |
+
BLOCK_M=PRE_BLOCK,
|
| 431 |
+
HEAD_DIM=HEAD_DIM
|
| 432 |
+
)
|
| 433 |
+
|
| 434 |
+
_N_cols = N_CTX_KV // chunk_size_k
|
| 435 |
+
_Hn = block_indices.shape[1]
|
| 436 |
+
_ki_list, _kl_list = [], []
|
| 437 |
+
for _h in range(_Hn):
|
| 438 |
+
_ki_h, _kl_h = create_indices_k_from_indices_q_varlen(
|
| 439 |
+
block_indices=block_indices[:, _h:_h+1],
|
| 440 |
+
N_cols_mask=_N_cols,
|
| 441 |
+
)
|
| 442 |
+
_ki_list.append(_ki_h)
|
| 443 |
+
_kl_list.append(_kl_h)
|
| 444 |
+
del _ki_h, _kl_h
|
| 445 |
+
block_indices_k = torch.cat(_ki_list, dim=1)
|
| 446 |
+
block_indices_k_lens = torch.cat(_kl_list, dim=1)
|
| 447 |
+
del _ki_list, _kl_list
|
| 448 |
+
|
| 449 |
+
block_indices = block_indices.contiguous()
|
| 450 |
+
block_indices_lens = block_indices_lens.contiguous()
|
| 451 |
+
block_indices_k = block_indices_k.contiguous()
|
| 452 |
+
block_indices_k_lens = block_indices_k_lens.contiguous()
|
| 453 |
+
|
| 454 |
+
has_kv_mask = 1 if kv_valid_mask is not None else 0
|
| 455 |
+
_kv_mask = kv_valid_mask if kv_valid_mask is not None else torch.empty(0, dtype=torch.bool, device=q.device)
|
| 456 |
+
|
| 457 |
+
config_key = 'BLOCK_N_DQ_LG=64' if chunk_size_k == 64 else 'default'
|
| 458 |
+
kernel_config = {} if os.environ.get('TRITON_AUTOTUNE_ENBALE', '0') == '1' else configs_bwd_dkdv_bsa_varlen_preset[config_key]
|
| 459 |
+
|
| 460 |
+
grid_dkdv = lambda args: (triton.cdiv(arg_k.shape[2], args["BLOCK_N"]), 1, arg_k.shape[0] * arg_k.shape[1])
|
| 461 |
+
_attn_bwd_dkdv_bsa_varlen_wrapper[grid_dkdv](
|
| 462 |
+
q, arg_k, v, sm_scale,
|
| 463 |
+
do,
|
| 464 |
+
dk, dv,
|
| 465 |
+
M,
|
| 466 |
+
delta,
|
| 467 |
+
block_indices_k,
|
| 468 |
+
block_indices_k_lens,
|
| 469 |
+
_kv_mask,
|
| 470 |
+
q.stride(0), q.stride(1), q.stride(2), q.stride(3),
|
| 471 |
+
k.stride(0), k.stride(1), k.stride(2), k.stride(3),
|
| 472 |
+
v.stride(0), v.stride(1), v.stride(2), v.stride(3),
|
| 473 |
+
dk.stride(0), dk.stride(1), dk.stride(2), dk.stride(3),
|
| 474 |
+
dv.stride(0), dv.stride(1), dv.stride(2), dv.stride(3),
|
| 475 |
+
do.stride(0), do.stride(1), do.stride(2), do.stride(3),
|
| 476 |
+
M.stride(0), M.stride(1), M.stride(2),
|
| 477 |
+
delta.stride(0), delta.stride(1), delta.stride(2),
|
| 478 |
+
block_indices_k.stride(0), block_indices_k.stride(1), block_indices_k.stride(2), block_indices_k.stride(3),
|
| 479 |
+
block_indices_k_lens.stride(0), block_indices_k_lens.stride(1), block_indices_k_lens.stride(2),
|
| 480 |
+
N_HEAD, N_CTX,
|
| 481 |
+
BLOCK_M=chunk_size_q,
|
| 482 |
+
BLOCK_N_DQ_LG=chunk_size_k,
|
| 483 |
+
HEAD_DIM=HEAD_DIM,
|
| 484 |
+
SPARSITY=sparsity,
|
| 485 |
+
HAS_KV_MASK=has_kv_mask,
|
| 486 |
+
**kernel_config
|
| 487 |
+
)
|
| 488 |
+
|
| 489 |
+
config_key = 'BLOCK_N_DQ_LG=64' if chunk_size_k == 64 else 'default'
|
| 490 |
+
if chunk_size_k > 128:
|
| 491 |
+
bwd_dq_func = _attn_bwd_dq_bsa_varlen_wrapper
|
| 492 |
+
kernel_config = {} if os.environ.get('TRITON_AUTOTUNE_ENBALE', '0') == '1' else configs_bwd_dq_bsa_varlen_preset[config_key]
|
| 493 |
+
else:
|
| 494 |
+
bwd_dq_func = _attn_bwd_dq_bsa_varlen_align_wrapper
|
| 495 |
+
kernel_config = {} if os.environ.get('TRITON_AUTOTUNE_ENBALE', '0') == '1' else configs_bwd_dq_bsa_varlen_align_preset[config_key]
|
| 496 |
+
|
| 497 |
+
grid_dq = lambda args: (triton.cdiv(q.shape[2], args["BLOCK_M"]), 1, q.shape[0] * q.shape[1])
|
| 498 |
+
bwd_dq_func[grid_dq](
|
| 499 |
+
q, arg_k, v,
|
| 500 |
+
do,
|
| 501 |
+
dq,
|
| 502 |
+
M,
|
| 503 |
+
delta,
|
| 504 |
+
block_indices,
|
| 505 |
+
block_indices_lens,
|
| 506 |
+
_kv_mask,
|
| 507 |
+
q.stride(0), q.stride(1), q.stride(2), q.stride(3),
|
| 508 |
+
k.stride(0), k.stride(1), k.stride(2), k.stride(3),
|
| 509 |
+
v.stride(0), v.stride(1), v.stride(2), v.stride(3),
|
| 510 |
+
dq.stride(0), dq.stride(1), dq.stride(2), dq.stride(3),
|
| 511 |
+
do.stride(0), do.stride(1), do.stride(2), do.stride(3),
|
| 512 |
+
M.stride(0), M.stride(1), M.stride(2),
|
| 513 |
+
delta.stride(0), delta.stride(1), delta.stride(2),
|
| 514 |
+
block_indices.stride(0), block_indices.stride(1), block_indices.stride(2), block_indices.stride(3),
|
| 515 |
+
block_indices_lens.stride(0), block_indices_lens.stride(1), block_indices_lens.stride(2),
|
| 516 |
+
N_HEAD, N_CTX,
|
| 517 |
+
BLOCK_M=chunk_size_q,
|
| 518 |
+
BLOCK_N_DQ_LG=chunk_size_k,
|
| 519 |
+
HEAD_DIM=HEAD_DIM,
|
| 520 |
+
SPARSITY=sparsity,
|
| 521 |
+
HAS_KV_MASK=has_kv_mask,
|
| 522 |
+
**kernel_config
|
| 523 |
+
)
|
| 524 |
+
|
| 525 |
+
@torch.compile
|
| 526 |
+
def make_block_indices_varlen_cp_list(block_indices, cp_size, num_blocks_k_full):
|
| 527 |
+
"""
|
| 528 |
+
Args:
|
| 529 |
+
block_indices: [B, H, num_blocks_q_per_cp_rank, num_blocks_k_full]
|
| 530 |
+
|
| 531 |
+
Return:
|
| 532 |
+
a list of [block_indices, block_indices_lens] for k from each cp_rank
|
| 533 |
+
- each block_indices starts from zero
|
| 534 |
+
- block_indices_lens indicates the valid number of elements in the last dimension of block_indices
|
| 535 |
+
"""
|
| 536 |
+
res = []
|
| 537 |
+
num_blocks_per_rank = num_blocks_k_full // cp_size
|
| 538 |
+
for i in range(cp_size):
|
| 539 |
+
block_indices_tmp = block_indices.clone()
|
| 540 |
+
min_block_idx = i * num_blocks_per_rank
|
| 541 |
+
block_indices_tmp -= min_block_idx
|
| 542 |
+
block_indices_tmp[block_indices_tmp < 0] = num_blocks_per_rank # block_indices_tmp < 0 indicate invalid indices, set them to num_blocks_per_rank in order to sort them to the tail, so that the first N elements of the block_indices indicated by block_indices_lens are valid
|
| 543 |
+
|
| 544 |
+
block_indices_tmp, _ = torch.sort(block_indices_tmp, dim=-1)
|
| 545 |
+
|
| 546 |
+
block_indices_tmp_lens = (block_indices_tmp < num_blocks_per_rank).sum(dim=-1)
|
| 547 |
+
|
| 548 |
+
res.append([block_indices_tmp, block_indices_tmp_lens])
|
| 549 |
+
|
| 550 |
+
return res
|
| 551 |
+
|
| 552 |
+
@torch.compile
|
| 553 |
+
def flash_attn_fwd_softmax_lse_correction(
|
| 554 |
+
softmax_lse: torch.Tensor,
|
| 555 |
+
softmax_lse_per_step: torch.Tensor,
|
| 556 |
+
):
|
| 557 |
+
"""Merge softmax stats of each step in Attention with context parallelism"""
|
| 558 |
+
max_scale = torch.max(softmax_lse, softmax_lse_per_step)
|
| 559 |
+
min_scale = torch.min(softmax_lse, softmax_lse_per_step)
|
| 560 |
+
lse_diff = min_scale - max_scale
|
| 561 |
+
lse_diff = lse_diff.nan_to_num(nan=0.) # handle cases: tensor(-inf) - tensor(-inf) = tensor(nan); In the current cp implementation, it is possible that lses of 2 cp ranks are both -inf, if no block is selected from both cp ranks. In such cases, the finally corrected lse should remain -inf.
|
| 562 |
+
new_scale = max_scale + torch.log1p(torch.exp(lse_diff)) # a + ln(1 + e^(b - a)) = ln(e^a) + ln(1 + e^(b - a)) = ln(e^a + e^b)
|
| 563 |
+
softmax_lse.copy_(new_scale)
|
| 564 |
+
|
| 565 |
+
@torch.compile
|
| 566 |
+
def flash_attn_fwd_out_correction_init(
|
| 567 |
+
out_init_step: torch.Tensor, # b h s d
|
| 568 |
+
softmax_lse: torch.Tensor, # b h s
|
| 569 |
+
softmax_lse_init_step: torch.Tensor,
|
| 570 |
+
):
|
| 571 |
+
"""Merge partial outputs of the first step in Attention with context parallelism"""
|
| 572 |
+
softmax_lse_corrected_exp = torch.exp(softmax_lse_init_step - softmax_lse)
|
| 573 |
+
softmax_lse_corrected_exp = softmax_lse_corrected_exp.unsqueeze(-1)
|
| 574 |
+
out_corrected = out_init_step * softmax_lse_corrected_exp
|
| 575 |
+
return out_corrected.to(out_init_step.dtype)
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
@torch.compile
|
| 579 |
+
def flash_attn_fwd_out_correction(
|
| 580 |
+
out: torch.Tensor,
|
| 581 |
+
out_per_step: torch.Tensor,
|
| 582 |
+
softmax_lse: torch.Tensor,
|
| 583 |
+
softmax_lse_per_step: torch.Tensor,
|
| 584 |
+
):
|
| 585 |
+
"""Merge partial outputs of each step in Attention with context parallelism"""
|
| 586 |
+
softmax_lse_corrected_exp = torch.exp(softmax_lse_per_step - softmax_lse)
|
| 587 |
+
softmax_lse_corrected_exp = softmax_lse_corrected_exp.unsqueeze(-1)
|
| 588 |
+
out_corrected = out_per_step * softmax_lse_corrected_exp
|
| 589 |
+
out.add_(out_corrected)
|
| 590 |
+
|
| 591 |
+
@torch.compile
|
| 592 |
+
def topk_sort(score, num_chunks_selected):
|
| 593 |
+
block_indices = torch.topk(score, num_chunks_selected)[1]
|
| 594 |
+
block_indices, _ = torch.sort(block_indices, dim=-1)
|
| 595 |
+
return block_indices
|
| 596 |
+
|
| 597 |
+
class _attention_bsa(torch.autograd.Function):
|
| 598 |
+
|
| 599 |
+
@staticmethod
|
| 600 |
+
def forward(ctx, q, k, v, chunk_size_q, chunk_size_k, sparsity, cdf_threshold, sm_scale,
|
| 601 |
+
kv_valid_mask=None, q_valid_mask=None, score_modifier=None):
|
| 602 |
+
HEAD_DIM_Q, HEAD_DIM_K = q.shape[-1], k.shape[-1]
|
| 603 |
+
HEAD_DIM_V = v.shape[-1]
|
| 604 |
+
assert HEAD_DIM_Q == HEAD_DIM_K and HEAD_DIM_K == HEAD_DIM_V
|
| 605 |
+
assert HEAD_DIM_K in {16, 32, 64, 128, 256}
|
| 606 |
+
|
| 607 |
+
# ---------------------- gating ----------------------
|
| 608 |
+
if q_valid_mask is not None:
|
| 609 |
+
q_cmp = masked_mean_pooling_compression(q, chunk_size_q, q_valid_mask)
|
| 610 |
+
elif kv_valid_mask is not None and q.shape[2] == k.shape[2]:
|
| 611 |
+
q_cmp = masked_mean_pooling_compression(q, chunk_size_q, kv_valid_mask)
|
| 612 |
+
else:
|
| 613 |
+
q_cmp = mean_pooling_compression(q, chunk_size_q)
|
| 614 |
+
|
| 615 |
+
if kv_valid_mask is not None:
|
| 616 |
+
k_cmp = masked_mean_pooling_compression(k, chunk_size_k, kv_valid_mask)
|
| 617 |
+
else:
|
| 618 |
+
k_cmp = mean_pooling_compression(k, chunk_size_k)
|
| 619 |
+
|
| 620 |
+
score = cal_score(q_cmp, k_cmp)
|
| 621 |
+
|
| 622 |
+
if score_modifier is not None:
|
| 623 |
+
score = score * score_modifier
|
| 624 |
+
|
| 625 |
+
if sparsity is not None and cdf_threshold is None:
|
| 626 |
+
block_indices, block_indices_lens = get_select_indices_topk_from_score(score, sparsity)
|
| 627 |
+
elif sparsity is None and cdf_threshold is not None:
|
| 628 |
+
block_indices, block_indices_lens = get_select_indices_cdf_from_score(score, cdf_threshold, sm_scale)
|
| 629 |
+
elif sparsity is not None and cdf_threshold is not None:
|
| 630 |
+
block_indices, block_indices_lens = get_select_indices_cdf_topk_from_score(score, sparsity, cdf_threshold, sm_scale)
|
| 631 |
+
else:
|
| 632 |
+
raise ValueError("Either sparsity or cdf_threshold must be provided")
|
| 633 |
+
|
| 634 |
+
# ---------------------- bsa ----------------------
|
| 635 |
+
|
| 636 |
+
o, lse = attn_fwd_bsa_varlen_triton(
|
| 637 |
+
q, k, v,
|
| 638 |
+
sm_scale, block_indices, block_indices_lens,
|
| 639 |
+
chunk_size_q, chunk_size_k,
|
| 640 |
+
sparsity,
|
| 641 |
+
kv_valid_mask=kv_valid_mask,
|
| 642 |
+
)
|
| 643 |
+
|
| 644 |
+
ctx.save_for_backward(q, k, v, o, lse, block_indices, block_indices_lens)
|
| 645 |
+
ctx.sm_scale = sm_scale
|
| 646 |
+
ctx.HEAD_DIM = HEAD_DIM_K
|
| 647 |
+
ctx.chunk_size_q = chunk_size_q
|
| 648 |
+
ctx.chunk_size_k = chunk_size_k
|
| 649 |
+
ctx.sparsity = sparsity
|
| 650 |
+
ctx.kv_valid_mask = kv_valid_mask
|
| 651 |
+
|
| 652 |
+
return o
|
| 653 |
+
|
| 654 |
+
@staticmethod
|
| 655 |
+
def backward(ctx, do):
|
| 656 |
+
q, k, v, o, lse, block_indices, block_indices_lens = ctx.saved_tensors
|
| 657 |
+
|
| 658 |
+
dq = torch.empty_like(q)
|
| 659 |
+
dk = torch.empty_like(k)
|
| 660 |
+
dv = torch.empty_like(v)
|
| 661 |
+
|
| 662 |
+
attn_bwd_bsa_varlen_triton(
|
| 663 |
+
do,
|
| 664 |
+
q,
|
| 665 |
+
k,
|
| 666 |
+
v,
|
| 667 |
+
o,
|
| 668 |
+
dq,
|
| 669 |
+
dk,
|
| 670 |
+
dv,
|
| 671 |
+
ctx.sm_scale,
|
| 672 |
+
lse,
|
| 673 |
+
block_indices,
|
| 674 |
+
block_indices_lens,
|
| 675 |
+
ctx.chunk_size_q,
|
| 676 |
+
ctx.chunk_size_k,
|
| 677 |
+
ctx.sparsity,
|
| 678 |
+
kv_valid_mask=ctx.kv_valid_mask,
|
| 679 |
+
)
|
| 680 |
+
|
| 681 |
+
return dq, dk, dv, None, None, None, None, None, None, None, None
|
| 682 |
+
|
| 683 |
+
flash_attn_bsa = _attention_bsa.apply
|
| 684 |
+
|
| 685 |
+
|
| 686 |
+
class _attention_bsa_audio_guided(torch.autograd.Function):
|
| 687 |
+
"""BSA with audio-guided score modulation (Gate 2: Audio Spatial Concentration Gate).
|
| 688 |
+
|
| 689 |
+
The audio saliency modulates the block-level QK scores BEFORE topk selection,
|
| 690 |
+
but the actual sparse attention Triton kernel remains unchanged. This ensures
|
| 691 |
+
Triton-level efficiency for the compute-heavy attention, while the lightweight
|
| 692 |
+
score modulation uses standard PyTorch ops (torch.compile friendly).
|
| 693 |
+
"""
|
| 694 |
+
|
| 695 |
+
@staticmethod
|
| 696 |
+
def forward(ctx, q, k, v, chunk_size_q, chunk_size_k, sparsity, cdf_threshold, sm_scale,
|
| 697 |
+
audio_block_saliency, audio_gate_value, audio_boost_gamma,
|
| 698 |
+
kv_valid_mask=None, q_valid_mask=None,
|
| 699 |
+
audio_norms_block_order=None, audio_weighted_lambda=0.0,
|
| 700 |
+
score_modifier=None):
|
| 701 |
+
"""
|
| 702 |
+
Args:
|
| 703 |
+
audio_block_saliency: [N_blocks_k] or [B, 1, N_blocks_k] — per-key-block saliency in [0,1]
|
| 704 |
+
audio_gate_value: scalar in [0,1] — Audio Spatial Concentration Gate g_a
|
| 705 |
+
audio_boost_gamma: scalar >= 0 — boost strength γ
|
| 706 |
+
audio_norms_block_order: [S] — per-token norms in block-rearranged order (for Path B)
|
| 707 |
+
audio_weighted_lambda: float — Path B weighting strength (0=standard pooling)
|
| 708 |
+
score_modifier: optional broadcastable tensor — external additive boost term
|
| 709 |
+
(e.g., γ_var * ρ from variance guidance). Combined with audio INSIDE
|
| 710 |
+
one boost bracket: score *= (1 + γ_audio * S̃ + score_modifier).
|
| 711 |
+
"""
|
| 712 |
+
HEAD_DIM_Q, HEAD_DIM_K = q.shape[-1], k.shape[-1]
|
| 713 |
+
HEAD_DIM_V = v.shape[-1]
|
| 714 |
+
assert HEAD_DIM_Q == HEAD_DIM_K and HEAD_DIM_K == HEAD_DIM_V
|
| 715 |
+
assert HEAD_DIM_K in {16, 32, 64, 128, 256}
|
| 716 |
+
|
| 717 |
+
B, H, Sq, D = q.shape
|
| 718 |
+
Sk = k.shape[2]
|
| 719 |
+
|
| 720 |
+
# ---------------------- gating (block-level scoring) ----------------------
|
| 721 |
+
if q_valid_mask is not None:
|
| 722 |
+
q_cmp = masked_mean_pooling_compression(q, chunk_size_q, q_valid_mask)
|
| 723 |
+
elif kv_valid_mask is not None and q.shape[2] == k.shape[2]:
|
| 724 |
+
q_cmp = masked_mean_pooling_compression(q, chunk_size_q, kv_valid_mask)
|
| 725 |
+
else:
|
| 726 |
+
q_cmp = mean_pooling_compression(q, chunk_size_q)
|
| 727 |
+
|
| 728 |
+
use_path_b = (audio_norms_block_order is not None and audio_weighted_lambda > 0)
|
| 729 |
+
if use_path_b:
|
| 730 |
+
k_cmp = audio_weighted_mean_pooling_compression(
|
| 731 |
+
k, chunk_size_k, audio_norms_block_order,
|
| 732 |
+
valid_mask=kv_valid_mask, lambda_w=audio_weighted_lambda,
|
| 733 |
+
)
|
| 734 |
+
elif kv_valid_mask is not None:
|
| 735 |
+
k_cmp = masked_mean_pooling_compression(k, chunk_size_k, kv_valid_mask)
|
| 736 |
+
else:
|
| 737 |
+
k_cmp = mean_pooling_compression(k, chunk_size_k)
|
| 738 |
+
|
| 739 |
+
score = cal_score(q_cmp, k_cmp) # [B, H, N_blocks_q, N_blocks_k]
|
| 740 |
+
|
| 741 |
+
# ---------------------- Unified Additive Boost (md 3.5.1.2) ----------------------
|
| 742 |
+
# I_{i,j} = P^QK * (1 + γ_audio * S̃ + additive_boost_from_external)
|
| 743 |
+
# All boost terms are ADDITIVE inside one bracket, then multiply P^QK.
|
| 744 |
+
N_blocks_k = score.shape[-1]
|
| 745 |
+
boost = torch.zeros(1, device=score.device, dtype=score.dtype)
|
| 746 |
+
|
| 747 |
+
has_audio = (audio_block_saliency is not None
|
| 748 |
+
and audio_block_saliency.numel() > 0
|
| 749 |
+
and audio_boost_gamma > 0)
|
| 750 |
+
if has_audio:
|
| 751 |
+
if audio_block_saliency.dim() == 1:
|
| 752 |
+
saliency = audio_block_saliency.view(1, 1, 1, -1)
|
| 753 |
+
else:
|
| 754 |
+
saliency = audio_block_saliency.unsqueeze(-2)
|
| 755 |
+
uniform_val = 1.0 / N_blocks_k
|
| 756 |
+
gated_saliency = audio_gate_value * saliency + (1.0 - audio_gate_value) * uniform_val
|
| 757 |
+
boost = boost + audio_boost_gamma * gated_saliency
|
| 758 |
+
|
| 759 |
+
if score_modifier is not None:
|
| 760 |
+
boost = boost + score_modifier
|
| 761 |
+
|
| 762 |
+
score = score * (1.0 + boost)
|
| 763 |
+
|
| 764 |
+
# ---------------------- topk selection from modulated scores ----------------------
|
| 765 |
+
if sparsity is not None and cdf_threshold is None:
|
| 766 |
+
block_indices, block_indices_lens = get_select_indices_topk_from_score(score, sparsity)
|
| 767 |
+
elif sparsity is None and cdf_threshold is not None:
|
| 768 |
+
block_indices, block_indices_lens = get_select_indices_cdf_from_score(score, cdf_threshold, sm_scale)
|
| 769 |
+
elif sparsity is not None and cdf_threshold is not None:
|
| 770 |
+
block_indices, block_indices_lens = get_select_indices_cdf_topk_from_score(score, sparsity, cdf_threshold, sm_scale)
|
| 771 |
+
else:
|
| 772 |
+
raise ValueError("Either sparsity or cdf_threshold must be provided")
|
| 773 |
+
|
| 774 |
+
# ---------------------- bsa (Triton kernel unchanged) ----------------------
|
| 775 |
+
o, lse = attn_fwd_bsa_varlen_triton(
|
| 776 |
+
q, k, v,
|
| 777 |
+
sm_scale, block_indices, block_indices_lens,
|
| 778 |
+
chunk_size_q, chunk_size_k,
|
| 779 |
+
sparsity,
|
| 780 |
+
kv_valid_mask=kv_valid_mask,
|
| 781 |
+
)
|
| 782 |
+
|
| 783 |
+
ctx.save_for_backward(q, k, v, o, lse, block_indices, block_indices_lens)
|
| 784 |
+
ctx.sm_scale = sm_scale
|
| 785 |
+
ctx.HEAD_DIM = HEAD_DIM_K
|
| 786 |
+
ctx.chunk_size_q = chunk_size_q
|
| 787 |
+
ctx.chunk_size_k = chunk_size_k
|
| 788 |
+
ctx.sparsity = sparsity
|
| 789 |
+
ctx.kv_valid_mask = kv_valid_mask
|
| 790 |
+
|
| 791 |
+
return o
|
| 792 |
+
|
| 793 |
+
@staticmethod
|
| 794 |
+
def backward(ctx, do):
|
| 795 |
+
q, k, v, o, lse, block_indices, block_indices_lens = ctx.saved_tensors
|
| 796 |
+
|
| 797 |
+
dq = torch.empty_like(q)
|
| 798 |
+
dk = torch.empty_like(k)
|
| 799 |
+
dv = torch.empty_like(v)
|
| 800 |
+
|
| 801 |
+
attn_bwd_bsa_varlen_triton(
|
| 802 |
+
do,
|
| 803 |
+
q,
|
| 804 |
+
k,
|
| 805 |
+
v,
|
| 806 |
+
o,
|
| 807 |
+
dq,
|
| 808 |
+
dk,
|
| 809 |
+
dv,
|
| 810 |
+
ctx.sm_scale,
|
| 811 |
+
lse,
|
| 812 |
+
block_indices,
|
| 813 |
+
block_indices_lens,
|
| 814 |
+
ctx.chunk_size_q,
|
| 815 |
+
ctx.chunk_size_k,
|
| 816 |
+
ctx.sparsity,
|
| 817 |
+
kv_valid_mask=ctx.kv_valid_mask,
|
| 818 |
+
)
|
| 819 |
+
|
| 820 |
+
# Order: q, k, v, chunk_size_q, chunk_size_k, sparsity, cdf_threshold, sm_scale,
|
| 821 |
+
# audio_block_saliency, audio_gate_value, audio_boost_gamma,
|
| 822 |
+
# kv_valid_mask, q_valid_mask, audio_norms_block_order, audio_weighted_lambda,
|
| 823 |
+
# score_modifier
|
| 824 |
+
return dq, dk, dv, None, None, None, None, None, None, None, None, None, None, None, None, None
|
| 825 |
+
|
| 826 |
+
|
| 827 |
+
flash_attn_bsa_audio_guided = _attention_bsa_audio_guided.apply
|
| 828 |
+
|
| 829 |
+
|
| 830 |
+
@torch.compile
|
| 831 |
+
def compute_timestep_reliability_gate(
|
| 832 |
+
timestep_ratio: torch.Tensor,
|
| 833 |
+
mode: str = "power",
|
| 834 |
+
power: float = 1.5,
|
| 835 |
+
scale: float = 10.0,
|
| 836 |
+
threshold: float = 0.7,
|
| 837 |
+
) -> torch.Tensor:
|
| 838 |
+
"""Compute Gate 1: Timestep Reliability Gate r_t (non-learnable).
|
| 839 |
+
|
| 840 |
+
At high noise (t→1): r_t→0 (don't trust audio guidance, bridge residual is noise-driven).
|
| 841 |
+
At low noise (t→0): r_t→1 (trust audio guidance, bridge residual reflects real saliency).
|
| 842 |
+
|
| 843 |
+
Two modes:
|
| 844 |
+
- "power": r_t = (1 - t)^power. Physically motivated by flow matching signal decay.
|
| 845 |
+
power=1.5 is recommended (moderate decay, default).
|
| 846 |
+
- "sigmoid": r_t = sigmoid(scale * (threshold - t)). Sharp transition.
|
| 847 |
+
|
| 848 |
+
Args:
|
| 849 |
+
timestep_ratio: scalar or [B] — normalized timestep in [0, 1] (0=clean, 1=noise)
|
| 850 |
+
mode: "power" (default) or "sigmoid"
|
| 851 |
+
power: exponent for power-law mode (default 1.5)
|
| 852 |
+
scale: sharpness for sigmoid mode (default 10.0)
|
| 853 |
+
threshold: midpoint for sigmoid mode (default 0.7)
|
| 854 |
+
|
| 855 |
+
Returns:
|
| 856 |
+
r_t: scalar in [0, 1]
|
| 857 |
+
"""
|
| 858 |
+
if mode == "power":
|
| 859 |
+
r_t = (1.0 - timestep_ratio.clamp(0.0, 1.0)) ** power
|
| 860 |
+
else:
|
| 861 |
+
r_t = torch.sigmoid(scale * (threshold - timestep_ratio))
|
| 862 |
+
return r_t
|
| 863 |
+
|
| 864 |
+
|
| 865 |
+
@torch.compile
|
| 866 |
+
def compute_audio_spatial_concentration_gate(
|
| 867 |
+
token_norms: torch.Tensor,
|
| 868 |
+
valid_mask: torch.Tensor = None,
|
| 869 |
+
alpha: float = 5.0,
|
| 870 |
+
beta: float = 2.0,
|
| 871 |
+
) -> torch.Tensor:
|
| 872 |
+
"""Compute Gate 2: Audio Spatial Concentration Gate g_a from per-token L2 norms.
|
| 873 |
+
|
| 874 |
+
g_a = sigmoid(alpha * CV - beta)
|
| 875 |
+
where CV = std(norms) / (mean(norms) + eps)
|
| 876 |
+
|
| 877 |
+
Args:
|
| 878 |
+
token_norms: [N_tokens] — L2 norms of bridge residual per video token
|
| 879 |
+
valid_mask: [N_tokens] bool — True for real tokens, False for padded
|
| 880 |
+
alpha, beta: fixed hyperparameters (non-learnable)
|
| 881 |
+
|
| 882 |
+
Returns:
|
| 883 |
+
g_a: scalar in [0, 1]
|
| 884 |
+
"""
|
| 885 |
+
eps = 1e-6
|
| 886 |
+
if valid_mask is not None:
|
| 887 |
+
mask_f = valid_mask.float()
|
| 888 |
+
count = mask_f.sum().clamp(min=1.0)
|
| 889 |
+
mean_a = (token_norms * mask_f).sum() / count
|
| 890 |
+
var_a = ((token_norms - mean_a).pow(2) * mask_f).sum() / count
|
| 891 |
+
else:
|
| 892 |
+
mean_a = token_norms.mean()
|
| 893 |
+
var_a = token_norms.var(unbiased=False)
|
| 894 |
+
|
| 895 |
+
std_a = var_a.sqrt()
|
| 896 |
+
cv = std_a / (mean_a + eps)
|
| 897 |
+
g_a = torch.sigmoid(alpha * cv - beta)
|
| 898 |
+
return g_a
|
| 899 |
+
|
| 900 |
+
|
| 901 |
+
@torch.compile
|
| 902 |
+
def audio_weighted_mean_pooling_compression(
|
| 903 |
+
x: torch.Tensor,
|
| 904 |
+
block_size: int,
|
| 905 |
+
token_norms: torch.Tensor,
|
| 906 |
+
valid_mask: torch.Tensor = None,
|
| 907 |
+
lambda_w: float = 1.0,
|
| 908 |
+
) -> torch.Tensor:
|
| 909 |
+
"""Audio-weighted mean pooling for K-side block representatives (Path B).
|
| 910 |
+
|
| 911 |
+
w_m = 1 + lambda_w * (a_m / max(a_m' in block) + eps)
|
| 912 |
+
K_bar_j = sum(w_m * K_m) / sum(w_m)
|
| 913 |
+
|
| 914 |
+
Automatically degrades to standard mean pooling when:
|
| 915 |
+
- silence: all a_m ≈ 0 → w_m ≈ 1
|
| 916 |
+
- high noise: all a_m similar → a_m/max ≈ 1 → uniform weighting
|
| 917 |
+
- lambda_w = 0: w_m = 1 exactly
|
| 918 |
+
|
| 919 |
+
Args:
|
| 920 |
+
x: [B, H, S, D] — K or V tokens (already in 3D-block rearranged order)
|
| 921 |
+
block_size: number of tokens per block (e.g., 64)
|
| 922 |
+
token_norms: [S] — per-token L2 norms of bridge residual (block-rearranged order)
|
| 923 |
+
valid_mask: [S] bool — True for real tokens, False for padded
|
| 924 |
+
lambda_w: weighting strength (default 1.0; 0=standard mean pooling)
|
| 925 |
+
|
| 926 |
+
Returns:
|
| 927 |
+
x_cmp: [B, H, N_blocks, D] — weighted block representatives
|
| 928 |
+
"""
|
| 929 |
+
B, H, S, D = x.shape
|
| 930 |
+
eps = 1e-6
|
| 931 |
+
num_blocks = math.ceil(S / block_size)
|
| 932 |
+
if S % block_size != 0:
|
| 933 |
+
pad_len = num_blocks * block_size - S
|
| 934 |
+
x = F.pad(x, (0, 0, 0, pad_len))
|
| 935 |
+
token_norms = F.pad(token_norms, (0, pad_len), value=0.0)
|
| 936 |
+
if valid_mask is not None:
|
| 937 |
+
valid_mask = F.pad(valid_mask, (0, pad_len), value=False)
|
| 938 |
+
|
| 939 |
+
# Reshape to [num_blocks, block_size]
|
| 940 |
+
norms_blocks = token_norms.view(num_blocks, block_size)
|
| 941 |
+
|
| 942 |
+
# Intra-block normalized weighting: w_m = 1 + λ * (a_m / max_in_block)
|
| 943 |
+
if valid_mask is not None:
|
| 944 |
+
mask_blocks = valid_mask.view(num_blocks, block_size).float()
|
| 945 |
+
# Mask out padded positions for max computation
|
| 946 |
+
norms_masked = norms_blocks * mask_blocks + (-1e9) * (1 - mask_blocks)
|
| 947 |
+
block_max = norms_masked.max(dim=1, keepdim=True).values.clamp(min=eps)
|
| 948 |
+
# Reset negatives from masking
|
| 949 |
+
norms_for_weight = norms_blocks * mask_blocks
|
| 950 |
+
else:
|
| 951 |
+
block_max = norms_blocks.max(dim=1, keepdim=True).values.clamp(min=eps)
|
| 952 |
+
norms_for_weight = norms_blocks
|
| 953 |
+
mask_blocks = None
|
| 954 |
+
|
| 955 |
+
# w_m = 1 + λ * (a_m / max)
|
| 956 |
+
weights = 1.0 + lambda_w * (norms_for_weight / block_max) # [num_blocks, block_size]
|
| 957 |
+
|
| 958 |
+
if mask_blocks is not None:
|
| 959 |
+
weights = weights * mask_blocks # zero out padded positions
|
| 960 |
+
|
| 961 |
+
# Reshape x for block-wise weighted mean: [B, H, num_blocks, block_size, D]
|
| 962 |
+
x_blocks = x.view(B, H, num_blocks, block_size, D)
|
| 963 |
+
|
| 964 |
+
# weights: [num_blocks, block_size] → [1, 1, num_blocks, block_size, 1] for broadcasting
|
| 965 |
+
w = weights.unsqueeze(0).unsqueeze(0).unsqueeze(-1) # [1, 1, num_blocks, block_size, 1]
|
| 966 |
+
w_sum = w.sum(dim=3, keepdim=True).clamp(min=1.0) # [1, 1, num_blocks, 1, 1]
|
| 967 |
+
|
| 968 |
+
x_cmp = (x_blocks * w).sum(dim=3) / w_sum.squeeze(3) # [B, H, num_blocks, D]
|
| 969 |
+
return x_cmp
|
| 970 |
+
|
| 971 |
+
|
| 972 |
+
@torch.compile
|
| 973 |
+
def compute_channel_variance_density(
|
| 974 |
+
x: torch.Tensor,
|
| 975 |
+
block_size: int,
|
| 976 |
+
bt: int, bh: int, bw: int,
|
| 977 |
+
valid_mask: torch.Tensor = None,
|
| 978 |
+
) -> torch.Tensor:
|
| 979 |
+
"""Compute per-block channel-variance information density (non-learnable).
|
| 980 |
+
|
| 981 |
+
Averages across all (local) heads for head-agnostic block-level statistics
|
| 982 |
+
(different heads project into different subspaces — single-head would bias
|
| 983 |
+
selection toward that head's variance pattern). Mean pooling stays in native
|
| 984 |
+
dtype (bf16) to avoid large float32 copies; only the tiny [num_blocks, D]
|
| 985 |
+
result converts to float32 for numerically precise variance computation.
|
| 986 |
+
|
| 987 |
+
Note (SP): under sequence parallelism each rank owns a head shard, so ρ is
|
| 988 |
+
computed from local heads only. This keeps the per-layer cost minimal (no
|
| 989 |
+
extra collective). ρ is a coarse per-block density signal whose estimate is
|
| 990 |
+
stable across head subsets, and each head's attention is computed on its own
|
| 991 |
+
rank, so the per-rank ρ is correct for that rank's heads.
|
| 992 |
+
|
| 993 |
+
Args:
|
| 994 |
+
x: [B, H, S, D] V tensor in 3D-block-rearranged order (V = x @ W_V, no RMSNorm/RoPE).
|
| 995 |
+
block_size: bt * bh * bw tokens per block.
|
| 996 |
+
bt, bh, bw: block shape in temporal, height, width.
|
| 997 |
+
valid_mask: [S] bool — True for real tokens, False for padding.
|
| 998 |
+
|
| 999 |
+
Returns:
|
| 1000 |
+
rho: [num_blocks] ∈ [0, 1] — normalized variance density per block.
|
| 1001 |
+
"""
|
| 1002 |
+
S, D = x.shape[2], x.shape[3]
|
| 1003 |
+
num_blocks = S // block_size
|
| 1004 |
+
spatial_size = bh * bw
|
| 1005 |
+
|
| 1006 |
+
# Average across heads in native dtype (bf16) — no large float32 copy.
|
| 1007 |
+
# x[0]: [H, S, D] → .mean(dim=0): [S, D]. bf16 mean is numerically stable.
|
| 1008 |
+
x_avg = x[0].mean(dim=0) # [S, D], bf16, head-agnostic
|
| 1009 |
+
x_blocks = x_avg.view(num_blocks, block_size, D) # [num_blocks, block_size, D]
|
| 1010 |
+
|
| 1011 |
+
if valid_mask is not None:
|
| 1012 |
+
mask_blocks = valid_mask.view(num_blocks, block_size)
|
| 1013 |
+
mask_f = mask_blocks.unsqueeze(-1).to(x_blocks.dtype) # [num_blocks, block_size, 1]
|
| 1014 |
+
counts = mask_f.sum(dim=1).clamp(min=1.0) # [num_blocks, 1]
|
| 1015 |
+
else:
|
| 1016 |
+
mask_f = None
|
| 1017 |
+
counts = None
|
| 1018 |
+
|
| 1019 |
+
# --- Spatial Channel Variance σ²_ch-s ---
|
| 1020 |
+
# Per-block mean per channel (bf16), then variance across channels (float32)
|
| 1021 |
+
if mask_f is not None:
|
| 1022 |
+
channel_means = (x_blocks * mask_f).sum(dim=1) / counts # [num_blocks, D], bf16
|
| 1023 |
+
else:
|
| 1024 |
+
channel_means = x_blocks.mean(dim=1) # [num_blocks, D], bf16
|
| 1025 |
+
|
| 1026 |
+
cm_f = channel_means.float() # [num_blocks, D] — only this is float32 (~2.5MB)
|
| 1027 |
+
global_cm = cm_f.mean(dim=-1, keepdim=True) # [num_blocks, 1]
|
| 1028 |
+
spatial_var = ((cm_f - global_cm) ** 2).mean(dim=-1) # [num_blocks]
|
| 1029 |
+
|
| 1030 |
+
# --- Temporal Channel Variance σ²_ch-t ---
|
| 1031 |
+
if bt > 1:
|
| 1032 |
+
x_temporal = x_blocks.view(num_blocks, bt, spatial_size, D)
|
| 1033 |
+
|
| 1034 |
+
if valid_mask is not None:
|
| 1035 |
+
mask_t_3d = mask_blocks.view(num_blocks, bt, spatial_size)
|
| 1036 |
+
mask_t = mask_t_3d.unsqueeze(-1).to(x_blocks.dtype) # [num_blocks, bt, spatial_size, 1]
|
| 1037 |
+
fc = mask_t.sum(dim=2).clamp(min=1.0) # [num_blocks, bt, 1]
|
| 1038 |
+
frame_means = (x_temporal * mask_t).sum(dim=2) / fc # [num_blocks, bt, D], bf16
|
| 1039 |
+
|
| 1040 |
+
# Frame-level validity: only diff between pairs where BOTH frames have ≥1 valid token
|
| 1041 |
+
frame_valid = mask_t_3d.any(dim=2) # [num_blocks, bt]
|
| 1042 |
+
both_valid = frame_valid[:, 1:] & frame_valid[:, :-1] # [num_blocks, bt-1]
|
| 1043 |
+
bv_f = both_valid.float().unsqueeze(-1) # [num_blocks, bt-1, 1]
|
| 1044 |
+
valid_pairs = bv_f.sum(dim=1).clamp(min=1.0) # [num_blocks, 1]
|
| 1045 |
+
|
| 1046 |
+
fm_f = frame_means.float()
|
| 1047 |
+
frame_diffs = fm_f[:, 1:, :] - fm_f[:, :-1, :] # [num_blocks, bt-1, D]
|
| 1048 |
+
temporal_var = ((frame_diffs.pow(2) * bv_f).sum(dim=1) / valid_pairs).mean(dim=-1)
|
| 1049 |
+
else:
|
| 1050 |
+
frame_means = x_temporal.mean(dim=2) # [num_blocks, bt, D], bf16
|
| 1051 |
+
fm_f = frame_means.float()
|
| 1052 |
+
frame_diffs = fm_f[:, 1:, :] - fm_f[:, :-1, :] # [num_blocks, bt-1, D]
|
| 1053 |
+
temporal_var = frame_diffs.pow(2).mean(dim=1).mean(dim=-1)
|
| 1054 |
+
else:
|
| 1055 |
+
temporal_var = torch.zeros(num_blocks, device=x.device, dtype=torch.float32)
|
| 1056 |
+
|
| 1057 |
+
# --- Combine and normalize to [0, 1] ---
|
| 1058 |
+
combined = spatial_var + temporal_var
|
| 1059 |
+
rho = combined / combined.max().clamp(min=1e-6)
|
| 1060 |
+
|
| 1061 |
+
return rho
|
| 1062 |
+
|
| 1063 |
+
|
| 1064 |
+
@torch.compile
|
| 1065 |
+
def compute_block_audio_saliency(
|
| 1066 |
+
token_norms: torch.Tensor,
|
| 1067 |
+
chunk_size: int,
|
| 1068 |
+
valid_mask: torch.Tensor = None,
|
| 1069 |
+
) -> torch.Tensor:
|
| 1070 |
+
"""Compute per-block audio saliency from per-token L2 norms.
|
| 1071 |
+
|
| 1072 |
+
For each block j: S_j = mean(norms in block j) / max(mean norms across all blocks)
|
| 1073 |
+
Then clamp to [0, 1].
|
| 1074 |
+
|
| 1075 |
+
Args:
|
| 1076 |
+
token_norms: [S] — per-token L2 norms (already in 3D-block rearranged order)
|
| 1077 |
+
chunk_size: block size (e.g., 64 for 4x4x4)
|
| 1078 |
+
valid_mask: [S] bool — True for real tokens, False for padded
|
| 1079 |
+
|
| 1080 |
+
Returns:
|
| 1081 |
+
block_saliency: [N_blocks] in [0, 1]
|
| 1082 |
+
"""
|
| 1083 |
+
S = token_norms.shape[0]
|
| 1084 |
+
num_blocks = math.ceil(S / chunk_size)
|
| 1085 |
+
if S % chunk_size != 0:
|
| 1086 |
+
pad_len = num_blocks * chunk_size - S
|
| 1087 |
+
token_norms = F.pad(token_norms, (0, pad_len), value=0.0)
|
| 1088 |
+
if valid_mask is not None:
|
| 1089 |
+
valid_mask = F.pad(valid_mask, (0, pad_len), value=False)
|
| 1090 |
+
|
| 1091 |
+
norms_blocks = token_norms.view(num_blocks, chunk_size)
|
| 1092 |
+
|
| 1093 |
+
if valid_mask is not None:
|
| 1094 |
+
mask_blocks = valid_mask.view(num_blocks, chunk_size).float()
|
| 1095 |
+
counts = mask_blocks.sum(dim=1).clamp(min=1.0)
|
| 1096 |
+
block_means = (norms_blocks * mask_blocks).sum(dim=1) / counts
|
| 1097 |
+
else:
|
| 1098 |
+
block_means = norms_blocks.mean(dim=1)
|
| 1099 |
+
|
| 1100 |
+
max_val = block_means.max().clamp(min=1e-6)
|
| 1101 |
+
block_saliency = block_means / max_val
|
| 1102 |
+
return block_saliency
|
| 1103 |
+
|
| 1104 |
+
|
| 1105 |
+
def rearrange_THW_to_3d_block(x, Nt, Nh, Nw, t, h, w, D):
|
| 1106 |
+
B, H, _, D = x.shape
|
| 1107 |
+
x = x.view(B, H, Nt, t, Nh, h, Nw, w, D)
|
| 1108 |
+
x = x.permute(0, 1, 2, 4, 6, 3, 5, 7, 8) # B H Nt Nh Nw t h w D
|
| 1109 |
+
return x.contiguous().view(B, H, Nt * Nh * Nw * t * h * w, D)
|
| 1110 |
+
|
| 1111 |
+
def rearrange_3d_block_to_THW(x, Nt, Nh, Nw, t, h, w, D):
|
| 1112 |
+
B, H, _, D = x.shape
|
| 1113 |
+
x = x.view(B, H, Nt, Nh, Nw, t, h, w, D)
|
| 1114 |
+
x = x.permute(0, 1, 2, 5, 3, 6, 4, 7, 8) # B H Nt t Nh h Nw w D
|
| 1115 |
+
return x.contiguous().view(B, H, Nt * t * Nh * h * Nw * w, D)
|
| 1116 |
+
|
| 1117 |
+
def rearrange_THW_to_3d_block_1d(mask, Nt, Nh, Nw, t, h, w):
|
| 1118 |
+
"""Rearrange a 1D bool mask from THW order to 3D block order (same permutation as tokens)."""
|
| 1119 |
+
mask = mask.view(Nt, t, Nh, h, Nw, w)
|
| 1120 |
+
mask = mask.permute(0, 2, 4, 1, 3, 5) # Nt Nh Nw t h w
|
| 1121 |
+
return mask.contiguous().view(-1)
|
| 1122 |
+
|
| 1123 |
+
def flash_attn_bsa_3d(
|
| 1124 |
+
q: torch.Tensor, # [B, H, Sq, D]
|
| 1125 |
+
k: torch.Tensor, # [B, H, Skv, D]
|
| 1126 |
+
v: torch.Tensor, # [B, H, Skv, D]
|
| 1127 |
+
latent_shape_q,
|
| 1128 |
+
latent_shape_k,
|
| 1129 |
+
# bsa_params
|
| 1130 |
+
sparsity=0.875,
|
| 1131 |
+
cdf_threshold=None,
|
| 1132 |
+
chunk_3d_shape_q=[4, 4, 8],
|
| 1133 |
+
chunk_3d_shape_k=[4, 4, 8],
|
| 1134 |
+
valid_mask=None, # [Sq] bool, True=valid token, False=padded
|
| 1135 |
+
) -> torch.Tensor:
|
| 1136 |
+
_, _, Sq, head_dim_q = q.shape
|
| 1137 |
+
_, _, Sk, head_dim_k = k.shape
|
| 1138 |
+
|
| 1139 |
+
assert head_dim_q == head_dim_k
|
| 1140 |
+
head_dim = head_dim_q
|
| 1141 |
+
|
| 1142 |
+
Tq, Hq, Wq = latent_shape_q
|
| 1143 |
+
Tk, Hk, Wk = latent_shape_k
|
| 1144 |
+
|
| 1145 |
+
assert Tq * Hq * Wq == Sq
|
| 1146 |
+
assert Tk * Hk * Wk == Sk
|
| 1147 |
+
|
| 1148 |
+
tq, hq, wq = chunk_3d_shape_q
|
| 1149 |
+
tk, hk, wk = chunk_3d_shape_k
|
| 1150 |
+
|
| 1151 |
+
assert Tq % tq == 0 and Hq % hq == 0 and Wq % wq == 0
|
| 1152 |
+
assert Tk % tk == 0 and Hk % hk == 0 and Wk % wk == 0
|
| 1153 |
+
|
| 1154 |
+
Ntq = Tq // tq
|
| 1155 |
+
Nhq = Hq // hq
|
| 1156 |
+
Nwq = Wq // wq
|
| 1157 |
+
|
| 1158 |
+
Ntk = Tk // tk
|
| 1159 |
+
Nhk = Hk // hk
|
| 1160 |
+
Nwk = Wk // wk
|
| 1161 |
+
|
| 1162 |
+
q = rearrange_THW_to_3d_block(q, Ntq, Nhq, Nwq, tq, hq, wq, q.shape[-1])
|
| 1163 |
+
k = rearrange_THW_to_3d_block(k, Ntk, Nhk, Nwk, tk, hk, wk, k.shape[-1])
|
| 1164 |
+
v = rearrange_THW_to_3d_block(v, Ntk, Nhk, Nwk, tk, hk, wk, v.shape[-1])
|
| 1165 |
+
|
| 1166 |
+
kv_valid_mask = None
|
| 1167 |
+
if valid_mask is not None:
|
| 1168 |
+
kv_valid_mask = rearrange_THW_to_3d_block_1d(valid_mask, Ntk, Nhk, Nwk, tk, hk, wk)
|
| 1169 |
+
|
| 1170 |
+
chunk_size_q = tq * hq * wq
|
| 1171 |
+
chunk_size_k = tk * hk * wk
|
| 1172 |
+
|
| 1173 |
+
output = flash_attn_bsa(q, k, v, chunk_size_q, chunk_size_k, sparsity, cdf_threshold, 1 / head_dim**0.5, kv_valid_mask)
|
| 1174 |
+
|
| 1175 |
+
output = rearrange_3d_block_to_THW(output, Ntq, Nhq, Nwq, tq, hq, wq, output.shape[-1])
|
| 1176 |
+
return output
|
| 1177 |
+
|
| 1178 |
+
|
| 1179 |
+
def flash_attn_bsa_3d_variance_guided(
|
| 1180 |
+
q: torch.Tensor,
|
| 1181 |
+
k: torch.Tensor,
|
| 1182 |
+
v: torch.Tensor,
|
| 1183 |
+
latent_shape_q,
|
| 1184 |
+
latent_shape_k,
|
| 1185 |
+
variance_boost_gamma: float = 1.0,
|
| 1186 |
+
sparsity=0.875,
|
| 1187 |
+
cdf_threshold=None,
|
| 1188 |
+
chunk_3d_shape_q=[4, 4, 4],
|
| 1189 |
+
chunk_3d_shape_k=[4, 4, 4],
|
| 1190 |
+
valid_mask=None,
|
| 1191 |
+
) -> torch.Tensor:
|
| 1192 |
+
"""BSA with channel-variance information density score modulation.
|
| 1193 |
+
|
| 1194 |
+
Completely independent from audio guidance — no shared state or code.
|
| 1195 |
+
|
| 1196 |
+
Computes ρ_j on V blocks (key/value side only), then applies:
|
| 1197 |
+
score_{i,j} *= (1 + γ * ρ_j)
|
| 1198 |
+
|
| 1199 |
+
Query-awareness comes from P^QK being per-query (zero-gate effect):
|
| 1200 |
+
- Sky query → lip key: P^QK ≈ 0, boost = 1.85, final ≈ 0 → not selected
|
| 1201 |
+
- Face query → lip key: P^QK ≈ 0.12, boost = 1.85, final = 0.222 → selected
|
| 1202 |
+
Same ρ_j, different P^QK → different top-k per query block.
|
| 1203 |
+
|
| 1204 |
+
Why V (not K or hidden states)?
|
| 1205 |
+
V = x @ W_V — raw linear projection, no RMSNorm, no RoPE distortion.
|
| 1206 |
+
V channel variance directly predicts information contribution I(V_block; Output):
|
| 1207 |
+
low variance → V rows near-parallel → output gain ≈ 0 regardless of attention weight.
|
| 1208 |
+
"""
|
| 1209 |
+
_, _, Sq, head_dim_q = q.shape
|
| 1210 |
+
_, _, Sk, head_dim_k = k.shape
|
| 1211 |
+
|
| 1212 |
+
assert head_dim_q == head_dim_k
|
| 1213 |
+
head_dim = head_dim_q
|
| 1214 |
+
|
| 1215 |
+
Tq, Hq, Wq = latent_shape_q
|
| 1216 |
+
Tk, Hk, Wk = latent_shape_k
|
| 1217 |
+
|
| 1218 |
+
assert Tq * Hq * Wq == Sq
|
| 1219 |
+
assert Tk * Hk * Wk == Sk
|
| 1220 |
+
|
| 1221 |
+
tq, hq, wq = chunk_3d_shape_q
|
| 1222 |
+
tk, hk, wk = chunk_3d_shape_k
|
| 1223 |
+
|
| 1224 |
+
assert Tq % tq == 0 and Hq % hq == 0 and Wq % wq == 0
|
| 1225 |
+
assert Tk % tk == 0 and Hk % hk == 0 and Wk % wk == 0
|
| 1226 |
+
|
| 1227 |
+
Ntq = Tq // tq
|
| 1228 |
+
Nhq = Hq // hq
|
| 1229 |
+
Nwq = Wq // wq
|
| 1230 |
+
|
| 1231 |
+
Ntk = Tk // tk
|
| 1232 |
+
Nhk = Hk // hk
|
| 1233 |
+
Nwk = Wk // wk
|
| 1234 |
+
|
| 1235 |
+
q = rearrange_THW_to_3d_block(q, Ntq, Nhq, Nwq, tq, hq, wq, q.shape[-1])
|
| 1236 |
+
k = rearrange_THW_to_3d_block(k, Ntk, Nhk, Nwk, tk, hk, wk, k.shape[-1])
|
| 1237 |
+
v = rearrange_THW_to_3d_block(v, Ntk, Nhk, Nwk, tk, hk, wk, v.shape[-1])
|
| 1238 |
+
|
| 1239 |
+
kv_valid_mask = None
|
| 1240 |
+
if valid_mask is not None:
|
| 1241 |
+
kv_valid_mask = rearrange_THW_to_3d_block_1d(valid_mask, Ntk, Nhk, Nwk, tk, hk, wk)
|
| 1242 |
+
|
| 1243 |
+
chunk_size_q = tq * hq * wq
|
| 1244 |
+
chunk_size_k = tk * hk * wk
|
| 1245 |
+
|
| 1246 |
+
with torch.no_grad():
|
| 1247 |
+
rho = compute_channel_variance_density(v, chunk_size_k, tk, hk, wk, kv_valid_mask)
|
| 1248 |
+
|
| 1249 |
+
# Multiplicative modifier for variance-only path: score *= (1 + γ * ρ)
|
| 1250 |
+
score_modifier = (
|
| 1251 |
+
1.0 + variance_boost_gamma * rho.to(q.dtype).view(1, 1, 1, -1)
|
| 1252 |
+
).contiguous()
|
| 1253 |
+
|
| 1254 |
+
output = flash_attn_bsa(
|
| 1255 |
+
q, k, v, chunk_size_q, chunk_size_k, sparsity, cdf_threshold,
|
| 1256 |
+
1 / head_dim**0.5, kv_valid_mask, None, score_modifier,
|
| 1257 |
+
)
|
| 1258 |
+
|
| 1259 |
+
output = rearrange_3d_block_to_THW(output, Ntq, Nhq, Nwq, tq, hq, wq, output.shape[-1])
|
| 1260 |
+
return output
|
| 1261 |
+
|
| 1262 |
+
|
| 1263 |
+
def _pad_1d_for_bsa(x, chunk_size):
|
| 1264 |
+
"""Pad a 1D sequence [B, H, S, D] to be divisible by chunk_size. Returns (padded_x, valid_mask_or_None, orig_len)."""
|
| 1265 |
+
B, H, S, D = x.shape
|
| 1266 |
+
remainder = S % chunk_size
|
| 1267 |
+
if remainder == 0:
|
| 1268 |
+
return x, None, S
|
| 1269 |
+
pad_len = chunk_size - remainder
|
| 1270 |
+
x_padded = F.pad(x, (0, 0, 0, pad_len)).contiguous()
|
| 1271 |
+
valid_mask = torch.zeros(S + pad_len, dtype=torch.bool, device=x.device)
|
| 1272 |
+
valid_mask[:S] = True
|
| 1273 |
+
return x_padded, valid_mask, S
|
| 1274 |
+
|
| 1275 |
+
|
| 1276 |
+
def flash_attn_bsa_cross(
|
| 1277 |
+
q: torch.Tensor,
|
| 1278 |
+
k: torch.Tensor,
|
| 1279 |
+
v: torch.Tensor,
|
| 1280 |
+
q_structure: str,
|
| 1281 |
+
k_structure: str,
|
| 1282 |
+
q_grid_size=None,
|
| 1283 |
+
k_grid_size=None,
|
| 1284 |
+
sparsity=0.875,
|
| 1285 |
+
cdf_threshold=None,
|
| 1286 |
+
chunk_size_q=64,
|
| 1287 |
+
chunk_size_k=64,
|
| 1288 |
+
chunk_3d_shape_q=None,
|
| 1289 |
+
chunk_3d_shape_k=None,
|
| 1290 |
+
) -> torch.Tensor:
|
| 1291 |
+
"""Cross-modal BSA supporting asymmetric Q/K structures (3D video vs 1D audio).
|
| 1292 |
+
|
| 1293 |
+
Args:
|
| 1294 |
+
q: [B, H, Sq, D]
|
| 1295 |
+
k: [B, H, Sk, D]
|
| 1296 |
+
v: [B, H, Sk, D]
|
| 1297 |
+
q_structure: "3d" (video with grid T,H,W) or "1d" (audio sequential)
|
| 1298 |
+
k_structure: "3d" or "1d"
|
| 1299 |
+
q_grid_size: (T, H, W) when q_structure="3d"
|
| 1300 |
+
k_grid_size: (T, H, W) when k_structure="3d"
|
| 1301 |
+
sparsity: fraction of K blocks to skip
|
| 1302 |
+
chunk_size_q: block size for 1D Q (must be multiple of 64)
|
| 1303 |
+
chunk_size_k: block size for 1D K (must be multiple of 64)
|
| 1304 |
+
chunk_3d_shape_q: [t,h,w] for 3D Q blocking
|
| 1305 |
+
chunk_3d_shape_k: [t,h,w] for 3D K blocking
|
| 1306 |
+
"""
|
| 1307 |
+
_, _, Sq, head_dim = q.shape
|
| 1308 |
+
_, _, Sk, _ = k.shape
|
| 1309 |
+
|
| 1310 |
+
q_valid_mask = None
|
| 1311 |
+
k_valid_mask = None
|
| 1312 |
+
q_orig_len = Sq
|
| 1313 |
+
k_orig_len = Sk
|
| 1314 |
+
|
| 1315 |
+
if q_structure == "3d":
|
| 1316 |
+
assert q_grid_size is not None
|
| 1317 |
+
Tq, Hq, Wq = q_grid_size
|
| 1318 |
+
tq, hq, wq = chunk_3d_shape_q or [4, 4, 4]
|
| 1319 |
+
pad_tq = (tq - Tq % tq) % tq
|
| 1320 |
+
pad_hq = (hq - Hq % hq) % hq
|
| 1321 |
+
pad_wq = (wq - Wq % wq) % wq
|
| 1322 |
+
|
| 1323 |
+
if pad_tq > 0 or pad_hq > 0 or pad_wq > 0:
|
| 1324 |
+
B, H, _, D = q.shape
|
| 1325 |
+
Tq_p, Hq_p, Wq_p = Tq + pad_tq, Hq + pad_hq, Wq + pad_wq
|
| 1326 |
+
idx_t = torch.arange(Tq_p, device=q.device)
|
| 1327 |
+
idx_h = torch.arange(Hq_p, device=q.device)
|
| 1328 |
+
idx_w = torch.arange(Wq_p, device=q.device)
|
| 1329 |
+
q_valid_mask = ((idx_t[:, None, None] < Tq) & (idx_h[None, :, None] < Hq) & (idx_w[None, None, :] < Wq)).reshape(-1).contiguous()
|
| 1330 |
+
q_3d = q.view(B, H, Tq, Hq, Wq, D)
|
| 1331 |
+
q = F.pad(q_3d, (0, 0, 0, pad_wq, 0, pad_hq, 0, pad_tq)).reshape(B, H, -1, D).contiguous()
|
| 1332 |
+
Tq, Hq, Wq = Tq_p, Hq_p, Wq_p
|
| 1333 |
+
|
| 1334 |
+
Ntq, Nhq, Nwq = Tq // tq, Hq // hq, Wq // wq
|
| 1335 |
+
q = rearrange_THW_to_3d_block(q, Ntq, Nhq, Nwq, tq, hq, wq, q.shape[-1])
|
| 1336 |
+
if q_valid_mask is not None:
|
| 1337 |
+
q_valid_mask = rearrange_THW_to_3d_block_1d(q_valid_mask, Ntq, Nhq, Nwq, tq, hq, wq)
|
| 1338 |
+
actual_chunk_size_q = tq * hq * wq
|
| 1339 |
+
else:
|
| 1340 |
+
q, q_valid_mask, q_orig_len = _pad_1d_for_bsa(q, chunk_size_q)
|
| 1341 |
+
actual_chunk_size_q = chunk_size_q
|
| 1342 |
+
Ntq = Nhq = Nwq = tq = hq = wq = None
|
| 1343 |
+
|
| 1344 |
+
if k_structure == "3d":
|
| 1345 |
+
assert k_grid_size is not None
|
| 1346 |
+
Tk, Hk, Wk = k_grid_size
|
| 1347 |
+
tk, hk, wk = chunk_3d_shape_k or [4, 4, 4]
|
| 1348 |
+
pad_tk = (tk - Tk % tk) % tk
|
| 1349 |
+
pad_hk = (hk - Hk % hk) % hk
|
| 1350 |
+
pad_wk = (wk - Wk % wk) % wk
|
| 1351 |
+
|
| 1352 |
+
if pad_tk > 0 or pad_hk > 0 or pad_wk > 0:
|
| 1353 |
+
B, H, _, D = k.shape
|
| 1354 |
+
Tk_p, Hk_p, Wk_p = Tk + pad_tk, Hk + pad_hk, Wk + pad_wk
|
| 1355 |
+
idx_t = torch.arange(Tk_p, device=k.device)
|
| 1356 |
+
idx_h = torch.arange(Hk_p, device=k.device)
|
| 1357 |
+
idx_w = torch.arange(Wk_p, device=k.device)
|
| 1358 |
+
k_valid_mask = ((idx_t[:, None, None] < Tk) & (idx_h[None, :, None] < Hk) & (idx_w[None, None, :] < Wk)).reshape(-1).contiguous()
|
| 1359 |
+
B, H, _, D = k.shape
|
| 1360 |
+
k_3d = k.view(B, H, Tk, Hk, Wk, D)
|
| 1361 |
+
k = F.pad(k_3d, (0, 0, 0, pad_wk, 0, pad_hk, 0, pad_tk)).reshape(B, H, -1, D).contiguous()
|
| 1362 |
+
v_3d = v.view(B, H, Tk, Hk, Wk, D)
|
| 1363 |
+
v = F.pad(v_3d, (0, 0, 0, pad_wk, 0, pad_hk, 0, pad_tk)).reshape(B, H, -1, D).contiguous()
|
| 1364 |
+
Tk, Hk, Wk = Tk_p, Hk_p, Wk_p
|
| 1365 |
+
|
| 1366 |
+
Ntk, Nhk, Nwk = Tk // tk, Hk // hk, Wk // wk
|
| 1367 |
+
k = rearrange_THW_to_3d_block(k, Ntk, Nhk, Nwk, tk, hk, wk, k.shape[-1])
|
| 1368 |
+
v = rearrange_THW_to_3d_block(v, Ntk, Nhk, Nwk, tk, hk, wk, v.shape[-1])
|
| 1369 |
+
if k_valid_mask is not None:
|
| 1370 |
+
k_valid_mask = rearrange_THW_to_3d_block_1d(k_valid_mask, Ntk, Nhk, Nwk, tk, hk, wk)
|
| 1371 |
+
actual_chunk_size_k = tk * hk * wk
|
| 1372 |
+
else:
|
| 1373 |
+
k, k_valid_mask_k, k_orig_len = _pad_1d_for_bsa(k, chunk_size_k)
|
| 1374 |
+
v, _, _ = _pad_1d_for_bsa(v, chunk_size_k)
|
| 1375 |
+
if k_valid_mask_k is not None:
|
| 1376 |
+
k_valid_mask = k_valid_mask_k
|
| 1377 |
+
actual_chunk_size_k = chunk_size_k
|
| 1378 |
+
Ntk = Nhk = Nwk = tk = hk = wk = None
|
| 1379 |
+
|
| 1380 |
+
output = flash_attn_bsa(q, k, v, actual_chunk_size_q, actual_chunk_size_k, sparsity, cdf_threshold, 1 / head_dim**0.5, k_valid_mask, q_valid_mask)
|
| 1381 |
+
|
| 1382 |
+
if q_structure == "3d":
|
| 1383 |
+
output = rearrange_3d_block_to_THW(output, Ntq, Nhq, Nwq, tq, hq, wq, output.shape[-1])
|
| 1384 |
+
if pad_tq > 0 or pad_hq > 0 or pad_wq > 0:
|
| 1385 |
+
B, H, _, D = output.shape
|
| 1386 |
+
Tq_orig, Hq_orig, Wq_orig = q_grid_size
|
| 1387 |
+
output = output.view(B, H, Tq, Hq, Wq, D)[:, :, :Tq_orig, :Hq_orig, :Wq_orig, :].contiguous().reshape(B, H, q_orig_len, D)
|
| 1388 |
+
else:
|
| 1389 |
+
if q_orig_len != output.shape[2]:
|
| 1390 |
+
output = output[:, :, :q_orig_len, :].contiguous()
|
| 1391 |
+
|
| 1392 |
+
return output
|
| 1393 |
+
|
| 1394 |
+
|
| 1395 |
+
def flash_attn_bsa_3d_audio_guided(
|
| 1396 |
+
q: torch.Tensor, # [B, H, Sq, D]
|
| 1397 |
+
k: torch.Tensor, # [B, H, Skv, D]
|
| 1398 |
+
v: torch.Tensor, # [B, H, Skv, D]
|
| 1399 |
+
latent_shape_q,
|
| 1400 |
+
latent_shape_k,
|
| 1401 |
+
audio_token_norms: torch.Tensor = None, # [Sq] per-token L2 norms of A->V bridge residual
|
| 1402 |
+
audio_gate_value: torch.Tensor = None, # scalar gate g_a in [0,1]
|
| 1403 |
+
audio_boost_gamma: torch.Tensor = None, # scalar boost γ >= 0
|
| 1404 |
+
sparsity=0.875,
|
| 1405 |
+
cdf_threshold=None,
|
| 1406 |
+
chunk_3d_shape_q=[4, 4, 4],
|
| 1407 |
+
chunk_3d_shape_k=[4, 4, 4],
|
| 1408 |
+
valid_mask=None, # [Sq] bool, True=valid, False=padded
|
| 1409 |
+
audio_weighted_lambda: float = 0.0, # Path B: weighted pooling strength (0=disabled)
|
| 1410 |
+
score_modifier: torch.Tensor = None, # external additive boost (e.g., γ_var * ρ from variance guidance)
|
| 1411 |
+
variance_boost_gamma: float = 0.0, # if > 0, compute ρ INSIDE on rearranged V (avoids double rearrange)
|
| 1412 |
+
) -> torch.Tensor:
|
| 1413 |
+
"""BSA with audio saliency score modulation.
|
| 1414 |
+
|
| 1415 |
+
Audio-specific: gated saliency boost + optional audio-weighted K pooling.
|
| 1416 |
+
|
| 1417 |
+
Combine modes for additional boosts (all additive inside one bracket):
|
| 1418 |
+
score *= (1 + γ_audio * S̃ + γ_var * ρ + score_modifier)
|
| 1419 |
+
|
| 1420 |
+
For audio + variance combination, prefer passing `variance_boost_gamma > 0`
|
| 1421 |
+
instead of `score_modifier`: that lets us compute ρ on the **already
|
| 1422 |
+
rearranged** V tensor, avoiding a second `rearrange_THW_to_3d_block(v)`
|
| 1423 |
+
on the caller side. Both produce identical results, but the in-function
|
| 1424 |
+
path saves one V permute+contiguous (≈30-80 MB memory bandwidth on 2K).
|
| 1425 |
+
"""
|
| 1426 |
+
_, _, Sq, head_dim_q = q.shape
|
| 1427 |
+
_, _, Sk, head_dim_k = k.shape
|
| 1428 |
+
|
| 1429 |
+
assert head_dim_q == head_dim_k
|
| 1430 |
+
head_dim = head_dim_q
|
| 1431 |
+
|
| 1432 |
+
Tq, Hq, Wq = latent_shape_q
|
| 1433 |
+
Tk, Hk, Wk = latent_shape_k
|
| 1434 |
+
|
| 1435 |
+
assert Tq * Hq * Wq == Sq
|
| 1436 |
+
assert Tk * Hk * Wk == Sk
|
| 1437 |
+
|
| 1438 |
+
tq, hq, wq = chunk_3d_shape_q
|
| 1439 |
+
tk, hk, wk = chunk_3d_shape_k
|
| 1440 |
+
|
| 1441 |
+
assert Tq % tq == 0 and Hq % hq == 0 and Wq % wq == 0
|
| 1442 |
+
assert Tk % tk == 0 and Hk % hk == 0 and Wk % wk == 0
|
| 1443 |
+
|
| 1444 |
+
Ntq = Tq // tq
|
| 1445 |
+
Nhq = Hq // hq
|
| 1446 |
+
Nwq = Wq // wq
|
| 1447 |
+
|
| 1448 |
+
Ntk = Tk // tk
|
| 1449 |
+
Nhk = Hk // hk
|
| 1450 |
+
Nwk = Wk // wk
|
| 1451 |
+
|
| 1452 |
+
q = rearrange_THW_to_3d_block(q, Ntq, Nhq, Nwq, tq, hq, wq, q.shape[-1])
|
| 1453 |
+
k = rearrange_THW_to_3d_block(k, Ntk, Nhk, Nwk, tk, hk, wk, k.shape[-1])
|
| 1454 |
+
v = rearrange_THW_to_3d_block(v, Ntk, Nhk, Nwk, tk, hk, wk, v.shape[-1])
|
| 1455 |
+
|
| 1456 |
+
kv_valid_mask = None
|
| 1457 |
+
if valid_mask is not None:
|
| 1458 |
+
kv_valid_mask = rearrange_THW_to_3d_block_1d(valid_mask, Ntk, Nhk, Nwk, tk, hk, wk)
|
| 1459 |
+
|
| 1460 |
+
chunk_size_q = tq * hq * wq
|
| 1461 |
+
chunk_size_k = tk * hk * wk
|
| 1462 |
+
|
| 1463 |
+
# Optional in-function variance density (avoids the caller re-rearranging V).
|
| 1464 |
+
# Folded additively into score_modifier so audio + variance share one boost
|
| 1465 |
+
# bracket — no cross-term, exactly matches the standalone variance path.
|
| 1466 |
+
if variance_boost_gamma is not None and variance_boost_gamma > 0:
|
| 1467 |
+
with torch.no_grad():
|
| 1468 |
+
_rho = compute_channel_variance_density(v, chunk_size_k, tk, hk, wk, kv_valid_mask)
|
| 1469 |
+
_var_term = (variance_boost_gamma * _rho.to(q.dtype).view(1, 1, 1, -1)).contiguous()
|
| 1470 |
+
score_modifier = (score_modifier + _var_term) if score_modifier is not None else _var_term
|
| 1471 |
+
|
| 1472 |
+
# Audio saliency computation (only when audio norms are provided)
|
| 1473 |
+
has_audio_norms = (audio_token_norms is not None and audio_token_norms.numel() > 0)
|
| 1474 |
+
if has_audio_norms:
|
| 1475 |
+
audio_norms_block_order = rearrange_THW_to_3d_block_1d(
|
| 1476 |
+
audio_token_norms, Ntk, Nhk, Nwk, tk, hk, wk
|
| 1477 |
+
)
|
| 1478 |
+
block_saliency = compute_block_audio_saliency(
|
| 1479 |
+
audio_norms_block_order, chunk_size_k, valid_mask=kv_valid_mask
|
| 1480 |
+
)
|
| 1481 |
+
else:
|
| 1482 |
+
block_saliency = None
|
| 1483 |
+
audio_norms_block_order = None
|
| 1484 |
+
if audio_gate_value is None:
|
| 1485 |
+
audio_gate_value = torch.ones(1, device=q.device, dtype=q.dtype)
|
| 1486 |
+
if audio_boost_gamma is None:
|
| 1487 |
+
audio_boost_gamma = torch.zeros(1, device=q.device, dtype=q.dtype)
|
| 1488 |
+
|
| 1489 |
+
output = flash_attn_bsa_audio_guided(
|
| 1490 |
+
q, k, v, chunk_size_q, chunk_size_k, sparsity, cdf_threshold,
|
| 1491 |
+
1 / head_dim**0.5,
|
| 1492 |
+
block_saliency, audio_gate_value, audio_boost_gamma,
|
| 1493 |
+
kv_valid_mask,
|
| 1494 |
+
None, # q_valid_mask (same grid in self-attn)
|
| 1495 |
+
audio_norms_block_order if audio_weighted_lambda > 0 else None,
|
| 1496 |
+
audio_weighted_lambda,
|
| 1497 |
+
score_modifier,
|
| 1498 |
+
)
|
| 1499 |
+
|
| 1500 |
+
output = rearrange_3d_block_to_THW(output, Ntq, Nhq, Nwq, tq, hq, wq, output.shape[-1])
|
| 1501 |
+
return output
|
| 1502 |
+
|
| 1503 |
+
|
hymm/models/modules/block_sparse_attention/common.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import triton
|
| 2 |
+
import triton.language as tl
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
if os.environ.get('TRITON_AUTOTUNE_ENBALE', '0') == '1':
|
| 6 |
+
autotune = triton.autotune
|
| 7 |
+
else:
|
| 8 |
+
def autotune(*args, **kwargs):
|
| 9 |
+
def decorator(func):
|
| 10 |
+
return func
|
| 11 |
+
return decorator
|
| 12 |
+
|
| 13 |
+
configs_gating_preset = {
|
| 14 |
+
'default': {
|
| 15 |
+
'BLOCK_M': 64,
|
| 16 |
+
'BLOCK_N': 64,
|
| 17 |
+
'num_stages': 3,
|
| 18 |
+
'num_warps': 8,
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
configs_gating = [
|
| 23 |
+
triton.Config({'BLOCK_M': BM, 'BLOCK_N': BN}, num_stages=s, num_warps=w) \
|
| 24 |
+
for BM in [64, 128] \
|
| 25 |
+
for BN in [32, 64] \
|
| 26 |
+
for s in [2, 3, 4, 5] \
|
| 27 |
+
for w in [4, 8] \
|
| 28 |
+
]
|
| 29 |
+
|
| 30 |
+
gating_reevaluate_keys = ["M", "N"] if os.environ.get('TRITON_REEVALUATE_KEY', '0') == '1' else []
|
| 31 |
+
@autotune(configs_gating, key=gating_reevaluate_keys)
|
| 32 |
+
@triton.jit
|
| 33 |
+
def _attn_fwd_gating(
|
| 34 |
+
Q, K, Out,
|
| 35 |
+
stride_qz, stride_qh, stride_qm, stride_qk,
|
| 36 |
+
stride_kz, stride_kh, stride_kn, stride_kk,
|
| 37 |
+
stride_oz, stride_oh, stride_om, stride_on,
|
| 38 |
+
H, M, N,
|
| 39 |
+
HEAD_DIM: tl.constexpr,
|
| 40 |
+
BLOCK_M: tl.constexpr,
|
| 41 |
+
BLOCK_N: tl.constexpr,
|
| 42 |
+
):
|
| 43 |
+
|
| 44 |
+
tl.static_assert(BLOCK_N <= HEAD_DIM)
|
| 45 |
+
start_m = tl.program_id(0)
|
| 46 |
+
off_hz = tl.program_id(1)
|
| 47 |
+
off_z = off_hz // H
|
| 48 |
+
off_h = off_hz % H
|
| 49 |
+
q_offset = off_z.to(tl.int64) * stride_qz + off_h.to(tl.int64) * stride_qh
|
| 50 |
+
k_offset = off_z.to(tl.int64) * stride_kz + off_h.to(tl.int64) * stride_kh
|
| 51 |
+
o_offset = off_z.to(tl.int64) * stride_oz + off_h.to(tl.int64) * stride_oh
|
| 52 |
+
|
| 53 |
+
# block pointers
|
| 54 |
+
Q_block_ptr = tl.make_block_ptr(
|
| 55 |
+
base=Q + q_offset,
|
| 56 |
+
shape=(M, HEAD_DIM),
|
| 57 |
+
strides=(stride_qm, stride_qk),
|
| 58 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 59 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 60 |
+
order=(1, 0),
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
K_block_ptr = tl.make_block_ptr(
|
| 64 |
+
base=K + k_offset,
|
| 65 |
+
shape=(HEAD_DIM, N),
|
| 66 |
+
strides=(stride_kk, stride_kn),
|
| 67 |
+
offsets=(0, 0),
|
| 68 |
+
block_shape=(HEAD_DIM, BLOCK_N),
|
| 69 |
+
order=(0, 1),
|
| 70 |
+
)
|
| 71 |
+
O_block_ptr = tl.make_block_ptr(
|
| 72 |
+
base=Out + o_offset,
|
| 73 |
+
shape=(M, N),
|
| 74 |
+
strides=(stride_om, stride_on),
|
| 75 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 76 |
+
block_shape=(BLOCK_M, BLOCK_N),
|
| 77 |
+
order=(1, 0),
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
# load q: it will stay in SRAM throughout
|
| 81 |
+
q = tl.load(Q_block_ptr, boundary_check=(0,))
|
| 82 |
+
for start_n in range(0, N, BLOCK_N):
|
| 83 |
+
start_n = tl.multiple_of(start_n, BLOCK_N)
|
| 84 |
+
# -- compute qk ----
|
| 85 |
+
k = tl.load(K_block_ptr, boundary_check=(1,))
|
| 86 |
+
qk = tl.dot(q, k)
|
| 87 |
+
|
| 88 |
+
tl.store(O_block_ptr, qk.to(Out.type.element_ty), boundary_check=(0, 1))
|
| 89 |
+
|
| 90 |
+
K_block_ptr = tl.advance(K_block_ptr, (0, BLOCK_N))
|
| 91 |
+
O_block_ptr = tl.advance(O_block_ptr, (0, BLOCK_N))
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
@triton.jit
|
| 95 |
+
def _attn_bwd_preprocess(
|
| 96 |
+
O, DO,
|
| 97 |
+
Delta, # output
|
| 98 |
+
N_CTX,
|
| 99 |
+
BLOCK_M: tl.constexpr,
|
| 100 |
+
HEAD_DIM: tl.constexpr
|
| 101 |
+
):
|
| 102 |
+
|
| 103 |
+
off_m = tl.program_id(0) * BLOCK_M + tl.arange(0, BLOCK_M)
|
| 104 |
+
off_hz = tl.program_id(1)
|
| 105 |
+
off_n = tl.arange(0, HEAD_DIM)
|
| 106 |
+
# load
|
| 107 |
+
o = tl.load(O + off_hz * HEAD_DIM * N_CTX + off_m[:, None] * HEAD_DIM + off_n[None, :])
|
| 108 |
+
do = tl.load(DO + off_hz * HEAD_DIM * N_CTX + off_m[:, None] * HEAD_DIM + off_n[None, :]).to(tl.float32)
|
| 109 |
+
delta = tl.sum(o * do, axis=1)
|
| 110 |
+
# write-back
|
| 111 |
+
tl.store(Delta + off_hz * N_CTX + off_m, delta)
|
hymm/models/modules/block_sparse_attention/dynamic_block_attention.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from typing import Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
# NOTE: `bsa_interface` pulls in Triton. We import its symbols lazily (inside the
|
| 7 |
+
# functions that launch kernels) so that the pure-PyTorch geometry helpers in
|
| 8 |
+
# this module remain importable / unit-testable on machines without Triton.
|
| 9 |
+
from .dynamic_block_shape import (
|
| 10 |
+
ALL_CANDIDATE_SHAPES,
|
| 11 |
+
ZONE_SIZE,
|
| 12 |
+
select_zone_shapes,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
_TILE = 64 # kernel tile size (Section 8.7: unified BLOCK_N=64)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# =====================================================================
|
| 19 |
+
# Per-shape local permutation precompute (constant; cached per device).
|
| 20 |
+
# =====================================================================
|
| 21 |
+
def _precompute_local_orders(device):
|
| 22 |
+
"""For each candidate shape, the [512] permutation mapping a rearranged
|
| 23 |
+
within-zone position -> local flat coord (lt*64 + lh*8 + lw).
|
| 24 |
+
|
| 25 |
+
Also returns, per shape, (product, tiles_per_block). All cached.
|
| 26 |
+
"""
|
| 27 |
+
cache = _precompute_local_orders._cache
|
| 28 |
+
if device in cache:
|
| 29 |
+
return cache[device]
|
| 30 |
+
|
| 31 |
+
zs = ZONE_SIZE
|
| 32 |
+
n = zs * zs * zs # 512
|
| 33 |
+
lf = torch.arange(n, device=device)
|
| 34 |
+
lt = lf // (zs * zs)
|
| 35 |
+
lh = (lf // zs) % zs
|
| 36 |
+
lw = lf % zs
|
| 37 |
+
|
| 38 |
+
orders = []
|
| 39 |
+
prods = []
|
| 40 |
+
tpb = []
|
| 41 |
+
for (bt, bh, bw) in ALL_CANDIDATE_SHAPES:
|
| 42 |
+
bsz = bt * bh * bw
|
| 43 |
+
Nh, Nw = zs // bh, zs // bw
|
| 44 |
+
nt, it = lt // bt, lt % bt
|
| 45 |
+
nh, ih = lh // bh, lh % bh
|
| 46 |
+
nw, iw = lw // bw, lw % bw
|
| 47 |
+
blk = (nt * Nh + nh) * Nw + nw # block index within zone
|
| 48 |
+
off = (it * bh + ih) * bw + iw # offset within block
|
| 49 |
+
rearr_pos = blk * bsz + off # [512]: local_flat -> rearr pos
|
| 50 |
+
order = torch.empty(n, dtype=torch.long, device=device)
|
| 51 |
+
order[rearr_pos] = lf # invert -> rearr pos -> local_flat
|
| 52 |
+
orders.append(order)
|
| 53 |
+
prods.append(bsz)
|
| 54 |
+
tpb.append(bsz // _TILE)
|
| 55 |
+
|
| 56 |
+
local_orders = torch.stack(orders, dim=0) # [C, 512]
|
| 57 |
+
products = torch.tensor(prods, dtype=torch.long, device=device) # [C]
|
| 58 |
+
tiles_per_block = torch.tensor(tpb, dtype=torch.long, device=device) # [C]
|
| 59 |
+
cache[device] = (local_orders, products, tiles_per_block)
|
| 60 |
+
return cache[device]
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
_precompute_local_orders._cache = {}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
# =====================================================================
|
| 67 |
+
# Pure BSA Triton wrapper (forward + backward through the kernel only).
|
| 68 |
+
# Mirrors bias_rectification._bsa_kernel_only but lives here to keep the
|
| 69 |
+
# dynamic path fully isolated. Selection / pooling stay in PyTorch outside.
|
| 70 |
+
# =====================================================================
|
| 71 |
+
class _dyn_bsa_kernel(torch.autograd.Function):
|
| 72 |
+
@staticmethod
|
| 73 |
+
def forward(ctx, q, k, v, sm_scale, block_indices, block_indices_lens,
|
| 74 |
+
chunk_size_q, chunk_size_k, sparsity, kv_valid_mask):
|
| 75 |
+
from .bsa_interface import attn_fwd_bsa_varlen_triton
|
| 76 |
+
o, lse = attn_fwd_bsa_varlen_triton(
|
| 77 |
+
q, k, v, sm_scale, block_indices, block_indices_lens,
|
| 78 |
+
chunk_size_q, chunk_size_k, sparsity, kv_valid_mask=kv_valid_mask,
|
| 79 |
+
)
|
| 80 |
+
ctx.save_for_backward(q, k, v, o, lse, block_indices, block_indices_lens)
|
| 81 |
+
ctx.sm_scale = sm_scale
|
| 82 |
+
ctx.chunk_size_q = chunk_size_q
|
| 83 |
+
ctx.chunk_size_k = chunk_size_k
|
| 84 |
+
ctx.sparsity = sparsity
|
| 85 |
+
ctx.kv_valid_mask = kv_valid_mask
|
| 86 |
+
return o
|
| 87 |
+
|
| 88 |
+
@staticmethod
|
| 89 |
+
def backward(ctx, do):
|
| 90 |
+
from .bsa_interface import attn_bwd_bsa_varlen_triton
|
| 91 |
+
q, k, v, o, lse, block_indices, block_indices_lens = ctx.saved_tensors
|
| 92 |
+
dq = torch.empty_like(q)
|
| 93 |
+
dk = torch.empty_like(k)
|
| 94 |
+
dv = torch.empty_like(v)
|
| 95 |
+
attn_bwd_bsa_varlen_triton(
|
| 96 |
+
do.contiguous(), q, k, v, o, dq, dk, dv,
|
| 97 |
+
ctx.sm_scale, lse, block_indices, block_indices_lens,
|
| 98 |
+
ctx.chunk_size_q, ctx.chunk_size_k, ctx.sparsity,
|
| 99 |
+
kv_valid_mask=ctx.kv_valid_mask,
|
| 100 |
+
)
|
| 101 |
+
return dq, dk, dv, None, None, None, None, None, None, None
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
# =====================================================================
|
| 105 |
+
# Geometry helpers (no grad).
|
| 106 |
+
# =====================================================================
|
| 107 |
+
def _build_rearrange_index(shape_ids, ZT, ZH, ZW, H, W, device):
|
| 108 |
+
"""Return src_idx[S], inv_idx[S] for the per-zone anisotropic rearrange.
|
| 109 |
+
|
| 110 |
+
src_idx[rearr_pos] = original_thw_index ; inv_idx = its inverse permutation.
|
| 111 |
+
Both contiguous int64.
|
| 112 |
+
"""
|
| 113 |
+
zs = ZONE_SIZE
|
| 114 |
+
Nz = ZT * ZH * ZW
|
| 115 |
+
local_orders, _, _ = _precompute_local_orders(device)
|
| 116 |
+
lo = local_orders[shape_ids] # [Nz, 512] local flat per rearr pos
|
| 117 |
+
lt = lo // (zs * zs)
|
| 118 |
+
lh = (lo // zs) % zs
|
| 119 |
+
lw = lo % zs
|
| 120 |
+
|
| 121 |
+
zidx = torch.arange(Nz, device=device)
|
| 122 |
+
zt = zidx // (ZH * ZW)
|
| 123 |
+
zh = (zidx // ZW) % ZH
|
| 124 |
+
zw = zidx % ZW
|
| 125 |
+
gt = zt[:, None] * zs + lt
|
| 126 |
+
gh = zh[:, None] * zs + lh
|
| 127 |
+
gw = zw[:, None] * zs + lw
|
| 128 |
+
global_thw = (gt * H + gh) * W + gw # [Nz, 512]
|
| 129 |
+
src_idx = global_thw.reshape(-1).contiguous() # [S]
|
| 130 |
+
# src_idx is a permutation, so its inverse is an O(S) scatter (cheaper than
|
| 131 |
+
# an O(S log S) argsort): inv_idx[src_idx[r]] = r.
|
| 132 |
+
S = src_idx.shape[0]
|
| 133 |
+
inv_idx = torch.empty_like(src_idx)
|
| 134 |
+
inv_idx[src_idx] = torch.arange(S, device=device)
|
| 135 |
+
return src_idx, inv_idx
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def _build_block_tile_maps(shape_ids, ZT, ZH, ZW, device):
|
| 139 |
+
"""Logical-block <-> tile bookkeeping for the rearranged sequence.
|
| 140 |
+
|
| 141 |
+
Returns:
|
| 142 |
+
block_id_of_tile [N_tiles] : logical-block id of each 64-tile
|
| 143 |
+
kblock_tiles [N_b+1, 2] : the (up-to-2) tile ids of each logical block
|
| 144 |
+
(row N_b is the sentinel = N_tiles, used for
|
| 145 |
+
invalid/sentinel selection slots)
|
| 146 |
+
N_b (int), N_tiles (int)
|
| 147 |
+
"""
|
| 148 |
+
Nz = ZT * ZH * ZW
|
| 149 |
+
_, products, tiles_per_block = _precompute_local_orders(device)
|
| 150 |
+
tpb = tiles_per_block[shape_ids] # [Nz] in {1,2}
|
| 151 |
+
nbz = (512 // products[shape_ids]) # blocks per zone [Nz] in {8,4}
|
| 152 |
+
N_tiles = Nz * (512 // _TILE) # = Nz * 8
|
| 153 |
+
N_b = int(nbz.sum().item())
|
| 154 |
+
|
| 155 |
+
# exclusive cumsum of blocks-per-zone -> per-zone block offset
|
| 156 |
+
block_offset = torch.zeros(Nz, dtype=torch.long, device=device)
|
| 157 |
+
if Nz > 1:
|
| 158 |
+
block_offset[1:] = torch.cumsum(nbz, dim=0)[:-1]
|
| 159 |
+
|
| 160 |
+
tile_g = torch.arange(N_tiles, device=device)
|
| 161 |
+
z_of_tile = tile_g // 8
|
| 162 |
+
tl_local = tile_g % 8
|
| 163 |
+
tpb_t = tpb[z_of_tile]
|
| 164 |
+
block_local = tl_local // tpb_t
|
| 165 |
+
pos_in_block = tl_local % tpb_t
|
| 166 |
+
block_id_of_tile = block_offset[z_of_tile] + block_local # [N_tiles]
|
| 167 |
+
|
| 168 |
+
kblock_tiles = torch.full((N_b + 1, 2), N_tiles, dtype=torch.long, device=device)
|
| 169 |
+
kblock_tiles[block_id_of_tile, pos_in_block] = tile_g
|
| 170 |
+
return block_id_of_tile, kblock_tiles, N_b, N_tiles
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def _logical_block_means(x_tile, tile_counts, block_id_of_tile, N_b):
|
| 174 |
+
"""Aggregate masked tile-means -> logical-block masked means.
|
| 175 |
+
|
| 176 |
+
block_mean = sum(tile_mean * tile_valid_count) / sum(tile_valid_count),
|
| 177 |
+
correctly averaging only valid tokens across the 1-or-2 tiles of a block.
|
| 178 |
+
|
| 179 |
+
x_tile: [B,H,N_tiles,D]; tile_counts: [N_tiles]; returns ([B,H,N_b,D], block_count[N_b]).
|
| 180 |
+
"""
|
| 181 |
+
B, Hn, N_tiles, D = x_tile.shape
|
| 182 |
+
w = x_tile * tile_counts.view(1, 1, N_tiles, 1).to(x_tile.dtype)
|
| 183 |
+
block_sum = x_tile.new_zeros(B, Hn, N_b, D)
|
| 184 |
+
block_sum.index_add_(2, block_id_of_tile, w)
|
| 185 |
+
block_count = tile_counts.new_zeros(N_b)
|
| 186 |
+
block_count.index_add_(0, block_id_of_tile, tile_counts.to(block_count.dtype))
|
| 187 |
+
block_mean = block_sum / block_count.clamp(min=1.0).view(1, 1, N_b, 1).to(x_tile.dtype)
|
| 188 |
+
return block_mean, block_count
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
# =====================================================================
|
| 192 |
+
# Public entry point.
|
| 193 |
+
# =====================================================================
|
| 194 |
+
def flash_attn_bsa_3d_dynamic(
|
| 195 |
+
q: torch.Tensor, # [B, H, S, D] THW order (already padded to mult of ZONE_SIZE)
|
| 196 |
+
k: torch.Tensor,
|
| 197 |
+
v: torch.Tensor,
|
| 198 |
+
grid_padded: Tuple[int, int, int],
|
| 199 |
+
*,
|
| 200 |
+
method: str, # "ivpq" or "penalty"
|
| 201 |
+
sparsity: Optional[float] = 0.9375,
|
| 202 |
+
cdf_threshold: Optional[float] = None,
|
| 203 |
+
audio_token_norms: Optional[torch.Tensor] = None, # [S] THW order, or None
|
| 204 |
+
valid_mask: Optional[torch.Tensor] = None, # [S] bool THW order, or None
|
| 205 |
+
lambda_a: float = 0.5,
|
| 206 |
+
tau_128: float = 0.15,
|
| 207 |
+
lambda_128: float = 1.0,
|
| 208 |
+
shape_ids: Optional[torch.Tensor] = None,
|
| 209 |
+
return_shape_ids: bool = False,
|
| 210 |
+
) -> torch.Tensor:
|
| 211 |
+
"""Dynamic-block-shape BSA for video self-attention. Returns [B,H,S,D] THW order.
|
| 212 |
+
|
| 213 |
+
shape_ids: if given, skip the (non-deterministic) shape selection and use it
|
| 214 |
+
verbatim — used to keep the checkpoint recompute identical to the forward.
|
| 215 |
+
return_shape_ids: if True, also return the shape_ids used (for caching).
|
| 216 |
+
"""
|
| 217 |
+
from .bsa_interface import (
|
| 218 |
+
mean_pooling_compression,
|
| 219 |
+
masked_mean_pooling_compression,
|
| 220 |
+
cal_score,
|
| 221 |
+
get_select_indices_topk_from_score,
|
| 222 |
+
get_select_indices_cdf_from_score,
|
| 223 |
+
get_select_indices_cdf_topk_from_score,
|
| 224 |
+
)
|
| 225 |
+
B, Hn, S, D = q.shape
|
| 226 |
+
T, H, W = grid_padded
|
| 227 |
+
assert T * H * W == S
|
| 228 |
+
assert T % ZONE_SIZE == 0 and H % ZONE_SIZE == 0 and W % ZONE_SIZE == 0
|
| 229 |
+
device = q.device
|
| 230 |
+
sm_scale = 1.0 / (D ** 0.5)
|
| 231 |
+
ZT, ZH, ZW = T // ZONE_SIZE, H // ZONE_SIZE, W // ZONE_SIZE
|
| 232 |
+
|
| 233 |
+
# ---- (1) per-zone shape decision (head-agnostic, SP-consistent) ----
|
| 234 |
+
with torch.no_grad():
|
| 235 |
+
if shape_ids is None:
|
| 236 |
+
v_headavg = v[0].mean(dim=0).view(T, H, W, D) # [T,H,W,D]
|
| 237 |
+
shape_ids = select_zone_shapes(
|
| 238 |
+
v_headavg, audio_token_norms, grid_padded, method,
|
| 239 |
+
lambda_a=lambda_a, tau_128=tau_128, lambda_128=lambda_128,
|
| 240 |
+
valid_mask_thw=valid_mask, sp_reduce=True,
|
| 241 |
+
) # [Nz] int64
|
| 242 |
+
|
| 243 |
+
# ---- (2) build the rearrange + tile/block bookkeeping ----
|
| 244 |
+
src_idx, inv_idx = _build_rearrange_index(shape_ids, ZT, ZH, ZW, H, W, device)
|
| 245 |
+
block_id_of_tile, kblock_tiles, N_b, N_tiles = _build_block_tile_maps(
|
| 246 |
+
shape_ids, ZT, ZH, ZW, device
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
# ---- (3) rearrange q/k/v into the tile-contiguous layout (differentiable) ----
|
| 250 |
+
q_re = q.index_select(2, src_idx).contiguous()
|
| 251 |
+
k_re = k.index_select(2, src_idx).contiguous()
|
| 252 |
+
v_re = v.index_select(2, src_idx).contiguous()
|
| 253 |
+
valid_re = valid_mask[src_idx].contiguous() if valid_mask is not None else None
|
| 254 |
+
|
| 255 |
+
# ---- (4) logical-block scoring + selection (no grad — selection only) ----
|
| 256 |
+
with torch.no_grad():
|
| 257 |
+
if valid_re is not None:
|
| 258 |
+
q_tile = masked_mean_pooling_compression(q_re, _TILE, valid_re)
|
| 259 |
+
k_tile = masked_mean_pooling_compression(k_re, _TILE, valid_re)
|
| 260 |
+
tile_counts = valid_re.view(N_tiles, _TILE).sum(dim=-1).float()
|
| 261 |
+
else:
|
| 262 |
+
q_tile = mean_pooling_compression(q_re, _TILE)
|
| 263 |
+
k_tile = mean_pooling_compression(k_re, _TILE)
|
| 264 |
+
tile_counts = torch.full((N_tiles,), float(_TILE), device=device)
|
| 265 |
+
|
| 266 |
+
q_cmp, _ = _logical_block_means(q_tile, tile_counts, block_id_of_tile, N_b)
|
| 267 |
+
k_cmp, block_count = _logical_block_means(k_tile, tile_counts, block_id_of_tile, N_b)
|
| 268 |
+
block_valid = block_count > 0 # [N_b]
|
| 269 |
+
del q_tile, k_tile
|
| 270 |
+
|
| 271 |
+
has_invalid = not bool(block_valid.all())
|
| 272 |
+
invalid_mask = ~block_valid.view(1, 1, 1, -1) if has_invalid else None
|
| 273 |
+
del block_valid, block_count
|
| 274 |
+
|
| 275 |
+
sel_blocks_list = []
|
| 276 |
+
for _h in range(Hn):
|
| 277 |
+
score_h = cal_score(q_cmp[:, _h:_h+1], k_cmp[:, _h:_h+1])
|
| 278 |
+
if invalid_mask is not None:
|
| 279 |
+
score_h = score_h.masked_fill(invalid_mask, float('-inf'))
|
| 280 |
+
if sparsity is not None and cdf_threshold is None:
|
| 281 |
+
sel_h, _ = get_select_indices_topk_from_score(score_h, sparsity)
|
| 282 |
+
elif sparsity is None and cdf_threshold is not None:
|
| 283 |
+
sel_h, _ = get_select_indices_cdf_from_score(score_h, cdf_threshold, sm_scale)
|
| 284 |
+
elif sparsity is not None and cdf_threshold is not None:
|
| 285 |
+
sel_h, _ = get_select_indices_cdf_topk_from_score(score_h, sparsity, cdf_threshold, sm_scale)
|
| 286 |
+
else:
|
| 287 |
+
raise ValueError("Either sparsity or cdf_threshold must be provided")
|
| 288 |
+
sel_blocks_list.append(sel_h)
|
| 289 |
+
del score_h, sel_h
|
| 290 |
+
del q_cmp, k_cmp, invalid_mask
|
| 291 |
+
|
| 292 |
+
sel_blocks = torch.cat(sel_blocks_list, dim=1)
|
| 293 |
+
del sel_blocks_list
|
| 294 |
+
# sel_blocks values in [0, N_b]; N_b is the sentinel (cdf/varlen padding).
|
| 295 |
+
sel_blocks = sel_blocks.clamp(max=N_b) # safety (int64 index)
|
| 296 |
+
|
| 297 |
+
# ---- (5) expand selected logical blocks -> tile-level indices ----
|
| 298 |
+
kblock_tiles = kblock_tiles.to(torch.int32)
|
| 299 |
+
t0 = kblock_tiles[:, 0][sel_blocks] # [B,H,N_bq,r] int32
|
| 300 |
+
t1 = kblock_tiles[:, 1][sel_blocks]
|
| 301 |
+
del sel_blocks, kblock_tiles
|
| 302 |
+
tile_sel = torch.cat([t0, t1], dim=-1) # [B,H,N_bq,2r] int32; values in [0, N_tiles]
|
| 303 |
+
del t0, t1
|
| 304 |
+
|
| 305 |
+
if valid_re is not None:
|
| 306 |
+
tile_is_valid = tile_counts > 0 # [N_tiles] bool
|
| 307 |
+
in_range = tile_sel < N_tiles
|
| 308 |
+
sel_valid = in_range & tile_is_valid[tile_sel.clamp(max=N_tiles - 1)]
|
| 309 |
+
tile_sel = torch.where(sel_valid, tile_sel, torch.full_like(tile_sel, N_tiles))
|
| 310 |
+
del tile_counts
|
| 311 |
+
|
| 312 |
+
tile_sel, _ = torch.sort(tile_sel, dim=-1) # int32 sort; valid first, sentinel(N_tiles) at tail
|
| 313 |
+
tile_lens_block = (tile_sel < N_tiles).sum(dim=-1).to(torch.int32)
|
| 314 |
+
|
| 315 |
+
# map q-block rows -> q-tile rows (a 128 q-block shares its row across its 2 tiles).
|
| 316 |
+
# tile_sel is already int32, so index_select yields the int32 block indices directly.
|
| 317 |
+
tile_block_indices = tile_sel.index_select(2, block_id_of_tile).contiguous()
|
| 318 |
+
tile_block_indices_lens = tile_lens_block.index_select(2, block_id_of_tile).contiguous()
|
| 319 |
+
del tile_sel, tile_lens_block, block_id_of_tile
|
| 320 |
+
|
| 321 |
+
# ---- (6) BSA Triton kernel at chunk=64 (autograd through q_re/k_re/v_re) ----
|
| 322 |
+
o_re = _dyn_bsa_kernel.apply(
|
| 323 |
+
q_re, k_re, v_re, sm_scale,
|
| 324 |
+
tile_block_indices, tile_block_indices_lens,
|
| 325 |
+
_TILE, _TILE, sparsity, valid_re,
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
# ---- (7) scatter back to original THW order ----
|
| 329 |
+
out = o_re.index_select(2, inv_idx).contiguous()
|
| 330 |
+
if return_shape_ids:
|
| 331 |
+
return out, shape_ids
|
| 332 |
+
return out
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
__all__ = ["flash_attn_bsa_3d_dynamic"]
|
hymm/models/modules/block_sparse_attention/dynamic_block_shape.py
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from typing import List, Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
CANDIDATE_SHAPES_64: List[Tuple[int, int, int]] = [
|
| 7 |
+
(4, 4, 4), # isotropic, default
|
| 8 |
+
(8, 2, 4), # long-T, narrow-H (lip/face)
|
| 9 |
+
(8, 4, 2), # long-T, narrow-W
|
| 10 |
+
(2, 4, 8), # short-T, wide-W (fast motion)
|
| 11 |
+
(2, 8, 4), # short-T, wide-H
|
| 12 |
+
(4, 2, 8), # mid-T, narrow-H wide-W
|
| 13 |
+
(4, 8, 2), # mid-T, wide-H narrow-W
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
CANDIDATE_SHAPES_128: List[Tuple[int, int, int]] = [
|
| 17 |
+
(2, 8, 8), # thin-T, large spatial
|
| 18 |
+
(8, 2, 8), # long-T, narrow-H wide-W
|
| 19 |
+
(8, 8, 2), # long-T, wide-H narrow-W
|
| 20 |
+
(4, 4, 8), # mid-T, wide-W
|
| 21 |
+
(4, 8, 4), # mid-T, wide-H
|
| 22 |
+
(8, 4, 4), # long-T, mid spatial (static sky)
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
+
ALL_CANDIDATE_SHAPES: List[Tuple[int, int, int]] = (
|
| 26 |
+
CANDIDATE_SHAPES_64 + CANDIDATE_SHAPES_128
|
| 27 |
+
)
|
| 28 |
+
NUM_CANDIDATES_64 = len(CANDIDATE_SHAPES_64)
|
| 29 |
+
NUM_CANDIDATES = len(ALL_CANDIDATE_SHAPES)
|
| 30 |
+
|
| 31 |
+
ZONE_SIZE = 8 # macro-zone edge length (Section 8.4.1); lcm(2,4,8)
|
| 32 |
+
|
| 33 |
+
_EPS = 1e-6
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def candidate_tensors(device, dtype=torch.float32):
|
| 37 |
+
"""Return (shapes[NUM_CANDIDATES,3], log2_shapes[NUM_CANDIDATES,3],
|
| 38 |
+
is_128[NUM_CANDIDATES], product[NUM_CANDIDATES]) on `device`.
|
| 39 |
+
|
| 40 |
+
Cached per (device, dtype) to avoid re-allocation each layer/step.
|
| 41 |
+
"""
|
| 42 |
+
key = (device, dtype)
|
| 43 |
+
cache = candidate_tensors._cache
|
| 44 |
+
if key not in cache:
|
| 45 |
+
shapes = torch.tensor(ALL_CANDIDATE_SHAPES, device=device, dtype=dtype) # [C,3]
|
| 46 |
+
log2_shapes = torch.log2(shapes)
|
| 47 |
+
prod = shapes.prod(dim=-1)
|
| 48 |
+
is_128 = prod > 64.5
|
| 49 |
+
cache[key] = (shapes, log2_shapes, is_128, prod)
|
| 50 |
+
return cache[key]
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
candidate_tensors._cache = {}
|
| 54 |
+
|
| 55 |
+
def _zone_reshape(x_thw_d: torch.Tensor, ZT: int, ZH: int, ZW: int) -> torch.Tensor:
|
| 56 |
+
"""[T,H,W,D] -> [num_zones, ZS,ZS,ZS, D] with zone order zt-major.
|
| 57 |
+
|
| 58 |
+
Memory-contiguous output (one permute+contiguous). T,H,W must be multiples
|
| 59 |
+
of ZONE_SIZE.
|
| 60 |
+
"""
|
| 61 |
+
T, H, W, D = x_thw_d.shape
|
| 62 |
+
zs = ZONE_SIZE
|
| 63 |
+
x = x_thw_d.reshape(ZT, zs, ZH, zs, ZW, zs, D)
|
| 64 |
+
x = x.permute(0, 2, 4, 1, 3, 5, 6).contiguous() # [ZT,ZH,ZW, zs,zs,zs, D]
|
| 65 |
+
return x.view(ZT * ZH * ZW, zs, zs, zs, D)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def compute_zone_aggregates(
|
| 69 |
+
v_headavg_thw_d: torch.Tensor,
|
| 70 |
+
ZT: int, ZH: int, ZW: int,
|
| 71 |
+
valid_mask_thw: Optional[torch.Tensor] = None,
|
| 72 |
+
):
|
| 73 |
+
"""Compute the per-zone *masked mean* aggregates that are linear in V.
|
| 74 |
+
|
| 75 |
+
Padded tokens (valid_mask == False) are excluded from every mean. Because
|
| 76 |
+
the valid mask is identical on every SP rank (it is a token-level grid
|
| 77 |
+
mask, not a head shard), the per-zone valid token *counts* are
|
| 78 |
+
rank-independent — so a masked-mean computed per rank and then AVG-all-
|
| 79 |
+
reduced across the SP group yields the exact global (head- and token-)
|
| 80 |
+
masked mean. This keeps the SP reduction a single cheap collective.
|
| 81 |
+
|
| 82 |
+
Returns (packed[num_zones, D + 3*ZS*D], frame_valid[num_zones, ZS]).
|
| 83 |
+
Packed last-dim layout:
|
| 84 |
+
[ 0 : D ] channel_means (masked mean over all tokens)
|
| 85 |
+
[ D : D + ZS*D ] frame_means_T (masked mean over H,W per t)
|
| 86 |
+
[ D + ZS*D : D + 2ZS*D] axis_means_H (masked mean over T,W per h)
|
| 87 |
+
[ D +2ZS*D : D + 3ZS*D] axis_means_W (masked mean over T,H per w)
|
| 88 |
+
`frame_valid[z,t]` marks t-frames that contain >=1 valid token (used to
|
| 89 |
+
exclude padded frames from the temporal-variance diff downstream).
|
| 90 |
+
"""
|
| 91 |
+
zs = ZONE_SIZE
|
| 92 |
+
D = v_headavg_thw_d.shape[-1]
|
| 93 |
+
xz = _zone_reshape(v_headavg_thw_d, ZT, ZH, ZW).float() # [Nz, t,h,w, D]
|
| 94 |
+
Nz = xz.shape[0]
|
| 95 |
+
|
| 96 |
+
if valid_mask_thw is not None:
|
| 97 |
+
mz = _zone_reshape(valid_mask_thw.reshape(*v_headavg_thw_d.shape[:3], 1).float(),
|
| 98 |
+
ZT, ZH, ZW) # [Nz, t,h,w, 1]
|
| 99 |
+
c_all = mz.sum(dim=(1, 2, 3)).clamp(min=1.0) # [Nz,1]
|
| 100 |
+
channel_means = (xz * mz).sum(dim=(1, 2, 3)) / c_all
|
| 101 |
+
cT = mz.sum(dim=(2, 3)).clamp(min=1.0) # [Nz, t, 1]
|
| 102 |
+
frame_means_T = (xz * mz).sum(dim=(2, 3)) / cT
|
| 103 |
+
cH = mz.sum(dim=(1, 3)).clamp(min=1.0)
|
| 104 |
+
axis_means_H = (xz * mz).sum(dim=(1, 3)) / cH
|
| 105 |
+
cW = mz.sum(dim=(1, 2)).clamp(min=1.0)
|
| 106 |
+
axis_means_W = (xz * mz).sum(dim=(1, 2)) / cW
|
| 107 |
+
m = mz.squeeze(-1)
|
| 108 |
+
t_valid = m.sum(dim=(2, 3)) > 0 # [Nz, t]
|
| 109 |
+
h_valid = m.sum(dim=(1, 3)) > 0 # [Nz, h]
|
| 110 |
+
w_valid = m.sum(dim=(1, 2)) > 0 # [Nz, w]
|
| 111 |
+
axis_valid = (t_valid, h_valid, w_valid)
|
| 112 |
+
else:
|
| 113 |
+
channel_means = xz.mean(dim=(1, 2, 3)) # [Nz, D]
|
| 114 |
+
frame_means_T = xz.mean(dim=(2, 3)) # [Nz, t, D]
|
| 115 |
+
axis_means_H = xz.mean(dim=(1, 3)) # [Nz, h, D]
|
| 116 |
+
axis_means_W = xz.mean(dim=(1, 2)) # [Nz, w, D]
|
| 117 |
+
ones = torch.ones(Nz, zs, dtype=torch.bool, device=xz.device)
|
| 118 |
+
axis_valid = (ones, ones, ones)
|
| 119 |
+
|
| 120 |
+
packed = torch.cat(
|
| 121 |
+
[
|
| 122 |
+
channel_means,
|
| 123 |
+
frame_means_T.reshape(Nz, zs * D),
|
| 124 |
+
axis_means_H.reshape(Nz, zs * D),
|
| 125 |
+
axis_means_W.reshape(Nz, zs * D),
|
| 126 |
+
],
|
| 127 |
+
dim=-1,
|
| 128 |
+
).contiguous()
|
| 129 |
+
return packed, axis_valid
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def _maybe_sp_average(packed: torch.Tensor) -> torch.Tensor:
|
| 133 |
+
"""AVG-all-reduce the packed aggregates across the SP group (no-op if SP off).
|
| 134 |
+
|
| 135 |
+
Mirrors the FSDP/SP convention used by bias_rectification: only the SP path
|
| 136 |
+
splits heads across ranks, so the head-agnostic shape decision needs this
|
| 137 |
+
reduction to be globally consistent. FSDP-only (no SP) sees identical data
|
| 138 |
+
on all ranks already → no collective.
|
| 139 |
+
"""
|
| 140 |
+
# Not wrapped in try/except on purpose: a swallowed import error would let one
|
| 141 |
+
# rank return early while the rest block inside the all_reduce, turning a
|
| 142 |
+
# loud failure into a silent NCCL hang.
|
| 143 |
+
import torch.distributed as dist
|
| 144 |
+
from hymm.utils.parallel_states import nccl_info, get_sequence_parallel_state
|
| 145 |
+
|
| 146 |
+
if (not get_sequence_parallel_state()) or nccl_info.sp_size <= 1:
|
| 147 |
+
return packed
|
| 148 |
+
out = packed.contiguous()
|
| 149 |
+
dist.all_reduce(out, op=dist.ReduceOp.SUM, group=nccl_info.sp_group)
|
| 150 |
+
out /= float(nccl_info.sp_size)
|
| 151 |
+
return out
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _unpack_aggregates(packed: torch.Tensor, D: int):
|
| 155 |
+
zs = ZONE_SIZE
|
| 156 |
+
Nz = packed.shape[0]
|
| 157 |
+
channel_means = packed[:, :D]
|
| 158 |
+
off = D
|
| 159 |
+
frame_means_T = packed[:, off:off + zs * D].reshape(Nz, zs, D); off += zs * D
|
| 160 |
+
axis_means_H = packed[:, off:off + zs * D].reshape(Nz, zs, D); off += zs * D
|
| 161 |
+
axis_means_W = packed[:, off:off + zs * D].reshape(Nz, zs, D); off += zs * D
|
| 162 |
+
return channel_means, frame_means_T, axis_means_H, axis_means_W
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def _axis_var(axis_means: torch.Tensor, valid: Optional[torch.Tensor] = None) -> torch.Tensor:
|
| 166 |
+
"""Variance across the ZS axis positions, averaged over channels. [Nz,ZS,D]->[Nz].
|
| 167 |
+
|
| 168 |
+
If `valid` [Nz,ZS] is given, only valid positions contribute (padded
|
| 169 |
+
axis positions are excluded so they cannot fabricate variance).
|
| 170 |
+
"""
|
| 171 |
+
if valid is None:
|
| 172 |
+
var_per_channel = axis_means.var(dim=1, unbiased=False) # [Nz, D]
|
| 173 |
+
return var_per_channel.mean(dim=-1) # [Nz]
|
| 174 |
+
vf = valid.unsqueeze(-1).to(axis_means.dtype) # [Nz,ZS,1]
|
| 175 |
+
n = vf.sum(dim=1).clamp(min=1.0) # [Nz,1]
|
| 176 |
+
mean = (axis_means * vf).sum(dim=1, keepdim=True) / n.unsqueeze(1)
|
| 177 |
+
var_per_channel = ((axis_means - mean) ** 2 * vf).sum(dim=1) / n
|
| 178 |
+
return var_per_channel.mean(dim=-1) # [Nz]
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _masked_var_1d(vals: torch.Tensor, valid: torch.Tensor) -> torch.Tensor:
|
| 182 |
+
"""Variance over the last axis restricted to valid positions. [Nz,L]->[Nz]."""
|
| 183 |
+
vf = valid.to(vals.dtype)
|
| 184 |
+
n = vf.sum(dim=-1).clamp(min=1.0)
|
| 185 |
+
mean = (vals * vf).sum(dim=-1) / n
|
| 186 |
+
return ((vals - mean.unsqueeze(-1)) ** 2 * vf).sum(dim=-1) / n
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def compute_audio_directional(
|
| 190 |
+
audio_norms_thw: Optional[torch.Tensor],
|
| 191 |
+
ZT: int, ZH: int, ZW: int,
|
| 192 |
+
valid_mask_thw: Optional[torch.Tensor] = None,
|
| 193 |
+
device: Optional[torch.device] = None,
|
| 194 |
+
):
|
| 195 |
+
"""Per-zone audio descriptor (Section 8.4.3).
|
| 196 |
+
|
| 197 |
+
Returns (a_bar[Nz], v_hat_T[Nz], v_hat_H[Nz], v_hat_W[Nz]) where a_bar is
|
| 198 |
+
the (max-normalized) mean audio-absorption and v_hat_d is the normalized
|
| 199 |
+
directional variance along axis d. All in float32.
|
| 200 |
+
|
| 201 |
+
`audio_norms_thw` is identical on every SP rank (all-gathered upstream), so
|
| 202 |
+
no collective is needed here.
|
| 203 |
+
"""
|
| 204 |
+
zs = ZONE_SIZE
|
| 205 |
+
Nz = ZT * ZH * ZW
|
| 206 |
+
if device is None:
|
| 207 |
+
device = (audio_norms_thw.device if audio_norms_thw is not None
|
| 208 |
+
else (valid_mask_thw.device if valid_mask_thw is not None else 'cpu'))
|
| 209 |
+
if audio_norms_thw is None:
|
| 210 |
+
z = torch.zeros(Nz, device=device, dtype=torch.float32)
|
| 211 |
+
return z, z.clone(), z.clone(), z.clone()
|
| 212 |
+
|
| 213 |
+
a = audio_norms_thw.reshape(ZT, zs, ZH, zs, ZW, zs).float()
|
| 214 |
+
a = a.permute(0, 2, 4, 1, 3, 5).reshape(Nz, zs, zs, zs) # [Nz,t,h,w]
|
| 215 |
+
|
| 216 |
+
if valid_mask_thw is not None:
|
| 217 |
+
m = valid_mask_thw.reshape(ZT, zs, ZH, zs, ZW, zs).float()
|
| 218 |
+
m = m.permute(0, 2, 4, 1, 3, 5).reshape(Nz, zs, zs, zs)
|
| 219 |
+
cnt = m.sum(dim=(1, 2, 3)).clamp(min=1.0)
|
| 220 |
+
a_bar = (a * m).sum(dim=(1, 2, 3)) / cnt
|
| 221 |
+
# Per-axis masked mean over the other two axes, then masked variance over
|
| 222 |
+
# the axis. Padded positions are excluded so they cannot fabricate audio
|
| 223 |
+
# directional structure (mirrors the V-feature masking).
|
| 224 |
+
cT = m.sum(dim=(2, 3)).clamp(min=1.0); aT = (a * m).sum(dim=(2, 3)) / cT # [Nz,t]
|
| 225 |
+
cH = m.sum(dim=(1, 3)).clamp(min=1.0); aH = (a * m).sum(dim=(1, 3)) / cH
|
| 226 |
+
cW = m.sum(dim=(1, 2)).clamp(min=1.0); aW = (a * m).sum(dim=(1, 2)) / cW
|
| 227 |
+
t_valid = m.sum(dim=(2, 3)) > 0
|
| 228 |
+
h_valid = m.sum(dim=(1, 3)) > 0
|
| 229 |
+
w_valid = m.sum(dim=(1, 2)) > 0
|
| 230 |
+
var_T = _masked_var_1d(aT, t_valid)
|
| 231 |
+
var_H = _masked_var_1d(aH, h_valid)
|
| 232 |
+
var_W = _masked_var_1d(aW, w_valid)
|
| 233 |
+
else:
|
| 234 |
+
a_bar = a.mean(dim=(1, 2, 3))
|
| 235 |
+
var_T = a.mean(dim=(2, 3)).var(dim=1, unbiased=False) # [Nz]
|
| 236 |
+
var_H = a.mean(dim=(1, 3)).var(dim=1, unbiased=False)
|
| 237 |
+
var_W = a.mean(dim=(1, 2)).var(dim=1, unbiased=False)
|
| 238 |
+
|
| 239 |
+
denom = (var_T + var_H + var_W).clamp(min=_EPS)
|
| 240 |
+
v_hat_T = var_T / denom
|
| 241 |
+
v_hat_H = var_H / denom
|
| 242 |
+
v_hat_W = var_W / denom
|
| 243 |
+
|
| 244 |
+
a_bar_norm = a_bar / a_bar.max().clamp(min=_EPS)
|
| 245 |
+
return a_bar_norm, v_hat_T, v_hat_H, v_hat_W
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def compute_effective_gradient(
|
| 249 |
+
v_headavg_thw_d: torch.Tensor,
|
| 250 |
+
audio_norms_thw: Optional[torch.Tensor],
|
| 251 |
+
ZT: int, ZH: int, ZW: int,
|
| 252 |
+
lambda_a: float = 0.5,
|
| 253 |
+
valid_mask_thw: Optional[torch.Tensor] = None,
|
| 254 |
+
sp_reduce: bool = True,
|
| 255 |
+
):
|
| 256 |
+
"""Compute (g[Nz,3], rho[Nz]) — Section 8.4.4 effective gradient + density.
|
| 257 |
+
|
| 258 |
+
g_d = aniso_ratio_d + lambda_a * a_bar_z * v_hat_d^audio (d in T,H,W)
|
| 259 |
+
rho = normalized (spatial + temporal) channel-variance information density.
|
| 260 |
+
"""
|
| 261 |
+
D = v_headavg_thw_d.shape[-1]
|
| 262 |
+
packed, axis_valid = compute_zone_aggregates(
|
| 263 |
+
v_headavg_thw_d, ZT, ZH, ZW, valid_mask_thw=valid_mask_thw
|
| 264 |
+
)
|
| 265 |
+
if sp_reduce:
|
| 266 |
+
packed = _maybe_sp_average(packed)
|
| 267 |
+
t_valid, h_valid, w_valid = axis_valid
|
| 268 |
+
channel_means, frame_means_T, axis_means_H, axis_means_W = _unpack_aggregates(packed, D)
|
| 269 |
+
|
| 270 |
+
# --- channel-variance information density rho (Section 3.3 @ zone level) ---
|
| 271 |
+
global_cm = channel_means.mean(dim=-1, keepdim=True)
|
| 272 |
+
spatial_var = ((channel_means - global_cm) ** 2).mean(dim=-1) # [Nz]
|
| 273 |
+
# Temporal diff only between adjacent frames that are BOTH valid (mirrors
|
| 274 |
+
# compute_channel_variance_density) so padded frames cannot fabricate motion.
|
| 275 |
+
both_valid = (t_valid[:, 1:] & t_valid[:, :-1]).unsqueeze(-1).to(frame_means_T.dtype) # [Nz,ZS-1,1]
|
| 276 |
+
n_pairs = both_valid.sum(dim=1).clamp(min=1.0) # [Nz,1]
|
| 277 |
+
frame_diffs = frame_means_T[:, 1:, :] - frame_means_T[:, :-1, :] # [Nz,ZS-1,D]
|
| 278 |
+
temporal_var = ((frame_diffs.pow(2) * both_valid).sum(dim=1) / n_pairs).mean(dim=-1) # [Nz]
|
| 279 |
+
rho_raw = spatial_var + temporal_var
|
| 280 |
+
rho = rho_raw / rho_raw.max().clamp(min=_EPS) # [Nz] in [0,1]
|
| 281 |
+
|
| 282 |
+
# --- anisotropy ratios (Section 8.4.2), padded axis positions excluded ---
|
| 283 |
+
var_T = _axis_var(frame_means_T, t_valid)
|
| 284 |
+
var_H = _axis_var(axis_means_H, h_valid)
|
| 285 |
+
var_W = _axis_var(axis_means_W, w_valid)
|
| 286 |
+
asum = (var_T + var_H + var_W).clamp(min=_EPS)
|
| 287 |
+
aniso_T = var_T / asum
|
| 288 |
+
aniso_H = var_H / asum
|
| 289 |
+
aniso_W = var_W / asum
|
| 290 |
+
|
| 291 |
+
# --- audio directional guidance (Section 8.4.3) ---
|
| 292 |
+
a_bar, v_hat_T, v_hat_H, v_hat_W = compute_audio_directional(
|
| 293 |
+
audio_norms_thw, ZT, ZH, ZW, valid_mask_thw,
|
| 294 |
+
device=v_headavg_thw_d.device,
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
g_T = aniso_T + lambda_a * a_bar * v_hat_T
|
| 298 |
+
g_H = aniso_H + lambda_a * a_bar * v_hat_H
|
| 299 |
+
g_W = aniso_W + lambda_a * a_bar * v_hat_W
|
| 300 |
+
g = torch.stack([g_T, g_H, g_W], dim=-1).clamp(min=1e-4) # [Nz,3]
|
| 301 |
+
return g, rho
|
| 302 |
+
|
| 303 |
+
def select_shapes_ivpq(
|
| 304 |
+
g: torch.Tensor, # [Nz, 3]
|
| 305 |
+
rho: torch.Tensor, # [Nz]
|
| 306 |
+
tau_128: float = 0.15,
|
| 307 |
+
) -> torch.Tensor:
|
| 308 |
+
"""IVPQ (Section 8.5): b_d* ∝ g_d^{-2/3}; nearest candidate in log2-space.
|
| 309 |
+
|
| 310 |
+
The candidate pool gating (rho vs tau_128) restricts high-info zones to the
|
| 311 |
+
7 64-token shapes and lets low-info zones also consider the 6 128-token
|
| 312 |
+
shapes (Section 8.5.3). Returns shape_id [Nz] (index into ALL_CANDIDATE_SHAPES).
|
| 313 |
+
"""
|
| 314 |
+
device = g.device
|
| 315 |
+
_, log2_shapes, is_128, _ = candidate_tensors(device, dtype=g.dtype) # [C,3], [C]
|
| 316 |
+
|
| 317 |
+
log2_ratio = (-2.0 / 3.0) * torch.log2(g) # [Nz, 3], ratio only
|
| 318 |
+
log2_ideal = log2_ratio + (6.0 - log2_ratio.sum(dim=-1, keepdim=True)) / 3.0
|
| 319 |
+
|
| 320 |
+
# log-space squared distance to each candidate: [Nz, C]
|
| 321 |
+
dist = ((log2_shapes.unsqueeze(0) - log2_ideal.unsqueeze(1)) ** 2).sum(dim=-1)
|
| 322 |
+
|
| 323 |
+
# Pool gating: forbid 128-token candidates for high-info zones (rho >= tau_128).
|
| 324 |
+
forbid = (rho.unsqueeze(1) >= tau_128) & is_128.unsqueeze(0) # [Nz, C]
|
| 325 |
+
dist = dist.masked_fill(forbid, float('inf'))
|
| 326 |
+
|
| 327 |
+
return dist.argmin(dim=-1).to(torch.int64)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def select_shapes_penalty(
|
| 331 |
+
g: torch.Tensor, # [Nz, 3]
|
| 332 |
+
rho: torch.Tensor, # [Nz]
|
| 333 |
+
tau_128: float = 0.15,
|
| 334 |
+
lambda_128: float = 1.0,
|
| 335 |
+
) -> torch.Tensor:
|
| 336 |
+
"""Penalty Matching (Section 8.6): argmin_c sum_d g_d * b_d (+128 bonus).
|
| 337 |
+
|
| 338 |
+
128-token candidates receive a density bonus ``-lambda_128 * (1 - rho)`` so
|
| 339 |
+
low-info zones can fairly select them; high-info zones (rho >= tau_128) are
|
| 340 |
+
still restricted to the 64-token pool. Returns shape_id [Nz].
|
| 341 |
+
"""
|
| 342 |
+
device = g.device
|
| 343 |
+
shapes, _, is_128, _ = candidate_tensors(device, dtype=g.dtype) # [C,3], [C]
|
| 344 |
+
|
| 345 |
+
penalty = (g.unsqueeze(1) * shapes.unsqueeze(0)).sum(dim=-1) # [Nz, C]
|
| 346 |
+
|
| 347 |
+
# 128-token density bonus (lowers penalty for low-info zones).
|
| 348 |
+
bonus = (lambda_128 * (1.0 - rho)).unsqueeze(1) * is_128.unsqueeze(0).to(g.dtype)
|
| 349 |
+
penalty = penalty - bonus
|
| 350 |
+
|
| 351 |
+
# Pool gating identical to IVPQ.
|
| 352 |
+
forbid = (rho.unsqueeze(1) >= tau_128) & is_128.unsqueeze(0)
|
| 353 |
+
penalty = penalty.masked_fill(forbid, float('inf'))
|
| 354 |
+
|
| 355 |
+
return penalty.argmin(dim=-1).to(torch.int64)
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def select_zone_shapes(
|
| 359 |
+
v_headavg_thw_d: torch.Tensor,
|
| 360 |
+
audio_norms_thw: Optional[torch.Tensor],
|
| 361 |
+
grid_padded: Tuple[int, int, int],
|
| 362 |
+
method: str,
|
| 363 |
+
lambda_a: float = 0.5,
|
| 364 |
+
tau_128: float = 0.15,
|
| 365 |
+
lambda_128: float = 1.0,
|
| 366 |
+
valid_mask_thw: Optional[torch.Tensor] = None,
|
| 367 |
+
sp_reduce: bool = True,
|
| 368 |
+
) -> torch.Tensor:
|
| 369 |
+
"""End-to-end: features -> g_d -> shape_id per zone. Returns int64 [num_zones].
|
| 370 |
+
|
| 371 |
+
Args:
|
| 372 |
+
v_headavg_thw_d: [T,H,W,D] head-averaged V (already padded). float ok.
|
| 373 |
+
audio_norms_thw: [T*H*W] per-token L2 norm of A->V bridge residual, or None.
|
| 374 |
+
grid_padded: (T,H,W), each a multiple of ZONE_SIZE.
|
| 375 |
+
method: "ivpq" or "penalty".
|
| 376 |
+
"""
|
| 377 |
+
T, H, W = grid_padded
|
| 378 |
+
assert T % ZONE_SIZE == 0 and H % ZONE_SIZE == 0 and W % ZONE_SIZE == 0, (
|
| 379 |
+
f"grid {grid_padded} must be multiple of zone size {ZONE_SIZE}"
|
| 380 |
+
)
|
| 381 |
+
ZT, ZH, ZW = T // ZONE_SIZE, H // ZONE_SIZE, W // ZONE_SIZE
|
| 382 |
+
|
| 383 |
+
g, rho = compute_effective_gradient(
|
| 384 |
+
v_headavg_thw_d, audio_norms_thw, ZT, ZH, ZW,
|
| 385 |
+
lambda_a=lambda_a, valid_mask_thw=valid_mask_thw, sp_reduce=sp_reduce,
|
| 386 |
+
)
|
| 387 |
+
if method == "ivpq":
|
| 388 |
+
return select_shapes_ivpq(g, rho, tau_128=tau_128)
|
| 389 |
+
elif method == "penalty":
|
| 390 |
+
return select_shapes_penalty(g, rho, tau_128=tau_128, lambda_128=lambda_128)
|
| 391 |
+
raise ValueError(f"unknown dynamic-block method: {method!r}")
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
__all__ = [
|
| 395 |
+
"CANDIDATE_SHAPES_64",
|
| 396 |
+
"CANDIDATE_SHAPES_128",
|
| 397 |
+
"ALL_CANDIDATE_SHAPES",
|
| 398 |
+
"NUM_CANDIDATES_64",
|
| 399 |
+
"NUM_CANDIDATES",
|
| 400 |
+
"ZONE_SIZE",
|
| 401 |
+
"candidate_tensors",
|
| 402 |
+
"compute_effective_gradient",
|
| 403 |
+
"select_shapes_ivpq",
|
| 404 |
+
"select_shapes_penalty",
|
| 405 |
+
"select_zone_shapes",
|
| 406 |
+
]
|
hymm/models/modules/block_sparse_attention/flash_attn_bsa_varlen_mask.py
ADDED
|
@@ -0,0 +1,1014 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import triton
|
| 2 |
+
import triton.language as tl
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
from .common import autotune
|
| 6 |
+
"""
|
| 7 |
+
TRITON_REEVALUATE_KEY=1
|
| 8 |
+
- autotune whenever params in reevaluate keys change
|
| 9 |
+
- use in benchmark script to fine the best config
|
| 10 |
+
|
| 11 |
+
TRITON_AUTOTUNE_ENBALE=1
|
| 12 |
+
- if set to 0, autotune will not work, and the related params must be passed to the function call.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
configs_fwd_bsa_varlen_preset = {
|
| 16 |
+
'default': {
|
| 17 |
+
'BLOCK_N': 64,
|
| 18 |
+
'num_stages': 3,
|
| 19 |
+
'num_warps': 8,
|
| 20 |
+
},
|
| 21 |
+
'BLOCK_N_LG=64': {
|
| 22 |
+
'BLOCK_N': 64,
|
| 23 |
+
'num_stages': 3,
|
| 24 |
+
'num_warps': 4,
|
| 25 |
+
},
|
| 26 |
+
}
|
| 27 |
+
configs_fwd_bsa_varlen = [
|
| 28 |
+
triton.Config({'BLOCK_N': BN}, num_stages=s, num_warps=w) \
|
| 29 |
+
for BN in [32, 64, 128] \
|
| 30 |
+
for s in [2, 3, 4, 5] \
|
| 31 |
+
for w in [4, 8] \
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
fwd_bsa_reevaluate_varlen_keys = ['N_CTX', 'BLOCK_M', 'BLOCK_N_LG', 'SPARSITY'] if os.environ.get('TRITON_REEVALUATE_KEY', '0') == '1' else []
|
| 35 |
+
@autotune(list(configs_fwd_bsa_varlen), key=fwd_bsa_reevaluate_varlen_keys)
|
| 36 |
+
@triton.jit
|
| 37 |
+
def _attn_fwd_bsa_varlen(
|
| 38 |
+
Q, K, V, sm_scale, M, Out,
|
| 39 |
+
block_indices, # [B, H, M_COMPRESS, S_MAX]
|
| 40 |
+
block_indices_lens, # [B, H, M_COMPRESS]
|
| 41 |
+
kv_valid_mask, # [N_CTX] bool, True=valid (shared across B,H)
|
| 42 |
+
stride_qz, stride_qh, stride_qm, stride_qk,
|
| 43 |
+
stride_kz, stride_kh, stride_kn, stride_kk,
|
| 44 |
+
stride_vz, stride_vh, stride_vn, stride_vk,
|
| 45 |
+
stride_oz, stride_oh, stride_om, stride_ok,
|
| 46 |
+
stride_bz, stride_bh, stride_bm, stride_bs,
|
| 47 |
+
stride_lz, stride_lh, stride_lm,
|
| 48 |
+
H, N_CTX,
|
| 49 |
+
HEAD_DIM: tl.constexpr,
|
| 50 |
+
BLOCK_M: tl.constexpr,
|
| 51 |
+
BLOCK_N_LG: tl.constexpr,
|
| 52 |
+
BLOCK_N: tl.constexpr,
|
| 53 |
+
SPARSITY: tl.constexpr,
|
| 54 |
+
HAS_KV_MASK: tl.constexpr,
|
| 55 |
+
):
|
| 56 |
+
|
| 57 |
+
start_m = tl.program_id(0)
|
| 58 |
+
off_hz = tl.program_id(1)
|
| 59 |
+
off_z = off_hz // H
|
| 60 |
+
off_h = off_hz % H
|
| 61 |
+
|
| 62 |
+
q_offset = off_z.to(tl.int64) * stride_qz + off_h.to(tl.int64) * stride_qh
|
| 63 |
+
k_offset = off_z.to(tl.int64) * stride_kz + off_h.to(tl.int64) * stride_kh
|
| 64 |
+
v_offset = off_z.to(tl.int64) * stride_vz + off_h.to(tl.int64) * stride_vh
|
| 65 |
+
o_offset = off_z.to(tl.int64) * stride_oz + off_h.to(tl.int64) * stride_oh
|
| 66 |
+
b_offset = off_z.to(tl.int64) * stride_bz + off_h.to(tl.int64) * stride_bh
|
| 67 |
+
l_offset = off_z.to(tl.int64) * stride_lz + off_h.to(tl.int64) * stride_lh
|
| 68 |
+
|
| 69 |
+
# block pointers
|
| 70 |
+
Q_block_ptr = tl.make_block_ptr(
|
| 71 |
+
base=Q + q_offset,
|
| 72 |
+
shape=(N_CTX, HEAD_DIM),
|
| 73 |
+
strides=(stride_qm, stride_qk),
|
| 74 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 75 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 76 |
+
order=(1, 0),
|
| 77 |
+
)
|
| 78 |
+
V_block_ptr = tl.make_block_ptr(
|
| 79 |
+
base=V + v_offset,
|
| 80 |
+
shape=(N_CTX, HEAD_DIM),
|
| 81 |
+
strides=(stride_vn, stride_vk),
|
| 82 |
+
offsets=(0, 0),
|
| 83 |
+
block_shape=(BLOCK_N, HEAD_DIM),
|
| 84 |
+
order=(1, 0),
|
| 85 |
+
)
|
| 86 |
+
KT_block_ptr = tl.make_block_ptr(
|
| 87 |
+
base=K + k_offset,
|
| 88 |
+
shape=(HEAD_DIM, N_CTX),
|
| 89 |
+
strides=(stride_kk, stride_kn),
|
| 90 |
+
offsets=(0, 0),
|
| 91 |
+
block_shape=(HEAD_DIM, BLOCK_N),
|
| 92 |
+
order=(0, 1),
|
| 93 |
+
)
|
| 94 |
+
O_block_ptr = tl.make_block_ptr(
|
| 95 |
+
base=Out + o_offset,
|
| 96 |
+
shape=(N_CTX, HEAD_DIM),
|
| 97 |
+
strides=(stride_om, stride_ok),
|
| 98 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 99 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 100 |
+
order=(1, 0),
|
| 101 |
+
)
|
| 102 |
+
block_indices += b_offset + start_m * stride_bm
|
| 103 |
+
block_indices_lens += l_offset + start_m * stride_lm
|
| 104 |
+
# initialize offsets
|
| 105 |
+
offs_m = start_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
| 106 |
+
|
| 107 |
+
# initialize pointer to m and l
|
| 108 |
+
m_i = tl.zeros([BLOCK_M], dtype=tl.float32) - float("inf")
|
| 109 |
+
l_i = tl.zeros([BLOCK_M], dtype=tl.float32) + 1.0
|
| 110 |
+
acc = tl.zeros([BLOCK_M, HEAD_DIM], dtype=tl.float32)
|
| 111 |
+
# load scales
|
| 112 |
+
qk_scale = sm_scale
|
| 113 |
+
qk_scale *= 1.44269504 # 1/ln2
|
| 114 |
+
# load q: it will stay in SRAM throughout
|
| 115 |
+
q = tl.load(Q_block_ptr)
|
| 116 |
+
S = tl.load(block_indices_lens)
|
| 117 |
+
for i in range(S):
|
| 118 |
+
block_id = tl.load(block_indices + i * stride_bs).to(tl.int32)
|
| 119 |
+
lo, hi = block_id * BLOCK_N_LG, (block_id + 1) * BLOCK_N_LG
|
| 120 |
+
lo = tl.multiple_of(lo, BLOCK_N)
|
| 121 |
+
KT_block_ptr_i = tl.advance(KT_block_ptr, (0, lo))
|
| 122 |
+
V_block_ptr_i = tl.advance(V_block_ptr, (lo, 0))
|
| 123 |
+
mask_offset = lo
|
| 124 |
+
|
| 125 |
+
# loop over k, v and update accumulator
|
| 126 |
+
for start_n in range(lo, hi, BLOCK_N):
|
| 127 |
+
start_n = tl.multiple_of(start_n, BLOCK_N)
|
| 128 |
+
# -- compute qk ----
|
| 129 |
+
kT = tl.load(KT_block_ptr_i)
|
| 130 |
+
qkT = tl.dot(q, kT)
|
| 131 |
+
|
| 132 |
+
if HAS_KV_MASK:
|
| 133 |
+
offs_n = mask_offset + tl.arange(0, BLOCK_N)
|
| 134 |
+
k_valid = tl.load(kv_valid_mask + offs_n)
|
| 135 |
+
qkT = tl.where(k_valid[None, :], qkT, float('-inf'))
|
| 136 |
+
|
| 137 |
+
m_ij = tl.maximum(m_i, tl.max(qkT, 1) * qk_scale)
|
| 138 |
+
qkT = qkT * qk_scale - m_ij[:, None]
|
| 139 |
+
p = tl.math.exp2(qkT)
|
| 140 |
+
|
| 141 |
+
if HAS_KV_MASK:
|
| 142 |
+
p = tl.where(k_valid[None, :], p, 0.0)
|
| 143 |
+
|
| 144 |
+
# -- update m_i and l_i
|
| 145 |
+
# Guard the all-(-inf) block case: if a selected block is fully padded
|
| 146 |
+
# (every key masked) AND it is the first processed block, m_i and m_ij
|
| 147 |
+
# are both -inf, so (m_i - m_ij) = -inf-(-inf) = NaN. Such a block
|
| 148 |
+
# contributes nothing (p is all-zero via the kv-mask above), so alpha
|
| 149 |
+
# must be 1.0 to leave acc / l_i untouched (alpha=0 would zero l_i and
|
| 150 |
+
# produce 0/0 = NaN in the epilogue for an all-padding-selection query).
|
| 151 |
+
# When any selected block has >=1 valid key (the common case) m_ij is
|
| 152 |
+
# finite and this is numerically identical to the original.
|
| 153 |
+
alpha = tl.where(m_ij == float("-inf"), 1.0, tl.math.exp2(m_i - m_ij))
|
| 154 |
+
l_ij = tl.sum(p, 1)
|
| 155 |
+
# -- update output accumulator --
|
| 156 |
+
acc = acc * alpha[:, None]
|
| 157 |
+
# update acc
|
| 158 |
+
v = tl.load(V_block_ptr_i)
|
| 159 |
+
acc = tl.dot(p.to(v.dtype), v, acc)
|
| 160 |
+
# update m_i and l_i
|
| 161 |
+
l_i = l_i * alpha + l_ij
|
| 162 |
+
m_i = m_ij
|
| 163 |
+
V_block_ptr_i = tl.advance(V_block_ptr_i, (BLOCK_N, 0))
|
| 164 |
+
KT_block_ptr_i = tl.advance(KT_block_ptr_i, (0, BLOCK_N))
|
| 165 |
+
mask_offset += BLOCK_N
|
| 166 |
+
|
| 167 |
+
# epilogue
|
| 168 |
+
m_i += tl.math.log2(l_i)
|
| 169 |
+
acc = acc / l_i[:, None]
|
| 170 |
+
m_ptrs = M + off_hz * N_CTX + offs_m
|
| 171 |
+
tl.store(m_ptrs, m_i)
|
| 172 |
+
tl.store(O_block_ptr, acc.to(Out.type.element_ty))
|
| 173 |
+
|
| 174 |
+
configs_fwd_bsa_varlen_align_preset = {
|
| 175 |
+
'default': {
|
| 176 |
+
'num_stages': 3,
|
| 177 |
+
'num_warps': 8,
|
| 178 |
+
},
|
| 179 |
+
'BLOCK_N_LG=64': {
|
| 180 |
+
'num_stages': 3,
|
| 181 |
+
'num_warps': 4,
|
| 182 |
+
},
|
| 183 |
+
}
|
| 184 |
+
configs_fwd_bsa_varlen_align = [
|
| 185 |
+
triton.Config({}, num_stages=s, num_warps=w) \
|
| 186 |
+
for s in [2, 3, 4, 5] \
|
| 187 |
+
for w in [4, 8] \
|
| 188 |
+
]
|
| 189 |
+
|
| 190 |
+
fwd_bsa_reevaluate_varlen_align_keys = ['N_CTX', 'BLOCK_M', 'BLOCK_N_LG', 'SPARSITY'] if os.environ.get('TRITON_REEVALUATE_KEY', '0') == '1' else []
|
| 191 |
+
@autotune(list(configs_fwd_bsa_varlen_align), key=fwd_bsa_reevaluate_varlen_align_keys)
|
| 192 |
+
@triton.jit
|
| 193 |
+
def _attn_fwd_bsa_varlen_align(
|
| 194 |
+
Q, K, V, sm_scale, M, Out,
|
| 195 |
+
block_indices, # [B, H, M_COMPRESS, S_MAX]
|
| 196 |
+
block_indices_lens, # [B, H, M_COMPRESS]
|
| 197 |
+
kv_valid_mask, # [N_CTX] bool
|
| 198 |
+
stride_qz, stride_qh, stride_qm, stride_qk,
|
| 199 |
+
stride_kz, stride_kh, stride_kn, stride_kk,
|
| 200 |
+
stride_vz, stride_vh, stride_vn, stride_vk,
|
| 201 |
+
stride_oz, stride_oh, stride_om, stride_on,
|
| 202 |
+
stride_bz, stride_bh, stride_bm, stride_bs,
|
| 203 |
+
stride_lz, stride_lh, stride_lm,
|
| 204 |
+
H, N_CTX,
|
| 205 |
+
HEAD_DIM: tl.constexpr,
|
| 206 |
+
BLOCK_M: tl.constexpr,
|
| 207 |
+
BLOCK_N_LG: tl.constexpr,
|
| 208 |
+
SPARSITY: tl.constexpr,
|
| 209 |
+
HAS_KV_MASK: tl.constexpr,
|
| 210 |
+
):
|
| 211 |
+
|
| 212 |
+
start_m = tl.program_id(0)
|
| 213 |
+
off_hz = tl.program_id(1)
|
| 214 |
+
off_z = off_hz // H
|
| 215 |
+
off_h = off_hz % H
|
| 216 |
+
|
| 217 |
+
q_offset = off_z.to(tl.int64) * stride_qz + off_h.to(tl.int64) * stride_qh
|
| 218 |
+
k_offset = off_z.to(tl.int64) * stride_kz + off_h.to(tl.int64) * stride_kh
|
| 219 |
+
v_offset = off_z.to(tl.int64) * stride_vz + off_h.to(tl.int64) * stride_vh
|
| 220 |
+
o_offset = off_z.to(tl.int64) * stride_oz + off_h.to(tl.int64) * stride_oh
|
| 221 |
+
b_offset = off_z.to(tl.int64) * stride_bz + off_h.to(tl.int64) * stride_bh
|
| 222 |
+
l_offset = off_z.to(tl.int64) * stride_lz + off_h.to(tl.int64) * stride_lh
|
| 223 |
+
|
| 224 |
+
# block pointers
|
| 225 |
+
Q_block_ptr = tl.make_block_ptr(
|
| 226 |
+
base=Q + q_offset,
|
| 227 |
+
shape=(N_CTX, HEAD_DIM),
|
| 228 |
+
strides=(stride_qm, stride_qk),
|
| 229 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 230 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 231 |
+
order=(1, 0),
|
| 232 |
+
)
|
| 233 |
+
V_block_ptr = tl.make_block_ptr(
|
| 234 |
+
base=V + v_offset,
|
| 235 |
+
shape=(N_CTX, HEAD_DIM),
|
| 236 |
+
strides=(stride_vn, stride_vk),
|
| 237 |
+
offsets=(0, 0),
|
| 238 |
+
block_shape=(BLOCK_N_LG, HEAD_DIM),
|
| 239 |
+
order=(1, 0),
|
| 240 |
+
)
|
| 241 |
+
KT_block_ptr = tl.make_block_ptr(
|
| 242 |
+
base=K + k_offset,
|
| 243 |
+
shape=(HEAD_DIM, N_CTX),
|
| 244 |
+
strides=(stride_kk, stride_kn),
|
| 245 |
+
offsets=(0, 0),
|
| 246 |
+
block_shape=(HEAD_DIM, BLOCK_N_LG),
|
| 247 |
+
order=(0, 1),
|
| 248 |
+
)
|
| 249 |
+
O_block_ptr = tl.make_block_ptr(
|
| 250 |
+
base=Out + o_offset,
|
| 251 |
+
shape=(N_CTX, HEAD_DIM),
|
| 252 |
+
strides=(stride_om, stride_on),
|
| 253 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 254 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 255 |
+
order=(1, 0),
|
| 256 |
+
)
|
| 257 |
+
block_indices += b_offset + start_m * stride_bm
|
| 258 |
+
block_indices_lens += l_offset + start_m * stride_lm
|
| 259 |
+
# initialize offsets
|
| 260 |
+
offs_m = start_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
| 261 |
+
|
| 262 |
+
# initialize pointer to m and l
|
| 263 |
+
m_i = tl.zeros([BLOCK_M], dtype=tl.float32) - float("inf")
|
| 264 |
+
l_i = tl.zeros([BLOCK_M], dtype=tl.float32) + 1.0
|
| 265 |
+
acc = tl.zeros([BLOCK_M, HEAD_DIM], dtype=tl.float32)
|
| 266 |
+
# load scales
|
| 267 |
+
qk_scale = sm_scale
|
| 268 |
+
qk_scale *= 1.44269504 # 1/ln2
|
| 269 |
+
# load q: it will stay in SRAM throughout
|
| 270 |
+
q = tl.load(Q_block_ptr)
|
| 271 |
+
S = tl.load(block_indices_lens)
|
| 272 |
+
for i in range(S):
|
| 273 |
+
block_id = tl.load(block_indices + i * stride_bs).to(tl.int32)
|
| 274 |
+
lo = block_id * BLOCK_N_LG
|
| 275 |
+
lo = tl.multiple_of(lo, BLOCK_N_LG)
|
| 276 |
+
KT_block_ptr_i = tl.advance(KT_block_ptr, (0, lo))
|
| 277 |
+
V_block_ptr_i = tl.advance(V_block_ptr, (lo, 0))
|
| 278 |
+
|
| 279 |
+
# -- compute qk ----
|
| 280 |
+
kT = tl.load(KT_block_ptr_i)
|
| 281 |
+
qkT = tl.dot(q, kT)
|
| 282 |
+
|
| 283 |
+
if HAS_KV_MASK:
|
| 284 |
+
offs_n = lo + tl.arange(0, BLOCK_N_LG)
|
| 285 |
+
k_valid = tl.load(kv_valid_mask + offs_n)
|
| 286 |
+
qkT = tl.where(k_valid[None, :], qkT, float('-inf'))
|
| 287 |
+
|
| 288 |
+
m_ij = tl.maximum(m_i, tl.max(qkT, 1) * qk_scale)
|
| 289 |
+
qkT = qkT * qk_scale - m_ij[:, None]
|
| 290 |
+
p = tl.math.exp2(qkT)
|
| 291 |
+
|
| 292 |
+
if HAS_KV_MASK:
|
| 293 |
+
p = tl.where(k_valid[None, :], p, 0.0)
|
| 294 |
+
|
| 295 |
+
# -- update m_i and l_i
|
| 296 |
+
# Guard the all-(-inf) block case: a fully-padded selected block processed
|
| 297 |
+
# first leaves m_i == m_ij == -inf, so (m_i - m_ij) = NaN. Such a block
|
| 298 |
+
# contributes nothing (p is all-zero via the kv-mask above); alpha must be
|
| 299 |
+
# 1.0 to leave acc / l_i untouched (alpha=0 would zero l_i -> 0/0 = NaN in
|
| 300 |
+
# the epilogue for an all-padding-selection query). Identical to the
|
| 301 |
+
# original whenever any selected block has >=1 valid key (the common case).
|
| 302 |
+
alpha = tl.where(m_ij == float("-inf"), 1.0, tl.math.exp2(m_i - m_ij))
|
| 303 |
+
l_ij = tl.sum(p, 1)
|
| 304 |
+
# -- update output accumulator --
|
| 305 |
+
acc = acc * alpha[:, None]
|
| 306 |
+
# update acc
|
| 307 |
+
v = tl.load(V_block_ptr_i)
|
| 308 |
+
acc = tl.dot(p.to(v.dtype), v, acc)
|
| 309 |
+
# update m_i and l_i
|
| 310 |
+
l_i = l_i * alpha + l_ij
|
| 311 |
+
m_i = m_ij
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
# epilogue
|
| 315 |
+
m_i += tl.math.log2(l_i)
|
| 316 |
+
acc = acc / l_i[:, None]
|
| 317 |
+
m_ptrs = M + off_hz * N_CTX + offs_m
|
| 318 |
+
tl.store(m_ptrs, m_i)
|
| 319 |
+
tl.store(O_block_ptr, acc.to(Out.type.element_ty))
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
# The main inner-loop logic for computing dK and dV.
|
| 323 |
+
@triton.jit
|
| 324 |
+
def _attn_bwd_dkdv_bsa_varlen(
|
| 325 |
+
dk, dv,
|
| 326 |
+
k, v,
|
| 327 |
+
Q, DO,
|
| 328 |
+
M, D,
|
| 329 |
+
block_indices,
|
| 330 |
+
block_indices_lens,
|
| 331 |
+
# shared by Q/K/V/DO.
|
| 332 |
+
stride_qm, stride_qk,
|
| 333 |
+
stride_dom, stride_dok,
|
| 334 |
+
stride_mm,
|
| 335 |
+
stride_dm,
|
| 336 |
+
stride_bm,
|
| 337 |
+
N_CTX,
|
| 338 |
+
kv_valid_mask,
|
| 339 |
+
k_start_pos,
|
| 340 |
+
BLOCK_N_K: tl.constexpr,
|
| 341 |
+
BLOCK_M1: tl.constexpr,
|
| 342 |
+
HEAD_DIM: tl.constexpr,
|
| 343 |
+
HAS_KV_MASK: tl.constexpr,
|
| 344 |
+
):
|
| 345 |
+
|
| 346 |
+
if HAS_KV_MASK:
|
| 347 |
+
offs_k = k_start_pos + tl.arange(0, BLOCK_N_K)
|
| 348 |
+
k_valid = tl.load(kv_valid_mask + offs_k)
|
| 349 |
+
|
| 350 |
+
QT_block_ptr = tl.make_block_ptr(
|
| 351 |
+
base=Q,
|
| 352 |
+
shape=(HEAD_DIM, N_CTX),
|
| 353 |
+
strides=(stride_qk, stride_qm),
|
| 354 |
+
offsets=(0, 0),
|
| 355 |
+
block_shape=(HEAD_DIM, BLOCK_M1),
|
| 356 |
+
order=(0, 1),
|
| 357 |
+
)
|
| 358 |
+
|
| 359 |
+
DO_block_ptr = tl.make_block_ptr(
|
| 360 |
+
base=DO,
|
| 361 |
+
shape=(N_CTX, HEAD_DIM),
|
| 362 |
+
strides=(stride_dom, stride_dok),
|
| 363 |
+
offsets=(0, 0),
|
| 364 |
+
block_shape=(BLOCK_M1, HEAD_DIM),
|
| 365 |
+
order=(1, 0),
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
S = tl.load(block_indices_lens)
|
| 369 |
+
for i in range(S):
|
| 370 |
+
block_id = tl.load(block_indices + i * stride_bm).to(tl.int32)
|
| 371 |
+
start_m = block_id * BLOCK_M1
|
| 372 |
+
start_m = tl.multiple_of(start_m, BLOCK_M1)
|
| 373 |
+
|
| 374 |
+
QT_block_ptr_i = tl.advance(QT_block_ptr, (0, start_m))
|
| 375 |
+
DO_block_ptr_i = tl.advance(DO_block_ptr, (start_m, 0))
|
| 376 |
+
|
| 377 |
+
qT = tl.load(QT_block_ptr_i)
|
| 378 |
+
offs_m = start_m + tl.arange(0, BLOCK_M1) * stride_mm
|
| 379 |
+
m = tl.load(M + offs_m)
|
| 380 |
+
kqT = tl.dot(k, qT)
|
| 381 |
+
if HAS_KV_MASK:
|
| 382 |
+
kqT = tl.where(k_valid[:, None], kqT, float('-inf'))
|
| 383 |
+
pT = tl.math.exp2(kqT - m[None, :])
|
| 384 |
+
if HAS_KV_MASK:
|
| 385 |
+
pT = tl.where(k_valid[:, None], pT, 0.0)
|
| 386 |
+
|
| 387 |
+
do = tl.load(DO_block_ptr_i)
|
| 388 |
+
# Compute dV.
|
| 389 |
+
ppT = pT
|
| 390 |
+
ppT = ppT.to(v.dtype)
|
| 391 |
+
dv += tl.dot(ppT, do)
|
| 392 |
+
# D (= delta) is pre-divided by ds_scale.
|
| 393 |
+
offs_d = start_m + tl.arange(0, BLOCK_M1) * stride_dm
|
| 394 |
+
Di = tl.load(D + offs_d)
|
| 395 |
+
# Compute dP and dS.
|
| 396 |
+
dpT = tl.dot(v, tl.trans(do)).to(tl.float32)
|
| 397 |
+
dsT = pT * (dpT - Di[None, :])
|
| 398 |
+
dsT = dsT.to(v.dtype)
|
| 399 |
+
dk += tl.dot(dsT, tl.trans(qT))
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
return dk, dv
|
| 403 |
+
|
| 404 |
+
|
| 405 |
+
# the main inner-loop logic for computing dQ
|
| 406 |
+
@triton.jit
|
| 407 |
+
def _attn_bwd_dq_bsa_varlen(
|
| 408 |
+
dq,
|
| 409 |
+
q, do,
|
| 410 |
+
m, d,
|
| 411 |
+
K, V,
|
| 412 |
+
N_CTX,
|
| 413 |
+
BLOCK_N2: tl.constexpr,
|
| 414 |
+
BLOCK_N_LG: tl.constexpr,
|
| 415 |
+
HEAD_DIM: tl.constexpr,
|
| 416 |
+
block_indices,
|
| 417 |
+
block_indices_lens,
|
| 418 |
+
stride_bn,
|
| 419 |
+
stride_kn, stride_kk,
|
| 420 |
+
stride_vn, stride_vk,
|
| 421 |
+
kv_valid_mask,
|
| 422 |
+
HAS_KV_MASK: tl.constexpr,
|
| 423 |
+
):
|
| 424 |
+
|
| 425 |
+
VT_block_ptr = tl.make_block_ptr(
|
| 426 |
+
base=V,
|
| 427 |
+
shape=(HEAD_DIM, N_CTX),
|
| 428 |
+
strides=(stride_vk, stride_vn),
|
| 429 |
+
offsets=(0, 0),
|
| 430 |
+
block_shape=(HEAD_DIM, BLOCK_N2),
|
| 431 |
+
order=(0, 1),
|
| 432 |
+
)
|
| 433 |
+
KT_block_ptr = tl.make_block_ptr(
|
| 434 |
+
base=K,
|
| 435 |
+
shape=(HEAD_DIM, N_CTX),
|
| 436 |
+
strides=(stride_kk, stride_kn),
|
| 437 |
+
offsets=(0, 0),
|
| 438 |
+
block_shape=(HEAD_DIM, BLOCK_N2),
|
| 439 |
+
order=(0, 1),
|
| 440 |
+
)
|
| 441 |
+
|
| 442 |
+
S = tl.load(block_indices_lens)
|
| 443 |
+
for i in range(S):
|
| 444 |
+
block_id = tl.load(block_indices + i * stride_bn).to(tl.int32)
|
| 445 |
+
lo, hi = block_id * BLOCK_N_LG, (block_id + 1) * BLOCK_N_LG
|
| 446 |
+
lo = tl.multiple_of(lo, BLOCK_N2)
|
| 447 |
+
|
| 448 |
+
KT_block_ptr_i = tl.advance(KT_block_ptr, (0, lo))
|
| 449 |
+
VT_block_ptr_i = tl.advance(VT_block_ptr, (0, lo))
|
| 450 |
+
mask_offset = lo
|
| 451 |
+
|
| 452 |
+
for start_n in range(lo, hi, BLOCK_N2):
|
| 453 |
+
start_n = tl.multiple_of(start_n, BLOCK_N2)
|
| 454 |
+
|
| 455 |
+
kT = tl.load(KT_block_ptr_i)
|
| 456 |
+
vT = tl.load(VT_block_ptr_i)
|
| 457 |
+
qkT = tl.dot(q, kT)
|
| 458 |
+
if HAS_KV_MASK:
|
| 459 |
+
offs_n = mask_offset + tl.arange(0, BLOCK_N2)
|
| 460 |
+
k_valid = tl.load(kv_valid_mask + offs_n)
|
| 461 |
+
qkT = tl.where(k_valid[None, :], qkT, float('-inf'))
|
| 462 |
+
p = tl.math.exp2(qkT - m)
|
| 463 |
+
if HAS_KV_MASK:
|
| 464 |
+
p = tl.where(k_valid[None, :], p, 0.0)
|
| 465 |
+
# Compute dP and dS.
|
| 466 |
+
dp = tl.dot(do, vT).to(tl.float32)
|
| 467 |
+
ds = p * (dp - d)
|
| 468 |
+
ds = ds.to(kT.dtype)
|
| 469 |
+
# Compute dQ.
|
| 470 |
+
dq += tl.dot(ds, tl.trans(kT))
|
| 471 |
+
# Increment pointers.
|
| 472 |
+
KT_block_ptr_i = tl.advance(KT_block_ptr_i, (0, BLOCK_N2))
|
| 473 |
+
VT_block_ptr_i = tl.advance(VT_block_ptr_i, (0, BLOCK_N2))
|
| 474 |
+
mask_offset += BLOCK_N2
|
| 475 |
+
|
| 476 |
+
return dq
|
| 477 |
+
|
| 478 |
+
# the main inner-loop logic for computing dQ
|
| 479 |
+
@triton.jit
|
| 480 |
+
def _attn_bwd_dq_bsa_varlen_align(
|
| 481 |
+
dq,
|
| 482 |
+
q, do,
|
| 483 |
+
m, d,
|
| 484 |
+
K, V,
|
| 485 |
+
N_CTX,
|
| 486 |
+
BLOCK_N_LG: tl.constexpr,
|
| 487 |
+
HEAD_DIM: tl.constexpr,
|
| 488 |
+
block_indices,
|
| 489 |
+
block_indices_lens,
|
| 490 |
+
stride_bn,
|
| 491 |
+
stride_kn, stride_kk,
|
| 492 |
+
stride_vn, stride_vk,
|
| 493 |
+
kv_valid_mask,
|
| 494 |
+
HAS_KV_MASK: tl.constexpr,
|
| 495 |
+
):
|
| 496 |
+
|
| 497 |
+
VT_block_ptr = tl.make_block_ptr(
|
| 498 |
+
base=V,
|
| 499 |
+
shape=(HEAD_DIM, N_CTX),
|
| 500 |
+
strides=(stride_vk, stride_vn),
|
| 501 |
+
offsets=(0, 0),
|
| 502 |
+
block_shape=(HEAD_DIM, BLOCK_N_LG),
|
| 503 |
+
order=(0, 1),
|
| 504 |
+
)
|
| 505 |
+
KT_block_ptr = tl.make_block_ptr(
|
| 506 |
+
base=K,
|
| 507 |
+
shape=(HEAD_DIM, N_CTX),
|
| 508 |
+
strides=(stride_kk, stride_kn),
|
| 509 |
+
offsets=(0, 0),
|
| 510 |
+
block_shape=(HEAD_DIM, BLOCK_N_LG),
|
| 511 |
+
order=(0, 1),
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
S = tl.load(block_indices_lens)
|
| 515 |
+
for i in range(S):
|
| 516 |
+
block_id = tl.load(block_indices + i * stride_bn).to(tl.int32)
|
| 517 |
+
lo = block_id * BLOCK_N_LG
|
| 518 |
+
lo = tl.multiple_of(lo, BLOCK_N_LG)
|
| 519 |
+
|
| 520 |
+
KT_block_ptr_i = tl.advance(KT_block_ptr, (0, lo))
|
| 521 |
+
VT_block_ptr_i = tl.advance(VT_block_ptr, (0, lo))
|
| 522 |
+
|
| 523 |
+
kT = tl.load(KT_block_ptr_i)
|
| 524 |
+
vT = tl.load(VT_block_ptr_i)
|
| 525 |
+
qkT = tl.dot(q, kT)
|
| 526 |
+
if HAS_KV_MASK:
|
| 527 |
+
offs_n = lo + tl.arange(0, BLOCK_N_LG)
|
| 528 |
+
k_valid = tl.load(kv_valid_mask + offs_n)
|
| 529 |
+
qkT = tl.where(k_valid[None, :], qkT, float('-inf'))
|
| 530 |
+
p = tl.math.exp2(qkT - m)
|
| 531 |
+
if HAS_KV_MASK:
|
| 532 |
+
p = tl.where(k_valid[None, :], p, 0.0)
|
| 533 |
+
# Compute dP and dS.
|
| 534 |
+
dp = tl.dot(do, vT).to(tl.float32)
|
| 535 |
+
ds = p * (dp - d)
|
| 536 |
+
ds = ds.to(kT.dtype)
|
| 537 |
+
# Compute dQ.
|
| 538 |
+
dq += tl.dot(ds, tl.trans(kT))
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
return dq
|
| 542 |
+
|
| 543 |
+
configs_bwd_dkdv_bsa_varlen_preset = {
|
| 544 |
+
'default': {
|
| 545 |
+
'BLOCK_N': 128,
|
| 546 |
+
'num_stages': 2,
|
| 547 |
+
'num_warps': 8,
|
| 548 |
+
},
|
| 549 |
+
'BLOCK_N_DQ_LG=64': {
|
| 550 |
+
'BLOCK_N': 64,
|
| 551 |
+
'num_stages': 2,
|
| 552 |
+
'num_warps': 4,
|
| 553 |
+
}
|
| 554 |
+
}
|
| 555 |
+
configs_bwd_dkdv_bsa_varlen = [
|
| 556 |
+
triton.Config({'BLOCK_N': BN}, num_stages=s, num_warps=w) \
|
| 557 |
+
for BN in [32, 64, 128] \
|
| 558 |
+
for s in [2, 3, 4, 5] \
|
| 559 |
+
for w in [4, 8] \
|
| 560 |
+
]
|
| 561 |
+
bwd_dkdv_bsa_varlen_reevaluate_keys = ['N_CTX', 'BLOCK_M', 'BLOCK_N_DQ_LG', 'SPARSITY'] if os.environ.get('TRITON_REEVALUATE_KEY', '0') == '1' else []
|
| 562 |
+
@autotune(list(configs_bwd_dkdv_bsa_varlen), key=bwd_dkdv_bsa_varlen_reevaluate_keys)
|
| 563 |
+
@triton.jit
|
| 564 |
+
def _attn_bwd_dkdv_bsa_varlen_wrapper(
|
| 565 |
+
Q, K, V, sm_scale, # softmax scale
|
| 566 |
+
DO,
|
| 567 |
+
DK, DV,
|
| 568 |
+
M, # lse (log2)
|
| 569 |
+
D,
|
| 570 |
+
block_indices,
|
| 571 |
+
block_indices_lens,
|
| 572 |
+
kv_valid_mask,
|
| 573 |
+
# qkv
|
| 574 |
+
stride_qz, stride_qh, stride_qm, stride_qk,
|
| 575 |
+
stride_kz, stride_kh, stride_kn, stride_kk,
|
| 576 |
+
stride_vz, stride_vh, stride_vn, stride_vk,
|
| 577 |
+
# dk dv do
|
| 578 |
+
stride_dkz, stride_dkh, stride_dkn, stride_dkk,
|
| 579 |
+
stride_dvz, stride_dvh, stride_dvn, stride_dvk,
|
| 580 |
+
stride_doz, stride_doh, stride_dom, stride_dok,
|
| 581 |
+
# m, d
|
| 582 |
+
stride_mz, stride_mh, stride_mm,
|
| 583 |
+
stride_dz, stride_dh, stride_dm,
|
| 584 |
+
#
|
| 585 |
+
stride_bz, stride_bh, stride_bn, stride_bm, # block_indices
|
| 586 |
+
stride_lz, stride_lh, stride_ln, # block_indices_lens
|
| 587 |
+
#
|
| 588 |
+
H, N_CTX,
|
| 589 |
+
BLOCK_M: tl.constexpr,
|
| 590 |
+
BLOCK_N: tl.constexpr,
|
| 591 |
+
BLOCK_N_DQ_LG: tl.constexpr, # logical block size
|
| 592 |
+
HEAD_DIM: tl.constexpr,
|
| 593 |
+
SPARSITY: tl.constexpr,
|
| 594 |
+
HAS_KV_MASK: tl.constexpr,
|
| 595 |
+
):
|
| 596 |
+
|
| 597 |
+
tl.static_assert(BLOCK_N_DQ_LG % BLOCK_N == 0)
|
| 598 |
+
start_n = tl.program_id(0)
|
| 599 |
+
|
| 600 |
+
off_hz = tl.program_id(2)
|
| 601 |
+
off_z = off_hz // H
|
| 602 |
+
off_h = off_hz % H
|
| 603 |
+
|
| 604 |
+
off_q = off_z.to(tl.int64) * stride_qz + off_h.to(tl.int64) * stride_qh
|
| 605 |
+
off_k = off_z.to(tl.int64) * stride_kz + off_h.to(tl.int64) * stride_kh
|
| 606 |
+
off_v = off_z.to(tl.int64) * stride_vz + off_h.to(tl.int64) * stride_vh
|
| 607 |
+
|
| 608 |
+
off_dk = off_z.to(tl.int64) * stride_dkz + off_h.to(tl.int64) * stride_dkh
|
| 609 |
+
off_dv = off_z.to(tl.int64) * stride_dvz + off_h.to(tl.int64) * stride_dvh
|
| 610 |
+
off_do = off_z.to(tl.int64) * stride_doz + off_h.to(tl.int64) * stride_doh
|
| 611 |
+
|
| 612 |
+
off_m = off_z.to(tl.int64) * stride_mz + off_h.to(tl.int64) * stride_mh
|
| 613 |
+
off_d = off_z.to(tl.int64) * stride_dz + off_h.to(tl.int64) * stride_dh
|
| 614 |
+
|
| 615 |
+
off_block_incides = off_z.to(tl.int64) * stride_bz + off_h.to(tl.int64) * stride_bh
|
| 616 |
+
off_block_incides_lens = off_z.to(tl.int64) * stride_lz + off_h.to(tl.int64) * stride_lh
|
| 617 |
+
|
| 618 |
+
# offset pointers for batch/head
|
| 619 |
+
Q += off_q
|
| 620 |
+
K += off_k
|
| 621 |
+
V += off_v
|
| 622 |
+
DO += off_do
|
| 623 |
+
DK += off_dk
|
| 624 |
+
DV += off_dv
|
| 625 |
+
|
| 626 |
+
M += off_m
|
| 627 |
+
D += off_d
|
| 628 |
+
block_indices += off_block_incides
|
| 629 |
+
block_indices_lens += off_block_incides_lens
|
| 630 |
+
|
| 631 |
+
# ---------------------------------------- [DKDV] ----------------------------------------
|
| 632 |
+
|
| 633 |
+
dv = tl.zeros([BLOCK_N, HEAD_DIM], dtype=tl.float32)
|
| 634 |
+
dk = tl.zeros([BLOCK_N, HEAD_DIM], dtype=tl.float32)
|
| 635 |
+
|
| 636 |
+
# load K and V: they stay in SRAM throughout the inner loop.
|
| 637 |
+
|
| 638 |
+
K_block_ptr = tl.make_block_ptr(
|
| 639 |
+
base=K,
|
| 640 |
+
shape=(N_CTX, HEAD_DIM),
|
| 641 |
+
strides=(stride_kn, stride_kk),
|
| 642 |
+
offsets=(start_n * BLOCK_N, 0),
|
| 643 |
+
block_shape=(BLOCK_N, HEAD_DIM),
|
| 644 |
+
order=(1, 0),
|
| 645 |
+
)
|
| 646 |
+
V_block_ptr = tl.make_block_ptr(
|
| 647 |
+
base=V,
|
| 648 |
+
shape=(N_CTX, HEAD_DIM),
|
| 649 |
+
strides=(stride_vn, stride_vk),
|
| 650 |
+
offsets=(start_n * BLOCK_N, 0),
|
| 651 |
+
block_shape=(BLOCK_N, HEAD_DIM),
|
| 652 |
+
order=(1, 0),
|
| 653 |
+
)
|
| 654 |
+
DK_block_ptr = tl.make_block_ptr(
|
| 655 |
+
base=DK,
|
| 656 |
+
shape=(N_CTX, HEAD_DIM),
|
| 657 |
+
strides=(stride_dkn, stride_dkk),
|
| 658 |
+
offsets=(start_n * BLOCK_N, 0),
|
| 659 |
+
block_shape=(BLOCK_N, HEAD_DIM),
|
| 660 |
+
order=(1, 0),
|
| 661 |
+
)
|
| 662 |
+
DV_block_ptr = tl.make_block_ptr(
|
| 663 |
+
base=DV,
|
| 664 |
+
shape=(N_CTX, HEAD_DIM),
|
| 665 |
+
strides=(stride_dvn, stride_dvk),
|
| 666 |
+
offsets=(start_n * BLOCK_N, 0),
|
| 667 |
+
block_shape=(BLOCK_N, HEAD_DIM),
|
| 668 |
+
order=(1, 0),
|
| 669 |
+
)
|
| 670 |
+
|
| 671 |
+
k = tl.load(K_block_ptr)
|
| 672 |
+
v = tl.load(V_block_ptr)
|
| 673 |
+
|
| 674 |
+
k_compress_idx = start_n * BLOCK_N // BLOCK_N_DQ_LG
|
| 675 |
+
block_indices_i = block_indices + k_compress_idx * stride_bn
|
| 676 |
+
block_indices_lens_i = block_indices_lens + k_compress_idx * stride_ln
|
| 677 |
+
|
| 678 |
+
dk, dv = _attn_bwd_dkdv_bsa_varlen(
|
| 679 |
+
dk, dv,
|
| 680 |
+
k, v,
|
| 681 |
+
Q, DO,
|
| 682 |
+
M, D,
|
| 683 |
+
block_indices_i,
|
| 684 |
+
block_indices_lens_i,
|
| 685 |
+
stride_qm, stride_qk,
|
| 686 |
+
stride_dom, stride_dok,
|
| 687 |
+
stride_mm,
|
| 688 |
+
stride_dm,
|
| 689 |
+
stride_bm,
|
| 690 |
+
N_CTX,
|
| 691 |
+
kv_valid_mask,
|
| 692 |
+
start_n * BLOCK_N,
|
| 693 |
+
BLOCK_N,
|
| 694 |
+
BLOCK_M,
|
| 695 |
+
HEAD_DIM,
|
| 696 |
+
HAS_KV_MASK,
|
| 697 |
+
)
|
| 698 |
+
|
| 699 |
+
# Write back dk
|
| 700 |
+
dk *= sm_scale # S = scale * QKT; dK = scale * QdST
|
| 701 |
+
tl.store(DK_block_ptr, dk.to(k.dtype))
|
| 702 |
+
|
| 703 |
+
# Write back dv
|
| 704 |
+
tl.store(DV_block_ptr, dv.to(v.dtype))
|
| 705 |
+
|
| 706 |
+
configs_bwd_dq_bsa_varlen_preset = {
|
| 707 |
+
'default': {
|
| 708 |
+
'BLOCK_N_DQ': 64,
|
| 709 |
+
'num_stages': 2,
|
| 710 |
+
'num_warps': 8,
|
| 711 |
+
},
|
| 712 |
+
'BLOCK_N_DQ_LG=64': {
|
| 713 |
+
'BLOCK_N_DQ': 64,
|
| 714 |
+
'num_stages': 2,
|
| 715 |
+
'num_warps': 4,
|
| 716 |
+
},
|
| 717 |
+
}
|
| 718 |
+
configs_bwd_dq_bsa_varlen = [
|
| 719 |
+
triton.Config({'BLOCK_N_DQ': BN}, num_stages=s, num_warps=w) \
|
| 720 |
+
for BN in [32, 64, 128] \
|
| 721 |
+
for s in [2, 3, 4, 5] \
|
| 722 |
+
for w in [4, 8] \
|
| 723 |
+
]
|
| 724 |
+
bwd_dq_bsa_varlen_reevaluate_keys = ['N_CTX', 'BLOCK_M', 'BLOCK_N_DQ_LG', 'SPARSITY'] if os.environ.get('TRITON_REEVALUATE_KEY', '0') == '1' else []
|
| 725 |
+
@autotune(list(configs_bwd_dq_bsa_varlen), key=bwd_dq_bsa_varlen_reevaluate_keys)
|
| 726 |
+
@triton.jit
|
| 727 |
+
def _attn_bwd_dq_bsa_varlen_wrapper(
|
| 728 |
+
Q, K, V,
|
| 729 |
+
DO,
|
| 730 |
+
DQ,
|
| 731 |
+
M, # lse (log2)
|
| 732 |
+
D,
|
| 733 |
+
block_indices,
|
| 734 |
+
block_indices_lens,
|
| 735 |
+
kv_valid_mask,
|
| 736 |
+
# qkv
|
| 737 |
+
stride_qz, stride_qh, stride_qm, stride_qk,
|
| 738 |
+
stride_kz, stride_kh, stride_kn, stride_kk,
|
| 739 |
+
stride_vz, stride_vh, stride_vn, stride_vk,
|
| 740 |
+
# dq do
|
| 741 |
+
stride_dqz, stride_dqh, stride_dqm, stride_dqk,
|
| 742 |
+
stride_doz, stride_doh, stride_dom, stride_dok,
|
| 743 |
+
# m, d
|
| 744 |
+
stride_mz, stride_mh, stride_mm,
|
| 745 |
+
stride_dz, stride_dh, stride_dm,
|
| 746 |
+
#
|
| 747 |
+
stride_bz, stride_bh, stride_bm, stride_bn, # block_indices
|
| 748 |
+
stride_lz, stride_lh, stride_lm, # block_indices_lens
|
| 749 |
+
#
|
| 750 |
+
H, N_CTX,
|
| 751 |
+
BLOCK_M: tl.constexpr,
|
| 752 |
+
BLOCK_N_DQ_LG: tl.constexpr, # logical block size
|
| 753 |
+
BLOCK_N_DQ: tl.constexpr,
|
| 754 |
+
HEAD_DIM: tl.constexpr,
|
| 755 |
+
SPARSITY: tl.constexpr,
|
| 756 |
+
HAS_KV_MASK: tl.constexpr,
|
| 757 |
+
):
|
| 758 |
+
|
| 759 |
+
tl.static_assert(BLOCK_N_DQ_LG % BLOCK_N_DQ == 0)
|
| 760 |
+
tl.static_assert(BLOCK_N_DQ_LG % BLOCK_M == 0)
|
| 761 |
+
|
| 762 |
+
LN2: tl.constexpr = 0.6931471824645996 # = ln(2)
|
| 763 |
+
|
| 764 |
+
start_m = tl.program_id(0)
|
| 765 |
+
|
| 766 |
+
off_hz = tl.program_id(2)
|
| 767 |
+
off_z = off_hz // H
|
| 768 |
+
off_h = off_hz % H
|
| 769 |
+
|
| 770 |
+
off_q = off_z.to(tl.int64) * stride_qz + off_h.to(tl.int64) * stride_qh
|
| 771 |
+
off_k = off_z.to(tl.int64) * stride_kz + off_h.to(tl.int64) * stride_kh
|
| 772 |
+
off_v = off_z.to(tl.int64) * stride_vz + off_h.to(tl.int64) * stride_vh
|
| 773 |
+
|
| 774 |
+
off_dq = off_z.to(tl.int64) * stride_dqz + off_h.to(tl.int64) * stride_dqh
|
| 775 |
+
off_do = off_z.to(tl.int64) * stride_doz + off_h.to(tl.int64) * stride_doh
|
| 776 |
+
|
| 777 |
+
off_m = off_z.to(tl.int64) * stride_mz + off_h.to(tl.int64) * stride_mh
|
| 778 |
+
off_d = off_z.to(tl.int64) * stride_dz + off_h.to(tl.int64) * stride_dh
|
| 779 |
+
|
| 780 |
+
off_block_incides = off_z.to(tl.int64) * stride_bz + off_h.to(tl.int64) * stride_bh
|
| 781 |
+
off_block_incides_lens = off_z.to(tl.int64) * stride_lz + off_h.to(tl.int64) * stride_lh
|
| 782 |
+
|
| 783 |
+
# offset pointers for batch/head
|
| 784 |
+
Q += off_q
|
| 785 |
+
K += off_k
|
| 786 |
+
V += off_v
|
| 787 |
+
DO += off_do
|
| 788 |
+
DQ += off_dq
|
| 789 |
+
|
| 790 |
+
M += off_m
|
| 791 |
+
D += off_d
|
| 792 |
+
block_indices += off_block_incides
|
| 793 |
+
block_indices_lens += off_block_incides_lens
|
| 794 |
+
|
| 795 |
+
# ---------------------------------------- [DQ] ----------------------------------------
|
| 796 |
+
|
| 797 |
+
Q_block_ptr = tl.make_block_ptr(
|
| 798 |
+
base=Q,
|
| 799 |
+
shape=(N_CTX, HEAD_DIM),
|
| 800 |
+
strides=(stride_qm, stride_qk),
|
| 801 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 802 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 803 |
+
order=(1, 0),
|
| 804 |
+
)
|
| 805 |
+
DO_block_ptr = tl.make_block_ptr(
|
| 806 |
+
base=DO,
|
| 807 |
+
shape=(N_CTX, HEAD_DIM),
|
| 808 |
+
strides=(stride_dom, stride_dok),
|
| 809 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 810 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 811 |
+
order=(1, 0),
|
| 812 |
+
)
|
| 813 |
+
DQ_block_ptr = tl.make_block_ptr(
|
| 814 |
+
base=DQ,
|
| 815 |
+
shape=(N_CTX, HEAD_DIM),
|
| 816 |
+
strides=(stride_dqm, stride_dqk),
|
| 817 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 818 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 819 |
+
order=(1, 0),
|
| 820 |
+
)
|
| 821 |
+
|
| 822 |
+
q = tl.load(Q_block_ptr)
|
| 823 |
+
do = tl.load(DO_block_ptr)
|
| 824 |
+
|
| 825 |
+
dq = tl.zeros([BLOCK_M, HEAD_DIM], dtype=tl.float32)
|
| 826 |
+
|
| 827 |
+
start_m = start_m * BLOCK_M
|
| 828 |
+
|
| 829 |
+
offs_m = start_m + tl.arange(0, BLOCK_M) * stride_mm
|
| 830 |
+
offs_d = start_m + tl.arange(0, BLOCK_M) * stride_dm
|
| 831 |
+
|
| 832 |
+
m = tl.load(M + offs_m)
|
| 833 |
+
m = m[:, None]
|
| 834 |
+
|
| 835 |
+
d = tl.load(D + offs_d)
|
| 836 |
+
d = d[:, None]
|
| 837 |
+
|
| 838 |
+
block_indices_m = block_indices + (start_m // BLOCK_M) * stride_bm
|
| 839 |
+
block_indices_lens_m = block_indices_lens + (start_m // BLOCK_M) * stride_lm
|
| 840 |
+
|
| 841 |
+
dq = _attn_bwd_dq_bsa_varlen(
|
| 842 |
+
dq,
|
| 843 |
+
q, do,
|
| 844 |
+
m, d,
|
| 845 |
+
K, V,
|
| 846 |
+
N_CTX,
|
| 847 |
+
BLOCK_N_DQ,
|
| 848 |
+
BLOCK_N_DQ_LG,
|
| 849 |
+
HEAD_DIM,
|
| 850 |
+
block_indices_m,
|
| 851 |
+
block_indices_lens_m,
|
| 852 |
+
stride_bn,
|
| 853 |
+
stride_kn, stride_kk,
|
| 854 |
+
stride_vn, stride_vk,
|
| 855 |
+
kv_valid_mask,
|
| 856 |
+
HAS_KV_MASK,
|
| 857 |
+
)
|
| 858 |
+
|
| 859 |
+
# Write back dQ.
|
| 860 |
+
dq *= LN2
|
| 861 |
+
tl.store(DQ_block_ptr, dq.to(q.dtype))
|
| 862 |
+
|
| 863 |
+
configs_bwd_dq_bsa_varlen_align_preset = {
|
| 864 |
+
'default': {
|
| 865 |
+
'num_stages': 2,
|
| 866 |
+
'num_warps': 8,
|
| 867 |
+
},
|
| 868 |
+
'BLOCK_N_DQ_LG=64': {
|
| 869 |
+
'num_stages': 2,
|
| 870 |
+
'num_warps': 4,
|
| 871 |
+
},
|
| 872 |
+
}
|
| 873 |
+
configs_bwd_dq_bsa_varlen_align = [
|
| 874 |
+
triton.Config({}, num_stages=s, num_warps=w) \
|
| 875 |
+
for s in [2, 3, 4, 5] \
|
| 876 |
+
for w in [4, 8] \
|
| 877 |
+
]
|
| 878 |
+
bwd_dq_bsa_varlen_align_reevaluate_keys = ['N_CTX', 'BLOCK_M', 'BLOCK_N_DQ_LG', 'SPARSITY'] if os.environ.get('TRITON_REEVALUATE_KEY', '0') == '1' else []
|
| 879 |
+
@autotune(list(configs_bwd_dq_bsa_varlen_align), key=bwd_dq_bsa_varlen_align_reevaluate_keys)
|
| 880 |
+
@triton.jit
|
| 881 |
+
def _attn_bwd_dq_bsa_varlen_align_wrapper(
|
| 882 |
+
Q, K, V,
|
| 883 |
+
DO,
|
| 884 |
+
DQ,
|
| 885 |
+
M, # lse (log2)
|
| 886 |
+
D,
|
| 887 |
+
block_indices,
|
| 888 |
+
block_indices_lens,
|
| 889 |
+
kv_valid_mask,
|
| 890 |
+
# qkv
|
| 891 |
+
stride_qz, stride_qh, stride_qm, stride_qk,
|
| 892 |
+
stride_kz, stride_kh, stride_kn, stride_kk,
|
| 893 |
+
stride_vz, stride_vh, stride_vn, stride_vk,
|
| 894 |
+
# dq do
|
| 895 |
+
stride_dqz, stride_dqh, stride_dqm, stride_dqk,
|
| 896 |
+
stride_doz, stride_doh, stride_dom, stride_dok,
|
| 897 |
+
# m, d
|
| 898 |
+
stride_mz, stride_mh, stride_mm,
|
| 899 |
+
stride_dz, stride_dh, stride_dm,
|
| 900 |
+
#
|
| 901 |
+
stride_bz, stride_bh, stride_bm, stride_bn, # block_indices
|
| 902 |
+
stride_lz, stride_lh, stride_lm, # block_indices_lens
|
| 903 |
+
#
|
| 904 |
+
H, N_CTX,
|
| 905 |
+
BLOCK_M: tl.constexpr,
|
| 906 |
+
BLOCK_N_DQ_LG: tl.constexpr, # logical block size
|
| 907 |
+
HEAD_DIM: tl.constexpr,
|
| 908 |
+
SPARSITY: tl.constexpr,
|
| 909 |
+
HAS_KV_MASK: tl.constexpr,
|
| 910 |
+
):
|
| 911 |
+
|
| 912 |
+
tl.static_assert(BLOCK_N_DQ_LG % BLOCK_N_DQ_LG == 0)
|
| 913 |
+
tl.static_assert(BLOCK_N_DQ_LG % BLOCK_M == 0)
|
| 914 |
+
|
| 915 |
+
LN2: tl.constexpr = 0.6931471824645996 # = ln(2)
|
| 916 |
+
|
| 917 |
+
start_m = tl.program_id(0)
|
| 918 |
+
|
| 919 |
+
off_hz = tl.program_id(2)
|
| 920 |
+
off_z = off_hz // H
|
| 921 |
+
off_h = off_hz % H
|
| 922 |
+
|
| 923 |
+
off_q = off_z.to(tl.int64) * stride_qz + off_h.to(tl.int64) * stride_qh
|
| 924 |
+
off_k = off_z.to(tl.int64) * stride_kz + off_h.to(tl.int64) * stride_kh
|
| 925 |
+
off_v = off_z.to(tl.int64) * stride_vz + off_h.to(tl.int64) * stride_vh
|
| 926 |
+
|
| 927 |
+
off_dq = off_z.to(tl.int64) * stride_dqz + off_h.to(tl.int64) * stride_dqh
|
| 928 |
+
off_do = off_z.to(tl.int64) * stride_doz + off_h.to(tl.int64) * stride_doh
|
| 929 |
+
|
| 930 |
+
off_m = off_z.to(tl.int64) * stride_mz + off_h.to(tl.int64) * stride_mh
|
| 931 |
+
off_d = off_z.to(tl.int64) * stride_dz + off_h.to(tl.int64) * stride_dh
|
| 932 |
+
|
| 933 |
+
off_block_incides = off_z.to(tl.int64) * stride_bz + off_h.to(tl.int64) * stride_bh
|
| 934 |
+
off_block_incides_lens = off_z.to(tl.int64) * stride_lz + off_h.to(tl.int64) * stride_lh
|
| 935 |
+
|
| 936 |
+
# offset pointers for batch/head
|
| 937 |
+
Q += off_q
|
| 938 |
+
K += off_k
|
| 939 |
+
V += off_v
|
| 940 |
+
DO += off_do
|
| 941 |
+
DQ += off_dq
|
| 942 |
+
|
| 943 |
+
M += off_m
|
| 944 |
+
D += off_d
|
| 945 |
+
block_indices += off_block_incides
|
| 946 |
+
block_indices_lens += off_block_incides_lens
|
| 947 |
+
|
| 948 |
+
# ---------------------------------------- [DQ] ----------------------------------------
|
| 949 |
+
|
| 950 |
+
Q_block_ptr = tl.make_block_ptr(
|
| 951 |
+
base=Q,
|
| 952 |
+
shape=(N_CTX, HEAD_DIM),
|
| 953 |
+
strides=(stride_qm, stride_qk),
|
| 954 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 955 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 956 |
+
order=(1, 0),
|
| 957 |
+
)
|
| 958 |
+
DO_block_ptr = tl.make_block_ptr(
|
| 959 |
+
base=DO,
|
| 960 |
+
shape=(N_CTX, HEAD_DIM),
|
| 961 |
+
strides=(stride_dom, stride_dok),
|
| 962 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 963 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 964 |
+
order=(1, 0),
|
| 965 |
+
)
|
| 966 |
+
DQ_block_ptr = tl.make_block_ptr(
|
| 967 |
+
base=DQ,
|
| 968 |
+
shape=(N_CTX, HEAD_DIM),
|
| 969 |
+
strides=(stride_dqm, stride_dqk),
|
| 970 |
+
offsets=(start_m * BLOCK_M, 0),
|
| 971 |
+
block_shape=(BLOCK_M, HEAD_DIM),
|
| 972 |
+
order=(1, 0),
|
| 973 |
+
)
|
| 974 |
+
|
| 975 |
+
q = tl.load(Q_block_ptr)
|
| 976 |
+
do = tl.load(DO_block_ptr)
|
| 977 |
+
|
| 978 |
+
dq = tl.zeros([BLOCK_M, HEAD_DIM], dtype=tl.float32)
|
| 979 |
+
|
| 980 |
+
start_m = start_m * BLOCK_M
|
| 981 |
+
|
| 982 |
+
offs_m = start_m + tl.arange(0, BLOCK_M) * stride_mm
|
| 983 |
+
offs_d = start_m + tl.arange(0, BLOCK_M) * stride_dm
|
| 984 |
+
|
| 985 |
+
m = tl.load(M + offs_m)
|
| 986 |
+
m = m[:, None]
|
| 987 |
+
|
| 988 |
+
# D (= delta) is pre-divided by ds_scale.
|
| 989 |
+
d = tl.load(D + offs_d)
|
| 990 |
+
d = d[:, None]
|
| 991 |
+
|
| 992 |
+
block_indices_m = block_indices + (start_m // BLOCK_M) * stride_bm
|
| 993 |
+
block_indices_lens_m = block_indices_lens + (start_m // BLOCK_M) * stride_lm
|
| 994 |
+
|
| 995 |
+
dq = _attn_bwd_dq_bsa_varlen_align(
|
| 996 |
+
dq,
|
| 997 |
+
q, do,
|
| 998 |
+
m, d,
|
| 999 |
+
K, V,
|
| 1000 |
+
N_CTX,
|
| 1001 |
+
BLOCK_N_DQ_LG,
|
| 1002 |
+
HEAD_DIM,
|
| 1003 |
+
block_indices_m,
|
| 1004 |
+
block_indices_lens_m,
|
| 1005 |
+
stride_bn,
|
| 1006 |
+
stride_kn, stride_kk,
|
| 1007 |
+
stride_vn, stride_vk,
|
| 1008 |
+
kv_valid_mask,
|
| 1009 |
+
HAS_KV_MASK,
|
| 1010 |
+
)
|
| 1011 |
+
|
| 1012 |
+
# Write back dQ.
|
| 1013 |
+
dq *= LN2
|
| 1014 |
+
tl.store(DQ_block_ptr, dq.to(q.dtype))
|
hymm/models/modules/dac_vae.py
ADDED
|
@@ -0,0 +1,1103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import math
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import List, Union
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import tqdm
|
| 10 |
+
from audiotools import AudioSignal
|
| 11 |
+
from audiotools.ml import BaseModel
|
| 12 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 13 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 14 |
+
from diffusers.utils.accelerate_utils import apply_forward_hook
|
| 15 |
+
from torch import nn
|
| 16 |
+
from torch.nn.utils import weight_norm
|
| 17 |
+
|
| 18 |
+
SUPPORTED_VERSIONS = ["1.0.0"]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class DACFile:
|
| 23 |
+
codes: torch.Tensor
|
| 24 |
+
|
| 25 |
+
# Metadata
|
| 26 |
+
chunk_length: int
|
| 27 |
+
original_length: int
|
| 28 |
+
input_db: float
|
| 29 |
+
channels: int
|
| 30 |
+
sample_rate: int
|
| 31 |
+
padding: bool
|
| 32 |
+
dac_version: str
|
| 33 |
+
|
| 34 |
+
def save(self, path):
|
| 35 |
+
artifacts = {
|
| 36 |
+
"codes": self.codes.numpy().astype(np.uint16),
|
| 37 |
+
"metadata": {
|
| 38 |
+
"input_db": self.input_db.numpy().astype(np.float32),
|
| 39 |
+
"original_length": self.original_length,
|
| 40 |
+
"sample_rate": self.sample_rate,
|
| 41 |
+
"chunk_length": self.chunk_length,
|
| 42 |
+
"channels": self.channels,
|
| 43 |
+
"padding": self.padding,
|
| 44 |
+
"dac_version": SUPPORTED_VERSIONS[-1],
|
| 45 |
+
},
|
| 46 |
+
}
|
| 47 |
+
path = Path(path).with_suffix(".dac")
|
| 48 |
+
with open(path, "wb") as f:
|
| 49 |
+
np.save(f, artifacts)
|
| 50 |
+
return path
|
| 51 |
+
|
| 52 |
+
@classmethod
|
| 53 |
+
def load(cls, path):
|
| 54 |
+
artifacts = np.load(path, allow_pickle=True)[()]
|
| 55 |
+
codes = torch.from_numpy(artifacts["codes"].astype(int))
|
| 56 |
+
if artifacts["metadata"].get("dac_version", None) not in SUPPORTED_VERSIONS:
|
| 57 |
+
raise RuntimeError(
|
| 58 |
+
f"Given file {path} can't be loaded with this version of descript-audio-codec."
|
| 59 |
+
)
|
| 60 |
+
return cls(codes=codes, **artifacts["metadata"])
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class CodecMixin:
|
| 64 |
+
@property
|
| 65 |
+
def padding(self):
|
| 66 |
+
if not hasattr(self, "_padding"):
|
| 67 |
+
self._padding = True
|
| 68 |
+
return self._padding
|
| 69 |
+
|
| 70 |
+
@padding.setter
|
| 71 |
+
def padding(self, value):
|
| 72 |
+
assert isinstance(value, bool)
|
| 73 |
+
|
| 74 |
+
layers = [
|
| 75 |
+
l for l in self.modules() if isinstance(l, (nn.Conv1d, nn.ConvTranspose1d))
|
| 76 |
+
]
|
| 77 |
+
|
| 78 |
+
for layer in layers:
|
| 79 |
+
if value:
|
| 80 |
+
if hasattr(layer, "original_padding"):
|
| 81 |
+
layer.padding = layer.original_padding
|
| 82 |
+
else:
|
| 83 |
+
layer.original_padding = layer.padding
|
| 84 |
+
layer.padding = tuple(0 for _ in range(len(layer.padding)))
|
| 85 |
+
|
| 86 |
+
self._padding = value
|
| 87 |
+
|
| 88 |
+
def get_delay(self):
|
| 89 |
+
# Any number works here, delay is invariant to input length
|
| 90 |
+
l_out = self.get_output_length(0)
|
| 91 |
+
L = l_out
|
| 92 |
+
|
| 93 |
+
layers = []
|
| 94 |
+
for layer in self.modules():
|
| 95 |
+
if isinstance(layer, (nn.Conv1d, nn.ConvTranspose1d)):
|
| 96 |
+
layers.append(layer)
|
| 97 |
+
|
| 98 |
+
for layer in reversed(layers):
|
| 99 |
+
d = layer.dilation[0]
|
| 100 |
+
k = layer.kernel_size[0]
|
| 101 |
+
s = layer.stride[0]
|
| 102 |
+
|
| 103 |
+
if isinstance(layer, nn.ConvTranspose1d):
|
| 104 |
+
L = ((L - d * (k - 1) - 1) / s) + 1
|
| 105 |
+
elif isinstance(layer, nn.Conv1d):
|
| 106 |
+
L = (L - 1) * s + d * (k - 1) + 1
|
| 107 |
+
|
| 108 |
+
L = math.ceil(L)
|
| 109 |
+
|
| 110 |
+
l_in = L
|
| 111 |
+
|
| 112 |
+
return (l_in - l_out) // 2
|
| 113 |
+
|
| 114 |
+
def get_output_length(self, input_length):
|
| 115 |
+
L = input_length
|
| 116 |
+
# Calculate output length
|
| 117 |
+
for layer in self.modules():
|
| 118 |
+
if isinstance(layer, (nn.Conv1d, nn.ConvTranspose1d)):
|
| 119 |
+
d = layer.dilation[0]
|
| 120 |
+
k = layer.kernel_size[0]
|
| 121 |
+
s = layer.stride[0]
|
| 122 |
+
|
| 123 |
+
if isinstance(layer, nn.Conv1d):
|
| 124 |
+
L = ((L - d * (k - 1) - 1) / s) + 1
|
| 125 |
+
elif isinstance(layer, nn.ConvTranspose1d):
|
| 126 |
+
L = (L - 1) * s + d * (k - 1) + 1
|
| 127 |
+
|
| 128 |
+
L = math.floor(L)
|
| 129 |
+
return L
|
| 130 |
+
|
| 131 |
+
@torch.no_grad()
|
| 132 |
+
def compress(
|
| 133 |
+
self,
|
| 134 |
+
audio_path_or_signal: Union[str, Path, AudioSignal],
|
| 135 |
+
win_duration: float = 1.0,
|
| 136 |
+
verbose: bool = False,
|
| 137 |
+
normalize_db: float = -16,
|
| 138 |
+
n_quantizers: int = None,
|
| 139 |
+
) -> DACFile:
|
| 140 |
+
"""Processes an audio signal from a file or AudioSignal object into
|
| 141 |
+
discrete codes. This function processes the signal in short windows,
|
| 142 |
+
using constant GPU memory.
|
| 143 |
+
|
| 144 |
+
Parameters
|
| 145 |
+
----------
|
| 146 |
+
audio_path_or_signal : Union[str, Path, AudioSignal]
|
| 147 |
+
audio signal to reconstruct
|
| 148 |
+
win_duration : float, optional
|
| 149 |
+
window duration in seconds, by default 5.0
|
| 150 |
+
verbose : bool, optional
|
| 151 |
+
by default False
|
| 152 |
+
normalize_db : float, optional
|
| 153 |
+
normalize db, by default -16
|
| 154 |
+
|
| 155 |
+
Returns
|
| 156 |
+
-------
|
| 157 |
+
DACFile
|
| 158 |
+
Object containing compressed codes and metadata
|
| 159 |
+
required for decompression
|
| 160 |
+
"""
|
| 161 |
+
audio_signal = audio_path_or_signal
|
| 162 |
+
if isinstance(audio_signal, (str, Path)):
|
| 163 |
+
audio_signal = AudioSignal.load_from_file_with_ffmpeg(str(audio_signal))
|
| 164 |
+
|
| 165 |
+
self.eval()
|
| 166 |
+
original_padding = self.padding
|
| 167 |
+
original_device = audio_signal.device
|
| 168 |
+
|
| 169 |
+
audio_signal = audio_signal.clone()
|
| 170 |
+
audio_signal = audio_signal.to_mono()
|
| 171 |
+
original_sr = audio_signal.sample_rate
|
| 172 |
+
|
| 173 |
+
resample_fn = audio_signal.resample
|
| 174 |
+
loudness_fn = audio_signal.loudness
|
| 175 |
+
|
| 176 |
+
# If audio is > 10 minutes long, use the ffmpeg versions
|
| 177 |
+
if audio_signal.signal_duration >= 10 * 60 * 60:
|
| 178 |
+
resample_fn = audio_signal.ffmpeg_resample
|
| 179 |
+
loudness_fn = audio_signal.ffmpeg_loudness
|
| 180 |
+
|
| 181 |
+
original_length = audio_signal.signal_length
|
| 182 |
+
resample_fn(self.sample_rate)
|
| 183 |
+
input_db = loudness_fn()
|
| 184 |
+
|
| 185 |
+
if normalize_db is not None:
|
| 186 |
+
audio_signal.normalize(normalize_db)
|
| 187 |
+
audio_signal.ensure_max_of_audio()
|
| 188 |
+
|
| 189 |
+
nb, nac, nt = audio_signal.audio_data.shape
|
| 190 |
+
audio_signal.audio_data = audio_signal.audio_data.reshape(nb * nac, 1, nt)
|
| 191 |
+
win_duration = (
|
| 192 |
+
audio_signal.signal_duration if win_duration is None else win_duration
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
if audio_signal.signal_duration <= win_duration:
|
| 196 |
+
# Unchunked compression (used if signal length < win duration)
|
| 197 |
+
self.padding = True
|
| 198 |
+
n_samples = nt
|
| 199 |
+
hop = nt
|
| 200 |
+
else:
|
| 201 |
+
# Chunked inference
|
| 202 |
+
self.padding = False
|
| 203 |
+
# Zero-pad signal on either side by the delay
|
| 204 |
+
audio_signal.zero_pad(self.delay, self.delay)
|
| 205 |
+
n_samples = int(win_duration * self.sample_rate)
|
| 206 |
+
# Round n_samples to nearest hop length multiple
|
| 207 |
+
n_samples = int(math.ceil(n_samples / self.hop_length) * self.hop_length)
|
| 208 |
+
hop = self.get_output_length(n_samples)
|
| 209 |
+
|
| 210 |
+
codes = []
|
| 211 |
+
range_fn = range if not verbose else tqdm.trange
|
| 212 |
+
|
| 213 |
+
for i in range_fn(0, nt, hop):
|
| 214 |
+
x = audio_signal[..., i : i + n_samples]
|
| 215 |
+
x = x.zero_pad(0, max(0, n_samples - x.shape[-1]))
|
| 216 |
+
|
| 217 |
+
audio_data = x.audio_data.to(self.device)
|
| 218 |
+
audio_data = self.preprocess(audio_data, self.sample_rate)
|
| 219 |
+
_, c, _, _, _ = self.encode(audio_data, n_quantizers)
|
| 220 |
+
codes.append(c.to(original_device))
|
| 221 |
+
chunk_length = c.shape[-1]
|
| 222 |
+
|
| 223 |
+
codes = torch.cat(codes, dim=-1)
|
| 224 |
+
|
| 225 |
+
dac_file = DACFile(
|
| 226 |
+
codes=codes,
|
| 227 |
+
chunk_length=chunk_length,
|
| 228 |
+
original_length=original_length,
|
| 229 |
+
input_db=input_db,
|
| 230 |
+
channels=nac,
|
| 231 |
+
sample_rate=original_sr,
|
| 232 |
+
padding=self.padding,
|
| 233 |
+
dac_version=SUPPORTED_VERSIONS[-1],
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
if n_quantizers is not None:
|
| 237 |
+
codes = codes[:, :n_quantizers, :]
|
| 238 |
+
|
| 239 |
+
self.padding = original_padding
|
| 240 |
+
return dac_file
|
| 241 |
+
|
| 242 |
+
@torch.no_grad()
|
| 243 |
+
def decompress(
|
| 244 |
+
self,
|
| 245 |
+
obj: Union[str, Path, DACFile],
|
| 246 |
+
verbose: bool = False,
|
| 247 |
+
) -> AudioSignal:
|
| 248 |
+
"""Reconstruct audio from a given .dac file
|
| 249 |
+
|
| 250 |
+
Parameters
|
| 251 |
+
----------
|
| 252 |
+
obj : Union[str, Path, DACFile]
|
| 253 |
+
.dac file location or corresponding DACFile object.
|
| 254 |
+
verbose : bool, optional
|
| 255 |
+
Prints progress if True, by default False
|
| 256 |
+
|
| 257 |
+
Returns
|
| 258 |
+
-------
|
| 259 |
+
AudioSignal
|
| 260 |
+
Object with the reconstructed audio
|
| 261 |
+
"""
|
| 262 |
+
self.eval()
|
| 263 |
+
if isinstance(obj, (str, Path)):
|
| 264 |
+
obj = DACFile.load(obj)
|
| 265 |
+
|
| 266 |
+
original_padding = self.padding
|
| 267 |
+
self.padding = obj.padding
|
| 268 |
+
|
| 269 |
+
range_fn = range if not verbose else tqdm.trange
|
| 270 |
+
codes = obj.codes
|
| 271 |
+
original_device = codes.device
|
| 272 |
+
chunk_length = obj.chunk_length
|
| 273 |
+
recons = []
|
| 274 |
+
|
| 275 |
+
for i in range_fn(0, codes.shape[-1], chunk_length):
|
| 276 |
+
c = codes[..., i : i + chunk_length].to(self.device)
|
| 277 |
+
z = self.quantizer.from_codes(c)[0]
|
| 278 |
+
r = self.decode(z)
|
| 279 |
+
recons.append(r.to(original_device))
|
| 280 |
+
|
| 281 |
+
recons = torch.cat(recons, dim=-1)
|
| 282 |
+
recons = AudioSignal(recons, self.sample_rate)
|
| 283 |
+
|
| 284 |
+
resample_fn = recons.resample
|
| 285 |
+
loudness_fn = recons.loudness
|
| 286 |
+
|
| 287 |
+
# If audio is > 10 minutes long, use the ffmpeg versions
|
| 288 |
+
if recons.signal_duration >= 10 * 60 * 60:
|
| 289 |
+
resample_fn = recons.ffmpeg_resample
|
| 290 |
+
loudness_fn = recons.ffmpeg_loudness
|
| 291 |
+
|
| 292 |
+
if obj.input_db is not None:
|
| 293 |
+
recons.normalize(obj.input_db)
|
| 294 |
+
|
| 295 |
+
resample_fn(obj.sample_rate)
|
| 296 |
+
|
| 297 |
+
if obj.original_length is not None:
|
| 298 |
+
recons = recons[..., : obj.original_length]
|
| 299 |
+
loudness_fn()
|
| 300 |
+
recons.audio_data = recons.audio_data.reshape(
|
| 301 |
+
-1, obj.channels, obj.original_length
|
| 302 |
+
)
|
| 303 |
+
else:
|
| 304 |
+
loudness_fn()
|
| 305 |
+
|
| 306 |
+
self.padding = original_padding
|
| 307 |
+
return recons
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def WNConv1d(*args, **kwargs):
|
| 311 |
+
return weight_norm(nn.Conv1d(*args, **kwargs))
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def WNConvTranspose1d(*args, **kwargs):
|
| 315 |
+
return weight_norm(nn.ConvTranspose1d(*args, **kwargs))
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
# Scripting this brings model speed up 1.4x
|
| 319 |
+
@torch.jit.script
|
| 320 |
+
def snake(x, alpha):
|
| 321 |
+
shape = x.shape
|
| 322 |
+
x = x.reshape(shape[0], shape[1], -1)
|
| 323 |
+
x = x + (alpha + 1e-9).reciprocal() * torch.sin(alpha * x).pow(2)
|
| 324 |
+
x = x.reshape(shape)
|
| 325 |
+
return x
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
class Snake1d(nn.Module):
|
| 329 |
+
def __init__(self, channels):
|
| 330 |
+
super().__init__()
|
| 331 |
+
self.alpha = nn.Parameter(torch.ones(1, channels, 1))
|
| 332 |
+
|
| 333 |
+
def forward(self, x):
|
| 334 |
+
return snake(x, self.alpha)
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
import torch.nn.functional as F
|
| 338 |
+
from einops import rearrange
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
class VectorQuantize(nn.Module):
|
| 342 |
+
"""
|
| 343 |
+
Implementation of VQ similar to Karpathy's repo:
|
| 344 |
+
https://github.com/karpathy/deep-vector-quantization
|
| 345 |
+
Additionally uses following tricks from Improved VQGAN
|
| 346 |
+
(https://arxiv.org/pdf/2110.04627.pdf):
|
| 347 |
+
1. Factorized codes: Perform nearest neighbor lookup in low-dimensional space
|
| 348 |
+
for improved codebook usage
|
| 349 |
+
2. l2-normalized codes: Converts euclidean distance to cosine similarity which
|
| 350 |
+
improves training stability
|
| 351 |
+
"""
|
| 352 |
+
|
| 353 |
+
def __init__(self, input_dim: int, codebook_size: int, codebook_dim: int):
|
| 354 |
+
super().__init__()
|
| 355 |
+
self.codebook_size = codebook_size
|
| 356 |
+
self.codebook_dim = codebook_dim
|
| 357 |
+
|
| 358 |
+
self.in_proj = WNConv1d(input_dim, codebook_dim, kernel_size=1)
|
| 359 |
+
self.out_proj = WNConv1d(codebook_dim, input_dim, kernel_size=1)
|
| 360 |
+
self.codebook = nn.Embedding(codebook_size, codebook_dim)
|
| 361 |
+
|
| 362 |
+
def forward(self, z):
|
| 363 |
+
"""Quantized the input tensor using a fixed codebook and returns
|
| 364 |
+
the corresponding codebook vectors
|
| 365 |
+
|
| 366 |
+
Parameters
|
| 367 |
+
----------
|
| 368 |
+
z : Tensor[B x D x T]
|
| 369 |
+
|
| 370 |
+
Returns
|
| 371 |
+
-------
|
| 372 |
+
Tensor[B x D x T]
|
| 373 |
+
Quantized continuous representation of input
|
| 374 |
+
Tensor[1]
|
| 375 |
+
Commitment loss to train encoder to predict vectors closer to codebook
|
| 376 |
+
entries
|
| 377 |
+
Tensor[1]
|
| 378 |
+
Codebook loss to update the codebook
|
| 379 |
+
Tensor[B x T]
|
| 380 |
+
Codebook indices (quantized discrete representation of input)
|
| 381 |
+
Tensor[B x D x T]
|
| 382 |
+
Projected latents (continuous representation of input before quantization)
|
| 383 |
+
"""
|
| 384 |
+
|
| 385 |
+
# Factorized codes (ViT-VQGAN) Project input into low-dimensional space
|
| 386 |
+
z_e = self.in_proj(z) # z_e : (B x D x T)
|
| 387 |
+
z_q, indices = self.decode_latents(z_e)
|
| 388 |
+
|
| 389 |
+
commitment_loss = F.mse_loss(z_e, z_q.detach(), reduction="none").mean([1, 2])
|
| 390 |
+
codebook_loss = F.mse_loss(z_q, z_e.detach(), reduction="none").mean([1, 2])
|
| 391 |
+
|
| 392 |
+
z_q = (
|
| 393 |
+
z_e + (z_q - z_e).detach()
|
| 394 |
+
) # noop in forward pass, straight-through gradient estimator in backward pass
|
| 395 |
+
|
| 396 |
+
z_q = self.out_proj(z_q)
|
| 397 |
+
|
| 398 |
+
return z_q, commitment_loss, codebook_loss, indices, z_e
|
| 399 |
+
|
| 400 |
+
def embed_code(self, embed_id):
|
| 401 |
+
return F.embedding(embed_id, self.codebook.weight)
|
| 402 |
+
|
| 403 |
+
def decode_code(self, embed_id):
|
| 404 |
+
return self.embed_code(embed_id).transpose(1, 2)
|
| 405 |
+
|
| 406 |
+
def decode_latents(self, latents):
|
| 407 |
+
encodings = rearrange(latents, "b d t -> (b t) d")
|
| 408 |
+
codebook = self.codebook.weight # codebook: (N x D)
|
| 409 |
+
|
| 410 |
+
# L2 normalize encodings and codebook (ViT-VQGAN)
|
| 411 |
+
encodings = F.normalize(encodings)
|
| 412 |
+
codebook = F.normalize(codebook)
|
| 413 |
+
|
| 414 |
+
# Compute euclidean distance with codebook
|
| 415 |
+
dist = (
|
| 416 |
+
encodings.pow(2).sum(1, keepdim=True)
|
| 417 |
+
- 2 * encodings @ codebook.t()
|
| 418 |
+
+ codebook.pow(2).sum(1, keepdim=True).t()
|
| 419 |
+
)
|
| 420 |
+
indices = rearrange((-dist).max(1)[1], "(b t) -> b t", b=latents.size(0))
|
| 421 |
+
z_q = self.decode_code(indices)
|
| 422 |
+
return z_q, indices
|
| 423 |
+
|
| 424 |
+
|
| 425 |
+
class ResidualVectorQuantize(nn.Module):
|
| 426 |
+
"""
|
| 427 |
+
Introduced in SoundStream: An end2end neural audio codec
|
| 428 |
+
https://arxiv.org/abs/2107.03312
|
| 429 |
+
"""
|
| 430 |
+
|
| 431 |
+
def __init__(
|
| 432 |
+
self,
|
| 433 |
+
input_dim: int = 512,
|
| 434 |
+
n_codebooks: int = 9,
|
| 435 |
+
codebook_size: int = 1024,
|
| 436 |
+
codebook_dim: Union[int, list] = 8,
|
| 437 |
+
quantizer_dropout: float = 0.0,
|
| 438 |
+
):
|
| 439 |
+
super().__init__()
|
| 440 |
+
if isinstance(codebook_dim, int):
|
| 441 |
+
codebook_dim = [codebook_dim for _ in range(n_codebooks)]
|
| 442 |
+
|
| 443 |
+
self.n_codebooks = n_codebooks
|
| 444 |
+
self.codebook_dim = codebook_dim
|
| 445 |
+
self.codebook_size = codebook_size
|
| 446 |
+
|
| 447 |
+
self.quantizers = nn.ModuleList(
|
| 448 |
+
[
|
| 449 |
+
VectorQuantize(input_dim, codebook_size, codebook_dim[i])
|
| 450 |
+
for i in range(n_codebooks)
|
| 451 |
+
]
|
| 452 |
+
)
|
| 453 |
+
self.quantizer_dropout = quantizer_dropout
|
| 454 |
+
|
| 455 |
+
def forward(self, z, n_quantizers: int = None):
|
| 456 |
+
"""Quantized the input tensor using a fixed set of `n` codebooks and returns
|
| 457 |
+
the corresponding codebook vectors
|
| 458 |
+
Parameters
|
| 459 |
+
----------
|
| 460 |
+
z : Tensor[B x D x T]
|
| 461 |
+
n_quantizers : int, optional
|
| 462 |
+
No. of quantizers to use
|
| 463 |
+
(n_quantizers < self.n_codebooks ex: for quantizer dropout)
|
| 464 |
+
Note: if `self.quantizer_dropout` is True, this argument is ignored
|
| 465 |
+
when in training mode, and a random number of quantizers is used.
|
| 466 |
+
Returns
|
| 467 |
+
-------
|
| 468 |
+
dict
|
| 469 |
+
A dictionary with the following keys:
|
| 470 |
+
|
| 471 |
+
"z" : Tensor[B x D x T]
|
| 472 |
+
Quantized continuous representation of input
|
| 473 |
+
"codes" : Tensor[B x N x T]
|
| 474 |
+
Codebook indices for each codebook
|
| 475 |
+
(quantized discrete representation of input)
|
| 476 |
+
"latents" : Tensor[B x N*D x T]
|
| 477 |
+
Projected latents (continuous representation of input before quantization)
|
| 478 |
+
"vq/commitment_loss" : Tensor[1]
|
| 479 |
+
Commitment loss to train encoder to predict vectors closer to codebook
|
| 480 |
+
entries
|
| 481 |
+
"vq/codebook_loss" : Tensor[1]
|
| 482 |
+
Codebook loss to update the codebook
|
| 483 |
+
"""
|
| 484 |
+
z_q = 0
|
| 485 |
+
residual = z
|
| 486 |
+
commitment_loss = 0
|
| 487 |
+
codebook_loss = 0
|
| 488 |
+
|
| 489 |
+
codebook_indices = []
|
| 490 |
+
latents = []
|
| 491 |
+
|
| 492 |
+
if n_quantizers is None:
|
| 493 |
+
n_quantizers = self.n_codebooks
|
| 494 |
+
if self.training:
|
| 495 |
+
n_quantizers = torch.ones((z.shape[0],)) * self.n_codebooks + 1
|
| 496 |
+
dropout = torch.randint(1, self.n_codebooks + 1, (z.shape[0],))
|
| 497 |
+
n_dropout = int(z.shape[0] * self.quantizer_dropout)
|
| 498 |
+
n_quantizers[:n_dropout] = dropout[:n_dropout]
|
| 499 |
+
n_quantizers = n_quantizers.to(z.device)
|
| 500 |
+
|
| 501 |
+
for i, quantizer in enumerate(self.quantizers):
|
| 502 |
+
if self.training is False and i >= n_quantizers:
|
| 503 |
+
break
|
| 504 |
+
|
| 505 |
+
z_q_i, commitment_loss_i, codebook_loss_i, indices_i, z_e_i = quantizer(
|
| 506 |
+
residual
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
# Create mask to apply quantizer dropout
|
| 510 |
+
mask = (
|
| 511 |
+
torch.full((z.shape[0],), fill_value=i, device=z.device) < n_quantizers
|
| 512 |
+
)
|
| 513 |
+
z_q = z_q + z_q_i * mask[:, None, None]
|
| 514 |
+
residual = residual - z_q_i
|
| 515 |
+
|
| 516 |
+
# Sum losses
|
| 517 |
+
commitment_loss += (commitment_loss_i * mask).mean()
|
| 518 |
+
codebook_loss += (codebook_loss_i * mask).mean()
|
| 519 |
+
|
| 520 |
+
codebook_indices.append(indices_i)
|
| 521 |
+
latents.append(z_e_i)
|
| 522 |
+
|
| 523 |
+
codes = torch.stack(codebook_indices, dim=1)
|
| 524 |
+
latents = torch.cat(latents, dim=1)
|
| 525 |
+
|
| 526 |
+
return z_q, codes, latents, commitment_loss, codebook_loss
|
| 527 |
+
|
| 528 |
+
def from_codes(self, codes: torch.Tensor):
|
| 529 |
+
"""Given the quantized codes, reconstruct the continuous representation
|
| 530 |
+
Parameters
|
| 531 |
+
----------
|
| 532 |
+
codes : Tensor[B x N x T]
|
| 533 |
+
Quantized discrete representation of input
|
| 534 |
+
Returns
|
| 535 |
+
-------
|
| 536 |
+
Tensor[B x D x T]
|
| 537 |
+
Quantized continuous representation of input
|
| 538 |
+
"""
|
| 539 |
+
z_q = 0.0
|
| 540 |
+
z_p = []
|
| 541 |
+
n_codebooks = codes.shape[1]
|
| 542 |
+
for i in range(n_codebooks):
|
| 543 |
+
z_p_i = self.quantizers[i].decode_code(codes[:, i, :])
|
| 544 |
+
z_p.append(z_p_i)
|
| 545 |
+
|
| 546 |
+
z_q_i = self.quantizers[i].out_proj(z_p_i)
|
| 547 |
+
z_q = z_q + z_q_i
|
| 548 |
+
return z_q, torch.cat(z_p, dim=1), codes
|
| 549 |
+
|
| 550 |
+
def from_latents(self, latents: torch.Tensor):
|
| 551 |
+
"""Given the unquantized latents, reconstruct the
|
| 552 |
+
continuous representation after quantization.
|
| 553 |
+
|
| 554 |
+
Parameters
|
| 555 |
+
----------
|
| 556 |
+
latents : Tensor[B x N x T]
|
| 557 |
+
Continuous representation of input after projection
|
| 558 |
+
|
| 559 |
+
Returns
|
| 560 |
+
-------
|
| 561 |
+
Tensor[B x D x T]
|
| 562 |
+
Quantized representation of full-projected space
|
| 563 |
+
Tensor[B x D x T]
|
| 564 |
+
Quantized representation of latent space
|
| 565 |
+
"""
|
| 566 |
+
z_q = 0
|
| 567 |
+
z_p = []
|
| 568 |
+
codes = []
|
| 569 |
+
dims = np.cumsum([0] + [q.codebook_dim for q in self.quantizers])
|
| 570 |
+
|
| 571 |
+
n_codebooks = np.where(dims <= latents.shape[1])[0].max(axis=0, keepdims=True)[
|
| 572 |
+
0
|
| 573 |
+
]
|
| 574 |
+
for i in range(n_codebooks):
|
| 575 |
+
j, k = dims[i], dims[i + 1]
|
| 576 |
+
z_p_i, codes_i = self.quantizers[i].decode_latents(latents[:, j:k, :])
|
| 577 |
+
z_p.append(z_p_i)
|
| 578 |
+
codes.append(codes_i)
|
| 579 |
+
|
| 580 |
+
z_q_i = self.quantizers[i].out_proj(z_p_i)
|
| 581 |
+
z_q = z_q + z_q_i
|
| 582 |
+
|
| 583 |
+
return z_q, torch.cat(z_p, dim=1), torch.stack(codes, dim=1)
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
class AbstractDistribution:
|
| 587 |
+
def sample(self):
|
| 588 |
+
raise NotImplementedError()
|
| 589 |
+
|
| 590 |
+
def mode(self):
|
| 591 |
+
raise NotImplementedError()
|
| 592 |
+
|
| 593 |
+
|
| 594 |
+
class DiracDistribution(AbstractDistribution):
|
| 595 |
+
def __init__(self, value):
|
| 596 |
+
self.value = value
|
| 597 |
+
|
| 598 |
+
def sample(self):
|
| 599 |
+
return self.value
|
| 600 |
+
|
| 601 |
+
def mode(self):
|
| 602 |
+
return self.value
|
| 603 |
+
|
| 604 |
+
|
| 605 |
+
class DiagonalGaussianDistribution(object):
|
| 606 |
+
def __init__(self, parameters, deterministic=False):
|
| 607 |
+
self.parameters = parameters
|
| 608 |
+
self.mean, self.logvar = torch.chunk(parameters, 2, dim=1)
|
| 609 |
+
self.logvar = torch.clamp(self.logvar, -30.0, 20.0)
|
| 610 |
+
self.deterministic = deterministic
|
| 611 |
+
self.std = torch.exp(0.5 * self.logvar)
|
| 612 |
+
self.var = torch.exp(self.logvar)
|
| 613 |
+
if self.deterministic:
|
| 614 |
+
self.var = self.std = torch.zeros_like(self.mean).to(device=self.parameters.device)
|
| 615 |
+
|
| 616 |
+
def sample(self):
|
| 617 |
+
x = self.mean + self.std * torch.randn(self.mean.shape).to(device=self.parameters.device)
|
| 618 |
+
return x
|
| 619 |
+
|
| 620 |
+
def kl(self, other=None):
|
| 621 |
+
if self.deterministic:
|
| 622 |
+
return torch.Tensor([0.0])
|
| 623 |
+
else:
|
| 624 |
+
if other is None:
|
| 625 |
+
return 0.5 * torch.mean(
|
| 626 |
+
torch.pow(self.mean, 2) + self.var - 1.0 - self.logvar,
|
| 627 |
+
dim=[1, 2],
|
| 628 |
+
)
|
| 629 |
+
else:
|
| 630 |
+
return 0.5 * torch.mean(
|
| 631 |
+
torch.pow(self.mean - other.mean, 2) / other.var
|
| 632 |
+
+ self.var / other.var
|
| 633 |
+
- 1.0
|
| 634 |
+
- self.logvar
|
| 635 |
+
+ other.logvar,
|
| 636 |
+
dim=[1, 2],
|
| 637 |
+
)
|
| 638 |
+
|
| 639 |
+
def nll(self, sample, dims=[1, 2]):
|
| 640 |
+
if self.deterministic:
|
| 641 |
+
return torch.Tensor([0.0])
|
| 642 |
+
logtwopi = np.log(2.0 * np.pi)
|
| 643 |
+
return 0.5 * torch.sum(
|
| 644 |
+
logtwopi + self.logvar + torch.pow(sample - self.mean, 2) / self.var,
|
| 645 |
+
dim=dims,
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
def mode(self):
|
| 649 |
+
return self.mean
|
| 650 |
+
|
| 651 |
+
|
| 652 |
+
def normal_kl(mean1, logvar1, mean2, logvar2):
|
| 653 |
+
"""
|
| 654 |
+
source: https://github.com/openai/guided-diffusion/blob/27c20a8fab9cb472df5d6bdd6c8d11c8f430b924/guided_diffusion/losses.py#L12
|
| 655 |
+
Compute the KL divergence between two gaussians.
|
| 656 |
+
Shapes are automatically broadcasted, so batches can be compared to
|
| 657 |
+
scalars, among other use cases.
|
| 658 |
+
"""
|
| 659 |
+
tensor = None
|
| 660 |
+
for obj in (mean1, logvar1, mean2, logvar2):
|
| 661 |
+
if isinstance(obj, torch.Tensor):
|
| 662 |
+
tensor = obj
|
| 663 |
+
break
|
| 664 |
+
assert tensor is not None, "at least one argument must be a Tensor"
|
| 665 |
+
|
| 666 |
+
# Force variances to be Tensors. Broadcasting helps convert scalars to
|
| 667 |
+
# Tensors, but it does not work for torch.exp().
|
| 668 |
+
logvar1, logvar2 = [x if isinstance(x, torch.Tensor) else torch.tensor(x).to(tensor) for x in (logvar1, logvar2)]
|
| 669 |
+
|
| 670 |
+
return 0.5 * (
|
| 671 |
+
-1.0 + logvar2 - logvar1 + torch.exp(logvar1 - logvar2) + ((mean1 - mean2) ** 2) * torch.exp(-logvar2)
|
| 672 |
+
)
|
| 673 |
+
|
| 674 |
+
|
| 675 |
+
def init_weights(m):
|
| 676 |
+
if isinstance(m, nn.Conv1d):
|
| 677 |
+
nn.init.trunc_normal_(m.weight, std=0.02)
|
| 678 |
+
nn.init.constant_(m.bias, 0)
|
| 679 |
+
|
| 680 |
+
|
| 681 |
+
class ResidualUnit(nn.Module):
|
| 682 |
+
def __init__(self, dim: int = 16, dilation: int = 1):
|
| 683 |
+
super().__init__()
|
| 684 |
+
pad = ((7 - 1) * dilation) // 2
|
| 685 |
+
self.block = nn.Sequential(
|
| 686 |
+
Snake1d(dim),
|
| 687 |
+
WNConv1d(dim, dim, kernel_size=7, dilation=dilation, padding=pad),
|
| 688 |
+
Snake1d(dim),
|
| 689 |
+
WNConv1d(dim, dim, kernel_size=1),
|
| 690 |
+
)
|
| 691 |
+
|
| 692 |
+
def forward(self, x):
|
| 693 |
+
y = self.block(x)
|
| 694 |
+
pad = (x.shape[-1] - y.shape[-1]) // 2
|
| 695 |
+
if pad > 0:
|
| 696 |
+
x = x[..., pad:-pad]
|
| 697 |
+
return x + y
|
| 698 |
+
|
| 699 |
+
|
| 700 |
+
class EncoderBlock(nn.Module):
|
| 701 |
+
def __init__(self, dim: int = 16, stride: int = 1):
|
| 702 |
+
super().__init__()
|
| 703 |
+
self.block = nn.Sequential(
|
| 704 |
+
ResidualUnit(dim // 2, dilation=1),
|
| 705 |
+
ResidualUnit(dim // 2, dilation=3),
|
| 706 |
+
ResidualUnit(dim // 2, dilation=9),
|
| 707 |
+
Snake1d(dim // 2),
|
| 708 |
+
WNConv1d(
|
| 709 |
+
dim // 2,
|
| 710 |
+
dim,
|
| 711 |
+
kernel_size=2 * stride,
|
| 712 |
+
stride=stride,
|
| 713 |
+
padding=math.ceil(stride / 2),
|
| 714 |
+
),
|
| 715 |
+
)
|
| 716 |
+
|
| 717 |
+
def forward(self, x):
|
| 718 |
+
return self.block(x)
|
| 719 |
+
|
| 720 |
+
|
| 721 |
+
class Encoder(nn.Module):
|
| 722 |
+
def __init__(
|
| 723 |
+
self,
|
| 724 |
+
d_model: int = 64,
|
| 725 |
+
strides: list = [2, 4, 8, 8],
|
| 726 |
+
d_latent: int = 64,
|
| 727 |
+
):
|
| 728 |
+
super().__init__()
|
| 729 |
+
# Create first convolution
|
| 730 |
+
self.block = [WNConv1d(1, d_model, kernel_size=7, padding=3)]
|
| 731 |
+
|
| 732 |
+
# Create EncoderBlocks that double channels as they downsample by `stride`
|
| 733 |
+
for stride in strides:
|
| 734 |
+
d_model *= 2
|
| 735 |
+
self.block += [EncoderBlock(d_model, stride=stride)]
|
| 736 |
+
|
| 737 |
+
# Create last convolution
|
| 738 |
+
self.block += [
|
| 739 |
+
Snake1d(d_model),
|
| 740 |
+
WNConv1d(d_model, d_latent, kernel_size=3, padding=1),
|
| 741 |
+
]
|
| 742 |
+
|
| 743 |
+
# Wrap black into nn.Sequential
|
| 744 |
+
self.block = nn.Sequential(*self.block)
|
| 745 |
+
self.enc_dim = d_model
|
| 746 |
+
|
| 747 |
+
def forward(self, x):
|
| 748 |
+
return self.block(x)
|
| 749 |
+
|
| 750 |
+
|
| 751 |
+
class DecoderBlock(nn.Module):
|
| 752 |
+
def __init__(self, input_dim: int = 16, output_dim: int = 8, stride: int = 1):
|
| 753 |
+
super().__init__()
|
| 754 |
+
self.block = nn.Sequential(
|
| 755 |
+
Snake1d(input_dim),
|
| 756 |
+
WNConvTranspose1d(
|
| 757 |
+
input_dim,
|
| 758 |
+
output_dim,
|
| 759 |
+
kernel_size=2 * stride,
|
| 760 |
+
stride=stride,
|
| 761 |
+
padding=math.ceil(stride / 2),
|
| 762 |
+
output_padding=stride % 2,
|
| 763 |
+
),
|
| 764 |
+
ResidualUnit(output_dim, dilation=1),
|
| 765 |
+
ResidualUnit(output_dim, dilation=3),
|
| 766 |
+
ResidualUnit(output_dim, dilation=9),
|
| 767 |
+
)
|
| 768 |
+
|
| 769 |
+
def forward(self, x):
|
| 770 |
+
return self.block(x)
|
| 771 |
+
|
| 772 |
+
|
| 773 |
+
class Decoder(nn.Module):
|
| 774 |
+
def __init__(
|
| 775 |
+
self,
|
| 776 |
+
input_channel,
|
| 777 |
+
channels,
|
| 778 |
+
rates,
|
| 779 |
+
d_out: int = 1,
|
| 780 |
+
):
|
| 781 |
+
super().__init__()
|
| 782 |
+
|
| 783 |
+
# Add first conv layer
|
| 784 |
+
layers = [WNConv1d(input_channel, channels, kernel_size=7, padding=3)]
|
| 785 |
+
|
| 786 |
+
# Add upsampling + MRF blocks
|
| 787 |
+
for i, stride in enumerate(rates):
|
| 788 |
+
input_dim = channels // 2**i
|
| 789 |
+
output_dim = channels // 2 ** (i + 1)
|
| 790 |
+
layers += [DecoderBlock(input_dim, output_dim, stride)]
|
| 791 |
+
|
| 792 |
+
# Add final conv layer
|
| 793 |
+
layers += [
|
| 794 |
+
Snake1d(output_dim),
|
| 795 |
+
WNConv1d(output_dim, d_out, kernel_size=7, padding=3),
|
| 796 |
+
nn.Tanh(),
|
| 797 |
+
]
|
| 798 |
+
|
| 799 |
+
self.model = nn.Sequential(*layers)
|
| 800 |
+
|
| 801 |
+
def forward(self, x):
|
| 802 |
+
return self.model(x)
|
| 803 |
+
|
| 804 |
+
|
| 805 |
+
class DAC(BaseModel, CodecMixin, ModelMixin, ConfigMixin):
|
| 806 |
+
|
| 807 |
+
@register_to_config
|
| 808 |
+
def __init__(
|
| 809 |
+
self,
|
| 810 |
+
encoder_dim: int = 64,
|
| 811 |
+
encoder_rates: List[int] = [2, 4, 8, 8],
|
| 812 |
+
latent_dim: int = None,
|
| 813 |
+
decoder_dim: int = 1536,
|
| 814 |
+
decoder_rates: List[int] = [8, 8, 4, 2],
|
| 815 |
+
n_codebooks: int = 9,
|
| 816 |
+
codebook_size: int = 1024,
|
| 817 |
+
codebook_dim: Union[int, list] = 8,
|
| 818 |
+
quantizer_dropout: bool = False,
|
| 819 |
+
sample_rate: int = 44100,
|
| 820 |
+
continuous: bool = False,
|
| 821 |
+
use_weight_norm: bool = True,
|
| 822 |
+
):
|
| 823 |
+
super().__init__()
|
| 824 |
+
|
| 825 |
+
self.encoder_dim = encoder_dim
|
| 826 |
+
self.encoder_rates = encoder_rates
|
| 827 |
+
self.decoder_dim = decoder_dim
|
| 828 |
+
self.decoder_rates = decoder_rates
|
| 829 |
+
self.sample_rate = sample_rate
|
| 830 |
+
self.continuous = continuous
|
| 831 |
+
self.use_weight_norm = use_weight_norm
|
| 832 |
+
|
| 833 |
+
if latent_dim is None:
|
| 834 |
+
latent_dim = encoder_dim * (2 ** len(encoder_rates))
|
| 835 |
+
|
| 836 |
+
self.latent_dim = latent_dim
|
| 837 |
+
|
| 838 |
+
self.hop_length = np.prod(encoder_rates)
|
| 839 |
+
self.encoder = Encoder(encoder_dim, encoder_rates, latent_dim)
|
| 840 |
+
|
| 841 |
+
if not continuous:
|
| 842 |
+
self.n_codebooks = n_codebooks
|
| 843 |
+
self.codebook_size = codebook_size
|
| 844 |
+
self.codebook_dim = codebook_dim
|
| 845 |
+
self.quantizer = ResidualVectorQuantize(
|
| 846 |
+
input_dim=latent_dim,
|
| 847 |
+
n_codebooks=n_codebooks,
|
| 848 |
+
codebook_size=codebook_size,
|
| 849 |
+
codebook_dim=codebook_dim,
|
| 850 |
+
quantizer_dropout=quantizer_dropout,
|
| 851 |
+
)
|
| 852 |
+
else:
|
| 853 |
+
self.quant_conv = torch.nn.Conv1d(latent_dim, 2 * latent_dim, 1)
|
| 854 |
+
self.post_quant_conv = torch.nn.Conv1d(latent_dim, latent_dim, 1)
|
| 855 |
+
|
| 856 |
+
self.decoder = Decoder(
|
| 857 |
+
latent_dim,
|
| 858 |
+
decoder_dim,
|
| 859 |
+
decoder_rates,
|
| 860 |
+
)
|
| 861 |
+
self.sample_rate = sample_rate
|
| 862 |
+
self.apply(init_weights)
|
| 863 |
+
|
| 864 |
+
self.delay = self.get_delay()
|
| 865 |
+
|
| 866 |
+
if not self.use_weight_norm:
|
| 867 |
+
self.remove_weight_norm()
|
| 868 |
+
|
| 869 |
+
@property
|
| 870 |
+
def dtype(self):
|
| 871 |
+
"""Get the dtype of the model parameters."""
|
| 872 |
+
# Return the dtype of the first parameter found
|
| 873 |
+
for param in self.parameters():
|
| 874 |
+
return param.dtype
|
| 875 |
+
return torch.float32 # fallback
|
| 876 |
+
|
| 877 |
+
@property
|
| 878 |
+
def device(self):
|
| 879 |
+
"""Get the device of the model parameters."""
|
| 880 |
+
# Return the device of the first parameter found
|
| 881 |
+
for param in self.parameters():
|
| 882 |
+
return param.device
|
| 883 |
+
return torch.device('cpu') # fallback
|
| 884 |
+
|
| 885 |
+
def preprocess(self, audio_data, sample_rate):
|
| 886 |
+
if sample_rate is None:
|
| 887 |
+
sample_rate = self.sample_rate
|
| 888 |
+
assert sample_rate == self.sample_rate
|
| 889 |
+
|
| 890 |
+
length = audio_data.shape[-1]
|
| 891 |
+
right_pad = math.ceil(length / self.hop_length) * self.hop_length - length
|
| 892 |
+
audio_data = nn.functional.pad(audio_data, (0, right_pad))
|
| 893 |
+
|
| 894 |
+
return audio_data
|
| 895 |
+
|
| 896 |
+
@apply_forward_hook
|
| 897 |
+
def encode(
|
| 898 |
+
self,
|
| 899 |
+
audio_data: torch.Tensor,
|
| 900 |
+
n_quantizers: int = None,
|
| 901 |
+
):
|
| 902 |
+
"""Encode given audio data and return quantized latent codes
|
| 903 |
+
|
| 904 |
+
Parameters
|
| 905 |
+
----------
|
| 906 |
+
audio_data : Tensor[B x 1 x T]
|
| 907 |
+
Audio data to encode
|
| 908 |
+
n_quantizers : int, optional
|
| 909 |
+
Number of quantizers to use, by default None
|
| 910 |
+
If None, all quantizers are used.
|
| 911 |
+
|
| 912 |
+
Returns
|
| 913 |
+
-------
|
| 914 |
+
dict
|
| 915 |
+
A dictionary with the following keys:
|
| 916 |
+
"z" : Tensor[B x D x T]
|
| 917 |
+
Quantized continuous representation of input
|
| 918 |
+
"codes" : Tensor[B x N x T]
|
| 919 |
+
Codebook indices for each codebook
|
| 920 |
+
(quantized discrete representation of input)
|
| 921 |
+
"latents" : Tensor[B x N*D x T]
|
| 922 |
+
Projected latents (continuous representation of input before quantization)
|
| 923 |
+
"vq/commitment_loss" : Tensor[1]
|
| 924 |
+
Commitment loss to train encoder to predict vectors closer to codebook
|
| 925 |
+
entries
|
| 926 |
+
"vq/codebook_loss" : Tensor[1]
|
| 927 |
+
Codebook loss to update the codebook
|
| 928 |
+
"length" : int
|
| 929 |
+
Number of samples in input audio
|
| 930 |
+
"""
|
| 931 |
+
z = self.encoder(audio_data) # [B x D x T]
|
| 932 |
+
if not self.continuous:
|
| 933 |
+
z, codes, latents, commitment_loss, codebook_loss = self.quantizer(z, n_quantizers)
|
| 934 |
+
else:
|
| 935 |
+
z = self.quant_conv(z) # [B x 2D x T]
|
| 936 |
+
z = DiagonalGaussianDistribution(z)
|
| 937 |
+
codes, latents, commitment_loss, codebook_loss = None, None, 0, 0
|
| 938 |
+
|
| 939 |
+
return z, codes, latents, commitment_loss, codebook_loss
|
| 940 |
+
|
| 941 |
+
@apply_forward_hook
|
| 942 |
+
def decode(self, z: torch.Tensor):
|
| 943 |
+
"""Decode given latent codes and return audio data
|
| 944 |
+
|
| 945 |
+
Parameters
|
| 946 |
+
----------
|
| 947 |
+
z : Tensor[B x D x T]
|
| 948 |
+
Quantized continuous representation of input
|
| 949 |
+
length : int, optional
|
| 950 |
+
Number of samples in output audio, by default None
|
| 951 |
+
|
| 952 |
+
Returns
|
| 953 |
+
-------
|
| 954 |
+
dict
|
| 955 |
+
A dictionary with the following keys:
|
| 956 |
+
"audio" : Tensor[B x 1 x length]
|
| 957 |
+
Decoded audio data.
|
| 958 |
+
"""
|
| 959 |
+
if not self.continuous:
|
| 960 |
+
audio = self.decoder(z)
|
| 961 |
+
else:
|
| 962 |
+
z = self.post_quant_conv(z)
|
| 963 |
+
audio = self.decoder(z)
|
| 964 |
+
|
| 965 |
+
return audio
|
| 966 |
+
|
| 967 |
+
def forward(
|
| 968 |
+
self,
|
| 969 |
+
audio_data: torch.Tensor,
|
| 970 |
+
sample_rate: int = None,
|
| 971 |
+
n_quantizers: int = None,
|
| 972 |
+
):
|
| 973 |
+
"""Model forward pass
|
| 974 |
+
|
| 975 |
+
Parameters
|
| 976 |
+
----------
|
| 977 |
+
audio_data : Tensor[B x 1 x T]
|
| 978 |
+
Audio data to encode
|
| 979 |
+
sample_rate : int, optional
|
| 980 |
+
Sample rate of audio data in Hz, by default None
|
| 981 |
+
If None, defaults to `self.sample_rate`
|
| 982 |
+
n_quantizers : int, optional
|
| 983 |
+
Number of quantizers to use, by default None.
|
| 984 |
+
If None, all quantizers are used.
|
| 985 |
+
|
| 986 |
+
Returns
|
| 987 |
+
-------
|
| 988 |
+
dict
|
| 989 |
+
A dictionary with the following keys:
|
| 990 |
+
"z" : Tensor[B x D x T]
|
| 991 |
+
Quantized continuous representation of input
|
| 992 |
+
"codes" : Tensor[B x N x T]
|
| 993 |
+
Codebook indices for each codebook
|
| 994 |
+
(quantized discrete representation of input)
|
| 995 |
+
"latents" : Tensor[B x N*D x T]
|
| 996 |
+
Projected latents (continuous representation of input before quantization)
|
| 997 |
+
"vq/commitment_loss" : Tensor[1]
|
| 998 |
+
Commitment loss to train encoder to predict vectors closer to codebook
|
| 999 |
+
entries
|
| 1000 |
+
"vq/codebook_loss" : Tensor[1]
|
| 1001 |
+
Codebook loss to update the codebook
|
| 1002 |
+
"length" : int
|
| 1003 |
+
Number of samples in input audio
|
| 1004 |
+
"audio" : Tensor[B x 1 x length]
|
| 1005 |
+
Decoded audio data.
|
| 1006 |
+
"""
|
| 1007 |
+
length = audio_data.shape[-1]
|
| 1008 |
+
audio_data = self.preprocess(audio_data, sample_rate)
|
| 1009 |
+
if not self.continuous:
|
| 1010 |
+
z, codes, latents, commitment_loss, codebook_loss = self.encode(audio_data, n_quantizers)
|
| 1011 |
+
|
| 1012 |
+
x = self.decode(z)
|
| 1013 |
+
return {
|
| 1014 |
+
"audio": x[..., :length],
|
| 1015 |
+
"z": z,
|
| 1016 |
+
"codes": codes,
|
| 1017 |
+
"latents": latents,
|
| 1018 |
+
"vq/commitment_loss": commitment_loss,
|
| 1019 |
+
"vq/codebook_loss": codebook_loss,
|
| 1020 |
+
}
|
| 1021 |
+
else:
|
| 1022 |
+
posterior, _, _, _, _ = self.encode(audio_data, n_quantizers)
|
| 1023 |
+
z = posterior.sample()
|
| 1024 |
+
x = self.decode(z)
|
| 1025 |
+
|
| 1026 |
+
kl_loss = posterior.kl()
|
| 1027 |
+
kl_loss = kl_loss.mean()
|
| 1028 |
+
|
| 1029 |
+
return {
|
| 1030 |
+
"audio": x[..., :length],
|
| 1031 |
+
"z": z,
|
| 1032 |
+
"kl_loss": kl_loss,
|
| 1033 |
+
}
|
| 1034 |
+
|
| 1035 |
+
def remove_weight_norm(self):
|
| 1036 |
+
"""
|
| 1037 |
+
Remove weight_norm from all modules in the model.
|
| 1038 |
+
This fuses the weight_g and weight_v parameters into a single weight parameter.
|
| 1039 |
+
Should be called before inference for better performance.
|
| 1040 |
+
Returns:
|
| 1041 |
+
self: The model with weight_norm removed
|
| 1042 |
+
"""
|
| 1043 |
+
from torch.nn.utils import remove_weight_norm
|
| 1044 |
+
num_removed = 0
|
| 1045 |
+
for name, module in list(self.named_modules()):
|
| 1046 |
+
if hasattr(module, "_forward_pre_hooks"):
|
| 1047 |
+
for hook_id, hook in list(module._forward_pre_hooks.items()):
|
| 1048 |
+
if "WeightNorm" in str(type(hook)):
|
| 1049 |
+
try:
|
| 1050 |
+
remove_weight_norm(module)
|
| 1051 |
+
num_removed += 1
|
| 1052 |
+
# print(f"Removed weight_norm from: {name}")
|
| 1053 |
+
except ValueError as e:
|
| 1054 |
+
print(f"Failed to remove weight_norm from {name}: {e}")
|
| 1055 |
+
if num_removed > 0:
|
| 1056 |
+
print(f"Successfully removed weight_norm from {num_removed} modules")
|
| 1057 |
+
self.use_weight_norm = False
|
| 1058 |
+
self.register_to_config(use_weight_norm=False)
|
| 1059 |
+
else:
|
| 1060 |
+
print("No weight_norm found in the model")
|
| 1061 |
+
return self
|
| 1062 |
+
|
| 1063 |
+
if __name__ == "__main__":
|
| 1064 |
+
from functools import partial
|
| 1065 |
+
|
| 1066 |
+
import numpy as np
|
| 1067 |
+
|
| 1068 |
+
model = DAC().to("cpu")
|
| 1069 |
+
|
| 1070 |
+
for n, m in model.named_modules():
|
| 1071 |
+
o = m.extra_repr()
|
| 1072 |
+
p = sum([np.prod(p.size()) for p in m.parameters()])
|
| 1073 |
+
fn = lambda o, p: o + f" {p/1e6:<.3f}M params."
|
| 1074 |
+
setattr(m, "extra_repr", partial(fn, o=o, p=p))
|
| 1075 |
+
print(model)
|
| 1076 |
+
print("Total # of params: ", sum([np.prod(p.size()) for p in model.parameters()]))
|
| 1077 |
+
|
| 1078 |
+
length = 88200 * 2
|
| 1079 |
+
x = torch.randn(1, 1, length).to(model.device)
|
| 1080 |
+
x.requires_grad_(True)
|
| 1081 |
+
x.retain_grad()
|
| 1082 |
+
|
| 1083 |
+
# Make a forward pass
|
| 1084 |
+
out = model(x)["audio"]
|
| 1085 |
+
print("Input shape:", x.shape)
|
| 1086 |
+
print("Output shape:", out.shape)
|
| 1087 |
+
|
| 1088 |
+
# Create gradient variable
|
| 1089 |
+
grad = torch.zeros_like(out)
|
| 1090 |
+
grad[:, :, grad.shape[-1] // 2] = 1
|
| 1091 |
+
|
| 1092 |
+
# Make a backward pass
|
| 1093 |
+
out.backward(grad)
|
| 1094 |
+
|
| 1095 |
+
# Check non-zero values
|
| 1096 |
+
gradmap = x.grad.squeeze(0)
|
| 1097 |
+
gradmap = (gradmap != 0).sum(0) # sum across features
|
| 1098 |
+
rf = (gradmap != 0).sum()
|
| 1099 |
+
|
| 1100 |
+
print(f"Receptive field: {rf.item()}")
|
| 1101 |
+
|
| 1102 |
+
x = AudioSignal(torch.randn(1, 1, 44100 * 60), 44100)
|
| 1103 |
+
model.decompress(model.compress(x, verbose=True), verbose=True)
|
hymm/models/modules/interactionv2.py
ADDED
|
@@ -0,0 +1,708 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
from typing import Dict, List, Tuple, Optional
|
| 5 |
+
|
| 6 |
+
from einops import rearrange
|
| 7 |
+
|
| 8 |
+
from .wan_video_dit import RMSNorm, AttentionModule, flash_attention
|
| 9 |
+
from hymm.utils.parallel_states import get_sequence_parallel_state, nccl_info, sp_seq_ctx
|
| 10 |
+
from hymm.utils.communications import (
|
| 11 |
+
all_to_all_4D,
|
| 12 |
+
sp_pad_num_heads, sp_pad_heads, sp_num_local_real_heads,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
class RotaryEmbedding(nn.Module):
|
| 16 |
+
inv_freq: torch.Tensor # fix linting for `register_buffer`
|
| 17 |
+
|
| 18 |
+
def __init__(self, base: float, dim: int, device=None):
|
| 19 |
+
super().__init__()
|
| 20 |
+
self.base = base
|
| 21 |
+
self.dim = dim
|
| 22 |
+
self.attention_scaling = 1.0
|
| 23 |
+
|
| 24 |
+
inv_freq = 1.0 / (
|
| 25 |
+
base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim))
|
| 26 |
+
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
| 27 |
+
self.original_inv_freq = self.inv_freq
|
| 28 |
+
|
| 29 |
+
@torch.no_grad()
|
| 30 |
+
def forward(self, x, position_ids):
|
| 31 |
+
inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1).to(x.device)
|
| 32 |
+
position_ids_expanded = position_ids[:, None, :].float()
|
| 33 |
+
|
| 34 |
+
device_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu"
|
| 35 |
+
with torch.autocast(device_type=device_type, enabled=False): # Force float32
|
| 36 |
+
freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2)
|
| 37 |
+
emb = torch.cat((freqs, freqs), dim=-1)
|
| 38 |
+
cos = emb.cos() * self.attention_scaling
|
| 39 |
+
sin = emb.sin() * self.attention_scaling
|
| 40 |
+
|
| 41 |
+
return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def rotate_half(x):
|
| 45 |
+
"""Rotates half the hidden dims of the input."""
|
| 46 |
+
x1 = x[..., : x.shape[-1] // 2]
|
| 47 |
+
x2 = x[..., x.shape[-1] // 2:]
|
| 48 |
+
return torch.cat((-x2, x1), dim=-1)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@torch.compile(fullgraph=True)
|
| 52 |
+
def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
|
| 53 |
+
"""Applies Rotary Position Embedding to the query and key tensors.
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
q (`torch.Tensor`): The query tensor.
|
| 57 |
+
k (`torch.Tensor`): The key tensor.
|
| 58 |
+
cos (`torch.Tensor`): The cosine part of the rotary embedding.
|
| 59 |
+
sin (`torch.Tensor`): The sine part of the rotary embedding.
|
| 60 |
+
position_ids (`torch.Tensor`, *optional*):
|
| 61 |
+
Deprecated and unused.
|
| 62 |
+
unsqueeze_dim (`int`, *optional*, defaults to 1):
|
| 63 |
+
The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
|
| 64 |
+
sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
|
| 65 |
+
that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and
|
| 66 |
+
k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes
|
| 67 |
+
cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have
|
| 68 |
+
the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.
|
| 69 |
+
Returns:
|
| 70 |
+
`tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
|
| 71 |
+
"""
|
| 72 |
+
cos = cos.unsqueeze(unsqueeze_dim)
|
| 73 |
+
sin = sin.unsqueeze(unsqueeze_dim)
|
| 74 |
+
q_embed = (q * cos) + (rotate_half(q) * sin)
|
| 75 |
+
k_embed = (k * cos) + (rotate_half(k) * sin)
|
| 76 |
+
return q_embed, k_embed
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class PerFrameAttentionPooling(nn.Module):
|
| 80 |
+
"""
|
| 81 |
+
Per-frame multi-head attention pooling.
|
| 82 |
+
|
| 83 |
+
Given a flattened token sequence [B, L, D] and grid size (T, H, W), perform a
|
| 84 |
+
single-query attention pooling over the H*W tokens for each time frame, producing
|
| 85 |
+
[B, T, D].
|
| 86 |
+
|
| 87 |
+
Inspired by SigLIP's Multihead Attention Pooling head (without MLP/residual stack).
|
| 88 |
+
"""
|
| 89 |
+
|
| 90 |
+
def __init__(self, dim: int, num_heads: int, eps: float = 1e-6):
|
| 91 |
+
super().__init__()
|
| 92 |
+
assert dim % num_heads == 0, "dim must be divisible by num_heads"
|
| 93 |
+
self.dim = dim
|
| 94 |
+
self.num_heads = num_heads
|
| 95 |
+
|
| 96 |
+
self.probe = nn.Parameter(torch.randn(1, 1, dim))
|
| 97 |
+
nn.init.normal_(self.probe, std=0.02)
|
| 98 |
+
|
| 99 |
+
self.attention = nn.MultiheadAttention(embed_dim=dim, num_heads=num_heads, batch_first=True)
|
| 100 |
+
self.layernorm = nn.LayerNorm(dim, eps=eps)
|
| 101 |
+
|
| 102 |
+
def forward(self, x: torch.Tensor, grid_size: Tuple[int, int, int]) -> torch.Tensor:
|
| 103 |
+
"""
|
| 104 |
+
Args:
|
| 105 |
+
x: [B, L, D], where L = T*H*W
|
| 106 |
+
grid_size: (T, H, W)
|
| 107 |
+
Returns:
|
| 108 |
+
pooled: [B, T, D]
|
| 109 |
+
"""
|
| 110 |
+
B, L, D = x.shape
|
| 111 |
+
T, H, W = grid_size
|
| 112 |
+
assert D == self.dim, f"Channel dimension mismatch: D={D} vs dim={self.dim}"
|
| 113 |
+
assert L == T * H * W, f"Flattened length mismatch: L={L} vs T*H*W={T * H * W}"
|
| 114 |
+
|
| 115 |
+
S = H * W
|
| 116 |
+
# Re-arrange tokens grouped by frame.
|
| 117 |
+
x_bt_s_d = x.view(B, T, S, D).contiguous().view(B * T, S, D) # [B*T, S, D]
|
| 118 |
+
|
| 119 |
+
# A learnable probe as the query (one query per frame).
|
| 120 |
+
probe = self.probe.expand(B * T, -1, -1) # [B*T, 1, D]
|
| 121 |
+
|
| 122 |
+
# Attention pooling: query=probe, key/value=H*W tokens within the frame.
|
| 123 |
+
pooled_bt_1_d = self.attention(probe, x_bt_s_d, x_bt_s_d, need_weights=False)[0] # [B*T, 1, D]
|
| 124 |
+
pooled_bt_d = pooled_bt_1_d.squeeze(1) # [B*T, D]
|
| 125 |
+
|
| 126 |
+
# Restore to [B, T, D].
|
| 127 |
+
pooled = pooled_bt_d.view(B, T, D)
|
| 128 |
+
pooled = self.layernorm(pooled)
|
| 129 |
+
return pooled
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
class CrossModalInteractionController:
|
| 133 |
+
"""
|
| 134 |
+
Strategy class that controls interactions between two towers.
|
| 135 |
+
Manages the interaction mapping between visual DiT (e.g. 30 layers) and audio DiT (e.g. 30 layers).
|
| 136 |
+
"""
|
| 137 |
+
|
| 138 |
+
def __init__(self, visual_layers: int = 30, audio_layers: int = 30):
|
| 139 |
+
self.visual_layers = visual_layers
|
| 140 |
+
self.audio_layers = audio_layers
|
| 141 |
+
self.min_layers = min(visual_layers, audio_layers)
|
| 142 |
+
|
| 143 |
+
def get_interaction_layers(self, strategy: str = "shallow_focus") -> Dict[str, List[Tuple[int, int]]]:
|
| 144 |
+
"""
|
| 145 |
+
Get interaction layer mappings.
|
| 146 |
+
|
| 147 |
+
Args:
|
| 148 |
+
strategy: interaction strategy
|
| 149 |
+
- "shallow_focus": emphasize shallow layers to avoid deep-layer asymmetry
|
| 150 |
+
- "distributed": distributed interactions across the network
|
| 151 |
+
- "progressive": dense shallow interactions, sparse deeper interactions
|
| 152 |
+
- "custom": custom interaction layers
|
| 153 |
+
|
| 154 |
+
Returns:
|
| 155 |
+
A dict containing mappings for 'v2a' (visual -> audio) and 'a2v' (audio -> visual).
|
| 156 |
+
"""
|
| 157 |
+
|
| 158 |
+
if strategy == "shallow_focus":
|
| 159 |
+
# Emphasize the first ~1/3 layers to avoid deep-layer asymmetry.
|
| 160 |
+
num_interact = min(10, self.min_layers // 3)
|
| 161 |
+
interact_layers = list(range(0, num_interact))
|
| 162 |
+
|
| 163 |
+
elif strategy == "distributed":
|
| 164 |
+
# Distribute interactions across the network (every few layers).
|
| 165 |
+
step = 3
|
| 166 |
+
interact_layers = list(range(0, self.min_layers, step))
|
| 167 |
+
|
| 168 |
+
elif strategy == "progressive":
|
| 169 |
+
# Progressive: dense shallow interactions, sparse deeper interactions.
|
| 170 |
+
shallow = list(range(0, min(8, self.min_layers))) # Dense for the first 8 layers.
|
| 171 |
+
if self.min_layers > 8:
|
| 172 |
+
deep = list(range(8, self.min_layers, 3)) # Every 3 layers afterwards.
|
| 173 |
+
interact_layers = shallow + deep
|
| 174 |
+
else:
|
| 175 |
+
interact_layers = shallow
|
| 176 |
+
|
| 177 |
+
elif strategy == "custom":
|
| 178 |
+
# Custom strategy: adjust as needed.
|
| 179 |
+
interact_layers = [0, 2, 4, 6, 8, 12, 16, 20] # Explicit layer indices.
|
| 180 |
+
interact_layers = [i for i in interact_layers if i < self.min_layers]
|
| 181 |
+
|
| 182 |
+
elif strategy == "full":
|
| 183 |
+
interact_layers = list(range(0, self.min_layers))
|
| 184 |
+
|
| 185 |
+
else:
|
| 186 |
+
raise ValueError(f"Unknown interaction strategy: {strategy}")
|
| 187 |
+
|
| 188 |
+
# Build bidirectional mapping.
|
| 189 |
+
mapping = {
|
| 190 |
+
'v2a': [(i, i) for i in interact_layers], # visual layer i -> audio layer i
|
| 191 |
+
'a2v': [(i, i) for i in interact_layers] # audio layer i -> visual layer i
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
return mapping
|
| 195 |
+
|
| 196 |
+
def should_interact(self, layer_idx: int, direction: str, interaction_mapping: Dict) -> bool:
|
| 197 |
+
"""
|
| 198 |
+
Check whether a given layer should interact.
|
| 199 |
+
|
| 200 |
+
Args:
|
| 201 |
+
layer_idx: current layer index
|
| 202 |
+
direction: interaction direction ('v2a' or 'a2v')
|
| 203 |
+
interaction_mapping: interaction mapping table
|
| 204 |
+
|
| 205 |
+
Returns:
|
| 206 |
+
bool: whether to interact
|
| 207 |
+
"""
|
| 208 |
+
if direction not in interaction_mapping:
|
| 209 |
+
return False
|
| 210 |
+
|
| 211 |
+
return any(src == layer_idx for src, _ in interaction_mapping[direction])
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
class ConditionalCrossAttention(nn.Module):
|
| 215 |
+
def __init__(self, dim: int, kv_dim: int, num_heads: int, eps: float = 1e-6):
|
| 216 |
+
super().__init__()
|
| 217 |
+
self.q_dim = dim
|
| 218 |
+
self.kv_dim = kv_dim
|
| 219 |
+
self.num_heads = num_heads
|
| 220 |
+
self.head_dim = self.q_dim // num_heads
|
| 221 |
+
|
| 222 |
+
self.q = nn.Linear(dim, dim)
|
| 223 |
+
self.k = nn.Linear(kv_dim, dim)
|
| 224 |
+
self.v = nn.Linear(kv_dim, dim)
|
| 225 |
+
self.o = nn.Linear(dim, dim)
|
| 226 |
+
self.norm_q = RMSNorm(dim, eps=eps)
|
| 227 |
+
self.norm_k = RMSNorm(dim, eps=eps)
|
| 228 |
+
|
| 229 |
+
self.attn = AttentionModule(self.num_heads)
|
| 230 |
+
|
| 231 |
+
self.enable_bsa = False
|
| 232 |
+
self.bsa_params = {}
|
| 233 |
+
|
| 234 |
+
# Cross-modal bridge: q and k/v come from two differently-sized SP-sharded
|
| 235 |
+
# streams, so each needs its own global length for the Ulysses all-to-all.
|
| 236 |
+
# MOVABridge sets these per direction (a2v: q=visual/kv=audio, v2a: swapped).
|
| 237 |
+
self.sp_q_stream = "visual"
|
| 238 |
+
self.sp_kv_stream = "audio"
|
| 239 |
+
|
| 240 |
+
def _run_cross_bsa(self, q_bhsd, k_bhsd, v_bhsd, q_structure, k_structure,
|
| 241 |
+
q_grid_size=None, k_grid_size=None):
|
| 242 |
+
"""Run cross-modal BSA between video (3D) and audio (1D)."""
|
| 243 |
+
from hymm.models.modules.block_sparse_attention import flash_attn_bsa_cross
|
| 244 |
+
return flash_attn_bsa_cross(
|
| 245 |
+
q_bhsd, k_bhsd, v_bhsd,
|
| 246 |
+
q_structure=q_structure,
|
| 247 |
+
k_structure=k_structure,
|
| 248 |
+
q_grid_size=q_grid_size,
|
| 249 |
+
k_grid_size=k_grid_size,
|
| 250 |
+
sparsity=self.bsa_params.get('sparsity', 0.9375),
|
| 251 |
+
cdf_threshold=self.bsa_params.get('cdf_threshold', None),
|
| 252 |
+
chunk_size_q=self.bsa_params.get('chunk_size_q', 64),
|
| 253 |
+
chunk_size_k=self.bsa_params.get('chunk_size_k', 64),
|
| 254 |
+
chunk_3d_shape_q=self.bsa_params.get('chunk_3d_shape_q', [4, 4, 4]),
|
| 255 |
+
chunk_3d_shape_k=self.bsa_params.get('chunk_3d_shape_k', [4, 4, 4]),
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
def forward(self, x: torch.Tensor, y: torch.Tensor, x_freqs: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 259 |
+
y_freqs: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 260 |
+
q_structure: str = "1d", k_structure: str = "1d",
|
| 261 |
+
q_grid_size=None, k_grid_size=None):
|
| 262 |
+
ctx = y
|
| 263 |
+
q = self.norm_q(self.q(x))
|
| 264 |
+
k = self.norm_k(self.k(ctx))
|
| 265 |
+
v = self.v(ctx)
|
| 266 |
+
if x_freqs is not None:
|
| 267 |
+
x_cos, x_sin = x_freqs
|
| 268 |
+
q_view = rearrange(q, 'b l (h d) -> b l h d', d=self.head_dim)
|
| 269 |
+
x_cos = x_cos.to(q_view.dtype).to(q_view.device)
|
| 270 |
+
x_sin = x_sin.to(q_view.dtype).to(q_view.device)
|
| 271 |
+
q_view, _ = apply_rotary_pos_emb(q_view, q_view, x_cos, x_sin, unsqueeze_dim=2)
|
| 272 |
+
q = rearrange(q_view, 'b l h d -> b l (h d)')
|
| 273 |
+
if y_freqs is not None:
|
| 274 |
+
y_cos, y_sin = y_freqs
|
| 275 |
+
k_view = rearrange(k, 'b l (h d) -> b l h d', d=self.head_dim)
|
| 276 |
+
y_cos = y_cos.to(k_view.dtype).to(k_view.device)
|
| 277 |
+
y_sin = y_sin.to(k_view.dtype).to(k_view.device)
|
| 278 |
+
_, k_view = apply_rotary_pos_emb(k_view, k_view, y_cos, y_sin, unsqueeze_dim=2)
|
| 279 |
+
k = rearrange(k_view, 'b l h d -> b l (h d)')
|
| 280 |
+
|
| 281 |
+
use_bsa = self.enable_bsa and self.bsa_params.get('sparsity', 0) > 0
|
| 282 |
+
|
| 283 |
+
if get_sequence_parallel_state():
|
| 284 |
+
sp_size = nccl_info.sp_size
|
| 285 |
+
sp_rank = nccl_info.rank_within_spgroup
|
| 286 |
+
# Same Ulysses head-padding contract as the DiT attentions. v2a bridge
|
| 287 |
+
# operates in the audio 12-head space, so with sp_size=8 the head dim is
|
| 288 |
+
# padded to 16; padded heads are sliced off before the BSA cross kernel
|
| 289 |
+
# / flash and truncated after, fully isolated from the real heads.
|
| 290 |
+
padded_heads = sp_pad_num_heads(self.num_heads, sp_size)
|
| 291 |
+
|
| 292 |
+
q, k, v = [rearrange(t, "b s (n d) -> b s n d", n=self.num_heads) for t in (q, k, v)]
|
| 293 |
+
if padded_heads != self.num_heads:
|
| 294 |
+
q = sp_pad_heads(q, padded_heads)
|
| 295 |
+
k = sp_pad_heads(k, padded_heads)
|
| 296 |
+
v = sp_pad_heads(v, padded_heads)
|
| 297 |
+
|
| 298 |
+
q_full_len = sp_seq_ctx.get(self.sp_q_stream)
|
| 299 |
+
kv_full_len = sp_seq_ctx.get(self.sp_kv_stream)
|
| 300 |
+
q = all_to_all_4D(q, nccl_info.sp_group, scatter_dim=2, gather_dim=1,
|
| 301 |
+
full_seq_len=q_full_len)
|
| 302 |
+
k = all_to_all_4D(k, nccl_info.sp_group, scatter_dim=2, gather_dim=1,
|
| 303 |
+
full_seq_len=kv_full_len)
|
| 304 |
+
v = all_to_all_4D(v, nccl_info.sp_group, scatter_dim=2, gather_dim=1,
|
| 305 |
+
full_seq_len=kv_full_len)
|
| 306 |
+
|
| 307 |
+
hpr = q.shape[2]
|
| 308 |
+
local_heads = sp_num_local_real_heads(self.num_heads, padded_heads, sp_size, sp_rank)
|
| 309 |
+
if local_heads > 0:
|
| 310 |
+
if local_heads != hpr:
|
| 311 |
+
q = q[:, :, :local_heads, :]
|
| 312 |
+
k = k[:, :, :local_heads, :]
|
| 313 |
+
v = v[:, :, :local_heads, :]
|
| 314 |
+
if use_bsa:
|
| 315 |
+
q_bsa = q.permute(0, 2, 1, 3).contiguous()
|
| 316 |
+
k_bsa = k.permute(0, 2, 1, 3).contiguous()
|
| 317 |
+
v_bsa = v.permute(0, 2, 1, 3).contiguous()
|
| 318 |
+
x_bsa = self._run_cross_bsa(q_bsa, k_bsa, v_bsa, q_structure, k_structure, q_grid_size, k_grid_size)
|
| 319 |
+
x = rearrange(x_bsa, "b n s d -> b s (n d)")
|
| 320 |
+
else:
|
| 321 |
+
x = flash_attention(q.flatten(2), k.flatten(2), v.flatten(2), num_heads=local_heads)
|
| 322 |
+
x = rearrange(x, "b s (n d) -> b s n d", n=local_heads)
|
| 323 |
+
if x.shape[2] != hpr:
|
| 324 |
+
x = sp_pad_heads(x, hpr)
|
| 325 |
+
else:
|
| 326 |
+
# Padding-only rank: no BSA cross / flash kernel is run (padding never
|
| 327 |
+
# enters the kernel). Keep x CONNECTED to q/k/v (value forced to zero)
|
| 328 |
+
# so the q/k/v backward all_to_all collectives still fire here, keeping
|
| 329 |
+
# NCCL symmetric and routing this rank's real-token grads back correctly.
|
| 330 |
+
# Cross-attention: q and k/v have DIFFERENT seq lengths, so connect k/v
|
| 331 |
+
# via scalar reductions (broadcast-safe). Output takes q's seq shape.
|
| 332 |
+
x = q * 0.0 + (k.sum() + v.sum()) * 0.0
|
| 333 |
+
x = all_to_all_4D(x, nccl_info.sp_group, scatter_dim=1, gather_dim=2,
|
| 334 |
+
full_seq_len=q_full_len)
|
| 335 |
+
if padded_heads != self.num_heads:
|
| 336 |
+
x = x[:, :, :self.num_heads, :]
|
| 337 |
+
x = x.flatten(2)
|
| 338 |
+
else:
|
| 339 |
+
if use_bsa:
|
| 340 |
+
q_bsa = rearrange(q, "b s (n d) -> b n s d", n=self.num_heads).contiguous()
|
| 341 |
+
k_bsa = rearrange(k, "b s (n d) -> b n s d", n=self.num_heads).contiguous()
|
| 342 |
+
v_bsa = rearrange(v, "b s (n d) -> b n s d", n=self.num_heads).contiguous()
|
| 343 |
+
x_bsa = self._run_cross_bsa(q_bsa, k_bsa, v_bsa, q_structure, k_structure, q_grid_size, k_grid_size)
|
| 344 |
+
x = rearrange(x_bsa, "b n s d -> b s (n d)")
|
| 345 |
+
else:
|
| 346 |
+
x = self.attn(q, k, v)
|
| 347 |
+
|
| 348 |
+
return self.o(x)
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
# from diffusers.models.attention import AdaLayerNorm
|
| 352 |
+
class AdaLayerNorm(nn.Module):
|
| 353 |
+
r"""
|
| 354 |
+
Norm layer modified to incorporate timestep embeddings.
|
| 355 |
+
|
| 356 |
+
Parameters:
|
| 357 |
+
embedding_dim (`int`): The size of each embedding vector.
|
| 358 |
+
num_embeddings (`int`, *optional*): The size of the embeddings dictionary.
|
| 359 |
+
output_dim (`int`, *optional*):
|
| 360 |
+
norm_elementwise_affine (`bool`, defaults to `False):
|
| 361 |
+
norm_eps (`bool`, defaults to `False`):
|
| 362 |
+
chunk_dim (`int`, defaults to `0`):
|
| 363 |
+
"""
|
| 364 |
+
|
| 365 |
+
def __init__(
|
| 366 |
+
self,
|
| 367 |
+
embedding_dim: int,
|
| 368 |
+
num_embeddings: Optional[int] = None,
|
| 369 |
+
output_dim: Optional[int] = None,
|
| 370 |
+
norm_elementwise_affine: bool = False,
|
| 371 |
+
norm_eps: float = 1e-5,
|
| 372 |
+
chunk_dim: int = 0,
|
| 373 |
+
):
|
| 374 |
+
super().__init__()
|
| 375 |
+
|
| 376 |
+
self.chunk_dim = chunk_dim
|
| 377 |
+
output_dim = output_dim or embedding_dim * 2
|
| 378 |
+
|
| 379 |
+
if num_embeddings is not None:
|
| 380 |
+
self.emb = nn.Embedding(num_embeddings, embedding_dim)
|
| 381 |
+
else:
|
| 382 |
+
self.emb = None
|
| 383 |
+
|
| 384 |
+
self.silu = nn.SiLU()
|
| 385 |
+
self.linear = nn.Linear(embedding_dim, output_dim)
|
| 386 |
+
self.norm = nn.LayerNorm(output_dim // 2, norm_eps, norm_elementwise_affine)
|
| 387 |
+
|
| 388 |
+
def forward(
|
| 389 |
+
self, x: torch.Tensor, timestep: Optional[torch.Tensor] = None, temb: Optional[torch.Tensor] = None
|
| 390 |
+
) -> torch.Tensor:
|
| 391 |
+
if self.emb is not None:
|
| 392 |
+
temb = self.emb(timestep)
|
| 393 |
+
|
| 394 |
+
temb = self.linear(self.silu(temb))
|
| 395 |
+
|
| 396 |
+
if self.chunk_dim == 2:
|
| 397 |
+
scale, shift = temb.chunk(2, dim=2)
|
| 398 |
+
# print(f"{x.shape = }, {scale.shape = }, {shift.shape = }")
|
| 399 |
+
elif self.chunk_dim == 1:
|
| 400 |
+
# This is a bit weird why we have the order of "shift, scale" here and "scale, shift" in the
|
| 401 |
+
# other if-branch. This branch is specific to CogVideoX and OmniGen for now.
|
| 402 |
+
shift, scale = temb.chunk(2, dim=1)
|
| 403 |
+
shift = shift[:, None, :]
|
| 404 |
+
scale = scale[:, None, :]
|
| 405 |
+
else:
|
| 406 |
+
scale, shift = temb.chunk(2, dim=0)
|
| 407 |
+
|
| 408 |
+
x = self.norm(x) * (1 + scale) + shift
|
| 409 |
+
return x
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
class ConditionalCrossAttentionBlock(nn.Module):
|
| 413 |
+
"""
|
| 414 |
+
A thin wrapper around ConditionalCrossAttention.
|
| 415 |
+
Applies LayerNorm to the conditioning input `y` before cross-attention.
|
| 416 |
+
"""
|
| 417 |
+
|
| 418 |
+
def __init__(self, dim: int, kv_dim: int, num_heads: int, eps: float = 1e-6, pooled_adaln: bool = False):
|
| 419 |
+
super().__init__()
|
| 420 |
+
self.y_norm = nn.LayerNorm(kv_dim, eps=eps)
|
| 421 |
+
self.inner = ConditionalCrossAttention(dim=dim, kv_dim=kv_dim, num_heads=num_heads, eps=eps)
|
| 422 |
+
self.pooled_adaln = pooled_adaln
|
| 423 |
+
if pooled_adaln:
|
| 424 |
+
self.per_frame_pooling = PerFrameAttentionPooling(kv_dim, num_heads=num_heads, eps=eps)
|
| 425 |
+
self.adaln = AdaLayerNorm(kv_dim, output_dim=dim * 2, chunk_dim=2)
|
| 426 |
+
|
| 427 |
+
def set_sp_streams(self, q_stream: str, kv_stream: str):
|
| 428 |
+
"""Declare which SP-sharded stream feeds q and which feeds k/v."""
|
| 429 |
+
self.inner.sp_q_stream = q_stream
|
| 430 |
+
self.inner.sp_kv_stream = kv_stream
|
| 431 |
+
|
| 432 |
+
def forward(
|
| 433 |
+
self,
|
| 434 |
+
x: torch.Tensor,
|
| 435 |
+
y: torch.Tensor,
|
| 436 |
+
x_freqs: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 437 |
+
y_freqs: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 438 |
+
video_grid_size: Optional[Tuple[int, int, int]] = None,
|
| 439 |
+
q_structure: str = "1d",
|
| 440 |
+
k_structure: str = "1d",
|
| 441 |
+
q_grid_size=None,
|
| 442 |
+
k_grid_size=None,
|
| 443 |
+
) -> torch.Tensor:
|
| 444 |
+
if self.pooled_adaln:
|
| 445 |
+
assert not get_sequence_parallel_state() or nccl_info.sp_size == 1, (
|
| 446 |
+
"pooled_adaln requires full-length y for per-frame pooling, "
|
| 447 |
+
"which is incompatible with SP > 1. Set sp_size=1 or disable pooled_adaln."
|
| 448 |
+
)
|
| 449 |
+
assert video_grid_size is not None, "video_grid_size must not be None"
|
| 450 |
+
pooled_y = self.per_frame_pooling(y, video_grid_size)
|
| 451 |
+
if pooled_y.shape[1] != x.shape[1]:
|
| 452 |
+
pooled_y = F.interpolate(
|
| 453 |
+
pooled_y.permute(0, 2, 1),
|
| 454 |
+
size=x.shape[1],
|
| 455 |
+
mode='linear',
|
| 456 |
+
align_corners=False,
|
| 457 |
+
).permute(0, 2, 1)
|
| 458 |
+
x = self.adaln(x, temb=pooled_y)
|
| 459 |
+
y = self.y_norm(y)
|
| 460 |
+
return self.inner(x=x, y=y, x_freqs=x_freqs, y_freqs=y_freqs,
|
| 461 |
+
q_structure=q_structure, k_structure=k_structure,
|
| 462 |
+
q_grid_size=q_grid_size, k_grid_size=k_grid_size)
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 466 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
class DualTowerConditionalBridge(ModelMixin, ConfigMixin):
|
| 470 |
+
"""
|
| 471 |
+
Dual-tower conditional bridge.
|
| 472 |
+
"""
|
| 473 |
+
_repeated_blocks = ("ConditionalCrossAttentionBlock",)
|
| 474 |
+
|
| 475 |
+
@register_to_config
|
| 476 |
+
def __init__(self,
|
| 477 |
+
visual_layers: int = 30,
|
| 478 |
+
audio_layers: int = 30,
|
| 479 |
+
visual_hidden_dim: int = 3072, # visual DiT hidden state dimension
|
| 480 |
+
audio_hidden_dim: int = 1536, # audio DiT hidden state dimension
|
| 481 |
+
audio_fps: float = 44100.0 / 2048.0,
|
| 482 |
+
head_dim: int = 128, # attention head dimension
|
| 483 |
+
interaction_strategy: str = "shallow_focus",
|
| 484 |
+
apply_cross_rope: bool = False, # whether to apply RoPE in cross-attention
|
| 485 |
+
apply_first_frame_bias_in_rope: bool = False,
|
| 486 |
+
# whether to account for 1/video_fps bias for the first frame in RoPE alignment
|
| 487 |
+
trainable_condition_scale: bool = False,
|
| 488 |
+
pooled_adaln: bool = False,
|
| 489 |
+
):
|
| 490 |
+
super().__init__()
|
| 491 |
+
|
| 492 |
+
self.visual_hidden_dim = visual_hidden_dim
|
| 493 |
+
self.audio_hidden_dim = audio_hidden_dim
|
| 494 |
+
self.audio_fps = audio_fps
|
| 495 |
+
self.head_dim = head_dim
|
| 496 |
+
self.apply_cross_rope = apply_cross_rope
|
| 497 |
+
self.apply_first_frame_bias_in_rope = apply_first_frame_bias_in_rope
|
| 498 |
+
self.trainable_condition_scale = trainable_condition_scale
|
| 499 |
+
self.pooled_adaln = pooled_adaln
|
| 500 |
+
if self.trainable_condition_scale:
|
| 501 |
+
self.condition_scale = nn.Parameter(torch.tensor([1.0], dtype=torch.float32))
|
| 502 |
+
else:
|
| 503 |
+
self.condition_scale = 1.0
|
| 504 |
+
|
| 505 |
+
self.controller = CrossModalInteractionController(visual_layers, audio_layers)
|
| 506 |
+
self.interaction_mapping = self.controller.get_interaction_layers(interaction_strategy)
|
| 507 |
+
|
| 508 |
+
# Conditional cross-attention modules operating at the DiT hidden-state level.
|
| 509 |
+
self.audio_to_video_conditioners = nn.ModuleDict() # audio hidden states -> visual DiT conditioning
|
| 510 |
+
self.video_to_audio_conditioners = nn.ModuleDict() # visual hidden states -> audio DiT conditioning
|
| 511 |
+
|
| 512 |
+
# Build conditioners for layers that should interact.
|
| 513 |
+
# audio hidden states condition the visual DiT
|
| 514 |
+
self.rotary = RotaryEmbedding(base=10000.0, dim=head_dim)
|
| 515 |
+
for v_layer, _ in self.interaction_mapping['a2v']:
|
| 516 |
+
self.audio_to_video_conditioners[str(v_layer)] = ConditionalCrossAttentionBlock(
|
| 517 |
+
dim=visual_hidden_dim, # 3072 (visual DiT hidden states)
|
| 518 |
+
kv_dim=audio_hidden_dim, # 1536 (audio DiT hidden states)
|
| 519 |
+
num_heads=visual_hidden_dim // head_dim, # derive number of heads from hidden dim
|
| 520 |
+
pooled_adaln=False # a2v typically does not need pooled AdaLN
|
| 521 |
+
)
|
| 522 |
+
|
| 523 |
+
# visual hidden states condition the audio DiT
|
| 524 |
+
for a_layer, _ in self.interaction_mapping['v2a']:
|
| 525 |
+
self.video_to_audio_conditioners[str(a_layer)] = ConditionalCrossAttentionBlock(
|
| 526 |
+
dim=audio_hidden_dim, # 1536 (audio DiT hidden states)
|
| 527 |
+
kv_dim=visual_hidden_dim, # 3072 (visual DiT hidden states)
|
| 528 |
+
num_heads=audio_hidden_dim // head_dim, # safe head count derivation
|
| 529 |
+
pooled_adaln=self.pooled_adaln
|
| 530 |
+
)
|
| 531 |
+
|
| 532 |
+
@torch.no_grad()
|
| 533 |
+
def build_aligned_freqs(self,
|
| 534 |
+
video_fps: float,
|
| 535 |
+
grid_size: Tuple[int, int, int],
|
| 536 |
+
audio_steps: int,
|
| 537 |
+
device: Optional[torch.device] = None,
|
| 538 |
+
dtype: Optional[torch.dtype] = None) -> Tuple[
|
| 539 |
+
Tuple[torch.Tensor, torch.Tensor], Tuple[torch.Tensor, torch.Tensor]]:
|
| 540 |
+
"""
|
| 541 |
+
Build aligned RoPE (cos, sin) pairs based on video fps, video grid size (f_v, h, w),
|
| 542 |
+
and audio sequence length `audio_steps` (with fixed audio fps = 44100/2048).
|
| 543 |
+
|
| 544 |
+
Returns:
|
| 545 |
+
visual_freqs: (cos_v, sin_v), shape [1, f_v*h*w, head_dim]
|
| 546 |
+
audio_freqs: (cos_a, sin_a), shape [1, audio_steps, head_dim]
|
| 547 |
+
"""
|
| 548 |
+
f_v, h, w = grid_size
|
| 549 |
+
L_v = f_v * h * w
|
| 550 |
+
L_a = int(audio_steps)
|
| 551 |
+
|
| 552 |
+
device = device or next(self.parameters()).device
|
| 553 |
+
dtype = dtype or torch.float32
|
| 554 |
+
|
| 555 |
+
# Audio positions: 0,1,2,...,L_a-1 (audio as reference).
|
| 556 |
+
audio_pos = torch.arange(L_a, device=device, dtype=torch.float32).unsqueeze(0)
|
| 557 |
+
|
| 558 |
+
# Video positions: align video frames to audio-step units.
|
| 559 |
+
# FIXME(dhyu): hard-coded VAE temporal stride = 4
|
| 560 |
+
if self.apply_first_frame_bias_in_rope:
|
| 561 |
+
# Account for the "first frame lasts 1/video_fps" bias.
|
| 562 |
+
video_effective_fps = float(video_fps) / 4.0
|
| 563 |
+
if f_v > 0:
|
| 564 |
+
t_starts = torch.zeros((f_v,), device=device, dtype=torch.float32)
|
| 565 |
+
if f_v > 1:
|
| 566 |
+
t_starts[1:] = (1.0 / float(video_fps)) + torch.arange(f_v - 1, device=device,
|
| 567 |
+
dtype=torch.float32) * (
|
| 568 |
+
1.0 / video_effective_fps)
|
| 569 |
+
else:
|
| 570 |
+
t_starts = torch.zeros((0,), device=device, dtype=torch.float32)
|
| 571 |
+
# Convert to audio-step units.
|
| 572 |
+
video_pos_per_frame = t_starts * float(self.audio_fps)
|
| 573 |
+
else:
|
| 574 |
+
# No first-frame bias: uniform alignment.
|
| 575 |
+
scale = float(self.audio_fps) / float(video_fps / 4.0)
|
| 576 |
+
video_pos_per_frame = torch.arange(f_v, device=device, dtype=torch.float32) * scale
|
| 577 |
+
# Flatten to f*h*w; tokens within the same frame share the same time position.
|
| 578 |
+
video_pos = video_pos_per_frame.repeat_interleave(h * w).unsqueeze(0)
|
| 579 |
+
|
| 580 |
+
# print(f"video fps: {video_fps}, audio fps: {self.audio_fps}, scale: {scale}")
|
| 581 |
+
# print(f"video pos: {video_pos.shape}, audio pos: {audio_pos.shape}")
|
| 582 |
+
|
| 583 |
+
# Build dummy x to produce cos/sin, dim=head_dim.
|
| 584 |
+
dummy_v = torch.zeros((1, L_v, self.head_dim), device=device, dtype=dtype)
|
| 585 |
+
dummy_a = torch.zeros((1, L_a, self.head_dim), device=device, dtype=dtype)
|
| 586 |
+
|
| 587 |
+
cos_v, sin_v = self.rotary(dummy_v, position_ids=video_pos)
|
| 588 |
+
cos_a, sin_a = self.rotary(dummy_a, position_ids=audio_pos)
|
| 589 |
+
|
| 590 |
+
return (cos_v, sin_v), (cos_a, sin_a)
|
| 591 |
+
|
| 592 |
+
def should_interact(self, layer_idx: int, direction: str) -> bool:
|
| 593 |
+
return self.controller.should_interact(layer_idx, direction, self.interaction_mapping)
|
| 594 |
+
|
| 595 |
+
def apply_conditional_control(self,
|
| 596 |
+
layer_idx: int,
|
| 597 |
+
direction: str,
|
| 598 |
+
primary_hidden_states: torch.Tensor,
|
| 599 |
+
condition_hidden_states: torch.Tensor,
|
| 600 |
+
x_freqs: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 601 |
+
y_freqs: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 602 |
+
condition_scale: Optional[float] = None,
|
| 603 |
+
video_grid_size: Optional[Tuple[int, int, int]] = None) -> torch.Tensor:
|
| 604 |
+
"""
|
| 605 |
+
Apply conditional control (at the DiT hidden-state level).
|
| 606 |
+
|
| 607 |
+
Args:
|
| 608 |
+
layer_idx: current layer index
|
| 609 |
+
direction: conditioning direction
|
| 610 |
+
- 'a2v': audio hidden states -> visual DiT
|
| 611 |
+
- 'v2a': visual hidden states -> audio DiT
|
| 612 |
+
primary_hidden_states: primary DiT hidden states [B, L, hidden_dim]
|
| 613 |
+
condition_hidden_states: condition DiT hidden states [B, L, hidden_dim]
|
| 614 |
+
condition_scale: conditioning strength (similar to CFG scale)
|
| 615 |
+
|
| 616 |
+
Returns:
|
| 617 |
+
Conditioned primary DiT hidden states [B, L, hidden_dim]
|
| 618 |
+
"""
|
| 619 |
+
|
| 620 |
+
if not self.controller.should_interact(layer_idx, direction, self.interaction_mapping):
|
| 621 |
+
return primary_hidden_states
|
| 622 |
+
|
| 623 |
+
if direction == 'a2v':
|
| 624 |
+
# audio hidden states condition the visual DiT
|
| 625 |
+
conditioner = self.audio_to_video_conditioners[str(layer_idx)]
|
| 626 |
+
|
| 627 |
+
elif direction == 'v2a':
|
| 628 |
+
# visual hidden states condition the audio DiT
|
| 629 |
+
conditioner = self.video_to_audio_conditioners[str(layer_idx)]
|
| 630 |
+
else:
|
| 631 |
+
raise ValueError(f"Invalid direction: {direction}")
|
| 632 |
+
|
| 633 |
+
conditioned_features = conditioner(
|
| 634 |
+
x=primary_hidden_states,
|
| 635 |
+
y=condition_hidden_states,
|
| 636 |
+
x_freqs=x_freqs,
|
| 637 |
+
y_freqs=y_freqs,
|
| 638 |
+
video_grid_size=video_grid_size,
|
| 639 |
+
)
|
| 640 |
+
|
| 641 |
+
if self.trainable_condition_scale and condition_scale is not None:
|
| 642 |
+
print(
|
| 643 |
+
"[WARN] This model has a trainable condition_scale, but an external "
|
| 644 |
+
f"condition_scale={condition_scale} was provided. The trainable condition_scale "
|
| 645 |
+
"will be ignored in favor of the external value."
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
scale = condition_scale if condition_scale is not None else self.condition_scale
|
| 649 |
+
|
| 650 |
+
primary_hidden_states = primary_hidden_states + conditioned_features * scale
|
| 651 |
+
|
| 652 |
+
return primary_hidden_states
|
| 653 |
+
|
| 654 |
+
def forward(
|
| 655 |
+
self,
|
| 656 |
+
layer_idx: int,
|
| 657 |
+
visual_hidden_states: torch.Tensor,
|
| 658 |
+
audio_hidden_states: torch.Tensor,
|
| 659 |
+
*,
|
| 660 |
+
x_freqs: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 661 |
+
y_freqs: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 662 |
+
a2v_condition_scale: Optional[float] = None,
|
| 663 |
+
v2a_condition_scale: Optional[float] = None,
|
| 664 |
+
condition_scale: Optional[float] = None,
|
| 665 |
+
video_grid_size: Optional[Tuple[int, int, int]] = None,
|
| 666 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 667 |
+
"""
|
| 668 |
+
Apply bidirectional conditional control to both visual/audio towers.
|
| 669 |
+
|
| 670 |
+
Args:
|
| 671 |
+
layer_idx: current layer index
|
| 672 |
+
visual_hidden_states: visual DiT hidden states
|
| 673 |
+
audio_hidden_states: audio DiT hidden states
|
| 674 |
+
x_freqs / y_freqs: cross-modal RoPE (cos, sin) pairs.
|
| 675 |
+
If provided, x_freqs is assumed to correspond to the primary tower and y_freqs
|
| 676 |
+
to the conditioning tower.
|
| 677 |
+
a2v_condition_scale: audio->visual conditioning strength (overrides global condition_scale)
|
| 678 |
+
v2a_condition_scale: visual->audio conditioning strength (overrides global condition_scale)
|
| 679 |
+
condition_scale: fallback conditioning strength when per-direction scale is None
|
| 680 |
+
video_grid_size: (F, H, W), used on the audio side when pooled_adaln is enabled
|
| 681 |
+
|
| 682 |
+
Returns:
|
| 683 |
+
(visual_hidden_states, audio_hidden_states), both conditioned in their respective directions.
|
| 684 |
+
"""
|
| 685 |
+
|
| 686 |
+
visual_conditioned = self.apply_conditional_control(
|
| 687 |
+
layer_idx=layer_idx,
|
| 688 |
+
direction="a2v",
|
| 689 |
+
primary_hidden_states=visual_hidden_states,
|
| 690 |
+
condition_hidden_states=audio_hidden_states,
|
| 691 |
+
x_freqs=x_freqs,
|
| 692 |
+
y_freqs=y_freqs,
|
| 693 |
+
condition_scale=a2v_condition_scale if a2v_condition_scale is not None else condition_scale,
|
| 694 |
+
video_grid_size=video_grid_size,
|
| 695 |
+
)
|
| 696 |
+
|
| 697 |
+
audio_conditioned = self.apply_conditional_control(
|
| 698 |
+
layer_idx=layer_idx,
|
| 699 |
+
direction="v2a",
|
| 700 |
+
primary_hidden_states=audio_hidden_states,
|
| 701 |
+
condition_hidden_states=visual_hidden_states,
|
| 702 |
+
x_freqs=y_freqs,
|
| 703 |
+
y_freqs=x_freqs,
|
| 704 |
+
condition_scale=v2a_condition_scale if v2a_condition_scale is not None else condition_scale,
|
| 705 |
+
video_grid_size=video_grid_size,
|
| 706 |
+
)
|
| 707 |
+
|
| 708 |
+
return visual_conditioned, audio_conditioned
|
hymm/models/modules/mova.py
ADDED
|
@@ -0,0 +1,964 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gc
|
| 2 |
+
import json
|
| 3 |
+
import os
|
| 4 |
+
from typing import Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
import torch.utils.checkpoint as torch_checkpoint
|
| 9 |
+
from diffusers import ModelMixin, ConfigMixin
|
| 10 |
+
|
| 11 |
+
from hymm.models.modules.wan_video_dit import (
|
| 12 |
+
DiTBlock,
|
| 13 |
+
WanModel,
|
| 14 |
+
sinusoidal_embedding_1d,
|
| 15 |
+
advance_dynamic_block_pass_id,
|
| 16 |
+
)
|
| 17 |
+
from hymm.models.modules.wan_audio_dit import WanAudioModel
|
| 18 |
+
from hymm.models.modules.interactionv2 import (
|
| 19 |
+
ConditionalCrossAttentionBlock,
|
| 20 |
+
DualTowerConditionalBridge,
|
| 21 |
+
)
|
| 22 |
+
from hymm.utils.parallel_states import get_sequence_parallel_state, nccl_info, sp_seq_ctx
|
| 23 |
+
from hymm.utils.communications import all_gather, sp_split, assert_sp_splittable
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def assemble_visual_freqs(freqs_tuple, t, h, w, device):
|
| 27 |
+
"""Assemble 3D RoPE complex-valued freqs for the video DiT.
|
| 28 |
+
|
| 29 |
+
Matches the inline assembly in WanModel.forward() and
|
| 30 |
+
MOVA/mova/diffusion/pipelines/pipeline_mova.py.
|
| 31 |
+
|
| 32 |
+
Args:
|
| 33 |
+
freqs_tuple: (f_freqs_cis, h_freqs_cis, w_freqs_cis) from
|
| 34 |
+
precompute_freqs_cis_3d. Each is a complex tensor.
|
| 35 |
+
t, h, w: temporal, height, width grid dimensions (after patchify).
|
| 36 |
+
device: target device.
|
| 37 |
+
|
| 38 |
+
Returns:
|
| 39 |
+
Complex tensor of shape [t*h*w, 1, head_dim//2].
|
| 40 |
+
"""
|
| 41 |
+
freqs = tuple(f.to(device) for f in freqs_tuple)
|
| 42 |
+
return torch.cat([
|
| 43 |
+
freqs[0][:t].view(t, 1, 1, -1).expand(t, h, w, -1),
|
| 44 |
+
freqs[1][:h].view(1, h, 1, -1).expand(t, h, w, -1),
|
| 45 |
+
freqs[2][:w].view(1, 1, w, -1).expand(t, h, w, -1),
|
| 46 |
+
], dim=-1).reshape(t * h * w, 1, -1)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def assemble_audio_freqs(freqs_tuple, f, device):
|
| 50 |
+
"""Assemble 1D RoPE complex-valued freqs for the audio DiT.
|
| 51 |
+
|
| 52 |
+
Matches the inline assembly in WanAudioModel.forward() and
|
| 53 |
+
MOVA/mova/diffusion/pipelines/pipeline_mova.py.
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
freqs_tuple: (f0, f1, f2) from precompute_freqs_cis.
|
| 57 |
+
f: sequence length (after audio patchify).
|
| 58 |
+
device: target device.
|
| 59 |
+
|
| 60 |
+
Returns:
|
| 61 |
+
Complex tensor of shape [f, 1, head_dim//2].
|
| 62 |
+
"""
|
| 63 |
+
return torch.cat([
|
| 64 |
+
freqs_tuple[0][:f].view(f, -1).expand(f, -1),
|
| 65 |
+
freqs_tuple[1][:f].view(f, -1).expand(f, -1),
|
| 66 |
+
freqs_tuple[2][:f].view(f, -1).expand(f, -1),
|
| 67 |
+
], dim=-1).reshape(f, 1, -1).to(device)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class FusedMOVABlock(nn.Module):
|
| 72 |
+
|
| 73 |
+
def __init__(self, video_block, audio_block,
|
| 74 |
+
a2v_conditioner=None, v2a_conditioner=None):
|
| 75 |
+
super().__init__()
|
| 76 |
+
self.video_block = video_block
|
| 77 |
+
self.audio_block = audio_block
|
| 78 |
+
self.a2v_conditioner = a2v_conditioner
|
| 79 |
+
self.v2a_conditioner = v2a_conditioner
|
| 80 |
+
|
| 81 |
+
# Mark inner DiTBlocks so apply_fsdp_checkpointing skips them,
|
| 82 |
+
# avoiding nested checkpointing (outer FusedMOVABlock already covers them).
|
| 83 |
+
self.video_block._inside_fused_block = True
|
| 84 |
+
self.audio_block._inside_fused_block = True
|
| 85 |
+
|
| 86 |
+
# Set to True by _apply_mova_fsdp_checkpointing (only when the YAML trigger fine_grained_gc is enabled) for blocks that ALSO get the outer block-level checkpoint
|
| 87 |
+
# wrapper. When on (and training), the a2v/v2a bridge submodules are
|
| 88 |
+
# additionally inner-checkpointed (deferred) so they are NOT co-resident
|
| 89 |
+
# with the video block at the video-FFN recompute peak. The video/audio
|
| 90 |
+
# DiT blocks run inline (Version B — see forward()). Pure recompute-
|
| 91 |
+
# schedule change: fwd/bwd numerics and loss are bit-identical to the
|
| 92 |
+
# default whole-block checkpointing.
|
| 93 |
+
self.fine_grained_gc = False
|
| 94 |
+
|
| 95 |
+
def forward(
|
| 96 |
+
self,
|
| 97 |
+
visual_x, audio_x,
|
| 98 |
+
visual_context, audio_context,
|
| 99 |
+
visual_t_mod, audio_t_mod,
|
| 100 |
+
visual_freqs, audio_freqs,
|
| 101 |
+
visual_rope_cos_sin=None, audio_rope_cos_sin=None,
|
| 102 |
+
a2v_scale=1.0, v2a_scale=1.0,
|
| 103 |
+
video_grid_size=None,
|
| 104 |
+
override_video_block=None,
|
| 105 |
+
timestep_ratio=None,
|
| 106 |
+
):
|
| 107 |
+
# Fine-grained (nested) checkpointing helper. wraps ONLY the a2v/v2a bridges; the video/audio
|
| 108 |
+
# blocks run inline. All arguments are passed POSITIONALLY in
|
| 109 |
+
# the submodule's signature order, so the computation is byte-for-byte identical to the un-checkpointed calls (checkpointing only re-runs forward; there are no RNG ops here).
|
| 110 |
+
inner_gc = self.training and self.fine_grained_gc
|
| 111 |
+
|
| 112 |
+
def _maybe_ckpt(fn, *args):
|
| 113 |
+
if inner_gc:
|
| 114 |
+
return torch_checkpoint.checkpoint(fn, *args, use_reentrant=False)
|
| 115 |
+
return fn(*args)
|
| 116 |
+
|
| 117 |
+
# Bridge conditioning — must use ORIGINAL states for both directions,
|
| 118 |
+
# matching DualTowerConditionalBridge.forward which computes a2v and v2a
|
| 119 |
+
# from the same unmodified inputs before returning both conditioned outputs.
|
| 120 |
+
visual_x_orig = visual_x
|
| 121 |
+
|
| 122 |
+
a2v_bridge_residual = None
|
| 123 |
+
if self.a2v_conditioner is not None:
|
| 124 |
+
# Positional order matches ConditionalCrossAttentionBlock.forward:
|
| 125 |
+
# (x, y, x_freqs, y_freqs, video_grid_size, q_structure, k_structure,
|
| 126 |
+
# q_grid_size, k_grid_size)
|
| 127 |
+
cond = _maybe_ckpt(
|
| 128 |
+
self.a2v_conditioner,
|
| 129 |
+
visual_x, audio_x,
|
| 130 |
+
visual_rope_cos_sin, audio_rope_cos_sin,
|
| 131 |
+
video_grid_size,
|
| 132 |
+
"3d", "1d",
|
| 133 |
+
video_grid_size, None,
|
| 134 |
+
)
|
| 135 |
+
# Store the raw bridge residual (before scaling) for audio guidance
|
| 136 |
+
a2v_bridge_residual = cond
|
| 137 |
+
visual_x = visual_x + cond * a2v_scale
|
| 138 |
+
|
| 139 |
+
if self.v2a_conditioner is not None:
|
| 140 |
+
cond = _maybe_ckpt(
|
| 141 |
+
self.v2a_conditioner,
|
| 142 |
+
audio_x, visual_x_orig,
|
| 143 |
+
audio_rope_cos_sin, visual_rope_cos_sin,
|
| 144 |
+
video_grid_size,
|
| 145 |
+
"1d", "3d",
|
| 146 |
+
None, video_grid_size,
|
| 147 |
+
)
|
| 148 |
+
audio_x = audio_x + cond * v2a_scale
|
| 149 |
+
|
| 150 |
+
# DiT blocks (allow video block override for video_dit_2).
|
| 151 |
+
# If this OOMs -> wrapping BOTH calls below in _maybe_ckpt(...) (positional order as in the bridges above).
|
| 152 |
+
# Numerically bit-identical either way (checkpointing only re-runs fwd).
|
| 153 |
+
vblock = override_video_block if override_video_block is not None else self.video_block
|
| 154 |
+
|
| 155 |
+
visual_x = vblock(
|
| 156 |
+
visual_x, visual_context, visual_t_mod, visual_freqs,
|
| 157 |
+
grid_size=video_grid_size,
|
| 158 |
+
a2v_bridge_residual=a2v_bridge_residual,
|
| 159 |
+
timestep_ratio=timestep_ratio,
|
| 160 |
+
)
|
| 161 |
+
audio_x = self.audio_block(audio_x, audio_context, audio_t_mod, audio_freqs)
|
| 162 |
+
|
| 163 |
+
return visual_x, audio_x
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
# ---------------------------------------------------------------------------
|
| 167 |
+
# MOVABridge — top-level wrapper
|
| 168 |
+
# ---------------------------------------------------------------------------
|
| 169 |
+
|
| 170 |
+
class MOVABridge(ModelMixin, ConfigMixin):
|
| 171 |
+
|
| 172 |
+
def __init__(
|
| 173 |
+
self,
|
| 174 |
+
video_dit: WanModel,
|
| 175 |
+
video_dit_2: Optional[WanModel],
|
| 176 |
+
audio_dit: WanAudioModel,
|
| 177 |
+
dual_tower_bridge: DualTowerConditionalBridge,
|
| 178 |
+
boundary_ratio: float = 0.9,
|
| 179 |
+
gradient_checkpoint: bool = False,
|
| 180 |
+
):
|
| 181 |
+
super().__init__()
|
| 182 |
+
self.boundary_ratio = boundary_ratio
|
| 183 |
+
self.gradient_checkpoint = gradient_checkpoint
|
| 184 |
+
|
| 185 |
+
min_layers = min(len(video_dit.blocks), len(audio_dit.blocks))
|
| 186 |
+
|
| 187 |
+
# --- Build fused blocks (video + audio + bridge per layer) ---
|
| 188 |
+
self.fusion_blocks = nn.ModuleList()
|
| 189 |
+
for i in range(min_layers):
|
| 190 |
+
key = str(i)
|
| 191 |
+
a2v = dual_tower_bridge.audio_to_video_conditioners[key] if key in dual_tower_bridge.audio_to_video_conditioners else None
|
| 192 |
+
v2a = dual_tower_bridge.video_to_audio_conditioners[key] if key in dual_tower_bridge.video_to_audio_conditioners else None
|
| 193 |
+
fused = FusedMOVABlock(
|
| 194 |
+
video_block=video_dit.blocks[i],
|
| 195 |
+
audio_block=audio_dit.blocks[i],
|
| 196 |
+
a2v_conditioner=a2v,
|
| 197 |
+
v2a_conditioner=v2a,
|
| 198 |
+
)
|
| 199 |
+
self.fusion_blocks.append(fused)
|
| 200 |
+
|
| 201 |
+
# Remaining video-only blocks (primary dit, beyond min_layers)
|
| 202 |
+
self.remaining_video_blocks = nn.ModuleList(
|
| 203 |
+
[video_dit.blocks[i] for i in range(min_layers, len(video_dit.blocks))]
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
# --- Clear extracted blocks to avoid double registration ---
|
| 207 |
+
video_dit.blocks = nn.ModuleList()
|
| 208 |
+
audio_dit.blocks = nn.ModuleList()
|
| 209 |
+
dual_tower_bridge.audio_to_video_conditioners = nn.ModuleDict()
|
| 210 |
+
dual_tower_bridge.video_to_audio_conditioners = nn.ModuleDict()
|
| 211 |
+
|
| 212 |
+
# Keep DiTs for utility functions (patchify, unpatchify, head, embeddings, freqs)
|
| 213 |
+
self.video_dit = video_dit
|
| 214 |
+
self.audio_dit = audio_dit
|
| 215 |
+
|
| 216 |
+
# Keep bridge for rotary / build_aligned_freqs / condition_scale
|
| 217 |
+
self.dual_tower_bridge = dual_tower_bridge
|
| 218 |
+
|
| 219 |
+
# Secondary video dit (kept as-is with its own blocks, not fused).
|
| 220 |
+
# Mark override blocks (0..min_layers-1) so they are skipped by
|
| 221 |
+
# apply_fsdp_checkpointing — they run inside a checkpointed
|
| 222 |
+
# FusedMOVABlock and must not be double-wrapped.
|
| 223 |
+
if video_dit_2 is not None:
|
| 224 |
+
for i in range(min(min_layers, len(video_dit_2.blocks))):
|
| 225 |
+
video_dit_2.blocks[i]._inside_fused_block = True
|
| 226 |
+
self.video_dit_2 = video_dit_2
|
| 227 |
+
|
| 228 |
+
# When True, ALL sparse-attention features (top-k/top-p BSA, audio guidance,
|
| 229 |
+
# variance guidance, taylor/rectified bias correction, dynamic block shape)
|
| 230 |
+
# are applied ONLY to the high-noise expert (video_dit = fusion_blocks +
|
| 231 |
+
# remaining_video_blocks). The low-noise expert (video_dit_2) then keeps the
|
| 232 |
+
# backbone's original dense full attention. Default False = sparse on BOTH
|
| 233 |
+
# experts (legacy behavior). Plain python attr (not an FSDP param/buffer).
|
| 234 |
+
self.sparse_high_noise_only = False
|
| 235 |
+
|
| 236 |
+
self._tag_sp_streams()
|
| 237 |
+
|
| 238 |
+
gc.collect()
|
| 239 |
+
|
| 240 |
+
def _tag_sp_streams(self):
|
| 241 |
+
"""Tell every attention which SP-sharded stream its tensors belong to.
|
| 242 |
+
|
| 243 |
+
Under sequence parallelism the visual and audio towers are chunked to
|
| 244 |
+
different lengths, and the Ulysses all-to-all has to undo the exact
|
| 245 |
+
``torch.chunk`` split of the stream it is handling. Attentions default to
|
| 246 |
+
"visual", so only the audio tower and the two bridge directions need
|
| 247 |
+
retagging here.
|
| 248 |
+
"""
|
| 249 |
+
from hymm.models.modules.wan_audio_dit import tag_audio_sp_stream
|
| 250 |
+
|
| 251 |
+
for fused in self.fusion_blocks:
|
| 252 |
+
tag_audio_sp_stream([fused.audio_block])
|
| 253 |
+
if fused.a2v_conditioner is not None:
|
| 254 |
+
# a2v: query = visual tokens, context = audio tokens
|
| 255 |
+
fused.a2v_conditioner.set_sp_streams("visual", "audio")
|
| 256 |
+
if fused.v2a_conditioner is not None:
|
| 257 |
+
# v2a: query = audio tokens, context = visual tokens
|
| 258 |
+
fused.v2a_conditioner.set_sp_streams("audio", "visual")
|
| 259 |
+
|
| 260 |
+
@classmethod
|
| 261 |
+
def from_mova_pretrained(cls, ckpt_path, torch_dtype=torch.bfloat16, logger=None):
|
| 262 |
+
"""
|
| 263 |
+
Load from a MOVA HuggingFace-style checkpoint directory.
|
| 264 |
+
|
| 265 |
+
The directory should contain subfolders:
|
| 266 |
+
video_dit/, video_dit_2/, audio_dit/, dual_tower_bridge/,
|
| 267 |
+
video_vae/, audio_vae/, text_encoder/, tokenizer/, scheduler/
|
| 268 |
+
|
| 269 |
+
Returns:
|
| 270 |
+
(mova_bridge, extra_components) where extra_components is a dict
|
| 271 |
+
containing {video_vae, audio_vae, text_encoder, tokenizer, scheduler,
|
| 272 |
+
boundary_ratio, audio_vae_type}.
|
| 273 |
+
"""
|
| 274 |
+
from diffusers.models.autoencoders import AutoencoderKLWan
|
| 275 |
+
from transformers import T5TokenizerFast, UMT5EncoderModel
|
| 276 |
+
|
| 277 |
+
from hymm.models.modules.dac_vae import DAC
|
| 278 |
+
from hymm.diffusion.schedulers.flow_match_pair import FlowMatchPairScheduler
|
| 279 |
+
|
| 280 |
+
_log = logger.info if logger else print
|
| 281 |
+
|
| 282 |
+
import time as _time
|
| 283 |
+
_load_start = _time.time()
|
| 284 |
+
_log(f"[MOVABridge] Loading from {ckpt_path}")
|
| 285 |
+
|
| 286 |
+
# Trainable components: load in requested dtype on CPU
|
| 287 |
+
_t0 = _time.time()
|
| 288 |
+
video_dit = WanModel.from_pretrained(
|
| 289 |
+
ckpt_path, subfolder="video_dit", torch_dtype=torch_dtype,
|
| 290 |
+
).cpu()
|
| 291 |
+
_log(f"[MOVABridge] Loaded video_dit ({_time.time()-_t0:.1f}s)")
|
| 292 |
+
|
| 293 |
+
video_dit_2_path = os.path.join(ckpt_path, "video_dit_2")
|
| 294 |
+
if os.path.isdir(video_dit_2_path):
|
| 295 |
+
_t0 = _time.time()
|
| 296 |
+
video_dit_2 = WanModel.from_pretrained(
|
| 297 |
+
ckpt_path, subfolder="video_dit_2", torch_dtype=torch_dtype,
|
| 298 |
+
).cpu()
|
| 299 |
+
_log(f"[MOVABridge] Loaded video_dit_2 ({_time.time()-_t0:.1f}s)")
|
| 300 |
+
else:
|
| 301 |
+
video_dit_2 = None
|
| 302 |
+
_log("[MOVABridge] No video_dit_2 found, skipping")
|
| 303 |
+
|
| 304 |
+
_t0 = _time.time()
|
| 305 |
+
audio_dit = WanAudioModel.from_pretrained(
|
| 306 |
+
ckpt_path, subfolder="audio_dit", torch_dtype=torch_dtype,
|
| 307 |
+
).cpu()
|
| 308 |
+
_log(f"[MOVABridge] Loaded audio_dit ({_time.time()-_t0:.1f}s)")
|
| 309 |
+
|
| 310 |
+
_t0 = _time.time()
|
| 311 |
+
dual_tower_bridge = DualTowerConditionalBridge.from_pretrained(
|
| 312 |
+
ckpt_path, subfolder="dual_tower_bridge", torch_dtype=torch_dtype,
|
| 313 |
+
).cpu()
|
| 314 |
+
_log(f"[MOVABridge] Loaded dual_tower_bridge ({_time.time()-_t0:.1f}s)")
|
| 315 |
+
|
| 316 |
+
# Frozen components: load in bf16 on CPU to save memory
|
| 317 |
+
frozen_dtype = torch.bfloat16
|
| 318 |
+
|
| 319 |
+
_t0 = _time.time()
|
| 320 |
+
video_vae = AutoencoderKLWan.from_pretrained(
|
| 321 |
+
ckpt_path, subfolder="video_vae", torch_dtype=frozen_dtype,
|
| 322 |
+
).cpu()
|
| 323 |
+
_log(f"[MOVABridge] Loaded video_vae (bf16, frozen, {_time.time()-_t0:.1f}s)")
|
| 324 |
+
|
| 325 |
+
_t0 = _time.time()
|
| 326 |
+
audio_vae = DAC.from_pretrained(
|
| 327 |
+
ckpt_path, subfolder="audio_vae", torch_dtype=frozen_dtype,
|
| 328 |
+
).cpu()
|
| 329 |
+
_log(f"[MOVABridge] Loaded audio_vae (bf16, frozen, {_time.time()-_t0:.1f}s)")
|
| 330 |
+
|
| 331 |
+
_t0 = _time.time()
|
| 332 |
+
text_encoder = UMT5EncoderModel.from_pretrained(
|
| 333 |
+
ckpt_path, subfolder="text_encoder", torch_dtype=frozen_dtype,
|
| 334 |
+
).to("cpu")
|
| 335 |
+
_log(f"[MOVABridge] Loaded text_encoder (bf16, frozen, {_time.time()-_t0:.1f}s)")
|
| 336 |
+
_log(f"[MOVABridge] Total model loading time: {_time.time()-_load_start:.1f}s")
|
| 337 |
+
|
| 338 |
+
tokenizer = T5TokenizerFast.from_pretrained(
|
| 339 |
+
ckpt_path, subfolder="tokenizer",
|
| 340 |
+
)
|
| 341 |
+
_log("[MOVABridge] Loaded tokenizer")
|
| 342 |
+
|
| 343 |
+
scheduler_path = os.path.join(ckpt_path, "scheduler")
|
| 344 |
+
if os.path.isdir(scheduler_path):
|
| 345 |
+
scheduler = FlowMatchPairScheduler.from_pretrained(
|
| 346 |
+
ckpt_path, subfolder="scheduler",
|
| 347 |
+
)
|
| 348 |
+
_log("[MOVABridge] Loaded scheduler from pretrained")
|
| 349 |
+
else:
|
| 350 |
+
scheduler = FlowMatchPairScheduler(shift=5.0)
|
| 351 |
+
_log("[MOVABridge] Using default FlowMatchPairScheduler(shift=5.0)")
|
| 352 |
+
|
| 353 |
+
model_index_path = os.path.join(ckpt_path, "model_index.json")
|
| 354 |
+
boundary_ratio = 0.9
|
| 355 |
+
audio_vae_type = "dac"
|
| 356 |
+
if os.path.exists(model_index_path):
|
| 357 |
+
with open(model_index_path, "r") as f:
|
| 358 |
+
model_index = json.load(f)
|
| 359 |
+
boundary_ratio = model_index.get("boundary_ratio", 0.9)
|
| 360 |
+
audio_vae_type = model_index.get("audio_vae_type", "dac")
|
| 361 |
+
|
| 362 |
+
bridge = cls(
|
| 363 |
+
video_dit=video_dit,
|
| 364 |
+
video_dit_2=video_dit_2,
|
| 365 |
+
audio_dit=audio_dit,
|
| 366 |
+
dual_tower_bridge=dual_tower_bridge,
|
| 367 |
+
boundary_ratio=boundary_ratio,
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
extra_components = {
|
| 371 |
+
"video_vae": video_vae,
|
| 372 |
+
"audio_vae": audio_vae,
|
| 373 |
+
"text_encoder": text_encoder,
|
| 374 |
+
"tokenizer": tokenizer,
|
| 375 |
+
"scheduler": scheduler,
|
| 376 |
+
"boundary_ratio": boundary_ratio,
|
| 377 |
+
"audio_vae_type": audio_vae_type,
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
return bridge, extra_components
|
| 381 |
+
|
| 382 |
+
# -----------------------------------------------------------------------
|
| 383 |
+
# FSDP helpers
|
| 384 |
+
# -----------------------------------------------------------------------
|
| 385 |
+
|
| 386 |
+
@staticmethod
|
| 387 |
+
def get_fsdp_no_split_modules():
|
| 388 |
+
"""Return module types that should not be split by FSDP."""
|
| 389 |
+
return (FusedMOVABlock, DiTBlock)
|
| 390 |
+
|
| 391 |
+
def get_fsdp_block_list(self):
|
| 392 |
+
"""Return a flat list of all blocks for FSDP wrapping."""
|
| 393 |
+
blocks = list(self.fusion_blocks) + list(self.remaining_video_blocks)
|
| 394 |
+
if self.video_dit_2 is not None:
|
| 395 |
+
blocks.extend(self.video_dit_2.blocks)
|
| 396 |
+
return blocks
|
| 397 |
+
|
| 398 |
+
def get_fsdp_nested_block_list(self):
|
| 399 |
+
"""Sub-blocks that need an FSDP unit of their own inside a parent block.
|
| 400 |
+
|
| 401 |
+
Each FusedMOVABlock owns the high-noise expert's video block, but on a
|
| 402 |
+
low-noise step that block is bypassed in favour of ``video_dit_2``. If it
|
| 403 |
+
stays in the fused block's parameter group it is still all-gathered on
|
| 404 |
+
every one of those steps -- twice, since activation checkpointing
|
| 405 |
+
re-runs the forward during backward -- and then discarded unused. Its own
|
| 406 |
+
group is simply never entered, the same way ``video_dit_2.blocks`` are
|
| 407 |
+
never entered on a high-noise step.
|
| 408 |
+
|
| 409 |
+
Only worth doing when a second expert exists; otherwise the video block
|
| 410 |
+
runs every step and splitting it just buys an extra collective.
|
| 411 |
+
"""
|
| 412 |
+
if self.video_dit_2 is None:
|
| 413 |
+
return []
|
| 414 |
+
return [fused.video_block for fused in self.fusion_blocks]
|
| 415 |
+
|
| 416 |
+
def get_fsdp_forward_tail_blocks(self):
|
| 417 |
+
"""Blocks that can be the LAST one executed in a forward pass.
|
| 418 |
+
|
| 419 |
+
FSDP re-gathers the final block almost immediately for backward, so
|
| 420 |
+
resharding it after forward is a wasted round trip. Which block ends the
|
| 421 |
+
forward depends on the expert this step routed to, so both candidates are
|
| 422 |
+
returned rather than just the last entry of ``get_fsdp_block_list()`` --
|
| 423 |
+
that entry belongs to ``video_dit_2`` and is idle on high-noise steps.
|
| 424 |
+
"""
|
| 425 |
+
tails = []
|
| 426 |
+
high_noise_tail = self.remaining_video_blocks or self.fusion_blocks
|
| 427 |
+
if len(high_noise_tail) > 0:
|
| 428 |
+
tails.append(high_noise_tail[-1])
|
| 429 |
+
if self.video_dit_2 is not None and len(self.video_dit_2.blocks) > 0:
|
| 430 |
+
tails.append(self.video_dit_2.blocks[-1])
|
| 431 |
+
return tails
|
| 432 |
+
|
| 433 |
+
# -----------------------------------------------------------------------
|
| 434 |
+
# BSA configuration
|
| 435 |
+
# -----------------------------------------------------------------------
|
| 436 |
+
|
| 437 |
+
def set_sparse_high_noise_only(self, flag: bool):
|
| 438 |
+
"""Restrict ALL video self-attn sparse features to the high-noise expert.
|
| 439 |
+
|
| 440 |
+
When ``flag`` is True, every subsequent ``configure_*`` call (BSA top-k/top-p,
|
| 441 |
+
audio guidance, variance guidance, taylor/rectified bias correction, IVPQ /
|
| 442 |
+
penalty dynamic block, layer-adaptive) skips the low-noise expert
|
| 443 |
+
(``video_dit_2``), leaving it on the backbone's dense full attention. The
|
| 444 |
+
high-noise expert (``fusion_blocks`` + ``remaining_video_blocks``) is
|
| 445 |
+
unaffected. Must be called BEFORE the ``configure_*`` methods. Default False
|
| 446 |
+
= sparse applied to BOTH experts (legacy behavior).
|
| 447 |
+
"""
|
| 448 |
+
self.sparse_high_noise_only = bool(flag)
|
| 449 |
+
|
| 450 |
+
def configure_bsa(self, enable_bsa: bool, bsa_params: dict = None,
|
| 451 |
+
enable_bsa_v2a: bool = False, bsa_params_v2a: dict = None):
|
| 452 |
+
"""Runtime configuration of Block Sparse Attention.
|
| 453 |
+
|
| 454 |
+
Args:
|
| 455 |
+
enable_bsa: enable BSA on video self-attention
|
| 456 |
+
bsa_params: params for video self-attn BSA
|
| 457 |
+
enable_bsa_v2a: enable BSA on v2a bridge cross-attention
|
| 458 |
+
(Q=audio 1D blocked internally, K=video 3D blocked)
|
| 459 |
+
a2v does NOT use BSA — audio K is too short.
|
| 460 |
+
bsa_params_v2a: params for v2a BSA
|
| 461 |
+
"""
|
| 462 |
+
self._bsa_enabled = enable_bsa
|
| 463 |
+
self._bsa_params = bsa_params or {}
|
| 464 |
+
for fused_block in self.fusion_blocks:
|
| 465 |
+
fused_block.video_block.self_attn.enable_bsa = enable_bsa
|
| 466 |
+
fused_block.video_block.self_attn.bsa_params = bsa_params or {}
|
| 467 |
+
if fused_block.v2a_conditioner is not None:
|
| 468 |
+
fused_block.v2a_conditioner.inner.enable_bsa = enable_bsa_v2a
|
| 469 |
+
fused_block.v2a_conditioner.inner.bsa_params = bsa_params_v2a or {}
|
| 470 |
+
for block in self.remaining_video_blocks:
|
| 471 |
+
block.self_attn.enable_bsa = enable_bsa
|
| 472 |
+
block.self_attn.bsa_params = bsa_params or {}
|
| 473 |
+
if self.video_dit_2 is not None and not self.sparse_high_noise_only:
|
| 474 |
+
for block in self.video_dit_2.blocks:
|
| 475 |
+
block.self_attn.enable_bsa = enable_bsa
|
| 476 |
+
block.self_attn.bsa_params = bsa_params or {}
|
| 477 |
+
|
| 478 |
+
# -----------------------------------------------------------------------
|
| 479 |
+
# Audio Guidance configuration
|
| 480 |
+
# -----------------------------------------------------------------------
|
| 481 |
+
|
| 482 |
+
def configure_audio_guidance(self, enable: bool, enable_concentration_gate: bool = False,
|
| 483 |
+
enable_timestep_reliability_gate: bool = False,
|
| 484 |
+
enable_audio_weighted_pooling: bool = False):
|
| 485 |
+
"""Runtime configuration of audio-guided BSA on video self-attention.
|
| 486 |
+
|
| 487 |
+
Two independent paths:
|
| 488 |
+
- Path A (enable=True): Multiplicative score fusion with optional gates.
|
| 489 |
+
- Path B (enable_audio_weighted_pooling=True): Audio-weighted K pooling.
|
| 490 |
+
|
| 491 |
+
Either or both can be enabled independently.
|
| 492 |
+
|
| 493 |
+
Args:
|
| 494 |
+
enable: whether to enable Path A (score modulation).
|
| 495 |
+
enable_concentration_gate: Gate 2 for Path A.
|
| 496 |
+
enable_timestep_reliability_gate: Gate 1 for Path A.
|
| 497 |
+
enable_audio_weighted_pooling: whether to enable Path B (weighted K pooling).
|
| 498 |
+
"""
|
| 499 |
+
for fused_block in self.fusion_blocks:
|
| 500 |
+
sa = fused_block.video_block.self_attn
|
| 501 |
+
sa.enable_audio_guidance = enable
|
| 502 |
+
sa.enable_audio_concentration_gate = enable_concentration_gate
|
| 503 |
+
sa.enable_timestep_reliability_gate = enable_timestep_reliability_gate
|
| 504 |
+
sa.enable_audio_weighted_pooling = enable_audio_weighted_pooling
|
| 505 |
+
for block in self.remaining_video_blocks:
|
| 506 |
+
sa = block.self_attn
|
| 507 |
+
sa.enable_audio_guidance = enable
|
| 508 |
+
sa.enable_audio_concentration_gate = enable_concentration_gate
|
| 509 |
+
sa.enable_timestep_reliability_gate = enable_timestep_reliability_gate
|
| 510 |
+
sa.enable_audio_weighted_pooling = enable_audio_weighted_pooling
|
| 511 |
+
if self.video_dit_2 is not None and not self.sparse_high_noise_only:
|
| 512 |
+
for block in self.video_dit_2.blocks:
|
| 513 |
+
sa = block.self_attn
|
| 514 |
+
sa.enable_audio_guidance = enable
|
| 515 |
+
sa.enable_audio_concentration_gate = enable_concentration_gate
|
| 516 |
+
sa.enable_timestep_reliability_gate = enable_timestep_reliability_gate
|
| 517 |
+
sa.enable_audio_weighted_pooling = enable_audio_weighted_pooling
|
| 518 |
+
|
| 519 |
+
# -----------------------------------------------------------------------
|
| 520 |
+
# Variance Guidance configuration (independent from audio guidance)
|
| 521 |
+
# -----------------------------------------------------------------------
|
| 522 |
+
|
| 523 |
+
def configure_variance_guidance(self, enable: bool):
|
| 524 |
+
"""Runtime configuration of channel-variance guided BSA on video self-attention.
|
| 525 |
+
|
| 526 |
+
Non-learnable, no gate. Computes ρ_j on V blocks (key/value side only):
|
| 527 |
+
score_{i,j} *= (1 + γ * ρ_j)
|
| 528 |
+
Query-awareness via P^QK zero-gate: same ρ_j, different QK scores per query
|
| 529 |
+
→ different top-k per query block. γ read from bsa_params['variance_boost_gamma'].
|
| 530 |
+
Completely independent from audio guidance — no shared code.
|
| 531 |
+
|
| 532 |
+
Args:
|
| 533 |
+
enable: whether to enable channel-variance guidance.
|
| 534 |
+
"""
|
| 535 |
+
for fused_block in self.fusion_blocks:
|
| 536 |
+
fused_block.video_block.self_attn.enable_variance_guidance = enable
|
| 537 |
+
for block in self.remaining_video_blocks:
|
| 538 |
+
block.self_attn.enable_variance_guidance = enable
|
| 539 |
+
if self.video_dit_2 is not None and not self.sparse_high_noise_only:
|
| 540 |
+
for block in self.video_dit_2.blocks:
|
| 541 |
+
block.self_attn.enable_variance_guidance = enable
|
| 542 |
+
|
| 543 |
+
def configure_taylor_sparse_attn(self, enable: bool):
|
| 544 |
+
"""LIVEditor: selected + non-selected (Taylor) in one unified softmax."""
|
| 545 |
+
for fused_block in self.fusion_blocks:
|
| 546 |
+
fused_block.video_block.self_attn.enable_taylor_sparse_attn = enable
|
| 547 |
+
if enable:
|
| 548 |
+
fused_block.video_block.self_attn.enable_rectified_sparse_attn = False
|
| 549 |
+
for block in self.remaining_video_blocks:
|
| 550 |
+
block.self_attn.enable_taylor_sparse_attn = enable
|
| 551 |
+
if enable:
|
| 552 |
+
block.self_attn.enable_rectified_sparse_attn = False
|
| 553 |
+
if self.video_dit_2 is not None and not self.sparse_high_noise_only:
|
| 554 |
+
for block in self.video_dit_2.blocks:
|
| 555 |
+
block.self_attn.enable_taylor_sparse_attn = enable
|
| 556 |
+
if enable:
|
| 557 |
+
block.self_attn.enable_rectified_sparse_attn = False
|
| 558 |
+
|
| 559 |
+
def configure_rectified_sparse_attn(self, enable: bool):
|
| 560 |
+
"""Rectified SpaAttn: o = R_n * o_spa + A_pool[nonsel] · V_pool."""
|
| 561 |
+
for fused_block in self.fusion_blocks:
|
| 562 |
+
fused_block.video_block.self_attn.enable_rectified_sparse_attn = enable
|
| 563 |
+
if enable:
|
| 564 |
+
fused_block.video_block.self_attn.enable_taylor_sparse_attn = False
|
| 565 |
+
for block in self.remaining_video_blocks:
|
| 566 |
+
block.self_attn.enable_rectified_sparse_attn = enable
|
| 567 |
+
if enable:
|
| 568 |
+
block.self_attn.enable_taylor_sparse_attn = False
|
| 569 |
+
if self.video_dit_2 is not None and not self.sparse_high_noise_only:
|
| 570 |
+
for block in self.video_dit_2.blocks:
|
| 571 |
+
block.self_attn.enable_rectified_sparse_attn = enable
|
| 572 |
+
if enable:
|
| 573 |
+
block.self_attn.enable_taylor_sparse_attn = False
|
| 574 |
+
|
| 575 |
+
def _video_self_attns(self):
|
| 576 |
+
"""Yield EVERY video self-attn (both experts). NOT gated by
|
| 577 |
+
sparse_high_noise_only — this enumerates all attention modules and is used
|
| 578 |
+
for setting numeric bsa_params (which are inert unless a feature is enabled).
|
| 579 |
+
Never used in the forward pass.
|
| 580 |
+
"""
|
| 581 |
+
for fused_block in self.fusion_blocks:
|
| 582 |
+
yield fused_block.video_block.self_attn
|
| 583 |
+
for block in self.remaining_video_blocks:
|
| 584 |
+
yield block.self_attn
|
| 585 |
+
if self.video_dit_2 is not None:
|
| 586 |
+
for block in self.video_dit_2.blocks:
|
| 587 |
+
yield block.self_attn
|
| 588 |
+
|
| 589 |
+
def _video_self_attns_with_idx(self):
|
| 590 |
+
"""Yield (global_video_layer_idx, self_attn) for every video self-attn.
|
| 591 |
+
|
| 592 |
+
Layer indexing matches the forward loop: fusion_blocks[i] -> i;
|
| 593 |
+
remaining_video_blocks[j] -> n_fused + j; video_dit_2.blocks[i] -> i
|
| 594 |
+
(the boundary model mirrors the same per-layer indices). NOT gated.
|
| 595 |
+
"""
|
| 596 |
+
n_fused = len(self.fusion_blocks)
|
| 597 |
+
for i, fused_block in enumerate(self.fusion_blocks):
|
| 598 |
+
yield i, fused_block.video_block.self_attn
|
| 599 |
+
for j, block in enumerate(self.remaining_video_blocks):
|
| 600 |
+
yield n_fused + j, block.self_attn
|
| 601 |
+
if self.video_dit_2 is not None:
|
| 602 |
+
for i, block in enumerate(self.video_dit_2.blocks):
|
| 603 |
+
yield i, block.self_attn
|
| 604 |
+
|
| 605 |
+
def _sparse_cfg_video_self_attns(self):
|
| 606 |
+
|
| 607 |
+
for fused_block in self.fusion_blocks:
|
| 608 |
+
yield fused_block.video_block.self_attn
|
| 609 |
+
for block in self.remaining_video_blocks:
|
| 610 |
+
yield block.self_attn
|
| 611 |
+
if self.video_dit_2 is not None and not self.sparse_high_noise_only:
|
| 612 |
+
for block in self.video_dit_2.blocks:
|
| 613 |
+
yield block.self_attn
|
| 614 |
+
|
| 615 |
+
def _sparse_cfg_video_self_attns_with_idx(self):
|
| 616 |
+
"""Indexed variant of ``_sparse_cfg_video_self_attns`` (low-noise expert
|
| 617 |
+
omitted when ``sparse_high_noise_only`` is set)."""
|
| 618 |
+
n_fused = len(self.fusion_blocks)
|
| 619 |
+
for i, fused_block in enumerate(self.fusion_blocks):
|
| 620 |
+
yield i, fused_block.video_block.self_attn
|
| 621 |
+
for j, block in enumerate(self.remaining_video_blocks):
|
| 622 |
+
yield n_fused + j, block.self_attn
|
| 623 |
+
if self.video_dit_2 is not None and not self.sparse_high_noise_only:
|
| 624 |
+
for i, block in enumerate(self.video_dit_2.blocks):
|
| 625 |
+
yield i, block.self_attn
|
| 626 |
+
|
| 627 |
+
@property
|
| 628 |
+
def _num_video_layers(self):
|
| 629 |
+
return len(self.fusion_blocks) + len(self.remaining_video_blocks)
|
| 630 |
+
|
| 631 |
+
@staticmethod
|
| 632 |
+
def _disable_non_dynamic_bsa_features(sa):
|
| 633 |
+
sa.enable_audio_guidance = False
|
| 634 |
+
sa.enable_audio_concentration_gate = False
|
| 635 |
+
sa.enable_timestep_reliability_gate = False
|
| 636 |
+
sa.enable_audio_weighted_pooling = False
|
| 637 |
+
sa.enable_variance_guidance = False
|
| 638 |
+
sa.enable_taylor_sparse_attn = False
|
| 639 |
+
sa.enable_rectified_sparse_attn = False
|
| 640 |
+
|
| 641 |
+
def configure_ivpq_dynamic_block(self, enable: bool):
|
| 642 |
+
"""IVPQ anisotropic dynamic block shape (Section 8.5)."""
|
| 643 |
+
for sa in self._sparse_cfg_video_self_attns():
|
| 644 |
+
sa.enable_ivpq_dynamic_block = enable
|
| 645 |
+
if enable:
|
| 646 |
+
sa.enable_penalty_dynamic_block = False
|
| 647 |
+
self._disable_non_dynamic_bsa_features(sa)
|
| 648 |
+
|
| 649 |
+
def configure_penalty_dynamic_block(self, enable: bool):
|
| 650 |
+
"""Penalty-Matching anisotropic dynamic block shape (Section 8.6)."""
|
| 651 |
+
for sa in self._sparse_cfg_video_self_attns():
|
| 652 |
+
sa.enable_penalty_dynamic_block = enable
|
| 653 |
+
if enable:
|
| 654 |
+
sa.enable_ivpq_dynamic_block = False
|
| 655 |
+
self._disable_non_dynamic_bsa_features(sa)
|
| 656 |
+
|
| 657 |
+
def configure_layer_adaptive_dynamic_block(self, enable: bool):
|
| 658 |
+
"""Layer-Adaptive Dynamic Block Shape wrapper (Section 8.2).
|
| 659 |
+
|
| 660 |
+
Must be configured AFTER ``configure_ivpq/penalty_dynamic_block`` (it only
|
| 661 |
+
takes effect when one of those is active). When ``enable``:
|
| 662 |
+
|
| 663 |
+
* shallow half of the video self-attn layers (idx < split) run FIXED
|
| 664 |
+
uniform-shape BSA;
|
| 665 |
+
* deep half (idx >= split, split = num_video_layers // 2) run the
|
| 666 |
+
dynamic-block method;
|
| 667 |
+
* a deep layer that HAS its own a2v bridge uses its OWN audio; only the
|
| 668 |
+
deep layers WITHOUT a bridge (the tail beyond audio_layers) reuse the
|
| 669 |
+
cache. The cache is seeded by the DEEPEST bridge layer — its
|
| 670 |
+
full-sequence ``||Δh_v||_2`` is cached (per forward) and reused by those
|
| 671 |
+
no-bridge deep layers so the audio-directional gradient still guides
|
| 672 |
+
their 64/128 + triple split, combined with each deep layer's own video
|
| 673 |
+
channel-variance.
|
| 674 |
+
|
| 675 |
+
Returns (split, cache_source_idx) for logging. Idempotent / safe to call
|
| 676 |
+
with ``enable=False`` to turn the wrapper off (back to all-layer dynamic).
|
| 677 |
+
"""
|
| 678 |
+
n_fused = len(self.fusion_blocks)
|
| 679 |
+
num_video_layers = self._num_video_layers
|
| 680 |
+
split = num_video_layers // 2
|
| 681 |
+
|
| 682 |
+
|
| 683 |
+
bridge_layers = [
|
| 684 |
+
i for i, fb in enumerate(self.fusion_blocks)
|
| 685 |
+
if getattr(fb, "a2v_conditioner", None) is not None
|
| 686 |
+
]
|
| 687 |
+
cache_source_idx = max(bridge_layers) if bridge_layers else None
|
| 688 |
+
|
| 689 |
+
# One mutable holder shared by reference across ALL video self-attns; reset
|
| 690 |
+
# at the start of every forward. Plain python attr → not an FSDP param/buffer.
|
| 691 |
+
if getattr(self, "_la_audio_cache", None) is None:
|
| 692 |
+
self._la_audio_cache = {"norms": None}
|
| 693 |
+
cache = self._la_audio_cache
|
| 694 |
+
|
| 695 |
+
for idx, sa in self._sparse_cfg_video_self_attns_with_idx():
|
| 696 |
+
sa.enable_layer_adaptive_dynamic_block = enable
|
| 697 |
+
sa._la_layer_idx = idx
|
| 698 |
+
sa._la_split = split
|
| 699 |
+
sa._la_is_cache_source = bool(
|
| 700 |
+
enable and cache_source_idx is not None and idx == cache_source_idx
|
| 701 |
+
)
|
| 702 |
+
sa._la_audio_cache = cache if enable else None
|
| 703 |
+
|
| 704 |
+
return split, cache_source_idx
|
| 705 |
+
|
| 706 |
+
# -----------------------------------------------------------------------
|
| 707 |
+
# Forward
|
| 708 |
+
# -----------------------------------------------------------------------
|
| 709 |
+
|
| 710 |
+
def forward(
|
| 711 |
+
self,
|
| 712 |
+
visual_latents: torch.Tensor,
|
| 713 |
+
audio_latents: torch.Tensor,
|
| 714 |
+
context: torch.Tensor,
|
| 715 |
+
timestep: torch.Tensor,
|
| 716 |
+
audio_context: Optional[torch.Tensor] = None,
|
| 717 |
+
audio_timestep: Optional[torch.Tensor] = None,
|
| 718 |
+
video_fps: float = 24.0,
|
| 719 |
+
num_train_timesteps: int = 1000,
|
| 720 |
+
condition_scale: float = 1.0,
|
| 721 |
+
a2v_condition_scale: Optional[float] = None,
|
| 722 |
+
v2a_condition_scale: Optional[float] = None,
|
| 723 |
+
use_video_dit_2: bool = False,
|
| 724 |
+
):
|
| 725 |
+
"""
|
| 726 |
+
Single denoising step. Reproduces MOVA's inference_single_step exactly.
|
| 727 |
+
"""
|
| 728 |
+
advance_dynamic_block_pass_id() # once per real forward (not on recompute)
|
| 729 |
+
|
| 730 |
+
use_dit_2 = use_video_dit_2 and self.video_dit_2 is not None
|
| 731 |
+
visual_dit = self.video_dit_2 if use_dit_2 else self.video_dit
|
| 732 |
+
|
| 733 |
+
if audio_context is None:
|
| 734 |
+
audio_context = context
|
| 735 |
+
if audio_timestep is None:
|
| 736 |
+
audio_timestep = timestep
|
| 737 |
+
|
| 738 |
+
# Timestep embeddings (force fp32 for numerical stability, same as MOVA)
|
| 739 |
+
with torch.autocast("cuda", dtype=torch.float32):
|
| 740 |
+
visual_t = visual_dit.time_embedding(
|
| 741 |
+
sinusoidal_embedding_1d(visual_dit.freq_dim, timestep)
|
| 742 |
+
)
|
| 743 |
+
visual_t_mod = visual_dit.time_projection(visual_t).unflatten(1, (6, visual_dit.dim))
|
| 744 |
+
|
| 745 |
+
audio_t = self.audio_dit.time_embedding(
|
| 746 |
+
sinusoidal_embedding_1d(self.audio_dit.freq_dim, audio_timestep)
|
| 747 |
+
)
|
| 748 |
+
audio_t_mod = self.audio_dit.time_projection(audio_t).unflatten(1, (6, self.audio_dit.dim))
|
| 749 |
+
|
| 750 |
+
model_dtype = visual_dit.dtype
|
| 751 |
+
visual_t = visual_t.to(model_dtype)
|
| 752 |
+
visual_t_mod = visual_t_mod.to(model_dtype)
|
| 753 |
+
audio_t = audio_t.to(model_dtype)
|
| 754 |
+
audio_t_mod = audio_t_mod.to(model_dtype)
|
| 755 |
+
|
| 756 |
+
visual_context_emb = visual_dit.text_embedding(context)
|
| 757 |
+
audio_context_emb = self.audio_dit.text_embedding(audio_context)
|
| 758 |
+
|
| 759 |
+
visual_x = visual_latents.to(model_dtype)
|
| 760 |
+
audio_x = audio_latents.to(model_dtype)
|
| 761 |
+
|
| 762 |
+
# Patchify
|
| 763 |
+
visual_x, (t, h, w) = visual_dit.patchify(visual_x)
|
| 764 |
+
grid_size = (t, h, w)
|
| 765 |
+
visual_freqs = assemble_visual_freqs(visual_dit.freqs, t, h, w, visual_x.device)
|
| 766 |
+
|
| 767 |
+
audio_x, (f,) = self.audio_dit.patchify(audio_x, None)
|
| 768 |
+
audio_freqs = assemble_audio_freqs(self.audio_dit.freqs, f, audio_x.device)
|
| 769 |
+
|
| 770 |
+
# Compute normalized timestep ratio for Timestep Reliability Gate
|
| 771 |
+
# In flow matching: timestep ∈ [0, 1] where 0=clean, 1=noise
|
| 772 |
+
# If timestep > 1 (e.g., in [0, 1000] convention), normalize
|
| 773 |
+
t_val = timestep.float().mean()
|
| 774 |
+
timestep_ratio = t_val if t_val <= 1.0 else t_val / float(num_train_timesteps)
|
| 775 |
+
|
| 776 |
+
# Forward through fused dual-tower blocks
|
| 777 |
+
visual_x, audio_x = self._forward_dual_tower(
|
| 778 |
+
visual_x=visual_x,
|
| 779 |
+
audio_x=audio_x,
|
| 780 |
+
visual_context=visual_context_emb,
|
| 781 |
+
audio_context=audio_context_emb,
|
| 782 |
+
visual_t_mod=visual_t_mod,
|
| 783 |
+
audio_t_mod=audio_t_mod,
|
| 784 |
+
visual_freqs=visual_freqs,
|
| 785 |
+
audio_freqs=audio_freqs,
|
| 786 |
+
grid_size=grid_size,
|
| 787 |
+
video_fps=video_fps,
|
| 788 |
+
condition_scale=condition_scale,
|
| 789 |
+
a2v_condition_scale=a2v_condition_scale,
|
| 790 |
+
v2a_condition_scale=v2a_condition_scale,
|
| 791 |
+
use_dit_2=use_dit_2,
|
| 792 |
+
timestep_ratio=timestep_ratio,
|
| 793 |
+
)
|
| 794 |
+
|
| 795 |
+
# Head projections (operate on SP-split sequence for efficiency)
|
| 796 |
+
visual_output = visual_dit.head(visual_x, visual_t)
|
| 797 |
+
audio_output = self.audio_dit.head(audio_x, audio_t)
|
| 798 |
+
|
| 799 |
+
# SP: gather back full sequences after head
|
| 800 |
+
if get_sequence_parallel_state():
|
| 801 |
+
visual_output = all_gather(visual_output, dim=1, group=nccl_info.sp_group,
|
| 802 |
+
full_seq_len=t * h * w)
|
| 803 |
+
audio_output = all_gather(audio_output, dim=1, group=nccl_info.sp_group,
|
| 804 |
+
full_seq_len=f)
|
| 805 |
+
|
| 806 |
+
visual_output = visual_dit.unpatchify(visual_output, grid_size)
|
| 807 |
+
audio_output = self.audio_dit.unpatchify(audio_output, (f,))
|
| 808 |
+
|
| 809 |
+
return visual_output, audio_output
|
| 810 |
+
|
| 811 |
+
def _forward_dual_tower(
|
| 812 |
+
self,
|
| 813 |
+
visual_x,
|
| 814 |
+
audio_x,
|
| 815 |
+
visual_context,
|
| 816 |
+
audio_context,
|
| 817 |
+
visual_t_mod,
|
| 818 |
+
audio_t_mod,
|
| 819 |
+
visual_freqs,
|
| 820 |
+
audio_freqs,
|
| 821 |
+
grid_size,
|
| 822 |
+
video_fps,
|
| 823 |
+
condition_scale=1.0,
|
| 824 |
+
a2v_condition_scale=None,
|
| 825 |
+
v2a_condition_scale=None,
|
| 826 |
+
use_dit_2=False,
|
| 827 |
+
timestep_ratio=None,
|
| 828 |
+
):
|
| 829 |
+
"""
|
| 830 |
+
Forward through fused dual-tower blocks + remaining visual-only blocks.
|
| 831 |
+
|
| 832 |
+
Ulysses SP flow:
|
| 833 |
+
- Before blocks: chunk sequences and freqs
|
| 834 |
+
- Inside each attention: all_to_all_4D scatter heads / gather seq
|
| 835 |
+
- After all blocks: all_gather to reconstruct full sequences
|
| 836 |
+
"""
|
| 837 |
+
sp_enabled = get_sequence_parallel_state()
|
| 838 |
+
|
| 839 |
+
# Build bridge cross-modal RoPE
|
| 840 |
+
if self.dual_tower_bridge.apply_cross_rope:
|
| 841 |
+
(visual_rope_cos_sin, audio_rope_cos_sin) = (
|
| 842 |
+
self.dual_tower_bridge.build_aligned_freqs(
|
| 843 |
+
video_fps=video_fps,
|
| 844 |
+
grid_size=grid_size,
|
| 845 |
+
audio_steps=audio_x.shape[1],
|
| 846 |
+
device=visual_x.device,
|
| 847 |
+
dtype=visual_x.dtype,
|
| 848 |
+
)
|
| 849 |
+
)
|
| 850 |
+
else:
|
| 851 |
+
visual_rope_cos_sin = None
|
| 852 |
+
audio_rope_cos_sin = None
|
| 853 |
+
|
| 854 |
+
# Resolve condition scales
|
| 855 |
+
bridge_scale = self.dual_tower_bridge.condition_scale
|
| 856 |
+
a2v_scale = (a2v_condition_scale if a2v_condition_scale is not None
|
| 857 |
+
else (condition_scale if condition_scale is not None
|
| 858 |
+
else bridge_scale))
|
| 859 |
+
v2a_scale = (v2a_condition_scale if v2a_condition_scale is not None
|
| 860 |
+
else (condition_scale if condition_scale is not None
|
| 861 |
+
else bridge_scale))
|
| 862 |
+
|
| 863 |
+
# --- SP: chunk sequences and freqs ---
|
| 864 |
+
if sp_enabled:
|
| 865 |
+
if getattr(nccl_info, 'use_dynamic_ring_attention', False):
|
| 866 |
+
raise NotImplementedError(
|
| 867 |
+
"MOVABridge does not support dynamic_ring_attention. "
|
| 868 |
+
"Please set use_dynamic_ring_attention=False."
|
| 869 |
+
)
|
| 870 |
+
sp_size = nccl_info.sp_size
|
| 871 |
+
sp_rank = nccl_info.rank_within_spgroup
|
| 872 |
+
|
| 873 |
+
# Publish the pre-shard lengths of both streams. Every Ulysses
|
| 874 |
+
# all-to-all below reads them from here instead of renegotiating the
|
| 875 |
+
# split with a per-call all_gather_object.
|
| 876 |
+
visual_seq_len = visual_x.shape[1]
|
| 877 |
+
audio_seq_len = audio_x.shape[1]
|
| 878 |
+
assert_sp_splittable(visual_seq_len, sp_size, "visual sequence")
|
| 879 |
+
assert_sp_splittable(audio_seq_len, sp_size, "audio sequence")
|
| 880 |
+
sp_seq_ctx.set(visual=visual_seq_len, audio=audio_seq_len)
|
| 881 |
+
|
| 882 |
+
visual_x = sp_split(visual_x, sp_size, sp_rank, dim=1)
|
| 883 |
+
audio_x = sp_split(audio_x, sp_size, sp_rank, dim=1)
|
| 884 |
+
visual_freqs = sp_split(visual_freqs, sp_size, sp_rank, dim=0)
|
| 885 |
+
audio_freqs = sp_split(audio_freqs, sp_size, sp_rank, dim=0)
|
| 886 |
+
|
| 887 |
+
if visual_rope_cos_sin is not None:
|
| 888 |
+
v_cos, v_sin = visual_rope_cos_sin
|
| 889 |
+
v_cos = sp_split(v_cos, sp_size, sp_rank, dim=1)
|
| 890 |
+
v_sin = sp_split(v_sin, sp_size, sp_rank, dim=1)
|
| 891 |
+
visual_rope_cos_sin = (v_cos, v_sin)
|
| 892 |
+
if audio_rope_cos_sin is not None:
|
| 893 |
+
a_cos, a_sin = audio_rope_cos_sin
|
| 894 |
+
a_cos = sp_split(a_cos, sp_size, sp_rank, dim=1)
|
| 895 |
+
a_sin = sp_split(a_sin, sp_size, sp_rank, dim=1)
|
| 896 |
+
audio_rope_cos_sin = (a_cos, a_sin)
|
| 897 |
+
|
| 898 |
+
use_gc = self.training and self.gradient_checkpoint
|
| 899 |
+
|
| 900 |
+
# Reset the layer-adaptive audio cache for this forward. The deepest-bridge
|
| 901 |
+
# cache-source layer seeds it; the no-bridge tail layers (which run AFTER it,
|
| 902 |
+
# sequentially) read it. During checkpoint recompute it still holds the
|
| 903 |
+
# forward value because the tail layers recompute first (reverse order) and
|
| 904 |
+
# the cache is detached/no-grad, so the seed→read order is preserved.
|
| 905 |
+
if getattr(self, "_la_audio_cache", None) is not None:
|
| 906 |
+
self._la_audio_cache["norms"] = None
|
| 907 |
+
|
| 908 |
+
# --- Fused dual-tower layers ---
|
| 909 |
+
for i, fused_block in enumerate(self.fusion_blocks):
|
| 910 |
+
override = None
|
| 911 |
+
if use_dit_2:
|
| 912 |
+
override = self.video_dit_2.blocks[i]
|
| 913 |
+
|
| 914 |
+
if use_gc:
|
| 915 |
+
visual_x, audio_x = torch_checkpoint.checkpoint(
|
| 916 |
+
fused_block,
|
| 917 |
+
visual_x, audio_x,
|
| 918 |
+
visual_context, audio_context,
|
| 919 |
+
visual_t_mod, audio_t_mod,
|
| 920 |
+
visual_freqs, audio_freqs,
|
| 921 |
+
visual_rope_cos_sin, audio_rope_cos_sin,
|
| 922 |
+
a2v_scale, v2a_scale,
|
| 923 |
+
grid_size,
|
| 924 |
+
override,
|
| 925 |
+
timestep_ratio,
|
| 926 |
+
use_reentrant=False,
|
| 927 |
+
)
|
| 928 |
+
else:
|
| 929 |
+
visual_x, audio_x = fused_block(
|
| 930 |
+
visual_x, audio_x,
|
| 931 |
+
visual_context, audio_context,
|
| 932 |
+
visual_t_mod, audio_t_mod,
|
| 933 |
+
visual_freqs, audio_freqs,
|
| 934 |
+
visual_rope_cos_sin=visual_rope_cos_sin,
|
| 935 |
+
audio_rope_cos_sin=audio_rope_cos_sin,
|
| 936 |
+
a2v_scale=a2v_scale, v2a_scale=v2a_scale,
|
| 937 |
+
video_grid_size=grid_size,
|
| 938 |
+
override_video_block=override,
|
| 939 |
+
timestep_ratio=timestep_ratio,
|
| 940 |
+
)
|
| 941 |
+
|
| 942 |
+
# --- Remaining video-only layers ---
|
| 943 |
+
if use_dit_2:
|
| 944 |
+
remaining = [
|
| 945 |
+
self.video_dit_2.blocks[i]
|
| 946 |
+
for i in range(len(self.fusion_blocks), len(self.video_dit_2.blocks))
|
| 947 |
+
]
|
| 948 |
+
else:
|
| 949 |
+
remaining = self.remaining_video_blocks
|
| 950 |
+
|
| 951 |
+
for block in remaining:
|
| 952 |
+
if use_gc:
|
| 953 |
+
visual_x = torch_checkpoint.checkpoint(
|
| 954 |
+
block, visual_x, visual_context, visual_t_mod, visual_freqs, grid_size,
|
| 955 |
+
None, timestep_ratio,
|
| 956 |
+
use_reentrant=False,
|
| 957 |
+
)
|
| 958 |
+
else:
|
| 959 |
+
visual_x = block(
|
| 960 |
+
visual_x, visual_context, visual_t_mod, visual_freqs,
|
| 961 |
+
grid_size=grid_size, timestep_ratio=timestep_ratio,
|
| 962 |
+
)
|
| 963 |
+
|
| 964 |
+
return visual_x, audio_x
|
hymm/models/modules/wan_audio_dit.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import math
|
| 3 |
+
from typing import Literal, Optional, Tuple
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 8 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 9 |
+
from einops import rearrange
|
| 10 |
+
|
| 11 |
+
from .wan_video_dit import DiTBlock
|
| 12 |
+
from hymm.utils.parallel_states import get_sequence_parallel_state, nccl_info, sp_seq_ctx
|
| 13 |
+
from hymm.utils.communications import all_gather, sp_split, assert_sp_splittable
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def tag_audio_sp_stream(blocks):
|
| 17 |
+
"""Mark a stack of DiTBlocks as operating on the audio SP stream.
|
| 18 |
+
|
| 19 |
+
The blocks are plain video DiTBlocks, so their attentions default to the
|
| 20 |
+
"visual" stream; without this the Ulysses all-to-all would look up the wrong
|
| 21 |
+
sequence length once MOVABridge fuses the two towers.
|
| 22 |
+
"""
|
| 23 |
+
for block in blocks:
|
| 24 |
+
block.self_attn.sp_stream = "audio"
|
| 25 |
+
block.cross_attn.sp_stream = "audio"
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def modulate(x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor):
|
| 29 |
+
# x is fp32 after layer norm
|
| 30 |
+
# print(f"{shift.dtype = }")
|
| 31 |
+
return (x * (1 + scale) + shift).to(shift.dtype)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def sinusoidal_embedding_1d(dim, position):
|
| 35 |
+
sinusoid = torch.outer(position.type(torch.float64), torch.pow(
|
| 36 |
+
10000, -torch.arange(dim//2, dtype=torch.float64, device=position.device).div(dim//2)))
|
| 37 |
+
x = torch.cat([torch.cos(sinusoid), torch.sin(sinusoid)], dim=1)
|
| 38 |
+
return x.to(position.dtype)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def precompute_freqs_cis_3d(dim: int, end: int = 1024, theta: float = 10000.0):
|
| 42 |
+
# 3d rope precompute
|
| 43 |
+
f_freqs_cis = precompute_freqs_cis(dim - 2 * (dim // 3), end, theta)
|
| 44 |
+
h_freqs_cis = precompute_freqs_cis(dim // 3, end, theta)
|
| 45 |
+
w_freqs_cis = precompute_freqs_cis(dim // 3, end, theta)
|
| 46 |
+
return f_freqs_cis, h_freqs_cis, w_freqs_cis
|
| 47 |
+
|
| 48 |
+
def legacy_precompute_freqs_cis_1d(dim: int, end: int = 16384, theta: float = 10000.0, base_tps=4.0, target_tps=44100/2048):
|
| 49 |
+
s = float(base_tps) / float(target_tps)
|
| 50 |
+
# 1d rope precompute
|
| 51 |
+
f_freqs_cis = precompute_freqs_cis(dim - 2 * (dim // 3), end, theta, s)
|
| 52 |
+
# Do not apply positional encoding to the remaining dimensions.
|
| 53 |
+
no_freqs_cis = precompute_freqs_cis(dim // 3, end, theta, s)
|
| 54 |
+
no_freqs_cis = torch.ones_like(no_freqs_cis)
|
| 55 |
+
return f_freqs_cis, no_freqs_cis, no_freqs_cis
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def precompute_freqs_cis_1d(dim: int, end: int = 16384, theta: float = 10000.0):
|
| 59 |
+
f_freqs_cis = precompute_freqs_cis(dim, end, theta)
|
| 60 |
+
return f_freqs_cis.chunk(3, dim=-1)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def precompute_freqs_cis(dim: int, end: int = 16384, theta: float = 10000.0, s: float = 1.0):
|
| 64 |
+
# 1d rope precompute
|
| 65 |
+
freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)
|
| 66 |
+
[: (dim // 2)].double() / dim))
|
| 67 |
+
pos = torch.arange(end, dtype=torch.float64, device=freqs.device) * s
|
| 68 |
+
freqs = torch.outer(pos, freqs)
|
| 69 |
+
freqs_cis = torch.polar(torch.ones_like(freqs), freqs) # complex64
|
| 70 |
+
return freqs_cis
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class MLP(torch.nn.Module):
|
| 74 |
+
def __init__(self, in_dim, out_dim, has_pos_emb=False):
|
| 75 |
+
super().__init__()
|
| 76 |
+
self.proj = torch.nn.Sequential(
|
| 77 |
+
nn.LayerNorm(in_dim),
|
| 78 |
+
nn.Linear(in_dim, in_dim),
|
| 79 |
+
nn.GELU(),
|
| 80 |
+
nn.Linear(in_dim, out_dim),
|
| 81 |
+
nn.LayerNorm(out_dim)
|
| 82 |
+
)
|
| 83 |
+
self.has_pos_emb = has_pos_emb
|
| 84 |
+
if has_pos_emb:
|
| 85 |
+
self.emb_pos = torch.nn.Parameter(torch.zeros((1, 514, 1280)))
|
| 86 |
+
|
| 87 |
+
def forward(self, x):
|
| 88 |
+
if self.has_pos_emb:
|
| 89 |
+
x = x + self.emb_pos.to(dtype=x.dtype, device=x.device)
|
| 90 |
+
return self.proj(x)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class Head(nn.Module):
|
| 94 |
+
def __init__(self, dim: int, out_dim: int, patch_size: Tuple[int, int, int], eps: float):
|
| 95 |
+
super().__init__()
|
| 96 |
+
self.dim = dim
|
| 97 |
+
self.patch_size = patch_size
|
| 98 |
+
self.norm = nn.LayerNorm(dim, eps=eps, elementwise_affine=False)
|
| 99 |
+
self.head = nn.Linear(dim, out_dim * math.prod(patch_size))
|
| 100 |
+
self.modulation = nn.Parameter(torch.randn(1, 2, dim) / dim**0.5)
|
| 101 |
+
|
| 102 |
+
def forward(self, x, t_mod):
|
| 103 |
+
# print(f"{t_mod.shape = }")
|
| 104 |
+
if len(t_mod.shape) == 3:
|
| 105 |
+
shift, scale = (self.modulation.unsqueeze(0).to(dtype=t_mod.dtype, device=t_mod.device) + t_mod.unsqueeze(2)).chunk(2, dim=2)
|
| 106 |
+
x = (self.head(self.norm(x) * (1 + scale.squeeze(2)) + shift.squeeze(2)))
|
| 107 |
+
else:
|
| 108 |
+
# NOTE: `t_mod` used to be [B, C]. When B=1 broadcasting works, but for B>1
|
| 109 |
+
# it does not align with [1, 2, C]. We therefore unsqueeze at dim=1 here.
|
| 110 |
+
shift, scale = (self.modulation.to(dtype=t_mod.dtype, device=t_mod.device) + t_mod.unsqueeze(1)).chunk(2, dim=1)
|
| 111 |
+
x = (self.head(self.norm(x) * (1 + scale) + shift))
|
| 112 |
+
return x
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class WanAudioModel(ModelMixin, ConfigMixin):
|
| 116 |
+
_repeated_blocks = ("DiTBlock",)
|
| 117 |
+
|
| 118 |
+
@register_to_config
|
| 119 |
+
def __init__(
|
| 120 |
+
self,
|
| 121 |
+
dim: int,
|
| 122 |
+
in_dim: int,
|
| 123 |
+
ffn_dim: int,
|
| 124 |
+
out_dim: int,
|
| 125 |
+
text_dim: int,
|
| 126 |
+
freq_dim: int,
|
| 127 |
+
eps: float,
|
| 128 |
+
patch_size: Tuple[int, int, int],
|
| 129 |
+
num_heads: int,
|
| 130 |
+
num_layers: int,
|
| 131 |
+
has_image_input: bool,
|
| 132 |
+
has_image_pos_emb: bool = False,
|
| 133 |
+
has_ref_conv: bool = False,
|
| 134 |
+
add_control_adapter: bool = False,
|
| 135 |
+
in_dim_control_adapter: int = 24,
|
| 136 |
+
seperated_timestep: bool = False,
|
| 137 |
+
require_vae_embedding: bool = True,
|
| 138 |
+
require_clip_embedding: bool = True,
|
| 139 |
+
fuse_vae_embedding_in_latents: bool = False,
|
| 140 |
+
vae_type: Literal["oobleck", "dac"] = "oobleck",
|
| 141 |
+
):
|
| 142 |
+
super().__init__()
|
| 143 |
+
self.dim = dim
|
| 144 |
+
self.freq_dim = freq_dim
|
| 145 |
+
self.has_image_input = has_image_input
|
| 146 |
+
self.patch_size = patch_size
|
| 147 |
+
self.seperated_timestep = seperated_timestep
|
| 148 |
+
self.require_vae_embedding = require_vae_embedding
|
| 149 |
+
self.require_clip_embedding = require_clip_embedding
|
| 150 |
+
self.fuse_vae_embedding_in_latents = fuse_vae_embedding_in_latents
|
| 151 |
+
self.vae_type = vae_type
|
| 152 |
+
# self.patch_embedding = nn.Conv3d(
|
| 153 |
+
# in_dim, dim, kernel_size=patch_size, stride=patch_size)
|
| 154 |
+
self.patch_embedding = nn.Conv1d(
|
| 155 |
+
in_dim, dim, kernel_size=patch_size, stride=patch_size
|
| 156 |
+
)
|
| 157 |
+
self.text_embedding = nn.Sequential(
|
| 158 |
+
nn.Linear(text_dim, dim),
|
| 159 |
+
nn.GELU(approximate='tanh'),
|
| 160 |
+
nn.Linear(dim, dim)
|
| 161 |
+
)
|
| 162 |
+
self.time_embedding = nn.Sequential(
|
| 163 |
+
nn.Linear(freq_dim, dim),
|
| 164 |
+
nn.SiLU(),
|
| 165 |
+
nn.Linear(dim, dim)
|
| 166 |
+
)
|
| 167 |
+
self.time_projection = nn.Sequential(
|
| 168 |
+
nn.SiLU(), nn.Linear(dim, dim * 6))
|
| 169 |
+
self.blocks = nn.ModuleList([
|
| 170 |
+
DiTBlock(has_image_input, dim, num_heads, ffn_dim, eps)
|
| 171 |
+
for _ in range(num_layers)
|
| 172 |
+
])
|
| 173 |
+
tag_audio_sp_stream(self.blocks)
|
| 174 |
+
self.head = Head(dim, out_dim, patch_size, eps)
|
| 175 |
+
head_dim = dim // num_heads
|
| 176 |
+
if vae_type == "oobleck":
|
| 177 |
+
self.freqs = legacy_precompute_freqs_cis_1d(head_dim, base_tps=4.0, target_tps=44100/2048)
|
| 178 |
+
elif vae_type == "dac":
|
| 179 |
+
self.freqs = precompute_freqs_cis_1d(head_dim)
|
| 180 |
+
else:
|
| 181 |
+
raise ValueError(f"Invalid VAE type: {vae_type}")
|
| 182 |
+
|
| 183 |
+
if has_image_input:
|
| 184 |
+
self.img_emb = MLP(1280, dim, has_pos_emb=has_image_pos_emb) # clip_feature_dim = 1280
|
| 185 |
+
if has_ref_conv:
|
| 186 |
+
self.ref_conv = nn.Conv2d(16, dim, kernel_size=(2, 2), stride=(2, 2))
|
| 187 |
+
self.has_image_pos_emb = has_image_pos_emb
|
| 188 |
+
self.has_ref_conv = has_ref_conv
|
| 189 |
+
self.control_adapter = None
|
| 190 |
+
|
| 191 |
+
def patchify(self, x: torch.Tensor, control_camera_latents_input: Optional[torch.Tensor] = None):
|
| 192 |
+
x = self.patch_embedding(x)
|
| 193 |
+
if self.control_adapter is not None and control_camera_latents_input is not None:
|
| 194 |
+
y_camera = self.control_adapter(control_camera_latents_input)
|
| 195 |
+
x = [u + v for u, v in zip(x, y_camera)]
|
| 196 |
+
x = x[0].unsqueeze(0)
|
| 197 |
+
grid_size = x.shape[2:]
|
| 198 |
+
x = rearrange(x, 'b c f -> b f c').contiguous()
|
| 199 |
+
return x, grid_size # x, grid_size: (f)
|
| 200 |
+
|
| 201 |
+
def unpatchify(self, x: torch.Tensor, grid_size: torch.Tensor):
|
| 202 |
+
return rearrange(
|
| 203 |
+
x, 'b f (p c) -> b c (f p)',
|
| 204 |
+
f=grid_size[0],
|
| 205 |
+
p=self.patch_size[0]
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
def forward(self,
|
| 209 |
+
x: torch.Tensor,
|
| 210 |
+
timestep: torch.Tensor,
|
| 211 |
+
context: torch.Tensor,
|
| 212 |
+
clip_feature: Optional[torch.Tensor] = None,
|
| 213 |
+
y: Optional[torch.Tensor] = None,
|
| 214 |
+
use_gradient_checkpointing: bool = False,
|
| 215 |
+
use_gradient_checkpointing_offload: bool = False,
|
| 216 |
+
**kwargs,
|
| 217 |
+
):
|
| 218 |
+
t = self.time_embedding(
|
| 219 |
+
sinusoidal_embedding_1d(self.freq_dim, timestep))
|
| 220 |
+
t_mod = self.time_projection(t).unflatten(1, (6, self.dim))
|
| 221 |
+
context = self.text_embedding(context)
|
| 222 |
+
|
| 223 |
+
if self.has_image_input:
|
| 224 |
+
x = torch.cat([x, y], dim=1) # (b, c_x + c_y, f, h, w)
|
| 225 |
+
clip_embdding = self.img_emb(clip_feature)
|
| 226 |
+
context = torch.cat([clip_embdding, context], dim=1)
|
| 227 |
+
|
| 228 |
+
x, (f, ) = self.patchify(x)
|
| 229 |
+
|
| 230 |
+
freqs = torch.cat([
|
| 231 |
+
self.freqs[0][:f].view(f, -1).expand(f, -1),
|
| 232 |
+
self.freqs[1][:f].view(f, -1).expand(f, -1),
|
| 233 |
+
self.freqs[2][:f].view(f, -1).expand(f, -1),
|
| 234 |
+
], dim=-1).reshape(f, 1, -1).to(x.device)
|
| 235 |
+
|
| 236 |
+
if get_sequence_parallel_state():
|
| 237 |
+
sp_size = nccl_info.sp_size
|
| 238 |
+
sp_rank = nccl_info.rank_within_spgroup
|
| 239 |
+
assert_sp_splittable(f, sp_size, "audio sequence")
|
| 240 |
+
sp_seq_ctx.set(audio=f)
|
| 241 |
+
x = sp_split(x, sp_size, sp_rank, dim=1)
|
| 242 |
+
freqs = sp_split(freqs, sp_size, sp_rank, dim=0)
|
| 243 |
+
|
| 244 |
+
def create_custom_forward(module):
|
| 245 |
+
def custom_forward(*inputs):
|
| 246 |
+
return module(*inputs)
|
| 247 |
+
return custom_forward
|
| 248 |
+
|
| 249 |
+
for block in self.blocks:
|
| 250 |
+
if self.training and use_gradient_checkpointing:
|
| 251 |
+
if use_gradient_checkpointing_offload:
|
| 252 |
+
with torch.autograd.graph.save_on_cpu():
|
| 253 |
+
x = torch.utils.checkpoint.checkpoint(
|
| 254 |
+
create_custom_forward(block),
|
| 255 |
+
x, context, t_mod, freqs,
|
| 256 |
+
use_reentrant=False,
|
| 257 |
+
)
|
| 258 |
+
else:
|
| 259 |
+
x = torch.utils.checkpoint.checkpoint(
|
| 260 |
+
create_custom_forward(block),
|
| 261 |
+
x, context, t_mod, freqs,
|
| 262 |
+
use_reentrant=False,
|
| 263 |
+
)
|
| 264 |
+
else:
|
| 265 |
+
x = block(x, context, t_mod, freqs)
|
| 266 |
+
|
| 267 |
+
x = self.head(x, t)
|
| 268 |
+
if get_sequence_parallel_state():
|
| 269 |
+
x = all_gather(x, dim=1, group=nccl_info.sp_group, full_seq_len=f)
|
| 270 |
+
x = self.unpatchify(x, (f, ))
|
| 271 |
+
return x
|
hymm/models/modules/wan_video_dit.py
ADDED
|
@@ -0,0 +1,1324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import math
|
| 3 |
+
from typing import Optional, Tuple
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 9 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 10 |
+
from einops import rearrange
|
| 11 |
+
from torch.distributed.tensor import DTensor
|
| 12 |
+
from torch.nn import RMSNorm
|
| 13 |
+
|
| 14 |
+
from hymm.utils.parallel_states import get_sequence_parallel_state, nccl_info, sp_seq_ctx
|
| 15 |
+
from hymm.utils.communications import (
|
| 16 |
+
all_to_all_4D, all_gather,
|
| 17 |
+
sp_pad_num_heads, sp_pad_heads, sp_num_local_real_heads,
|
| 18 |
+
sp_split, assert_sp_splittable,
|
| 19 |
+
)
|
| 20 |
+
from hymm.models.modules.block_sparse_attention import flash_attn_bsa_3d
|
| 21 |
+
|
| 22 |
+
_DYN_BLOCK_FWD_ID = 0
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def advance_dynamic_block_pass_id():
|
| 26 |
+
"""Call once at the start of each real outer forward (never from recompute)."""
|
| 27 |
+
global _DYN_BLOCK_FWD_ID
|
| 28 |
+
_DYN_BLOCK_FWD_ID += 1
|
| 29 |
+
return _DYN_BLOCK_FWD_ID
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def current_dynamic_block_pass_id():
|
| 33 |
+
return _DYN_BLOCK_FWD_ID
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
from flash_attn_interface import flash_attn_func as flash_attn_3_func
|
| 38 |
+
print("flash_attn_interface loaded from flash_attn_interface")
|
| 39 |
+
FLASH_ATTN_3_AVAILABLE = True
|
| 40 |
+
except ModuleNotFoundError:
|
| 41 |
+
FLASH_ATTN_3_AVAILABLE = False
|
| 42 |
+
try:
|
| 43 |
+
from kernels import get_kernel
|
| 44 |
+
flash_attn_kernel = get_kernel("kernels-community/flash-attn3")
|
| 45 |
+
flash_attn_3_func = flash_attn_kernel.flash_attn_func
|
| 46 |
+
print("flash_attn_interface loaded from kernels-community/flash-attn3")
|
| 47 |
+
FLASH_ATTN_3_AVAILABLE = True
|
| 48 |
+
except ModuleNotFoundError:
|
| 49 |
+
FLASH_ATTN_3_AVAILABLE = False
|
| 50 |
+
|
| 51 |
+
try:
|
| 52 |
+
import flash_attn
|
| 53 |
+
FLASH_ATTN_2_AVAILABLE = True
|
| 54 |
+
except ModuleNotFoundError:
|
| 55 |
+
FLASH_ATTN_2_AVAILABLE = False
|
| 56 |
+
|
| 57 |
+
try:
|
| 58 |
+
from sageattention import sageattn
|
| 59 |
+
SAGE_ATTN_AVAILABLE = True
|
| 60 |
+
except ModuleNotFoundError:
|
| 61 |
+
SAGE_ATTN_AVAILABLE = False
|
| 62 |
+
|
| 63 |
+
try:
|
| 64 |
+
from yunchang import LongContextAttention
|
| 65 |
+
from yunchang.kernels import AttnType
|
| 66 |
+
LONG_CONTEXT_ATTN_AVAILABLE = True
|
| 67 |
+
except Exception:
|
| 68 |
+
LONG_CONTEXT_ATTN_AVAILABLE = False
|
| 69 |
+
LongContextAttention = None
|
| 70 |
+
# Fallback so module import succeeds even when yunchang isn't available.
|
| 71 |
+
# `USPAttention` will error if instantiated without LongContextAttention.
|
| 72 |
+
class AttnType: # type: ignore
|
| 73 |
+
FA = None
|
| 74 |
+
FA3 = None
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def flash_attention(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, num_heads: int, compatibility_mode=False):
|
| 78 |
+
if compatibility_mode:
|
| 79 |
+
q = rearrange(q, "b s (n d) -> b n s d", n=num_heads)
|
| 80 |
+
k = rearrange(k, "b s (n d) -> b n s d", n=num_heads)
|
| 81 |
+
v = rearrange(v, "b s (n d) -> b n s d", n=num_heads)
|
| 82 |
+
x = F.scaled_dot_product_attention(q, k, v)
|
| 83 |
+
x = rearrange(x, "b n s d -> b s (n d)", n=num_heads)
|
| 84 |
+
elif FLASH_ATTN_3_AVAILABLE:
|
| 85 |
+
q = rearrange(q, "b s (n d) -> b s n d", n=num_heads)
|
| 86 |
+
k = rearrange(k, "b s (n d) -> b s n d", n=num_heads)
|
| 87 |
+
v = rearrange(v, "b s (n d) -> b s n d", n=num_heads)
|
| 88 |
+
x = flash_attn_3_func(q, k, v)
|
| 89 |
+
if isinstance(x,tuple):
|
| 90 |
+
x = x[0]
|
| 91 |
+
x = rearrange(x, "b s n d -> b s (n d)", n=num_heads)
|
| 92 |
+
elif FLASH_ATTN_2_AVAILABLE:
|
| 93 |
+
q = rearrange(q, "b s (n d) -> b s n d", n=num_heads)
|
| 94 |
+
k = rearrange(k, "b s (n d) -> b s n d", n=num_heads)
|
| 95 |
+
v = rearrange(v, "b s (n d) -> b s n d", n=num_heads)
|
| 96 |
+
x = flash_attn.flash_attn_func(q, k, v)
|
| 97 |
+
x = rearrange(x, "b s n d -> b s (n d)", n=num_heads)
|
| 98 |
+
elif SAGE_ATTN_AVAILABLE:
|
| 99 |
+
q = rearrange(q, "b s (n d) -> b s n d", n=num_heads)
|
| 100 |
+
k = rearrange(k, "b s (n d) -> b s n d", n=num_heads)
|
| 101 |
+
v = rearrange(v, "b s (n d) -> b s n d", n=num_heads)
|
| 102 |
+
x = sageattn(q, k, v, tensor_layout="NHD")
|
| 103 |
+
x = rearrange(x, "b s n d -> b s (n d)", n=num_heads)
|
| 104 |
+
else:
|
| 105 |
+
q = rearrange(q, "b s (n d) -> b n s d", n=num_heads)
|
| 106 |
+
k = rearrange(k, "b s (n d) -> b n s d", n=num_heads)
|
| 107 |
+
v = rearrange(v, "b s (n d) -> b n s d", n=num_heads)
|
| 108 |
+
x = F.scaled_dot_product_attention(q, k, v)
|
| 109 |
+
x = rearrange(x, "b n s d -> b s (n d)", n=num_heads)
|
| 110 |
+
return x
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
@torch.compile(fullgraph=True)
|
| 114 |
+
def modulate(x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor):
|
| 115 |
+
return (x * (1 + scale) + shift)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def sinusoidal_embedding_1d(dim, position):
|
| 119 |
+
sinusoid = torch.outer(position.type(torch.float64), torch.pow(
|
| 120 |
+
10000, -torch.arange(dim//2, dtype=torch.float64, device=position.device).div(dim//2)))
|
| 121 |
+
x = torch.cat([torch.cos(sinusoid), torch.sin(sinusoid)], dim=1)
|
| 122 |
+
return x.to(position.dtype)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def precompute_freqs_cis_3d(dim: int, end: int = 1024, theta: float = 10000.0):
|
| 126 |
+
# 3d rope precompute
|
| 127 |
+
f_freqs_cis = precompute_freqs_cis(dim - 2 * (dim // 3), end, theta)
|
| 128 |
+
h_freqs_cis = precompute_freqs_cis(dim // 3, end, theta)
|
| 129 |
+
w_freqs_cis = precompute_freqs_cis(dim // 3, end, theta)
|
| 130 |
+
return f_freqs_cis, h_freqs_cis, w_freqs_cis
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def precompute_freqs_cis(dim: int, end: int = 1024, theta: float = 10000.0):
|
| 134 |
+
# 1d rope precompute
|
| 135 |
+
freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)
|
| 136 |
+
[: (dim // 2)].double() / dim))
|
| 137 |
+
freqs = torch.outer(torch.arange(end, device=freqs.device), freqs)
|
| 138 |
+
freqs_cis = torch.polar(torch.ones_like(freqs), freqs) # complex64
|
| 139 |
+
return freqs_cis
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def _rope_rotate(x_4d, freqs):
|
| 143 |
+
"""RoPE via sin/cos rotation in the input dtype — avoids float64 allocation.
|
| 144 |
+
|
| 145 |
+
Complex multiply (a+bi)(c+di) = (ac-bd) + (ad+bc)i is equivalent to:
|
| 146 |
+
out_even = x_even * cos - x_odd * sin
|
| 147 |
+
out_odd = x_even * sin + x_odd * cos
|
| 148 |
+
where freqs = cos + i*sin (complex64).
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
x_4d: [B, S, n, head_dim] in bf16/fp32.
|
| 152 |
+
freqs: [S, 1, head_dim//2] complex64.
|
| 153 |
+
Returns:
|
| 154 |
+
[B, S, n*head_dim] in same dtype as x_4d.
|
| 155 |
+
"""
|
| 156 |
+
cos_f = freqs.real.unsqueeze(0).to(x_4d.dtype) # [1, S, 1, head_dim//2]
|
| 157 |
+
sin_f = freqs.imag.unsqueeze(0).to(x_4d.dtype) # [1, S, 1, head_dim//2]
|
| 158 |
+
x_pairs = x_4d.unflatten(-1, (-1, 2)) # [..., head_dim//2, 2]
|
| 159 |
+
x_even = x_pairs[..., 0] # view
|
| 160 |
+
x_odd = x_pairs[..., 1] # view
|
| 161 |
+
out_even = x_even * cos_f
|
| 162 |
+
out_even.sub_(x_odd * sin_f)
|
| 163 |
+
out_odd = x_even * sin_f
|
| 164 |
+
out_odd.add_(x_odd * cos_f)
|
| 165 |
+
return torch.stack([out_even, out_odd], dim=-1).flatten(-2).flatten(2)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
@torch.amp.autocast('cuda', enabled=False)
|
| 169 |
+
def rope_apply(x, freqs, num_heads):
|
| 170 |
+
x = rearrange(x, "b s (n d) -> b s n d", n=num_heads)
|
| 171 |
+
x_out = torch.view_as_complex(x.to(torch.float64).reshape(x.shape[0], x.shape[1], x.shape[2], -1, 2))
|
| 172 |
+
x_out = torch.view_as_real(x_out * freqs).flatten(2)
|
| 173 |
+
return x_out.to(x.dtype)
|
| 174 |
+
# return _rope_rotate(x, freqs)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
@torch.amp.autocast('cuda', enabled=False)
|
| 178 |
+
def rope_apply_head_dim(x, freqs, head_dim):
|
| 179 |
+
x = rearrange(x, "b s (n d) -> b s n d", d=head_dim)
|
| 180 |
+
x_out = torch.view_as_complex(x.to(torch.float64).reshape(x.shape[0], x.shape[1], x.shape[2], -1, 2))
|
| 181 |
+
x_out = torch.view_as_real(x_out * freqs).flatten(2)
|
| 182 |
+
return x_out.to(x.dtype)
|
| 183 |
+
# return _rope_rotate(x, freqs)
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
class SlowRMSNorm(nn.Module):
|
| 187 |
+
def __init__(self, dim, eps=1e-5):
|
| 188 |
+
super().__init__()
|
| 189 |
+
self.eps = eps
|
| 190 |
+
self.weight = nn.Parameter(torch.ones(dim))
|
| 191 |
+
|
| 192 |
+
def norm(self, x):
|
| 193 |
+
return x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps)
|
| 194 |
+
|
| 195 |
+
def forward(self, x):
|
| 196 |
+
dtype = x.dtype
|
| 197 |
+
return (self.norm(x.float()) * self.weight).to(dtype)
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
class AttentionModule(nn.Module):
|
| 201 |
+
def __init__(self, num_heads):
|
| 202 |
+
super().__init__()
|
| 203 |
+
self.num_heads = num_heads
|
| 204 |
+
|
| 205 |
+
def forward(self, q, k, v):
|
| 206 |
+
x = flash_attention(q=q, k=k, v=v, num_heads=self.num_heads)
|
| 207 |
+
return x
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
class SelfAttention(nn.Module):
|
| 211 |
+
def __init__(self, dim: int, num_heads: int, eps: float = 1e-6,
|
| 212 |
+
enable_bsa: bool = False, bsa_params: dict = None,
|
| 213 |
+
enable_audio_guidance: bool = False,
|
| 214 |
+
enable_audio_concentration_gate: bool = False,
|
| 215 |
+
enable_timestep_reliability_gate: bool = False,
|
| 216 |
+
enable_variance_guidance: bool = False):
|
| 217 |
+
super().__init__()
|
| 218 |
+
self.dim = dim
|
| 219 |
+
self.num_heads = num_heads
|
| 220 |
+
self.head_dim = dim // num_heads
|
| 221 |
+
|
| 222 |
+
self.q = nn.Linear(dim, dim)
|
| 223 |
+
self.k = nn.Linear(dim, dim)
|
| 224 |
+
self.v = nn.Linear(dim, dim)
|
| 225 |
+
self.o = nn.Linear(dim, dim)
|
| 226 |
+
self.norm_q = RMSNorm(dim, eps=eps)
|
| 227 |
+
self.norm_k = RMSNorm(dim, eps=eps)
|
| 228 |
+
|
| 229 |
+
self.attn = AttentionModule(self.num_heads)
|
| 230 |
+
self.enable_bsa = enable_bsa
|
| 231 |
+
self.bsa_params = bsa_params or {}
|
| 232 |
+
|
| 233 |
+
# Which SP-sharded stream this attention operates on. MOVABridge retags
|
| 234 |
+
# the audio tower to "audio"; see sp_seq_ctx in parallel_states.
|
| 235 |
+
self.sp_stream = "visual"
|
| 236 |
+
|
| 237 |
+
# --- Audio Guidance (Section 3.5.1 Path A: Post-QK Score Modulation) ---
|
| 238 |
+
self.enable_audio_guidance = enable_audio_guidance
|
| 239 |
+
self.enable_audio_concentration_gate = enable_audio_concentration_gate
|
| 240 |
+
self.enable_timestep_reliability_gate = enable_timestep_reliability_gate
|
| 241 |
+
# --- Audio Weighted Pooling (Section 3.5.2 Path B: Pre-QK Representation Enhancement) ---
|
| 242 |
+
self.enable_audio_weighted_pooling = False
|
| 243 |
+
|
| 244 |
+
# --- Channel-Variance Guidance (non-learnable, no gate) ---
|
| 245 |
+
self.enable_variance_guidance = enable_variance_guidance
|
| 246 |
+
|
| 247 |
+
# --- Bias correction (two independent methods, at most one enabled) ---
|
| 248 |
+
self.enable_taylor_sparse_attn = False # LIVEditor method
|
| 249 |
+
self.enable_rectified_sparse_attn = False # Rectified SpaAttn method
|
| 250 |
+
|
| 251 |
+
# --- Anisotropic Dynamic Block Shape (Section 8; two methods, at most
|
| 252 |
+
# one enabled). When EITHER is on, ALL of audio-guidance,
|
| 253 |
+
# variance-guidance and bias-correction are disabled (full isolation).
|
| 254 |
+
self.enable_ivpq_dynamic_block = False # Section 8.5
|
| 255 |
+
self.enable_penalty_dynamic_block = False # Section 8.6
|
| 256 |
+
|
| 257 |
+
# (fwd_id, grid_padded, shape_ids) cache; training-only, reused on recompute.
|
| 258 |
+
self._dyn_shape_cache = None
|
| 259 |
+
|
| 260 |
+
# --- Layer-Adaptive Dynamic Block Shape (Section 8.2) ---
|
| 261 |
+
# Optional wrapper on top of a dynamic-block method. When enabled:
|
| 262 |
+
# * shallow layers (idx < _la_split) use FIXED uniform-shape BSA;
|
| 263 |
+
# * deep layers (idx >= _la_split) use the dynamic-block method;
|
| 264 |
+
# * deep layers WITH their own a2v bridge use their own audio; deep
|
| 265 |
+
# layers WITHOUT a bridge (tail beyond audio_layers) reuse the cached
|
| 266 |
+
# audio norms (seeded by the deepest bridge layer = cache-source) to
|
| 267 |
+
# drive the audio-directional gradient, combined with the deep layer's
|
| 268 |
+
# own video channel-variance.
|
| 269 |
+
# All fields are plain attributes set by configure_layer_adaptive_dynamic_block.
|
| 270 |
+
self.enable_layer_adaptive_dynamic_block = False
|
| 271 |
+
self._la_layer_idx = None # this self-attn's global video-layer index
|
| 272 |
+
self._la_split = None # shallow/deep boundary (num_video_layers // 2)
|
| 273 |
+
self._la_is_cache_source = False # deepest shallow bridge layer → seeds the cache
|
| 274 |
+
self._la_audio_cache = None # shared {'norms': tensor|None} across all video self-attns
|
| 275 |
+
|
| 276 |
+
_bsa_fallback_logged = False
|
| 277 |
+
_path_b_in_correction_warned = False
|
| 278 |
+
|
| 279 |
+
def _check_bsa(self, grid_size):
|
| 280 |
+
"""
|
| 281 |
+
Check if BSA can be applied. Returns (can_use, fallback_reason).
|
| 282 |
+
Divisibility is NOT required — internal padding + masking handles arbitrary T/H/W.
|
| 283 |
+
When sparsity=0, BSA selects ALL blocks and produces results identical to full attention.
|
| 284 |
+
"""
|
| 285 |
+
if not self.enable_bsa:
|
| 286 |
+
return False, None
|
| 287 |
+
if grid_size is None:
|
| 288 |
+
return False, "grid_size is None"
|
| 289 |
+
T, H, W = grid_size
|
| 290 |
+
if T <= 1:
|
| 291 |
+
return False, f"T={T} <= 1 (image mode)"
|
| 292 |
+
return True, None
|
| 293 |
+
|
| 294 |
+
def _prepare_audio_guidance(self, audio_token_norms, q_bhsd, valid_mask,
|
| 295 |
+
T, H, W, pad_t, pad_h, pad_w, need_pad,
|
| 296 |
+
timestep_ratio, use_path_a, use_path_b):
|
| 297 |
+
"""Prepare audio guidance parameters (isolated from variance guidance)."""
|
| 298 |
+
from hymm.models.modules.block_sparse_attention import (
|
| 299 |
+
compute_audio_spatial_concentration_gate,
|
| 300 |
+
compute_timestep_reliability_gate,
|
| 301 |
+
)
|
| 302 |
+
if need_pad:
|
| 303 |
+
norms_3d = audio_token_norms.view(T, H, W)
|
| 304 |
+
norms_padded = F.pad(norms_3d, (0, pad_w, 0, pad_h, 0, pad_t), value=0.0)
|
| 305 |
+
audio_norms_padded = norms_padded.reshape(-1).contiguous()
|
| 306 |
+
else:
|
| 307 |
+
audio_norms_padded = audio_token_norms.contiguous()
|
| 308 |
+
|
| 309 |
+
audio_gate = torch.ones(1, device=q_bhsd.device, dtype=q_bhsd.dtype)
|
| 310 |
+
audio_boost_gamma = torch.zeros(1, device=q_bhsd.device, dtype=q_bhsd.dtype)
|
| 311 |
+
audio_weighted_lambda = 0.0
|
| 312 |
+
|
| 313 |
+
if use_path_a:
|
| 314 |
+
if self.enable_timestep_reliability_gate and timestep_ratio is not None:
|
| 315 |
+
r_t = compute_timestep_reliability_gate(
|
| 316 |
+
timestep_ratio,
|
| 317 |
+
mode=self.bsa_params.get('timestep_gate_mode', 'power'),
|
| 318 |
+
power=self.bsa_params.get('timestep_gate_power', 1.5),
|
| 319 |
+
scale=self.bsa_params.get('timestep_gate_scale', 10.0),
|
| 320 |
+
threshold=self.bsa_params.get('timestep_gate_threshold', 0.7),
|
| 321 |
+
)
|
| 322 |
+
else:
|
| 323 |
+
r_t = torch.ones(1, device=q_bhsd.device, dtype=q_bhsd.dtype)
|
| 324 |
+
|
| 325 |
+
if self.enable_audio_concentration_gate:
|
| 326 |
+
g_a = compute_audio_spatial_concentration_gate(
|
| 327 |
+
audio_norms_padded, valid_mask=valid_mask)
|
| 328 |
+
else:
|
| 329 |
+
g_a = torch.ones(1, device=q_bhsd.device, dtype=q_bhsd.dtype)
|
| 330 |
+
|
| 331 |
+
audio_gate = r_t * g_a
|
| 332 |
+
audio_boost_gamma = torch.tensor(
|
| 333 |
+
self.bsa_params.get('audio_boost_gamma', 1.0),
|
| 334 |
+
device=q_bhsd.device, dtype=q_bhsd.dtype,
|
| 335 |
+
)
|
| 336 |
+
|
| 337 |
+
if use_path_b:
|
| 338 |
+
audio_weighted_lambda = self.bsa_params.get('audio_weighted_lambda', 1.0)
|
| 339 |
+
|
| 340 |
+
return audio_norms_padded, audio_gate, audio_boost_gamma, audio_weighted_lambda
|
| 341 |
+
|
| 342 |
+
def _run_bsa_dynamic(self, q_bhsd, k_bhsd, v_bhsd, grid_size, audio_token_norms=None):
|
| 343 |
+
"""Anisotropic Dynamic Block Shape BSA (Section 8) — video self-attn only.
|
| 344 |
+
|
| 345 |
+
Fully isolated from `_run_bsa`'s uniform path. Pads T/H/W to a multiple of
|
| 346 |
+
the macro-zone size (8) using the SAME internal padding+mask+unpad
|
| 347 |
+
contract, builds a `valid_mask` so padded tokens never participate, then
|
| 348 |
+
defers all the heavy lifting (per-zone shape selection, anisotropic
|
| 349 |
+
rearrange, logical-block selection, Triton kernel reuse, scatter back) to
|
| 350 |
+
`flash_attn_bsa_3d_dynamic`.
|
| 351 |
+
|
| 352 |
+
`audio_token_norms` (per-token ||Δh_v||_2 of the A→V bridge residual, in
|
| 353 |
+
THW order, full sequence) drives the audio-directional gradient when
|
| 354 |
+
available; it is optional (silence/no-bridge → video-only shape decision).
|
| 355 |
+
"""
|
| 356 |
+
from hymm.models.modules.block_sparse_attention.dynamic_block_attention import (
|
| 357 |
+
flash_attn_bsa_3d_dynamic,
|
| 358 |
+
)
|
| 359 |
+
from hymm.models.modules.block_sparse_attention.dynamic_block_shape import ZONE_SIZE
|
| 360 |
+
|
| 361 |
+
T, H, W = grid_size
|
| 362 |
+
B, n_heads, S, D = q_bhsd.shape
|
| 363 |
+
|
| 364 |
+
pad_t = (ZONE_SIZE - T % ZONE_SIZE) % ZONE_SIZE
|
| 365 |
+
pad_h = (ZONE_SIZE - H % ZONE_SIZE) % ZONE_SIZE
|
| 366 |
+
pad_w = (ZONE_SIZE - W % ZONE_SIZE) % ZONE_SIZE
|
| 367 |
+
need_pad = pad_t > 0 or pad_h > 0 or pad_w > 0
|
| 368 |
+
|
| 369 |
+
valid_mask = None
|
| 370 |
+
audio_norms_p = audio_token_norms
|
| 371 |
+
if need_pad:
|
| 372 |
+
T_p, H_p, W_p = T + pad_t, H + pad_h, W + pad_w
|
| 373 |
+
idx_t = torch.arange(T_p, device=q_bhsd.device)
|
| 374 |
+
idx_h = torch.arange(H_p, device=q_bhsd.device)
|
| 375 |
+
idx_w = torch.arange(W_p, device=q_bhsd.device)
|
| 376 |
+
valid_mask = (
|
| 377 |
+
(idx_t[:, None, None] < T)
|
| 378 |
+
& (idx_h[None, :, None] < H)
|
| 379 |
+
& (idx_w[None, None, :] < W)
|
| 380 |
+
).reshape(-1).contiguous()
|
| 381 |
+
|
| 382 |
+
def _pad_3d(t):
|
| 383 |
+
t_3d = t.view(B, n_heads, T, H, W, D)
|
| 384 |
+
t_padded = F.pad(t_3d, (0, 0, 0, pad_w, 0, pad_h, 0, pad_t))
|
| 385 |
+
return t_padded.reshape(B, n_heads, -1, D).contiguous()
|
| 386 |
+
|
| 387 |
+
q_bhsd = _pad_3d(q_bhsd)
|
| 388 |
+
k_bhsd = _pad_3d(k_bhsd)
|
| 389 |
+
v_bhsd = _pad_3d(v_bhsd)
|
| 390 |
+
if audio_token_norms is not None:
|
| 391 |
+
norms_3d = audio_token_norms.reshape(T, H, W)
|
| 392 |
+
audio_norms_p = F.pad(
|
| 393 |
+
norms_3d, (0, pad_w, 0, pad_h, 0, pad_t), value=0.0
|
| 394 |
+
).reshape(-1).contiguous()
|
| 395 |
+
grid_padded = (T_p, H_p, W_p)
|
| 396 |
+
else:
|
| 397 |
+
grid_padded = grid_size
|
| 398 |
+
|
| 399 |
+
method = "ivpq" if self.enable_ivpq_dynamic_block else "penalty"
|
| 400 |
+
|
| 401 |
+
# Training-only: reuse this forward's shape decision on the recompute
|
| 402 |
+
# (cache hit when fwd_id+grid match). Eval bypasses the cache entirely.
|
| 403 |
+
use_shape_cache = self.training
|
| 404 |
+
cached_shape_ids = None
|
| 405 |
+
if use_shape_cache:
|
| 406 |
+
cur_fwd_id = current_dynamic_block_pass_id()
|
| 407 |
+
cached = self._dyn_shape_cache
|
| 408 |
+
if (cached is not None and cached[0] == cur_fwd_id
|
| 409 |
+
and cached[1] == grid_padded):
|
| 410 |
+
cached_shape_ids = cached[2]
|
| 411 |
+
|
| 412 |
+
out, used_shape_ids = flash_attn_bsa_3d_dynamic(
|
| 413 |
+
q_bhsd, k_bhsd, v_bhsd, grid_padded,
|
| 414 |
+
method=method,
|
| 415 |
+
sparsity=self.bsa_params.get('sparsity', 0.9375),
|
| 416 |
+
cdf_threshold=self.bsa_params.get('cdf_threshold', None),
|
| 417 |
+
audio_token_norms=audio_norms_p,
|
| 418 |
+
valid_mask=valid_mask,
|
| 419 |
+
lambda_a=self.bsa_params.get('dynamic_block_lambda_a', 0.5),
|
| 420 |
+
tau_128=self.bsa_params.get('dynamic_block_tau_128', 0.15),
|
| 421 |
+
lambda_128=self.bsa_params.get('dynamic_block_lambda_128', 1.0),
|
| 422 |
+
shape_ids=cached_shape_ids,
|
| 423 |
+
return_shape_ids=True,
|
| 424 |
+
)
|
| 425 |
+
|
| 426 |
+
if use_shape_cache:
|
| 427 |
+
self._dyn_shape_cache = (cur_fwd_id, grid_padded, used_shape_ids)
|
| 428 |
+
|
| 429 |
+
if need_pad:
|
| 430 |
+
out_3d = out.view(B, n_heads, T_p, H_p, W_p, D)
|
| 431 |
+
out = out_3d[:, :, :T, :H, :W, :].contiguous().reshape(B, n_heads, T * H * W, D)
|
| 432 |
+
return out
|
| 433 |
+
|
| 434 |
+
def _run_bsa(self, q_bhsd, k_bhsd, v_bhsd, grid_size, audio_token_norms=None, timestep_ratio=None):
|
| 435 |
+
"""Run BSA with internal temporal+spatial padding/masking/unpadding.
|
| 436 |
+
|
| 437 |
+
Execution order (when all features are enabled):
|
| 438 |
+
1. Padding (temporal + spatial) + valid_mask creation
|
| 439 |
+
2. Audio/Variance score modulation (multiplicative boost)
|
| 440 |
+
3. Block selection (top-k / top-p)
|
| 441 |
+
4. BSA Triton kernel → o_spa
|
| 442 |
+
5. **Bias rectification** (post-hoc, training-free, Section 3.6):
|
| 443 |
+
a. Reproduce block-level scoring (O(N_q*N_k), negligible cost)
|
| 444 |
+
b. Compute R_n (selected weight fraction), M_n (query sharpness)
|
| 445 |
+
c. Compute O_nonsel_pool (Taylor approximation from non-selected blocks)
|
| 446 |
+
d. flat: O = R_n * O_spa + O_nonsel_pool
|
| 447 |
+
sharp: O = R_n * O_spa
|
| 448 |
+
6. Unpadding
|
| 449 |
+
|
| 450 |
+
Bias correction controlled by enable_taylor_sparse_attn / enable_rectified_sparse_attn.
|
| 451 |
+
Isolated from audio/variance guidance code paths.
|
| 452 |
+
"""
|
| 453 |
+
# === Anisotropic Dynamic Block Shape (Section 8) — fully isolated path ===
|
| 454 |
+
# When a dynamic-block trigger is on we route to a dedicated method and
|
| 455 |
+
# never touch the uniform-shape / audio / variance / bias-correction code
|
| 456 |
+
# below. The two dynamic methods are mutually exclusive with each other
|
| 457 |
+
# and with all the other BSA features (enforced in configure_* + asserts).
|
| 458 |
+
use_dynamic = self.enable_ivpq_dynamic_block or self.enable_penalty_dynamic_block
|
| 459 |
+
if use_dynamic and self.enable_layer_adaptive_dynamic_block \
|
| 460 |
+
and self._la_layer_idx is not None and self._la_split is not None:
|
| 461 |
+
# Layer-adaptive (Section 8.2): shallow half → FIXED uniform-shape BSA
|
| 462 |
+
# (falls through to the uniform dispatch below); deep half → dynamic.
|
| 463 |
+
if self._la_layer_idx < self._la_split:
|
| 464 |
+
use_dynamic = False
|
| 465 |
+
if use_dynamic:
|
| 466 |
+
return self._run_bsa_dynamic(
|
| 467 |
+
q_bhsd, k_bhsd, v_bhsd, grid_size, audio_token_norms=audio_token_norms,
|
| 468 |
+
)
|
| 469 |
+
|
| 470 |
+
from hymm.models.modules.block_sparse_attention import flash_attn_bsa_3d_audio_guided
|
| 471 |
+
|
| 472 |
+
T, H, W = grid_size
|
| 473 |
+
cq = self.bsa_params.get('chunk_3d_shape_q', [4, 4, 4])
|
| 474 |
+
ck = self.bsa_params.get('chunk_3d_shape_k', [4, 4, 4])
|
| 475 |
+
chunk_t = max(cq[0], ck[0])
|
| 476 |
+
chunk_h = max(cq[1], ck[1])
|
| 477 |
+
chunk_w = max(cq[2], ck[2])
|
| 478 |
+
|
| 479 |
+
pad_t = (chunk_t - T % chunk_t) % chunk_t
|
| 480 |
+
pad_h = (chunk_h - H % chunk_h) % chunk_h
|
| 481 |
+
pad_w = (chunk_w - W % chunk_w) % chunk_w
|
| 482 |
+
|
| 483 |
+
B, n_heads, S, D = q_bhsd.shape
|
| 484 |
+
need_pad = pad_t > 0 or pad_h > 0 or pad_w > 0
|
| 485 |
+
|
| 486 |
+
if need_pad:
|
| 487 |
+
T_p, H_p, W_p = T + pad_t, H + pad_h, W + pad_w
|
| 488 |
+
|
| 489 |
+
idx_t = torch.arange(T_p, device=q_bhsd.device)
|
| 490 |
+
idx_h = torch.arange(H_p, device=q_bhsd.device)
|
| 491 |
+
idx_w = torch.arange(W_p, device=q_bhsd.device)
|
| 492 |
+
valid_mask = (
|
| 493 |
+
(idx_t[:, None, None] < T)
|
| 494 |
+
& (idx_h[None, :, None] < H)
|
| 495 |
+
& (idx_w[None, None, :] < W)
|
| 496 |
+
).reshape(-1).contiguous()
|
| 497 |
+
|
| 498 |
+
def _pad_3d(t):
|
| 499 |
+
t_3d = t.view(B, n_heads, T, H, W, D)
|
| 500 |
+
t_padded = F.pad(t_3d, (0, 0, 0, pad_w, 0, pad_h, 0, pad_t))
|
| 501 |
+
return t_padded.reshape(B, n_heads, -1, D).contiguous()
|
| 502 |
+
|
| 503 |
+
q_bhsd = _pad_3d(q_bhsd)
|
| 504 |
+
k_bhsd = _pad_3d(k_bhsd)
|
| 505 |
+
v_bhsd = _pad_3d(v_bhsd)
|
| 506 |
+
grid_padded = (T_p, H_p, W_p)
|
| 507 |
+
else:
|
| 508 |
+
grid_padded = grid_size
|
| 509 |
+
valid_mask = None
|
| 510 |
+
|
| 511 |
+
# --- Separate guidance-specific keys from core BSA params ---
|
| 512 |
+
_GUIDANCE_ONLY_KEYS = ('audio_boost_gamma', 'audio_weighted_lambda',
|
| 513 |
+
'timestep_gate_mode', 'timestep_gate_power',
|
| 514 |
+
'timestep_gate_scale', 'timestep_gate_threshold',
|
| 515 |
+
'variance_boost_gamma',
|
| 516 |
+
'taylor_alpha_f',
|
| 517 |
+
# dynamic-block knobs — never go to the uniform kernel
|
| 518 |
+
# (used by shallow layers under layer-adaptive mode)
|
| 519 |
+
'dynamic_block_lambda_a', 'dynamic_block_tau_128',
|
| 520 |
+
'dynamic_block_lambda_128')
|
| 521 |
+
bsa_kwargs = {k: v for k, v in self.bsa_params.items() if k not in _GUIDANCE_ONLY_KEYS}
|
| 522 |
+
|
| 523 |
+
has_audio_norms = (audio_token_norms is not None and audio_token_norms.numel() > 0)
|
| 524 |
+
use_path_a = self.enable_audio_guidance and has_audio_norms
|
| 525 |
+
use_path_b = self.enable_audio_weighted_pooling and has_audio_norms
|
| 526 |
+
use_audio = use_path_a or use_path_b
|
| 527 |
+
use_variance = self.enable_variance_guidance
|
| 528 |
+
|
| 529 |
+
# =====================================================================
|
| 530 |
+
# Prepare audio guidance data (shared by both dispatch and bias_rect).
|
| 531 |
+
# Done once, used in whichever path is active.
|
| 532 |
+
# =====================================================================
|
| 533 |
+
audio_norms_padded = audio_gate = audio_boost_gamma = audio_weighted_lambda = None
|
| 534 |
+
if use_audio:
|
| 535 |
+
audio_norms_padded, audio_gate, audio_boost_gamma, audio_weighted_lambda = (
|
| 536 |
+
self._prepare_audio_guidance(
|
| 537 |
+
audio_token_norms, q_bhsd, valid_mask, T, H, W,
|
| 538 |
+
pad_t, pad_h, pad_w, need_pad, timestep_ratio,
|
| 539 |
+
use_path_a, use_path_b,
|
| 540 |
+
)
|
| 541 |
+
)
|
| 542 |
+
|
| 543 |
+
# =====================================================================
|
| 544 |
+
# Dispatch: 3 mutually exclusive modes
|
| 545 |
+
# 1. Taylor sparse attn (LIVEditor) — selected + Taylor in one softmax
|
| 546 |
+
# 2. Rectified sparse attn (Rectified SpaAttn) — R_n * o_spa + o_ncri
|
| 547 |
+
# 3. Plain BSA (default) — original paths, unchanged
|
| 548 |
+
# Audio/variance guidance affects block SELECTION only (score_modifier).
|
| 549 |
+
# =====================================================================
|
| 550 |
+
use_correction = self.enable_taylor_sparse_attn or self.enable_rectified_sparse_attn
|
| 551 |
+
|
| 552 |
+
if use_correction:
|
| 553 |
+
# Bias rectification lives in a fully-isolated module so that the
|
| 554 |
+
# audio-guidance / variance-guidance code paths above are untouched.
|
| 555 |
+
from hymm.models.modules.block_sparse_attention.bias_rectification import (
|
| 556 |
+
bsa_taylor_sparse_attn, bsa_rectified_sparse_attn,
|
| 557 |
+
)
|
| 558 |
+
from hymm.models.modules.block_sparse_attention.bsa_interface import (
|
| 559 |
+
rearrange_THW_to_3d_block, rearrange_3d_block_to_THW,
|
| 560 |
+
rearrange_THW_to_3d_block_1d,
|
| 561 |
+
)
|
| 562 |
+
|
| 563 |
+
_ck = bsa_kwargs.get('chunk_3d_shape_k', [4, 4, 4])
|
| 564 |
+
_cq = bsa_kwargs.get('chunk_3d_shape_q', [4, 4, 4])
|
| 565 |
+
_tk, _hk, _wk = _ck[0], _ck[1], _ck[2]
|
| 566 |
+
_tq, _hq, _wq = _cq[0], _cq[1], _cq[2]
|
| 567 |
+
_Tp, _Hp, _Wp = grid_padded
|
| 568 |
+
_Ntk, _Nhk, _Nwk = _Tp // _tk, _Hp // _hk, _Wp // _wk
|
| 569 |
+
_Ntq, _Nhq, _Nwq = _Tp // _tq, _Hp // _hq, _Wp // _wq
|
| 570 |
+
|
| 571 |
+
# Rearrange once, reuse for variance/audio/correction
|
| 572 |
+
q_ro = rearrange_THW_to_3d_block(q_bhsd, _Ntq, _Nhq, _Nwq, _tq, _hq, _wq, D)
|
| 573 |
+
k_ro = rearrange_THW_to_3d_block(k_bhsd, _Ntk, _Nhk, _Nwk, _tk, _hk, _wk, D)
|
| 574 |
+
v_ro = rearrange_THW_to_3d_block(v_bhsd, _Ntk, _Nhk, _Nwk, _tk, _hk, _wk, D)
|
| 575 |
+
kv_vm_ro = rearrange_THW_to_3d_block_1d(valid_mask, _Ntk, _Nhk, _Nwk, _tk, _hk, _wk) if valid_mask is not None else None
|
| 576 |
+
|
| 577 |
+
score_mod = None
|
| 578 |
+
if use_variance:
|
| 579 |
+
from hymm.models.modules.block_sparse_attention.bsa_interface import compute_channel_variance_density
|
| 580 |
+
with torch.no_grad():
|
| 581 |
+
_rho = compute_channel_variance_density(v_ro, _tk*_hk*_wk, _tk, _hk, _wk, kv_vm_ro)
|
| 582 |
+
score_mod = self.bsa_params.get('variance_boost_gamma', 1.0) * _rho.to(q_bhsd.dtype).view(1, 1, 1, -1)
|
| 583 |
+
|
| 584 |
+
if use_audio and audio_norms_padded is not None:
|
| 585 |
+
# Path A boost: γ_audio * (g_a * S̃_j + (1 - g_a) / N_bk)
|
| 586 |
+
# All ops stay on-GPU. Avoid `.item()` here — `audio_gate` and
|
| 587 |
+
# `audio_boost_gamma` are 1-element CUDA tensors, and `.item()`
|
| 588 |
+
# synchronises the GPU pipeline (each call drains the queue;
|
| 589 |
+
# 30 layers x 50 steps x 2 (checkpoint recompute) = 3000 syncs
|
| 590 |
+
# per training step). Use plain tensor broadcasting instead.
|
| 591 |
+
from hymm.models.modules.block_sparse_attention.bsa_interface import compute_block_audio_saliency
|
| 592 |
+
_anorms_bo = rearrange_THW_to_3d_block_1d(audio_norms_padded, _Ntk, _Nhk, _Nwk, _tk, _hk, _wk)
|
| 593 |
+
_bsal = compute_block_audio_saliency(_anorms_bo, _tk*_hk*_wk, valid_mask=kv_vm_ro)
|
| 594 |
+
_N_bk = _Ntk * _Nhk * _Nwk
|
| 595 |
+
# Cast scalar/Python audio_gate to a 1-elem tensor for uniform broadcast.
|
| 596 |
+
if not torch.is_tensor(audio_gate):
|
| 597 |
+
audio_gate = torch.tensor(audio_gate, device=q_bhsd.device, dtype=q_bhsd.dtype)
|
| 598 |
+
if not torch.is_tensor(audio_boost_gamma):
|
| 599 |
+
audio_boost_gamma = torch.tensor(audio_boost_gamma, device=q_bhsd.device, dtype=q_bhsd.dtype)
|
| 600 |
+
_gv = audio_gate.to(q_bhsd.dtype)
|
| 601 |
+
_bg = audio_boost_gamma.to(q_bhsd.dtype)
|
| 602 |
+
_gated = _gv * _bsal.view(1, 1, 1, -1).to(q_bhsd.dtype) + (1.0 - _gv) / _N_bk
|
| 603 |
+
audio_term = _bg * _gated
|
| 604 |
+
score_mod = (score_mod + audio_term) if score_mod is not None else audio_term
|
| 605 |
+
|
| 606 |
+
# Warn-once: Path B (audio-weighted K pooling) has no effect in the
|
| 607 |
+
# bias-rectification path. Taylor / Rectified methods use plain mean
|
| 608 |
+
# pooling for K (so autograd through pooling is correct). Path A
|
| 609 |
+
# boost (audio_boost_gamma) still applies.
|
| 610 |
+
if self.enable_audio_weighted_pooling and not SelfAttention._path_b_in_correction_warned:
|
| 611 |
+
print(
|
| 612 |
+
"[BSA] enable_audio_weighted_pooling (Path B) is silently ignored "
|
| 613 |
+
"when enable_taylor_sparse_attn / enable_rectified_sparse_attn is "
|
| 614 |
+
"active. Path A boost still applies."
|
| 615 |
+
)
|
| 616 |
+
SelfAttention._path_b_in_correction_warned = True
|
| 617 |
+
|
| 618 |
+
if score_mod is not None:
|
| 619 |
+
score_mod = (1.0 + score_mod).contiguous()
|
| 620 |
+
|
| 621 |
+
_sm = 1.0 / (D ** 0.5)
|
| 622 |
+
_sp = bsa_kwargs.get('sparsity', 0.9375)
|
| 623 |
+
_cdf = bsa_kwargs.get('cdf_threshold', None)
|
| 624 |
+
if self.enable_taylor_sparse_attn:
|
| 625 |
+
out = bsa_taylor_sparse_attn(
|
| 626 |
+
q_ro, k_ro, v_ro,
|
| 627 |
+
chunk_size_q=_tq * _hq * _wq,
|
| 628 |
+
chunk_size_k=_tk * _hk * _wk,
|
| 629 |
+
sparsity=_sp, cdf_threshold=_cdf, sm_scale=_sm,
|
| 630 |
+
alpha_f=self.bsa_params.get('taylor_alpha_f', 0.5),
|
| 631 |
+
kv_valid_mask=kv_vm_ro, score_modifier=score_mod,
|
| 632 |
+
)
|
| 633 |
+
else:
|
| 634 |
+
out = bsa_rectified_sparse_attn(
|
| 635 |
+
q_ro, k_ro, v_ro,
|
| 636 |
+
chunk_size_q=_tq * _hq * _wq,
|
| 637 |
+
chunk_size_k=_tk * _hk * _wk,
|
| 638 |
+
sparsity=_sp, cdf_threshold=_cdf, sm_scale=_sm,
|
| 639 |
+
kv_valid_mask=kv_vm_ro, score_modifier=score_mod,
|
| 640 |
+
)
|
| 641 |
+
out = rearrange_3d_block_to_THW(out, _Ntq, _Nhq, _Nwq, _tq, _hq, _wq, D)
|
| 642 |
+
|
| 643 |
+
else:
|
| 644 |
+
# ---------- Original dispatch paths (completely unchanged) ----------
|
| 645 |
+
if use_variance and not use_audio:
|
| 646 |
+
from hymm.models.modules.block_sparse_attention import flash_attn_bsa_3d_variance_guided
|
| 647 |
+
out = flash_attn_bsa_3d_variance_guided(
|
| 648 |
+
q_bhsd, k_bhsd, v_bhsd, grid_padded, grid_padded,
|
| 649 |
+
variance_boost_gamma=self.bsa_params.get('variance_boost_gamma', 1.0),
|
| 650 |
+
valid_mask=valid_mask, **bsa_kwargs,
|
| 651 |
+
)
|
| 652 |
+
elif use_audio and not use_variance:
|
| 653 |
+
out = flash_attn_bsa_3d_audio_guided(
|
| 654 |
+
q_bhsd, k_bhsd, v_bhsd, grid_padded, grid_padded,
|
| 655 |
+
audio_token_norms=audio_norms_padded,
|
| 656 |
+
audio_gate_value=audio_gate,
|
| 657 |
+
audio_boost_gamma=audio_boost_gamma,
|
| 658 |
+
valid_mask=valid_mask,
|
| 659 |
+
audio_weighted_lambda=audio_weighted_lambda,
|
| 660 |
+
**bsa_kwargs,
|
| 661 |
+
)
|
| 662 |
+
elif use_audio and use_variance:
|
| 663 |
+
# Pass variance_boost_gamma so ρ is computed INSIDE on the already-
|
| 664 |
+
# rearranged V — saves one large `rearrange_THW_to_3d_block(v)` per
|
| 665 |
+
# layer per step (significant on 2K where V is ~600K tokens).
|
| 666 |
+
# Mathematically identical to the explicit-score_modifier route.
|
| 667 |
+
out = flash_attn_bsa_3d_audio_guided(
|
| 668 |
+
q_bhsd, k_bhsd, v_bhsd, grid_padded, grid_padded,
|
| 669 |
+
audio_token_norms=audio_norms_padded,
|
| 670 |
+
audio_gate_value=audio_gate,
|
| 671 |
+
audio_boost_gamma=audio_boost_gamma,
|
| 672 |
+
valid_mask=valid_mask,
|
| 673 |
+
audio_weighted_lambda=audio_weighted_lambda,
|
| 674 |
+
variance_boost_gamma=self.bsa_params.get('variance_boost_gamma', 1.0),
|
| 675 |
+
**bsa_kwargs,
|
| 676 |
+
)
|
| 677 |
+
else:
|
| 678 |
+
out = flash_attn_bsa_3d(
|
| 679 |
+
q_bhsd, k_bhsd, v_bhsd, grid_padded, grid_padded,
|
| 680 |
+
valid_mask=valid_mask, **bsa_kwargs,
|
| 681 |
+
)
|
| 682 |
+
|
| 683 |
+
if need_pad:
|
| 684 |
+
out_3d = out.view(B, n_heads, T_p, H_p, W_p, D)
|
| 685 |
+
out = out_3d[:, :, :T, :H, :W, :].contiguous().reshape(B, n_heads, T * H * W, D)
|
| 686 |
+
|
| 687 |
+
return out
|
| 688 |
+
|
| 689 |
+
def forward(self, x, freqs, grid_size=None, a2v_bridge_residual=None, timestep_ratio=None):
|
| 690 |
+
"""
|
| 691 |
+
Args:
|
| 692 |
+
a2v_bridge_residual: [B, S, D] — A→V bridge cross-attention output (before scale).
|
| 693 |
+
Used for audio-guided BSA scoring. Only effective when
|
| 694 |
+
enable_audio_guidance=True and BSA is active.
|
| 695 |
+
timestep_ratio: scalar in [0,1] — normalized timestep (0=clean, 1=noise).
|
| 696 |
+
Used for Timestep Reliability Gate. Only effective when
|
| 697 |
+
enable_timestep_reliability_gate=True.
|
| 698 |
+
"""
|
| 699 |
+
q = self.norm_q(self.q(x))
|
| 700 |
+
k = self.norm_k(self.k(x))
|
| 701 |
+
v = self.v(x)
|
| 702 |
+
if isinstance(freqs, DTensor):
|
| 703 |
+
freqs = freqs.to_local()
|
| 704 |
+
q = rope_apply_head_dim(q, freqs, self.head_dim)
|
| 705 |
+
k = rope_apply_head_dim(k, freqs, self.head_dim)
|
| 706 |
+
|
| 707 |
+
use_bsa, fallback_reason = self._check_bsa(grid_size)
|
| 708 |
+
|
| 709 |
+
if self.enable_bsa and not use_bsa and not SelfAttention._bsa_fallback_logged:
|
| 710 |
+
print(f"[BSA] Falling back to full attention: {fallback_reason} "
|
| 711 |
+
f"(grid_size={grid_size})")
|
| 712 |
+
SelfAttention._bsa_fallback_logged = True
|
| 713 |
+
|
| 714 |
+
# Compute per-token audio norms for audio guidance (before SP reshuffling).
|
| 715 |
+
# Also needed by the dynamic-block path for the audio-directional gradient.
|
| 716 |
+
audio_token_norms = None
|
| 717 |
+
# Layer-adaptive dynamic block (Section 8.2): only effective with a dynamic method.
|
| 718 |
+
la_on = (self.enable_layer_adaptive_dynamic_block
|
| 719 |
+
and (self.enable_ivpq_dynamic_block or self.enable_penalty_dynamic_block)
|
| 720 |
+
and self._la_layer_idx is not None and self._la_split is not None)
|
| 721 |
+
la_is_deep = la_on and (self._la_layer_idx >= self._la_split)
|
| 722 |
+
|
| 723 |
+
use_any_audio = use_bsa and (
|
| 724 |
+
self.enable_audio_guidance or self.enable_audio_weighted_pooling
|
| 725 |
+
or self.enable_ivpq_dynamic_block or self.enable_penalty_dynamic_block
|
| 726 |
+
)
|
| 727 |
+
# Decide whether THIS layer needs to compute audio norms (a per-layer
|
| 728 |
+
# all-gather under SP). Under layer-adaptive mode we avoid wasted work:
|
| 729 |
+
# * shallow layers run FIXED BSA (ignore audio) → only the designated
|
| 730 |
+
# cache-source layer computes audio (to seed the shallow→deep cache);
|
| 731 |
+
# * deep layers compute their own only if they actually have a bridge
|
| 732 |
+
# (otherwise audio stays None and they reuse the cache below).
|
| 733 |
+
if la_on:
|
| 734 |
+
need_audio = True if la_is_deep else self._la_is_cache_source
|
| 735 |
+
else:
|
| 736 |
+
need_audio = use_any_audio
|
| 737 |
+
sp_full_len = sp_seq_ctx.get(self.sp_stream)
|
| 738 |
+
|
| 739 |
+
if need_audio and a2v_bridge_residual is not None:
|
| 740 |
+
# a2v_bridge_residual: [B, S_local, D] (SP-split) or [B, S, D] (no SP)
|
| 741 |
+
# Compute per-token L2 norm: ||Δh_v[m]||_2
|
| 742 |
+
with torch.no_grad():
|
| 743 |
+
local_norms = a2v_bridge_residual.norm(dim=-1) # [B, S_local]
|
| 744 |
+
|
| 745 |
+
if get_sequence_parallel_state():
|
| 746 |
+
# All-gather norms across SP ranks to get full sequence
|
| 747 |
+
local_norms_4d = local_norms.unsqueeze(-1) # [B, S_local, 1]
|
| 748 |
+
full_norms = all_gather(local_norms_4d, dim=1, group=nccl_info.sp_group,
|
| 749 |
+
full_seq_len=sp_full_len)
|
| 750 |
+
audio_token_norms = full_norms.squeeze(-1)[0] # [S]
|
| 751 |
+
else:
|
| 752 |
+
audio_token_norms = local_norms[0] # [S]
|
| 753 |
+
|
| 754 |
+
# Seed the shallow→deep cache (detached, full-seq THW order, SP-consistent).
|
| 755 |
+
# `audio_token_norms` is computed under no_grad above (no graph retained).
|
| 756 |
+
if la_on and self._la_is_cache_source and self._la_audio_cache is not None:
|
| 757 |
+
self._la_audio_cache['norms'] = audio_token_norms.contiguous()
|
| 758 |
+
|
| 759 |
+
# Deep layers under layer-adaptive: reuse cached shallow audio when this layer
|
| 760 |
+
# has no bridge of its own (own audio_token_norms is None for those layers).
|
| 761 |
+
if la_is_deep and audio_token_norms is None and self._la_audio_cache is not None:
|
| 762 |
+
audio_token_norms = self._la_audio_cache.get('norms', None)
|
| 763 |
+
|
| 764 |
+
if get_sequence_parallel_state():
|
| 765 |
+
sp_size = nccl_info.sp_size
|
| 766 |
+
sp_rank = nccl_info.rank_within_spgroup
|
| 767 |
+
# Ulysses requires num_heads % sp_size == 0. When it is not (e.g. audio
|
| 768 |
+
# DiT = 12 heads with sp_size=8), pad the head dim with ZERO heads to the
|
| 769 |
+
# next multiple. Padded heads are sliced off before any attention compute
|
| 770 |
+
# and truncated after the inverse all_to_all, so they never enter the
|
| 771 |
+
# BSA Triton / flash kernel and never affect the real heads' fwd/bwd.
|
| 772 |
+
# When num_heads is already divisible (e.g. video = 40 heads), padded_heads
|
| 773 |
+
# == num_heads and this path is bit-identical to the original code.
|
| 774 |
+
padded_heads = sp_pad_num_heads(self.num_heads, sp_size)
|
| 775 |
+
|
| 776 |
+
q, k, v = [rearrange(t, "b s (n d) -> b s n d", n=self.num_heads) for t in (q, k, v)]
|
| 777 |
+
if padded_heads != self.num_heads:
|
| 778 |
+
q = sp_pad_heads(q, padded_heads)
|
| 779 |
+
k = sp_pad_heads(k, padded_heads)
|
| 780 |
+
v = sp_pad_heads(v, padded_heads)
|
| 781 |
+
|
| 782 |
+
q = all_to_all_4D(q, nccl_info.sp_group, scatter_dim=2, gather_dim=1,
|
| 783 |
+
full_seq_len=sp_full_len)
|
| 784 |
+
k = all_to_all_4D(k, nccl_info.sp_group, scatter_dim=2, gather_dim=1,
|
| 785 |
+
full_seq_len=sp_full_len)
|
| 786 |
+
v = all_to_all_4D(v, nccl_info.sp_group, scatter_dim=2, gather_dim=1,
|
| 787 |
+
full_seq_len=sp_full_len)
|
| 788 |
+
|
| 789 |
+
hpr = q.shape[2] # heads-per-rank after scatter (= padded_heads // sp_size)
|
| 790 |
+
local_heads = sp_num_local_real_heads(self.num_heads, padded_heads, sp_size, sp_rank)
|
| 791 |
+
if local_heads > 0:
|
| 792 |
+
if local_heads != hpr:
|
| 793 |
+
# Mixed rank: keep only the real-head prefix. The slice stays
|
| 794 |
+
# connected to the all_to_all output, so its backward yields grad
|
| 795 |
+
# for all hpr local heads (zeros for the padded tail) and the
|
| 796 |
+
# q/k/v all_to_all collectives still fire here.
|
| 797 |
+
q = q[:, :, :local_heads, :]
|
| 798 |
+
k = k[:, :, :local_heads, :]
|
| 799 |
+
v = v[:, :, :local_heads, :]
|
| 800 |
+
if use_bsa:
|
| 801 |
+
q_bsa = q.permute(0, 2, 1, 3).contiguous()
|
| 802 |
+
k_bsa = k.permute(0, 2, 1, 3).contiguous()
|
| 803 |
+
v_bsa = v.permute(0, 2, 1, 3).contiguous()
|
| 804 |
+
x_bsa = self._run_bsa(q_bsa, k_bsa, v_bsa, grid_size,
|
| 805 |
+
audio_token_norms=audio_token_norms,
|
| 806 |
+
timestep_ratio=timestep_ratio)
|
| 807 |
+
x = rearrange(x_bsa, "b n s d -> b s (n d)")
|
| 808 |
+
else:
|
| 809 |
+
x = flash_attention(q.flatten(2), k.flatten(2), v.flatten(2), num_heads=local_heads)
|
| 810 |
+
x = rearrange(x, "b s (n d) -> b s n d", n=local_heads)
|
| 811 |
+
if x.shape[2] != hpr:
|
| 812 |
+
# Re-pad to hpr so the inverse all_to_all (head gather) is balanced.
|
| 813 |
+
x = sp_pad_heads(x, hpr)
|
| 814 |
+
else:
|
| 815 |
+
# Padding-only rank: this rank holds NO real heads, so NO attention
|
| 816 |
+
# kernel is run (padding never enters the BSA Triton / flash kernel).
|
| 817 |
+
# CRITICAL: x must stay CONNECTED to q/k/v (value forced to zero) so the
|
| 818 |
+
# backward all_to_all for q/k/v still fires on this rank. This keeps the
|
| 819 |
+
# NCCL collectives symmetric across ranks (no deadlock) AND lets this
|
| 820 |
+
# rank's real tokens receive their correct q/k/v gradients, which are
|
| 821 |
+
# computed on the head-owner ranks and routed back through the all_to_all.
|
| 822 |
+
# `* 0.0` guarantees zero contribution to forward/backward of real heads.
|
| 823 |
+
x = (q + k + v) * 0.0
|
| 824 |
+
x = all_to_all_4D(x, nccl_info.sp_group, scatter_dim=1, gather_dim=2,
|
| 825 |
+
full_seq_len=sp_full_len)
|
| 826 |
+
if padded_heads != self.num_heads:
|
| 827 |
+
x = x[:, :, :self.num_heads, :]
|
| 828 |
+
x = x.flatten(2)
|
| 829 |
+
else:
|
| 830 |
+
if use_bsa:
|
| 831 |
+
q_bsa = rearrange(q, "b s (n d) -> b n s d", n=self.num_heads).contiguous()
|
| 832 |
+
k_bsa = rearrange(k, "b s (n d) -> b n s d", n=self.num_heads).contiguous()
|
| 833 |
+
v_bsa = rearrange(v, "b s (n d) -> b n s d", n=self.num_heads).contiguous()
|
| 834 |
+
x_bsa = self._run_bsa(q_bsa, k_bsa, v_bsa, grid_size,
|
| 835 |
+
audio_token_norms=audio_token_norms,
|
| 836 |
+
timestep_ratio=timestep_ratio)
|
| 837 |
+
x = rearrange(x_bsa, "b n s d -> b s (n d)")
|
| 838 |
+
else:
|
| 839 |
+
x = self.attn(q, k, v)
|
| 840 |
+
|
| 841 |
+
return self.o(x)
|
| 842 |
+
|
| 843 |
+
|
| 844 |
+
class USPAttention(nn.Module):
|
| 845 |
+
def __init__(self, num_heads: int, attn_type=AttnType.FA):
|
| 846 |
+
super().__init__()
|
| 847 |
+
if not LONG_CONTEXT_ATTN_AVAILABLE or LongContextAttention is None:
|
| 848 |
+
raise RuntimeError(
|
| 849 |
+
"USPAttention requires `yunchang` (LongContextAttention). "
|
| 850 |
+
"Please install/enable yunchang or avoid using USPAttention."
|
| 851 |
+
)
|
| 852 |
+
self.num_heads = num_heads
|
| 853 |
+
self.attn = LongContextAttention(ring_impl_type="basic", attn_type=attn_type)
|
| 854 |
+
|
| 855 |
+
def forward(self, q, k, v):
|
| 856 |
+
q = rearrange(q, "b s (n d) -> b s n d", n=self.num_heads)
|
| 857 |
+
k = rearrange(k, "b s (n d) -> b s n d", n=self.num_heads)
|
| 858 |
+
v = rearrange(v, "b s (n d) -> b s n d", n=self.num_heads)
|
| 859 |
+
x = self.attn(q, k, v)
|
| 860 |
+
return rearrange(x, "b s n d -> b s (n d)")
|
| 861 |
+
|
| 862 |
+
|
| 863 |
+
class CrossAttention(nn.Module):
|
| 864 |
+
def __init__(self, dim: int, num_heads: int, eps: float = 1e-6, has_image_input: bool = False):
|
| 865 |
+
super().__init__()
|
| 866 |
+
self.dim = dim
|
| 867 |
+
self.num_heads = num_heads
|
| 868 |
+
self.head_dim = dim // num_heads
|
| 869 |
+
|
| 870 |
+
self.q = nn.Linear(dim, dim)
|
| 871 |
+
self.k = nn.Linear(dim, dim)
|
| 872 |
+
self.v = nn.Linear(dim, dim)
|
| 873 |
+
self.o = nn.Linear(dim, dim)
|
| 874 |
+
self.norm_q = RMSNorm(dim, eps=eps)
|
| 875 |
+
self.norm_k = RMSNorm(dim, eps=eps)
|
| 876 |
+
self.has_image_input = has_image_input
|
| 877 |
+
if has_image_input:
|
| 878 |
+
self.k_img = nn.Linear(dim, dim)
|
| 879 |
+
self.v_img = nn.Linear(dim, dim)
|
| 880 |
+
self.norm_k_img = RMSNorm(dim, eps=eps)
|
| 881 |
+
|
| 882 |
+
self.attn = AttentionModule(self.num_heads)
|
| 883 |
+
|
| 884 |
+
# Query stream for SP; the text context is replicated, not sharded.
|
| 885 |
+
self.sp_stream = "visual"
|
| 886 |
+
|
| 887 |
+
def forward(self, x: torch.Tensor, y: torch.Tensor):
|
| 888 |
+
if self.has_image_input:
|
| 889 |
+
img = y[:, :257]
|
| 890 |
+
ctx = y[:, 257:]
|
| 891 |
+
else:
|
| 892 |
+
ctx = y
|
| 893 |
+
q = self.norm_q(self.q(x))
|
| 894 |
+
k = self.norm_k(self.k(ctx))
|
| 895 |
+
v = self.v(ctx)
|
| 896 |
+
|
| 897 |
+
sp_enabled = get_sequence_parallel_state()
|
| 898 |
+
|
| 899 |
+
if sp_enabled:
|
| 900 |
+
sp_size = nccl_info.sp_size
|
| 901 |
+
sp_rank = nccl_info.rank_within_spgroup
|
| 902 |
+
# Same Ulysses head-padding contract as SelfAttention: pad to a multiple
|
| 903 |
+
# of sp_size, slice real heads before compute, truncate after. The k/v
|
| 904 |
+
# context is NOT all_to_all'd (replicated); it is padded then sliced by
|
| 905 |
+
# head_start so each rank takes the same global heads as its q.
|
| 906 |
+
padded_heads = sp_pad_num_heads(self.num_heads, sp_size)
|
| 907 |
+
|
| 908 |
+
q_4d = rearrange(q, "b s (n d) -> b s n d", n=self.num_heads)
|
| 909 |
+
k_4d = rearrange(k, "b s (n d) -> b s n d", n=self.num_heads)
|
| 910 |
+
v_4d = rearrange(v, "b s (n d) -> b s n d", n=self.num_heads)
|
| 911 |
+
if padded_heads != self.num_heads:
|
| 912 |
+
q_4d = sp_pad_heads(q_4d, padded_heads)
|
| 913 |
+
k_4d = sp_pad_heads(k_4d, padded_heads)
|
| 914 |
+
v_4d = sp_pad_heads(v_4d, padded_heads)
|
| 915 |
+
|
| 916 |
+
sp_full_len = sp_seq_ctx.get(self.sp_stream)
|
| 917 |
+
q_4d = all_to_all_4D(q_4d, nccl_info.sp_group, scatter_dim=2, gather_dim=1,
|
| 918 |
+
full_seq_len=sp_full_len)
|
| 919 |
+
hpr = q_4d.shape[2]
|
| 920 |
+
head_start = sp_rank * hpr
|
| 921 |
+
k_4d = k_4d[:, :, head_start:head_start + hpr, :]
|
| 922 |
+
v_4d = v_4d[:, :, head_start:head_start + hpr, :]
|
| 923 |
+
|
| 924 |
+
local_heads = sp_num_local_real_heads(self.num_heads, padded_heads, sp_size, sp_rank)
|
| 925 |
+
if local_heads > 0:
|
| 926 |
+
if local_heads != hpr:
|
| 927 |
+
q_4d = q_4d[:, :, :local_heads, :]
|
| 928 |
+
k_4d = k_4d[:, :, :local_heads, :]
|
| 929 |
+
v_4d = v_4d[:, :, :local_heads, :]
|
| 930 |
+
out = flash_attention(q_4d.flatten(2), k_4d.flatten(2), v_4d.flatten(2), num_heads=local_heads)
|
| 931 |
+
out = rearrange(out, "b s (n d) -> b s n d", n=local_heads)
|
| 932 |
+
if self.has_image_input:
|
| 933 |
+
k_img = self.norm_k_img(self.k_img(img))
|
| 934 |
+
v_img = self.v_img(img)
|
| 935 |
+
k_img_4d = rearrange(k_img, "b s (n d) -> b s n d", n=self.num_heads)
|
| 936 |
+
v_img_4d = rearrange(v_img, "b s (n d) -> b s n d", n=self.num_heads)
|
| 937 |
+
if padded_heads != self.num_heads:
|
| 938 |
+
k_img_4d = sp_pad_heads(k_img_4d, padded_heads)
|
| 939 |
+
v_img_4d = sp_pad_heads(v_img_4d, padded_heads)
|
| 940 |
+
k_img_4d = k_img_4d[:, :, head_start:head_start + local_heads, :]
|
| 941 |
+
v_img_4d = v_img_4d[:, :, head_start:head_start + local_heads, :]
|
| 942 |
+
img_out = flash_attention(q_4d.flatten(2), k_img_4d.flatten(2), v_img_4d.flatten(2), num_heads=local_heads)
|
| 943 |
+
img_out = rearrange(img_out, "b s (n d) -> b s n d", n=local_heads)
|
| 944 |
+
out = out + img_out
|
| 945 |
+
if out.shape[2] != hpr:
|
| 946 |
+
out = sp_pad_heads(out, hpr)
|
| 947 |
+
else:
|
| 948 |
+
# Padding-only rank: no kernel. Keep out CONNECTED to q_4d (the only
|
| 949 |
+
# all_to_all'd tensor here; k/v are local context slices with no
|
| 950 |
+
# collective) so q's backward all_to_all fires on this rank too.
|
| 951 |
+
# `* 0.0` forces zero so it never affects real heads' fwd/bwd.
|
| 952 |
+
out = q_4d * 0.0
|
| 953 |
+
out = all_to_all_4D(out, nccl_info.sp_group, scatter_dim=1, gather_dim=2,
|
| 954 |
+
full_seq_len=sp_full_len)
|
| 955 |
+
if padded_heads != self.num_heads:
|
| 956 |
+
out = out[:, :, :self.num_heads, :]
|
| 957 |
+
x = out.flatten(2)
|
| 958 |
+
else:
|
| 959 |
+
x = self.attn(q, k, v)
|
| 960 |
+
if self.has_image_input:
|
| 961 |
+
k_img = self.norm_k_img(self.k_img(img))
|
| 962 |
+
v_img = self.v_img(img)
|
| 963 |
+
y = flash_attention(q, k_img, v_img, num_heads=self.num_heads)
|
| 964 |
+
x = x + y
|
| 965 |
+
|
| 966 |
+
return self.o(x)
|
| 967 |
+
|
| 968 |
+
|
| 969 |
+
class GateModule(nn.Module):
|
| 970 |
+
def __init__(self,):
|
| 971 |
+
super().__init__()
|
| 972 |
+
|
| 973 |
+
def forward(self, x, gate, residual):
|
| 974 |
+
return x + gate * residual
|
| 975 |
+
|
| 976 |
+
class DiTBlock(nn.Module):
|
| 977 |
+
def __init__(self, has_image_input: bool, dim: int, num_heads: int, ffn_dim: int, eps: float = 1e-6,
|
| 978 |
+
enable_bsa: bool = False, bsa_params: dict = None,
|
| 979 |
+
enable_audio_guidance: bool = False,
|
| 980 |
+
enable_audio_concentration_gate: bool = False,
|
| 981 |
+
enable_variance_guidance: bool = False):
|
| 982 |
+
super().__init__()
|
| 983 |
+
self.dim = dim
|
| 984 |
+
self.num_heads = num_heads
|
| 985 |
+
self.ffn_dim = ffn_dim
|
| 986 |
+
|
| 987 |
+
self.self_attn = SelfAttention(
|
| 988 |
+
dim, num_heads, eps,
|
| 989 |
+
enable_bsa=enable_bsa, bsa_params=bsa_params,
|
| 990 |
+
enable_audio_guidance=enable_audio_guidance,
|
| 991 |
+
enable_audio_concentration_gate=enable_audio_concentration_gate,
|
| 992 |
+
enable_variance_guidance=enable_variance_guidance,
|
| 993 |
+
)
|
| 994 |
+
self.cross_attn = CrossAttention(
|
| 995 |
+
dim, num_heads, eps, has_image_input=has_image_input)
|
| 996 |
+
self.norm1 = nn.LayerNorm(dim, eps=eps, elementwise_affine=False)
|
| 997 |
+
self.norm2 = nn.LayerNorm(dim, eps=eps, elementwise_affine=False)
|
| 998 |
+
self.norm3 = nn.LayerNorm(dim, eps=eps)
|
| 999 |
+
self.ffn = nn.Sequential(nn.Linear(dim, ffn_dim), nn.GELU(
|
| 1000 |
+
approximate='tanh'), nn.Linear(ffn_dim, dim))
|
| 1001 |
+
self.modulation = nn.Parameter(torch.randn(1, 6, dim) / dim**0.5)
|
| 1002 |
+
self.gate = GateModule()
|
| 1003 |
+
|
| 1004 |
+
def forward(self, x, context, t_mod, freqs, grid_size=None, a2v_bridge_residual=None, timestep_ratio=None):
|
| 1005 |
+
has_seq = len(t_mod.shape) == 4
|
| 1006 |
+
chunk_dim = 2 if has_seq else 1
|
| 1007 |
+
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = (
|
| 1008 |
+
self.modulation.to(dtype=t_mod.dtype, device=t_mod.device) + t_mod).chunk(6, dim=chunk_dim)
|
| 1009 |
+
if has_seq:
|
| 1010 |
+
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = (
|
| 1011 |
+
shift_msa.squeeze(2), scale_msa.squeeze(2), gate_msa.squeeze(2),
|
| 1012 |
+
shift_mlp.squeeze(2), scale_mlp.squeeze(2), gate_mlp.squeeze(2),
|
| 1013 |
+
)
|
| 1014 |
+
input_x = modulate(self.norm1(x), shift_msa, scale_msa)
|
| 1015 |
+
x = self.gate(x, gate_msa, self.self_attn(
|
| 1016 |
+
input_x, freqs, grid_size=grid_size,
|
| 1017 |
+
a2v_bridge_residual=a2v_bridge_residual,
|
| 1018 |
+
timestep_ratio=timestep_ratio,
|
| 1019 |
+
))
|
| 1020 |
+
x = x + self.cross_attn(self.norm3(x), context)
|
| 1021 |
+
input_x = modulate(self.norm2(x), shift_mlp, scale_mlp)
|
| 1022 |
+
x = self.gate(x, gate_mlp, self.ffn(input_x))
|
| 1023 |
+
return x
|
| 1024 |
+
|
| 1025 |
+
|
| 1026 |
+
class MLP(torch.nn.Module):
|
| 1027 |
+
def __init__(self, in_dim, out_dim, has_pos_emb=False):
|
| 1028 |
+
super().__init__()
|
| 1029 |
+
self.proj = torch.nn.Sequential(
|
| 1030 |
+
nn.LayerNorm(in_dim),
|
| 1031 |
+
nn.Linear(in_dim, in_dim),
|
| 1032 |
+
nn.GELU(),
|
| 1033 |
+
nn.Linear(in_dim, out_dim),
|
| 1034 |
+
nn.LayerNorm(out_dim)
|
| 1035 |
+
)
|
| 1036 |
+
self.has_pos_emb = has_pos_emb
|
| 1037 |
+
if has_pos_emb:
|
| 1038 |
+
self.emb_pos = torch.nn.Parameter(torch.zeros((1, 514, 1280)))
|
| 1039 |
+
|
| 1040 |
+
def forward(self, x):
|
| 1041 |
+
if self.has_pos_emb:
|
| 1042 |
+
x = x + self.emb_pos.to(dtype=x.dtype, device=x.device)
|
| 1043 |
+
return self.proj(x)
|
| 1044 |
+
|
| 1045 |
+
|
| 1046 |
+
class Head(nn.Module):
|
| 1047 |
+
def __init__(self, dim: int, out_dim: int, patch_size: Tuple[int, int, int], eps: float):
|
| 1048 |
+
super().__init__()
|
| 1049 |
+
self.dim = dim
|
| 1050 |
+
self.patch_size = patch_size
|
| 1051 |
+
self.norm = nn.LayerNorm(dim, eps=eps, elementwise_affine=False)
|
| 1052 |
+
self.head = nn.Linear(dim, out_dim * math.prod(patch_size))
|
| 1053 |
+
self.modulation = nn.Parameter(torch.randn(1, 2, dim) / dim**0.5)
|
| 1054 |
+
|
| 1055 |
+
def forward(self, x, t_mod):
|
| 1056 |
+
if len(t_mod.shape) == 3:
|
| 1057 |
+
shift, scale = (self.modulation.unsqueeze(0).to(dtype=t_mod.dtype, device=t_mod.device) + t_mod.unsqueeze(2)).chunk(2, dim=2)
|
| 1058 |
+
x = (self.head(self.norm(x) * (1 + scale.squeeze(2)) + shift.squeeze(2)))
|
| 1059 |
+
else:
|
| 1060 |
+
shift, scale = (self.modulation.to(dtype=t_mod.dtype, device=t_mod.device) + t_mod).chunk(2, dim=1)
|
| 1061 |
+
x = (self.head(self.norm(x) * (1 + scale) + shift))
|
| 1062 |
+
return x
|
| 1063 |
+
|
| 1064 |
+
|
| 1065 |
+
class WanModel(ModelMixin, ConfigMixin):
|
| 1066 |
+
_repeated_blocks = ("DiTBlock",)
|
| 1067 |
+
|
| 1068 |
+
@register_to_config
|
| 1069 |
+
def __init__(
|
| 1070 |
+
self,
|
| 1071 |
+
dim: int,
|
| 1072 |
+
in_dim: int,
|
| 1073 |
+
ffn_dim: int,
|
| 1074 |
+
out_dim: int,
|
| 1075 |
+
text_dim: int,
|
| 1076 |
+
freq_dim: int,
|
| 1077 |
+
eps: float,
|
| 1078 |
+
patch_size: Tuple[int, int, int],
|
| 1079 |
+
num_heads: int,
|
| 1080 |
+
num_layers: int,
|
| 1081 |
+
has_image_input: bool,
|
| 1082 |
+
has_image_pos_emb: bool = False,
|
| 1083 |
+
has_ref_conv: bool = False,
|
| 1084 |
+
add_control_adapter: bool = False,
|
| 1085 |
+
in_dim_control_adapter: int = 24,
|
| 1086 |
+
seperated_timestep: bool = False,
|
| 1087 |
+
require_vae_embedding: bool = True,
|
| 1088 |
+
require_clip_embedding: bool = True,
|
| 1089 |
+
fuse_vae_embedding_in_latents: bool = False,
|
| 1090 |
+
enable_bsa: bool = False,
|
| 1091 |
+
bsa_params: dict = None,
|
| 1092 |
+
enable_variance_guidance: bool = False,
|
| 1093 |
+
):
|
| 1094 |
+
super().__init__()
|
| 1095 |
+
self.dim = dim
|
| 1096 |
+
self.freq_dim = freq_dim
|
| 1097 |
+
self.has_image_input = has_image_input
|
| 1098 |
+
self.patch_size = patch_size
|
| 1099 |
+
self.seperated_timestep = seperated_timestep
|
| 1100 |
+
self.require_vae_embedding = require_vae_embedding
|
| 1101 |
+
self.require_clip_embedding = require_clip_embedding
|
| 1102 |
+
self.fuse_vae_embedding_in_latents = fuse_vae_embedding_in_latents
|
| 1103 |
+
self.enable_bsa = enable_bsa
|
| 1104 |
+
self.bsa_params = bsa_params
|
| 1105 |
+
self.enable_variance_guidance = enable_variance_guidance
|
| 1106 |
+
|
| 1107 |
+
self.patch_embedding = nn.Conv3d(
|
| 1108 |
+
in_dim, dim, kernel_size=patch_size, stride=patch_size)
|
| 1109 |
+
self.text_embedding = nn.Sequential(
|
| 1110 |
+
nn.Linear(text_dim, dim),
|
| 1111 |
+
nn.GELU(approximate='tanh'),
|
| 1112 |
+
nn.Linear(dim, dim)
|
| 1113 |
+
)
|
| 1114 |
+
self.time_embedding = nn.Sequential(
|
| 1115 |
+
nn.Linear(freq_dim, dim),
|
| 1116 |
+
nn.SiLU(),
|
| 1117 |
+
nn.Linear(dim, dim)
|
| 1118 |
+
)
|
| 1119 |
+
self.time_projection = nn.Sequential(
|
| 1120 |
+
nn.SiLU(), nn.Linear(dim, dim * 6))
|
| 1121 |
+
self.blocks = nn.ModuleList([
|
| 1122 |
+
DiTBlock(has_image_input, dim, num_heads, ffn_dim, eps,
|
| 1123 |
+
enable_bsa=enable_bsa, bsa_params=bsa_params,
|
| 1124 |
+
enable_audio_guidance=False,
|
| 1125 |
+
enable_audio_concentration_gate=False,
|
| 1126 |
+
enable_variance_guidance=enable_variance_guidance)
|
| 1127 |
+
for _ in range(num_layers)
|
| 1128 |
+
])
|
| 1129 |
+
self.head = Head(dim, out_dim, patch_size, eps)
|
| 1130 |
+
head_dim = dim // num_heads
|
| 1131 |
+
self.freqs = precompute_freqs_cis_3d(head_dim)
|
| 1132 |
+
|
| 1133 |
+
if has_image_input:
|
| 1134 |
+
self.img_emb = MLP(1280, dim, has_pos_emb=has_image_pos_emb) # clip_feature_dim = 1280
|
| 1135 |
+
if has_ref_conv:
|
| 1136 |
+
self.ref_conv = nn.Conv2d(16, dim, kernel_size=(2, 2), stride=(2, 2))
|
| 1137 |
+
self.has_image_pos_emb = has_image_pos_emb
|
| 1138 |
+
self.has_ref_conv = has_ref_conv
|
| 1139 |
+
self.control_adapter = None
|
| 1140 |
+
|
| 1141 |
+
def patchify(self, x: torch.Tensor,control_camera_latents_input: torch.Tensor = None):
|
| 1142 |
+
# NOTE(dhyu): avoid slow_conv
|
| 1143 |
+
x = x.contiguous(memory_format=torch.channels_last_3d)
|
| 1144 |
+
x = self.patch_embedding(x)
|
| 1145 |
+
if self.control_adapter is not None and control_camera_latents_input is not None:
|
| 1146 |
+
y_camera = self.control_adapter(control_camera_latents_input)
|
| 1147 |
+
x = [u + v for u, v in zip(x, y_camera)]
|
| 1148 |
+
x = x[0].unsqueeze(0)
|
| 1149 |
+
grid_size = x.shape[2:]
|
| 1150 |
+
x = rearrange(x, 'b c f h w -> b (f h w) c').contiguous()
|
| 1151 |
+
return x, grid_size # x, grid_size: (f, h, w)
|
| 1152 |
+
|
| 1153 |
+
def unpatchify(self, x: torch.Tensor, grid_size: torch.Tensor):
|
| 1154 |
+
return rearrange(
|
| 1155 |
+
x, 'b (f h w) (x y z c) -> b c (f x) (h y) (w z)',
|
| 1156 |
+
f=grid_size[0], h=grid_size[1], w=grid_size[2],
|
| 1157 |
+
x=self.patch_size[0], y=self.patch_size[1], z=self.patch_size[2]
|
| 1158 |
+
)
|
| 1159 |
+
|
| 1160 |
+
def forward(self,
|
| 1161 |
+
x: torch.Tensor,
|
| 1162 |
+
timestep: torch.Tensor,
|
| 1163 |
+
context: torch.Tensor,
|
| 1164 |
+
clip_feature: Optional[torch.Tensor] = None,
|
| 1165 |
+
y: Optional[torch.Tensor] = None,
|
| 1166 |
+
use_gradient_checkpointing: bool = False,
|
| 1167 |
+
use_gradient_checkpointing_offload: bool = False,
|
| 1168 |
+
**kwargs,
|
| 1169 |
+
):
|
| 1170 |
+
advance_dynamic_block_pass_id() # once per real forward (not on recompute)
|
| 1171 |
+
t = self.time_embedding(
|
| 1172 |
+
sinusoidal_embedding_1d(self.freq_dim, timestep))
|
| 1173 |
+
t_mod = self.time_projection(t).unflatten(1, (6, self.dim))
|
| 1174 |
+
context = self.text_embedding(context)
|
| 1175 |
+
|
| 1176 |
+
if self.has_image_input:
|
| 1177 |
+
x = torch.cat([x, y], dim=1) # (b, c_x + c_y, f, h, w)
|
| 1178 |
+
clip_embdding = self.img_emb(clip_feature)
|
| 1179 |
+
context = torch.cat([clip_embdding, context], dim=1)
|
| 1180 |
+
|
| 1181 |
+
x, (f, h, w) = self.patchify(x)
|
| 1182 |
+
|
| 1183 |
+
freqs = torch.cat([
|
| 1184 |
+
self.freqs[0][:f].view(f, 1, 1, -1).expand(f, h, w, -1),
|
| 1185 |
+
self.freqs[1][:h].view(1, h, 1, -1).expand(f, h, w, -1),
|
| 1186 |
+
self.freqs[2][:w].view(1, 1, w, -1).expand(f, h, w, -1)
|
| 1187 |
+
], dim=-1).reshape(f * h * w, 1, -1).to(x.device)
|
| 1188 |
+
|
| 1189 |
+
visual_seq_len = f * h * w
|
| 1190 |
+
if get_sequence_parallel_state():
|
| 1191 |
+
sp_size = nccl_info.sp_size
|
| 1192 |
+
sp_rank = nccl_info.rank_within_spgroup
|
| 1193 |
+
assert_sp_splittable(visual_seq_len, sp_size, "visual sequence")
|
| 1194 |
+
sp_seq_ctx.set(visual=visual_seq_len)
|
| 1195 |
+
x = sp_split(x, sp_size, sp_rank, dim=1)
|
| 1196 |
+
freqs = sp_split(freqs, sp_size, sp_rank, dim=0)
|
| 1197 |
+
|
| 1198 |
+
def create_custom_forward(module):
|
| 1199 |
+
def custom_forward(*inputs):
|
| 1200 |
+
return module(*inputs)
|
| 1201 |
+
return custom_forward
|
| 1202 |
+
|
| 1203 |
+
grid_size = (f, h, w) if self.enable_bsa else None
|
| 1204 |
+
|
| 1205 |
+
for block in self.blocks:
|
| 1206 |
+
if self.training and use_gradient_checkpointing:
|
| 1207 |
+
if use_gradient_checkpointing_offload:
|
| 1208 |
+
with torch.autograd.graph.save_on_cpu():
|
| 1209 |
+
x = torch.utils.checkpoint.checkpoint(
|
| 1210 |
+
create_custom_forward(block),
|
| 1211 |
+
x, context, t_mod, freqs, grid_size,
|
| 1212 |
+
use_reentrant=False,
|
| 1213 |
+
)
|
| 1214 |
+
else:
|
| 1215 |
+
x = torch.utils.checkpoint.checkpoint(
|
| 1216 |
+
create_custom_forward(block),
|
| 1217 |
+
x, context, t_mod, freqs, grid_size,
|
| 1218 |
+
use_reentrant=False,
|
| 1219 |
+
)
|
| 1220 |
+
else:
|
| 1221 |
+
x = block(x, context, t_mod, freqs, grid_size=grid_size)
|
| 1222 |
+
|
| 1223 |
+
x = self.head(x, t)
|
| 1224 |
+
if get_sequence_parallel_state():
|
| 1225 |
+
x = all_gather(x, dim=1, group=nccl_info.sp_group,
|
| 1226 |
+
full_seq_len=visual_seq_len)
|
| 1227 |
+
x = self.unpatchify(x, (f, h, w))
|
| 1228 |
+
return x
|
| 1229 |
+
|
| 1230 |
+
def configure_bsa(self, enable_bsa: bool, bsa_params: dict = None):
|
| 1231 |
+
"""Runtime configuration of Block Sparse Attention on all video self-attention blocks."""
|
| 1232 |
+
self.enable_bsa = enable_bsa
|
| 1233 |
+
self.bsa_params = bsa_params
|
| 1234 |
+
for block in self.blocks:
|
| 1235 |
+
block.self_attn.enable_bsa = enable_bsa
|
| 1236 |
+
block.self_attn.bsa_params = bsa_params or {}
|
| 1237 |
+
|
| 1238 |
+
def configure_audio_guidance(self, enable: bool, enable_concentration_gate: bool = False):
|
| 1239 |
+
"""Runtime configuration of audio-guided BSA on all video self-attention blocks.
|
| 1240 |
+
|
| 1241 |
+
Audio guidance modulates block-level QK scores with audio saliency
|
| 1242 |
+
from the A→V bridge residual. Only effective when BSA is also enabled.
|
| 1243 |
+
|
| 1244 |
+
The boost strength γ (audio_boost_gamma) is read from bsa_params at runtime.
|
| 1245 |
+
Set bsa_params['audio_boost_gamma'] to control the modulation strength
|
| 1246 |
+
(default 1.0; 0.0 disables modulation).
|
| 1247 |
+
|
| 1248 |
+
Args:
|
| 1249 |
+
enable: whether to enable audio-guided score modulation.
|
| 1250 |
+
enable_concentration_gate: whether to enable Gate 2 (Audio Spatial
|
| 1251 |
+
Concentration Gate) which down-weights audio saliency when the
|
| 1252 |
+
bridge residual is spatially uniform (silence/ambient scenarios).
|
| 1253 |
+
"""
|
| 1254 |
+
for block in self.blocks:
|
| 1255 |
+
sa = block.self_attn
|
| 1256 |
+
sa.enable_audio_guidance = enable
|
| 1257 |
+
sa.enable_audio_concentration_gate = enable_concentration_gate
|
| 1258 |
+
|
| 1259 |
+
def configure_variance_guidance(self, enable: bool):
|
| 1260 |
+
"""Runtime configuration of channel-variance guidance on all video self-attention blocks.
|
| 1261 |
+
|
| 1262 |
+
Non-learnable, no gate. When enabled, block-level QK scores are boosted:
|
| 1263 |
+
score_{i,j} *= (1 + γ * ρ_j)
|
| 1264 |
+
where ρ_j is the V-block channel-variance density ∈ [0,1] (key/value side only).
|
| 1265 |
+
Query-awareness comes from P^QK being per-query (zero-gate effect):
|
| 1266 |
+
each query block has its own QK scores → different top-k after boost.
|
| 1267 |
+
|
| 1268 |
+
Completely independent from audio guidance — no shared code or state.
|
| 1269 |
+
Only effective when BSA (enable_bsa) is also enabled.
|
| 1270 |
+
Boost strength γ is read from bsa_params['variance_boost_gamma'] (default 1.0).
|
| 1271 |
+
|
| 1272 |
+
Args:
|
| 1273 |
+
enable: whether to enable channel-variance guidance.
|
| 1274 |
+
"""
|
| 1275 |
+
self.enable_variance_guidance = enable
|
| 1276 |
+
for block in self.blocks:
|
| 1277 |
+
block.self_attn.enable_variance_guidance = enable
|
| 1278 |
+
|
| 1279 |
+
def configure_taylor_sparse_attn(self, enable: bool):
|
| 1280 |
+
"""LIVEditor: selected (exact) + non-selected (Taylor) in one unified softmax.
|
| 1281 |
+
Weights sum to exactly 1. No R_n. Mutually exclusive with rectified."""
|
| 1282 |
+
for block in self.blocks:
|
| 1283 |
+
block.self_attn.enable_taylor_sparse_attn = enable
|
| 1284 |
+
if enable:
|
| 1285 |
+
block.self_attn.enable_rectified_sparse_attn = False
|
| 1286 |
+
|
| 1287 |
+
def configure_rectified_sparse_attn(self, enable: bool):
|
| 1288 |
+
"""Rectified SpaAttn: o = R_n * o_spa + A_pool[nonsel] · V_pool.
|
| 1289 |
+
Mutually exclusive with taylor."""
|
| 1290 |
+
for block in self.blocks:
|
| 1291 |
+
block.self_attn.enable_rectified_sparse_attn = enable
|
| 1292 |
+
if enable:
|
| 1293 |
+
block.self_attn.enable_taylor_sparse_attn = False
|
| 1294 |
+
|
| 1295 |
+
def _disable_non_dynamic_bsa_features(self, sa):
|
| 1296 |
+
"""Turn off every other BSA feature on a SelfAttention module so the
|
| 1297 |
+
dynamic-block path is fully isolated (Section 8 requirement)."""
|
| 1298 |
+
sa.enable_audio_guidance = False
|
| 1299 |
+
sa.enable_audio_concentration_gate = False
|
| 1300 |
+
sa.enable_timestep_reliability_gate = False
|
| 1301 |
+
sa.enable_audio_weighted_pooling = False
|
| 1302 |
+
sa.enable_variance_guidance = False
|
| 1303 |
+
sa.enable_taylor_sparse_attn = False
|
| 1304 |
+
sa.enable_rectified_sparse_attn = False
|
| 1305 |
+
|
| 1306 |
+
def configure_ivpq_dynamic_block(self, enable: bool):
|
| 1307 |
+
"""IVPQ anisotropic dynamic block shape (Section 8.5). Video self-attn only.
|
| 1308 |
+
Mutually exclusive with penalty-matching AND with all other BSA features."""
|
| 1309 |
+
for block in self.blocks:
|
| 1310 |
+
sa = block.self_attn
|
| 1311 |
+
sa.enable_ivpq_dynamic_block = enable
|
| 1312 |
+
if enable:
|
| 1313 |
+
sa.enable_penalty_dynamic_block = False
|
| 1314 |
+
self._disable_non_dynamic_bsa_features(sa)
|
| 1315 |
+
|
| 1316 |
+
def configure_penalty_dynamic_block(self, enable: bool):
|
| 1317 |
+
"""Penalty-Matching anisotropic dynamic block shape (Section 8.6). Video
|
| 1318 |
+
self-attn only. Mutually exclusive with IVPQ AND all other BSA features."""
|
| 1319 |
+
for block in self.blocks:
|
| 1320 |
+
sa = block.self_attn
|
| 1321 |
+
sa.enable_penalty_dynamic_block = enable
|
| 1322 |
+
if enable:
|
| 1323 |
+
sa.enable_ivpq_dynamic_block = False
|
| 1324 |
+
self._disable_non_dynamic_bsa_features(sa)
|
hymm/utils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
hymm/utils/communications.py
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from typing import Any, List, Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.distributed as dist
|
| 6 |
+
from torch.nn import functional as F
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def broadcast(input_: torch.Tensor, group: dist.ProcessGroup):
|
| 12 |
+
src = dist.get_global_rank(group, 0)
|
| 13 |
+
dist.broadcast(input_, src=src, group=group)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# ---------------------------------------------------------------------------
|
| 17 |
+
# Shard bookkeeping
|
| 18 |
+
#
|
| 19 |
+
# Sequences are split across SP ranks with ``torch.chunk`` semantics: every rank
|
| 20 |
+
# gets ``ceil(S / sp)`` tokens except the last, which gets the remainder. The
|
| 21 |
+
# collectives below need those sizes, but a rank cannot infer the global length
|
| 22 |
+
# S from its own shard. Rather than negotiating it at runtime with a
|
| 23 |
+
# ``dist.all_gather_object`` on every call (which costs a pickle round-trip plus
|
| 24 |
+
# a device sync ~800 times per training step), the towers publish S through
|
| 25 |
+
# ``sp_seq_ctx`` and pass it down as ``full_seq_len``.
|
| 26 |
+
# ---------------------------------------------------------------------------
|
| 27 |
+
|
| 28 |
+
def sp_chunk_sizes(total: int, world_size: int) -> List[int]:
|
| 29 |
+
"""Shard sizes produced by ``torch.chunk(x, world_size)`` along one dim."""
|
| 30 |
+
chunk = (total + world_size - 1) // world_size
|
| 31 |
+
return [min(chunk, max(total - i * chunk, 0)) for i in range(world_size)]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def assert_sp_splittable(total: int, world_size: int, name: str = "sequence"):
|
| 35 |
+
"""``torch.chunk`` silently returns fewer than ``world_size`` chunks when the
|
| 36 |
+
sequence is very short, which would make high SP ranks index out of range."""
|
| 37 |
+
if world_size <= 1:
|
| 38 |
+
return
|
| 39 |
+
sizes = sp_chunk_sizes(total, world_size)
|
| 40 |
+
assert sizes[-1] > 0, (
|
| 41 |
+
f"{name} length {total} is too short for sp_size={world_size}: "
|
| 42 |
+
f"the trailing SP rank(s) would receive zero tokens. Reduce sp_size or "
|
| 43 |
+
f"increase the resolution / duration bucket."
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def sp_split(x: torch.Tensor, world_size: int, rank: int, dim: int) -> torch.Tensor:
|
| 48 |
+
"""``torch.chunk``-compatible shard selection that never indexes past the end."""
|
| 49 |
+
if world_size <= 1:
|
| 50 |
+
return x
|
| 51 |
+
sizes = sp_chunk_sizes(x.shape[dim], world_size)
|
| 52 |
+
return torch.split(x, sizes, dim=dim)[rank]
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# ---------------------------------------------------------------------------
|
| 56 |
+
# Ulysses head padding (for num_heads not divisible by sp_size, e.g. audio
|
| 57 |
+
# DiT = 12 heads with sp_size = 8).
|
| 58 |
+
#
|
| 59 |
+
# `all_to_all_4D` requires `num_heads % sp_size == 0` because it scatters the
|
| 60 |
+
# head dim evenly across SP ranks. These helpers let a module pad its head dim
|
| 61 |
+
# with ZERO heads up to the next multiple of sp_size purely at runtime, with a
|
| 62 |
+
# strict isolation contract enforced by the call sites:
|
| 63 |
+
#
|
| 64 |
+
# 1. Padding is appended (never weight-level) -> pretrained checkpoints load
|
| 65 |
+
# unchanged.
|
| 66 |
+
# 2. After the head-scatter all_to_all, each rank holds the CONTIGUOUS global
|
| 67 |
+
# head block [r*hpr, (r+1)*hpr); real heads are the prefix [0, num_heads),
|
| 68 |
+
# so the real heads on a rank are always its FIRST `local_real` local heads.
|
| 69 |
+
# Call sites slice those off BEFORE any attention compute, so padded (zero)
|
| 70 |
+
# heads NEVER enter the BSA Triton / flash kernels and cannot produce NaNs
|
| 71 |
+
# or perturb the real heads.
|
| 72 |
+
# 3. Ranks that hold only padding heads (local_real == 0) skip the kernel
|
| 73 |
+
# entirely and emit a zero-width head slice; they still join the all_to_all
|
| 74 |
+
# collectives so distributed/FSDP execution stays in lock-step.
|
| 75 |
+
# 4. After the inverse all_to_all the head dim is truncated back to num_heads,
|
| 76 |
+
# so forward and backward for the real heads are bit-identical to the
|
| 77 |
+
# unpadded computation (padding contributes exactly zero gradient).
|
| 78 |
+
# ---------------------------------------------------------------------------
|
| 79 |
+
|
| 80 |
+
def sp_pad_num_heads(num_heads: int, sp_size: int) -> int:
|
| 81 |
+
"""Smallest multiple of ``sp_size`` that is >= ``num_heads``."""
|
| 82 |
+
remainder = num_heads % sp_size
|
| 83 |
+
if remainder == 0:
|
| 84 |
+
return num_heads
|
| 85 |
+
return num_heads + (sp_size - remainder)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def sp_pad_heads(x: torch.Tensor, padded_heads: int, dim: int = 2) -> torch.Tensor:
|
| 89 |
+
"""Append zero-valued heads along ``dim`` so the head count becomes
|
| 90 |
+
``padded_heads``. Autograd-safe: gradients routed to the appended heads are
|
| 91 |
+
discarded by the caller's later truncation, leaving the real heads' grads
|
| 92 |
+
untouched. Returns ``x`` unchanged when no padding is needed."""
|
| 93 |
+
cur = x.shape[dim]
|
| 94 |
+
if cur >= padded_heads:
|
| 95 |
+
return x
|
| 96 |
+
pad_shape = list(x.shape)
|
| 97 |
+
pad_shape[dim] = padded_heads - cur
|
| 98 |
+
pad = x.new_zeros(pad_shape)
|
| 99 |
+
return torch.cat([x, pad], dim=dim)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def sp_num_local_real_heads(num_real_heads: int, padded_heads: int,
|
| 103 |
+
sp_size: int, sp_rank: int) -> int:
|
| 104 |
+
"""Number of this rank's local heads that are REAL after ``all_to_all_4D``
|
| 105 |
+
evenly distributes ``padded_heads``. Real heads are the global prefix
|
| 106 |
+
``[0, num_real_heads)`` and rank ``r`` owns the contiguous global block
|
| 107 |
+
``[r*hpr, (r+1)*hpr)`` (hpr = padded_heads // sp_size), so the real heads are
|
| 108 |
+
always this rank's FIRST ``k`` local heads."""
|
| 109 |
+
hpr = padded_heads // sp_size
|
| 110 |
+
return max(0, min(num_real_heads - sp_rank * hpr, hpr))
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
# ---------------------------------------------------------------------------
|
| 114 |
+
# Ulysses all-to-all
|
| 115 |
+
# ---------------------------------------------------------------------------
|
| 116 |
+
|
| 117 |
+
_UNKNOWN_SEQLEN_WARNED = False
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def _negotiate_seq_lens(local_len: int, group) -> List[int]:
|
| 121 |
+
global _UNKNOWN_SEQLEN_WARNED
|
| 122 |
+
if not _UNKNOWN_SEQLEN_WARNED:
|
| 123 |
+
print(
|
| 124 |
+
"[SP] all_to_all_4D called without full_seq_len; falling back to a "
|
| 125 |
+
"per-call all_gather_object. This is correct but slow -- publish the "
|
| 126 |
+
"sequence length through hymm.utils.parallel_states.sp_seq_ctx."
|
| 127 |
+
)
|
| 128 |
+
_UNKNOWN_SEQLEN_WARNED = True
|
| 129 |
+
seq_lens = [None] * dist.get_world_size(group)
|
| 130 |
+
dist.all_gather_object(seq_lens, local_len, group)
|
| 131 |
+
return seq_lens
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def _all_to_all_4D(
|
| 135 |
+
input: torch.Tensor,
|
| 136 |
+
scatter_idx: int = 2,
|
| 137 |
+
gather_idx: int = 1,
|
| 138 |
+
group=None,
|
| 139 |
+
full_seq_len: Optional[int] = None,
|
| 140 |
+
) -> torch.Tensor:
|
| 141 |
+
"""all-to-all for QKV.
|
| 142 |
+
|
| 143 |
+
Args:
|
| 144 |
+
input: a 4D tensor sharded along the scatter dim.
|
| 145 |
+
scatter_idx / gather_idx: 2/1 scatters heads and gathers sequence,
|
| 146 |
+
1/2 does the inverse.
|
| 147 |
+
full_seq_len: global (unsharded) sequence length. Required for the 2/1
|
| 148 |
+
direction, where it is the only way to know how much padding the
|
| 149 |
+
trailing shard carries.
|
| 150 |
+
"""
|
| 151 |
+
assert (
|
| 152 |
+
input.dim() == 4
|
| 153 |
+
), f"input must be 4D tensor, got {input.dim()} and shape {input.shape}"
|
| 154 |
+
|
| 155 |
+
seq_world_size = dist.get_world_size(group)
|
| 156 |
+
|
| 157 |
+
if scatter_idx == 2 and gather_idx == 1:
|
| 158 |
+
# (bs, seqlen/P, hc, hs) -> (bs, seqlen, hc/P, hs)
|
| 159 |
+
if full_seq_len is not None:
|
| 160 |
+
sizes = sp_chunk_sizes(int(full_seq_len), seq_world_size)
|
| 161 |
+
gap = sizes[0] - sizes[-1]
|
| 162 |
+
else:
|
| 163 |
+
seq_lens = _negotiate_seq_lens(input.shape[1], group)
|
| 164 |
+
gap = seq_lens[0] - seq_lens[-1] if seq_lens[-1] != seq_lens[0] else 0
|
| 165 |
+
assert gap >= 0, "only the trailing SP rank may hold a shorter shard"
|
| 166 |
+
|
| 167 |
+
if gap > 0 and dist.get_group_rank(group, dist.get_rank()) == seq_world_size - 1:
|
| 168 |
+
input = F.pad(input, (0, 0, 0, 0, 0, gap))
|
| 169 |
+
|
| 170 |
+
bs, shard_seqlen, hc, hs = input.shape
|
| 171 |
+
seqlen = shard_seqlen * seq_world_size
|
| 172 |
+
assert hc % seq_world_size == 0, (
|
| 173 |
+
f'Invalid Head size: {hc}, which should be divisible by spsize {seq_world_size}'
|
| 174 |
+
)
|
| 175 |
+
shard_hc = hc // seq_world_size
|
| 176 |
+
|
| 177 |
+
# transpose groups of heads with the seq-len parallel dimension, so that we can scatter them!
|
| 178 |
+
# (bs, seqlen/P, hc, hs) -reshape-> (bs, seq_len/P, P, hc/P, hs) -transpose(0,2)-> (P, seq_len/P, bs, hc/P, hs)
|
| 179 |
+
input_t = (
|
| 180 |
+
input.reshape(bs, shard_seqlen, seq_world_size, shard_hc, hs)
|
| 181 |
+
.transpose(0, 2)
|
| 182 |
+
.contiguous()
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
if seq_world_size > 1:
|
| 186 |
+
output = torch.empty_like(input_t)
|
| 187 |
+
# Stream-ordered: NCCL enqueues on the current stream and every
|
| 188 |
+
# consumer below is on that same stream, so no host sync is needed.
|
| 189 |
+
dist.all_to_all_single(output, input_t, group=group)
|
| 190 |
+
else:
|
| 191 |
+
output = input_t
|
| 192 |
+
|
| 193 |
+
output = output.reshape(seqlen, bs, shard_hc, hs)
|
| 194 |
+
# (seq_len, bs, hc/P, hs) -reshape-> (bs, seq_len, hc/P, hs)
|
| 195 |
+
output = output.transpose(0, 1).contiguous().reshape(bs, seqlen, shard_hc, hs)
|
| 196 |
+
if gap > 0:
|
| 197 |
+
output = output[:, :-gap]
|
| 198 |
+
|
| 199 |
+
return output
|
| 200 |
+
|
| 201 |
+
elif scatter_idx == 1 and gather_idx == 2:
|
| 202 |
+
# (bs, seqlen, hc/P, hs) -> (bs, seqlen/P, hc, hs)
|
| 203 |
+
# `seqlen` here is already the global length, so the split is derived
|
| 204 |
+
# locally with no negotiation.
|
| 205 |
+
bs, seqlen, shard_hc, hs = input.shape
|
| 206 |
+
|
| 207 |
+
hc = shard_hc * seq_world_size
|
| 208 |
+
if seqlen % seq_world_size != 0:
|
| 209 |
+
new_seqlen = (seqlen // seq_world_size + 1) * seq_world_size
|
| 210 |
+
gap = new_seqlen - seqlen
|
| 211 |
+
input = F.pad(input, (0, 0, 0, 0, 0, gap))
|
| 212 |
+
bs, seqlen, shard_hc, hs = input.shape
|
| 213 |
+
else:
|
| 214 |
+
gap = 0
|
| 215 |
+
|
| 216 |
+
shard_seqlen = seqlen // seq_world_size
|
| 217 |
+
|
| 218 |
+
# (bs, seqlen, hc/P, hs) -reshape-> (bs, P, seq_len/P, hc/P, hs) -transpose(0, 3)-> (hc/P, P, seqlen/P, bs, hs) -transpose(0, 1) -> (P, hc/P, seqlen/P, bs, hs)
|
| 219 |
+
input_t = (
|
| 220 |
+
input.reshape(bs, seq_world_size, shard_seqlen, shard_hc, hs)
|
| 221 |
+
.transpose(0, 3)
|
| 222 |
+
.transpose(0, 1)
|
| 223 |
+
.contiguous()
|
| 224 |
+
.reshape(seq_world_size, shard_hc, shard_seqlen, bs, hs)
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
if seq_world_size > 1:
|
| 228 |
+
output = torch.empty_like(input_t)
|
| 229 |
+
dist.all_to_all_single(output, input_t, group=group)
|
| 230 |
+
else:
|
| 231 |
+
output = input_t
|
| 232 |
+
|
| 233 |
+
output = output.reshape(hc, shard_seqlen, bs, hs)
|
| 234 |
+
# (hc, seqlen/N, bs, hs) -tranpose(0,2)-> (bs, seqlen/N, hc, hs)
|
| 235 |
+
output = output.transpose(0, 2).contiguous().reshape(bs, shard_seqlen, hc, hs)
|
| 236 |
+
|
| 237 |
+
if gap > 0 and dist.get_group_rank(group, dist.get_rank()) == seq_world_size - 1:
|
| 238 |
+
output = output[:, :-gap]
|
| 239 |
+
|
| 240 |
+
return output
|
| 241 |
+
else:
|
| 242 |
+
raise RuntimeError("scatter_idx must be 1 or 2 and gather_idx must be 1 or 2")
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
class SeqAllToAll4D(torch.autograd.Function):
|
| 246 |
+
@staticmethod
|
| 247 |
+
def forward(
|
| 248 |
+
ctx: Any,
|
| 249 |
+
group: dist.ProcessGroup,
|
| 250 |
+
input: torch.Tensor,
|
| 251 |
+
scatter_idx: int,
|
| 252 |
+
gather_idx: int,
|
| 253 |
+
full_seq_len: Optional[int] = None,
|
| 254 |
+
) -> torch.Tensor:
|
| 255 |
+
ctx.group = group
|
| 256 |
+
ctx.scatter_idx = scatter_idx
|
| 257 |
+
ctx.gather_idx = gather_idx
|
| 258 |
+
ctx.full_seq_len = full_seq_len
|
| 259 |
+
|
| 260 |
+
return _all_to_all_4D(input, scatter_idx, gather_idx, group=group,
|
| 261 |
+
full_seq_len=full_seq_len)
|
| 262 |
+
|
| 263 |
+
@staticmethod
|
| 264 |
+
def backward(ctx: Any, *grad_output: torch.Tensor):
|
| 265 |
+
return (
|
| 266 |
+
None,
|
| 267 |
+
SeqAllToAll4D.apply(
|
| 268 |
+
ctx.group, *grad_output, ctx.gather_idx, ctx.scatter_idx,
|
| 269 |
+
ctx.full_seq_len,
|
| 270 |
+
),
|
| 271 |
+
None,
|
| 272 |
+
None,
|
| 273 |
+
None,
|
| 274 |
+
)
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def all_to_all_4D(
|
| 278 |
+
input_: torch.Tensor,
|
| 279 |
+
group: dist.ProcessGroup,
|
| 280 |
+
scatter_dim: int = 2,
|
| 281 |
+
gather_dim: int = 1,
|
| 282 |
+
full_seq_len: Optional[int] = None,
|
| 283 |
+
):
|
| 284 |
+
return SeqAllToAll4D.apply(group, input_, scatter_dim, gather_dim, full_seq_len)
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def _all_to_all(
|
| 288 |
+
input_: torch.Tensor,
|
| 289 |
+
world_size: int,
|
| 290 |
+
group: dist.ProcessGroup,
|
| 291 |
+
scatter_dim: int,
|
| 292 |
+
gather_dim: int,
|
| 293 |
+
):
|
| 294 |
+
input_list = [
|
| 295 |
+
t.contiguous() for t in torch.tensor_split(input_, world_size, scatter_dim)
|
| 296 |
+
]
|
| 297 |
+
output_list = [torch.empty_like(input_list[0]) for _ in range(world_size)]
|
| 298 |
+
dist.all_to_all(output_list, input_list, group=group)
|
| 299 |
+
return torch.cat(output_list, dim=gather_dim).contiguous()
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
class _AllToAll(torch.autograd.Function):
|
| 303 |
+
"""All-to-all communication.
|
| 304 |
+
|
| 305 |
+
Args:
|
| 306 |
+
input_: input matrix
|
| 307 |
+
process_group: communication group
|
| 308 |
+
scatter_dim: scatter dimension
|
| 309 |
+
gather_dim: gather dimension
|
| 310 |
+
"""
|
| 311 |
+
|
| 312 |
+
@staticmethod
|
| 313 |
+
def forward(ctx, input_, process_group, scatter_dim, gather_dim):
|
| 314 |
+
ctx.process_group = process_group
|
| 315 |
+
ctx.scatter_dim = scatter_dim
|
| 316 |
+
ctx.gather_dim = gather_dim
|
| 317 |
+
ctx.world_size = dist.get_world_size(process_group)
|
| 318 |
+
output = _all_to_all(
|
| 319 |
+
input_, ctx.world_size, process_group, scatter_dim, gather_dim
|
| 320 |
+
)
|
| 321 |
+
return output
|
| 322 |
+
|
| 323 |
+
@staticmethod
|
| 324 |
+
def backward(ctx, grad_output):
|
| 325 |
+
grad_output = _all_to_all(
|
| 326 |
+
grad_output,
|
| 327 |
+
ctx.world_size,
|
| 328 |
+
ctx.process_group,
|
| 329 |
+
ctx.gather_dim,
|
| 330 |
+
ctx.scatter_dim,
|
| 331 |
+
)
|
| 332 |
+
return (
|
| 333 |
+
grad_output,
|
| 334 |
+
None,
|
| 335 |
+
None,
|
| 336 |
+
None,
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
def all_to_all(
|
| 341 |
+
input_: torch.Tensor, group: dist.ProcessGroup, scatter_dim: int = 2, gather_dim: int = 1
|
| 342 |
+
):
|
| 343 |
+
return _AllToAll.apply(input_, group, scatter_dim, gather_dim)
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
class _AllGather(torch.autograd.Function):
|
| 347 |
+
"""All-gather along ``dim`` with autograd support.
|
| 348 |
+
|
| 349 |
+
``full_seq_len`` lets both the forward gather and the backward split derive
|
| 350 |
+
the per-rank shard sizes locally, so neither direction needs the shape
|
| 351 |
+
negotiation collective.
|
| 352 |
+
"""
|
| 353 |
+
|
| 354 |
+
@staticmethod
|
| 355 |
+
def forward(ctx, input_, dim, group, full_seq_len=None):
|
| 356 |
+
world_size = dist.get_world_size(group)
|
| 357 |
+
rank = dist.get_group_rank(group, dist.get_rank())
|
| 358 |
+
|
| 359 |
+
if full_seq_len is not None:
|
| 360 |
+
sizes = sp_chunk_sizes(int(full_seq_len), world_size)
|
| 361 |
+
else:
|
| 362 |
+
shapes = [None] * world_size
|
| 363 |
+
dist.all_gather_object(shapes, input_.shape[dim], group)
|
| 364 |
+
sizes = [int(s) for s in shapes]
|
| 365 |
+
|
| 366 |
+
ctx.dim = dim
|
| 367 |
+
ctx.group = group
|
| 368 |
+
ctx.sizes = sizes
|
| 369 |
+
ctx.rank = rank
|
| 370 |
+
|
| 371 |
+
input_ = input_.contiguous()
|
| 372 |
+
if all(s == sizes[0] for s in sizes):
|
| 373 |
+
# Uniform shards: a single ncclAllGather instead of the
|
| 374 |
+
# broadcast-per-rank fallback PyTorch falls back to for ragged inputs.
|
| 375 |
+
if dim == 0:
|
| 376 |
+
out_shape = list(input_.shape)
|
| 377 |
+
out_shape[0] = sizes[0] * world_size
|
| 378 |
+
flat = torch.empty(out_shape, dtype=input_.dtype, device=input_.device)
|
| 379 |
+
dist.all_gather_into_tensor(flat, input_, group=group)
|
| 380 |
+
return flat
|
| 381 |
+
tensor_list = [torch.empty_like(input_) for _ in range(world_size)]
|
| 382 |
+
dist.all_gather(tensor_list, input_, group=group)
|
| 383 |
+
return torch.cat(tensor_list, dim=dim)
|
| 384 |
+
|
| 385 |
+
tensor_list = []
|
| 386 |
+
for size in sizes:
|
| 387 |
+
shape = list(input_.shape)
|
| 388 |
+
shape[dim] = size
|
| 389 |
+
tensor_list.append(
|
| 390 |
+
torch.empty(shape, dtype=input_.dtype, device=input_.device)
|
| 391 |
+
)
|
| 392 |
+
dist.all_gather(tensor_list, input_, group=group)
|
| 393 |
+
return torch.cat(tensor_list, dim=dim)
|
| 394 |
+
|
| 395 |
+
@staticmethod
|
| 396 |
+
def backward(ctx, grad_output):
|
| 397 |
+
grad_input = torch.split(grad_output, ctx.sizes, dim=ctx.dim)[ctx.rank]
|
| 398 |
+
return grad_input.contiguous(), None, None, None
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
def all_gather(input_: torch.Tensor, dim: int = 1, group=None,
|
| 402 |
+
full_seq_len: Optional[int] = None):
|
| 403 |
+
"""Gather ``input_`` shards along ``dim`` across the SP group.
|
| 404 |
+
|
| 405 |
+
Args:
|
| 406 |
+
input_: this rank's shard.
|
| 407 |
+
dim: concatenation dimension.
|
| 408 |
+
group: SP process group.
|
| 409 |
+
full_seq_len: global length along ``dim``. When supplied the shard sizes
|
| 410 |
+
are computed locally, avoiding a shape-negotiation collective in both
|
| 411 |
+
the forward and the backward pass.
|
| 412 |
+
"""
|
| 413 |
+
return _AllGather.apply(input_, dim, group, full_seq_len)
|
hymm/utils/parallel_states.py
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Distributed / sequence-parallel state for MOVA.
|
| 2 |
+
|
| 3 |
+
The topology follows HunyuanVideo-1.5: two independent device meshes over the
|
| 4 |
+
same world.
|
| 5 |
+
|
| 6 |
+
world_mesh = [dp, sp] -> data parallel x Ulysses sequence parallel
|
| 7 |
+
fsdp_mesh = [dp_replicate, fsdp_shard] -> FSDP2 parameter sharding
|
| 8 |
+
|
| 9 |
+
SP shards activations, FSDP shards parameters, so the two meshes are
|
| 10 |
+
orthogonal and ``fsdp_shard`` deliberately spans SP ranks as well.
|
| 11 |
+
|
| 12 |
+
Model code only ever touches :data:`nccl_info` and
|
| 13 |
+
:func:`get_sequence_parallel_state`, which are kept as thin views over the
|
| 14 |
+
active :class:`ParallelDims`, so training and inference share one init path.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
import random
|
| 19 |
+
from dataclasses import dataclass, field
|
| 20 |
+
from datetime import timedelta
|
| 21 |
+
from typing import Any, Optional
|
| 22 |
+
|
| 23 |
+
import numpy as np
|
| 24 |
+
import torch
|
| 25 |
+
import torch.distributed as dist
|
| 26 |
+
from torch.distributed.device_mesh import init_device_mesh
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# ---------------------------------------------------------------------------
|
| 30 |
+
# Topology
|
| 31 |
+
# ---------------------------------------------------------------------------
|
| 32 |
+
|
| 33 |
+
@dataclass
|
| 34 |
+
class ParallelDims:
|
| 35 |
+
sp: int = 1
|
| 36 |
+
dp_replicate: int = 1
|
| 37 |
+
world_size: int = -1
|
| 38 |
+
use_dynamic_ring_attention: bool = False
|
| 39 |
+
|
| 40 |
+
world_mesh: Any = field(default=None, init=False, repr=False)
|
| 41 |
+
fsdp_mesh: Any = field(default=None, init=False, repr=False)
|
| 42 |
+
sp_group: Any = field(default=None, init=False, repr=False)
|
| 43 |
+
sp_rank: int = field(default=0, init=False)
|
| 44 |
+
dp_rank: int = field(default=0, init=False)
|
| 45 |
+
dp_size: int = field(default=1, init=False)
|
| 46 |
+
|
| 47 |
+
def __post_init__(self):
|
| 48 |
+
if self.world_size == -1:
|
| 49 |
+
if dist.is_initialized():
|
| 50 |
+
self.world_size = dist.get_world_size()
|
| 51 |
+
else:
|
| 52 |
+
self.world_size = int(os.environ.get("WORLD_SIZE", "1"))
|
| 53 |
+
if self.dp_replicate == -1:
|
| 54 |
+
# One replica per node; FSDP shards within a node.
|
| 55 |
+
local_world = int(os.environ.get("LOCAL_WORLD_SIZE", "8"))
|
| 56 |
+
assert self.world_size % local_world == 0, (
|
| 57 |
+
f"world_size({self.world_size}) must be divisible by "
|
| 58 |
+
f"LOCAL_WORLD_SIZE({local_world}) for dp_replicate=-1"
|
| 59 |
+
)
|
| 60 |
+
self.dp_replicate = self.world_size // local_world
|
| 61 |
+
assert self.sp >= 1 and self.dp_replicate >= 1
|
| 62 |
+
assert self.world_size % self.sp == 0, (
|
| 63 |
+
f"world_size({self.world_size}) must be divisible by sp({self.sp})"
|
| 64 |
+
)
|
| 65 |
+
assert self.world_size % self.dp_replicate == 0, (
|
| 66 |
+
f"world_size({self.world_size}) must be divisible by "
|
| 67 |
+
f"dp_replicate({self.dp_replicate})"
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
# -- mesh -------------------------------------------------------------
|
| 71 |
+
|
| 72 |
+
def build_mesh(self, device_type: str = "cuda"):
|
| 73 |
+
self.world_mesh = init_device_mesh(
|
| 74 |
+
device_type,
|
| 75 |
+
(self.world_size // self.sp, self.sp),
|
| 76 |
+
mesh_dim_names=("dp", "sp"),
|
| 77 |
+
)
|
| 78 |
+
self.fsdp_mesh = init_device_mesh(
|
| 79 |
+
device_type,
|
| 80 |
+
(self.dp_replicate, self.world_size // self.dp_replicate),
|
| 81 |
+
mesh_dim_names=("dp_replicate", "fsdp_shard"),
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
self.dp_rank = self.world_mesh["dp"].get_local_rank()
|
| 85 |
+
self.dp_size = self.world_mesh["dp"].size()
|
| 86 |
+
if self.sp_enabled:
|
| 87 |
+
self.sp_rank = self.world_mesh["sp"].get_local_rank()
|
| 88 |
+
self.sp_group = self.world_mesh["sp"].get_group()
|
| 89 |
+
else:
|
| 90 |
+
self.sp_rank = 0
|
| 91 |
+
self.sp_group = None
|
| 92 |
+
return self.world_mesh
|
| 93 |
+
|
| 94 |
+
# -- properties -------------------------------------------------------
|
| 95 |
+
|
| 96 |
+
@property
|
| 97 |
+
def sp_enabled(self) -> bool:
|
| 98 |
+
return self.sp > 1
|
| 99 |
+
|
| 100 |
+
@property
|
| 101 |
+
def dp_enabled(self) -> bool:
|
| 102 |
+
return self.dp_size > 1
|
| 103 |
+
|
| 104 |
+
@property
|
| 105 |
+
def dp_replicate_enabled(self) -> bool:
|
| 106 |
+
return self.dp_replicate > 1
|
| 107 |
+
|
| 108 |
+
@property
|
| 109 |
+
def dp_shard_enabled(self) -> bool:
|
| 110 |
+
return (self.world_size // self.dp_replicate) > 1
|
| 111 |
+
|
| 112 |
+
@property
|
| 113 |
+
def pp_enabled(self) -> bool:
|
| 114 |
+
return False
|
| 115 |
+
|
| 116 |
+
@property
|
| 117 |
+
def sp_mesh(self):
|
| 118 |
+
return self.world_mesh["sp"]
|
| 119 |
+
|
| 120 |
+
@property
|
| 121 |
+
def dp_mesh(self):
|
| 122 |
+
return self.world_mesh["dp"]
|
| 123 |
+
|
| 124 |
+
@property
|
| 125 |
+
def sp_src_rank(self) -> int:
|
| 126 |
+
"""Global rank of local rank 0 inside this rank's SP group."""
|
| 127 |
+
if not self.sp_enabled:
|
| 128 |
+
return dist.get_rank() if dist.is_initialized() else 0
|
| 129 |
+
return dist.get_global_rank(self.sp_group, 0)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
# ---------------------------------------------------------------------------
|
| 133 |
+
# Legacy view used by the model code (nccl_info.sp_group / .sp_size / ...)
|
| 134 |
+
# ---------------------------------------------------------------------------
|
| 135 |
+
|
| 136 |
+
class COMM_INFO:
|
| 137 |
+
def __init__(self):
|
| 138 |
+
self.sp_group = None
|
| 139 |
+
self.sp_size = 1
|
| 140 |
+
self.global_rank = 0
|
| 141 |
+
self.rank_within_spgroup = 0
|
| 142 |
+
self.parallel_dims = None
|
| 143 |
+
self.device_mesh = None
|
| 144 |
+
self.use_dynamic_ring_attention = False
|
| 145 |
+
self.sp_stream = None
|
| 146 |
+
self.sp_rank_list = None
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
nccl_info = COMM_INFO()
|
| 150 |
+
_SEQUENCE_PARALLEL_STATE = False
|
| 151 |
+
_PARALLEL_DIMS: Optional[ParallelDims] = None
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def set_sequence_parallel_state(state: bool):
|
| 155 |
+
global _SEQUENCE_PARALLEL_STATE
|
| 156 |
+
_SEQUENCE_PARALLEL_STATE = state
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def get_sequence_parallel_state() -> bool:
|
| 160 |
+
return _SEQUENCE_PARALLEL_STATE
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def get_parallel_state() -> ParallelDims:
|
| 164 |
+
"""Active topology, or a mesh-free single-rank default if none was set up.
|
| 165 |
+
|
| 166 |
+
The fallback deliberately does NOT build a device mesh: ``init_device_mesh``
|
| 167 |
+
is collective, and creating one from an incidental accessor call would mean
|
| 168 |
+
some ranks entering a collective their peers never reach.
|
| 169 |
+
"""
|
| 170 |
+
global _PARALLEL_DIMS
|
| 171 |
+
if _PARALLEL_DIMS is None:
|
| 172 |
+
_PARALLEL_DIMS = ParallelDims(sp=1, dp_replicate=1, world_size=1)
|
| 173 |
+
return _PARALLEL_DIMS
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
# ---------------------------------------------------------------------------
|
| 177 |
+
# Init
|
| 178 |
+
# ---------------------------------------------------------------------------
|
| 179 |
+
|
| 180 |
+
def init_distributed(timeout_seconds: int = 5400) -> int:
|
| 181 |
+
"""Initialise the default process group and bind this rank's device.
|
| 182 |
+
|
| 183 |
+
``device_id`` is passed on purpose: it makes PyTorch build the default NCCL
|
| 184 |
+
communicator eagerly during ``init_process_group`` instead of lazily on the
|
| 185 |
+
first collective. Lazy communicator creation is a classic source of hangs
|
| 186 |
+
when different ranks reach their first collective through different code
|
| 187 |
+
paths (e.g. the MoE expert alternating between steps).
|
| 188 |
+
|
| 189 |
+
The timeout here covers *startup*, where a rank can legitimately sit in a
|
| 190 |
+
barrier for a long time while a peer streams a 15B checkpoint off a network
|
| 191 |
+
filesystem. Shrink it with :func:`set_collective_timeout` once the training
|
| 192 |
+
loop starts, where a long wait means a genuine desync rather than slow I/O.
|
| 193 |
+
"""
|
| 194 |
+
local_rank = int(os.environ.get("LOCAL_RANK", "0"))
|
| 195 |
+
if torch.cuda.is_available():
|
| 196 |
+
torch.cuda.set_device(local_rank)
|
| 197 |
+
if not dist.is_initialized():
|
| 198 |
+
kwargs = {}
|
| 199 |
+
if torch.cuda.is_available():
|
| 200 |
+
kwargs["device_id"] = torch.device("cuda", local_rank)
|
| 201 |
+
dist.init_process_group(
|
| 202 |
+
backend="nccl",
|
| 203 |
+
timeout=timedelta(seconds=timeout_seconds),
|
| 204 |
+
**kwargs,
|
| 205 |
+
)
|
| 206 |
+
return local_rank
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def set_collective_timeout(timeout_seconds: int, logger=None):
|
| 210 |
+
"""Re-arm the watchdog with a tighter per-collective deadline.
|
| 211 |
+
|
| 212 |
+
Called once the steady-state training loop begins. A short deadline is what
|
| 213 |
+
turns a desync into an abort with a stack trace instead of a job that sits
|
| 214 |
+
idle until someone notices. Applied to every group we created, since the
|
| 215 |
+
watchdog is per-communicator.
|
| 216 |
+
|
| 217 |
+
Failures are swallowed deliberately: this touches a private torch API and
|
| 218 |
+
performs no collective, so a rank that cannot re-arm simply keeps the
|
| 219 |
+
startup timeout rather than diverging from its peers.
|
| 220 |
+
"""
|
| 221 |
+
if not dist.is_initialized():
|
| 222 |
+
return False
|
| 223 |
+
try:
|
| 224 |
+
from torch.distributed.distributed_c10d import _set_pg_timeout
|
| 225 |
+
except ImportError:
|
| 226 |
+
if logger is not None:
|
| 227 |
+
logger.warning("[dist] _set_pg_timeout unavailable; keeping the startup timeout")
|
| 228 |
+
return False
|
| 229 |
+
|
| 230 |
+
timeout = timedelta(seconds=int(timeout_seconds))
|
| 231 |
+
groups = [None]
|
| 232 |
+
pd = _PARALLEL_DIMS
|
| 233 |
+
if pd is not None:
|
| 234 |
+
if pd.world_mesh is not None:
|
| 235 |
+
groups += [pd.world_mesh["sp"].get_group(), pd.world_mesh["dp"].get_group()]
|
| 236 |
+
if pd.fsdp_mesh is not None:
|
| 237 |
+
groups += [pd.fsdp_mesh["fsdp_shard"].get_group(),
|
| 238 |
+
pd.fsdp_mesh["dp_replicate"].get_group()]
|
| 239 |
+
for group in groups:
|
| 240 |
+
try:
|
| 241 |
+
_set_pg_timeout(timeout, group)
|
| 242 |
+
except Exception as exc: # noqa: BLE001
|
| 243 |
+
if logger is not None:
|
| 244 |
+
logger.warning(f"[dist] could not re-arm timeout on {group}: {exc}")
|
| 245 |
+
return False
|
| 246 |
+
if logger is not None:
|
| 247 |
+
logger.info(f"[dist] per-collective timeout tightened to {timeout_seconds}s for training")
|
| 248 |
+
return True
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def initialize_parallel_state(
|
| 252 |
+
sp: int = 1,
|
| 253 |
+
dp_replicate: int = 1,
|
| 254 |
+
use_dynamic_ring_attention: bool = False,
|
| 255 |
+
warmup: bool = True,
|
| 256 |
+
) -> ParallelDims:
|
| 257 |
+
"""Build the device meshes and publish them to the model-facing globals."""
|
| 258 |
+
global _PARALLEL_DIMS
|
| 259 |
+
|
| 260 |
+
parallel_dims = ParallelDims(
|
| 261 |
+
sp=sp,
|
| 262 |
+
dp_replicate=dp_replicate,
|
| 263 |
+
use_dynamic_ring_attention=use_dynamic_ring_attention,
|
| 264 |
+
)
|
| 265 |
+
if dist.is_initialized():
|
| 266 |
+
parallel_dims.build_mesh("cuda")
|
| 267 |
+
|
| 268 |
+
nccl_info.sp_size = parallel_dims.sp
|
| 269 |
+
nccl_info.global_rank = dist.get_rank() if dist.is_initialized() else 0
|
| 270 |
+
nccl_info.rank_within_spgroup = parallel_dims.sp_rank
|
| 271 |
+
nccl_info.sp_group = parallel_dims.sp_group
|
| 272 |
+
nccl_info.parallel_dims = parallel_dims
|
| 273 |
+
nccl_info.device_mesh = parallel_dims.world_mesh
|
| 274 |
+
nccl_info.use_dynamic_ring_attention = use_dynamic_ring_attention
|
| 275 |
+
if use_dynamic_ring_attention and parallel_dims.sp_enabled:
|
| 276 |
+
nccl_info.sp_stream = torch.cuda.Stream()
|
| 277 |
+
nccl_info.sp_rank_list = parallel_dims.world_mesh["sp"].mesh.tolist()
|
| 278 |
+
|
| 279 |
+
set_sequence_parallel_state(parallel_dims.sp_enabled)
|
| 280 |
+
_PARALLEL_DIMS = parallel_dims
|
| 281 |
+
|
| 282 |
+
if warmup and dist.is_initialized():
|
| 283 |
+
warmup_process_groups(parallel_dims)
|
| 284 |
+
return parallel_dims
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def warmup_process_groups(parallel_dims: Optional[ParallelDims] = None):
|
| 288 |
+
"""Force NCCL communicator creation for every group we will ever use.
|
| 289 |
+
|
| 290 |
+
Every rank walks the same group list in the same order, so all communicators
|
| 291 |
+
exist before the training loop starts. Without this, the first collective on
|
| 292 |
+
a lazily-created group can land while other ranks are inside a different
|
| 293 |
+
collective, which NCCL cannot untangle and the watchdog eventually kills.
|
| 294 |
+
"""
|
| 295 |
+
if not dist.is_initialized():
|
| 296 |
+
return
|
| 297 |
+
parallel_dims = parallel_dims or get_parallel_state()
|
| 298 |
+
device = torch.device("cuda", torch.cuda.current_device())
|
| 299 |
+
probe = torch.zeros(1, device=device)
|
| 300 |
+
|
| 301 |
+
groups = [None] # default process group
|
| 302 |
+
if parallel_dims.world_mesh is not None:
|
| 303 |
+
groups.append(parallel_dims.world_mesh["sp"].get_group())
|
| 304 |
+
groups.append(parallel_dims.world_mesh["dp"].get_group())
|
| 305 |
+
if parallel_dims.fsdp_mesh is not None:
|
| 306 |
+
groups.append(parallel_dims.fsdp_mesh["fsdp_shard"].get_group())
|
| 307 |
+
groups.append(parallel_dims.fsdp_mesh["dp_replicate"].get_group())
|
| 308 |
+
|
| 309 |
+
for group in groups:
|
| 310 |
+
dist.all_reduce(probe, group=group)
|
| 311 |
+
torch.cuda.synchronize()
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def destroy_sequence_parallel_group():
|
| 315 |
+
if dist.is_initialized():
|
| 316 |
+
dist.destroy_process_group()
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
# ---------------------------------------------------------------------------
|
| 320 |
+
# Sequence-parallel sequence-length context
|
| 321 |
+
# ---------------------------------------------------------------------------
|
| 322 |
+
|
| 323 |
+
class SPSequenceContext:
|
| 324 |
+
"""Full (pre-shard) sequence length of each SP-sharded stream.
|
| 325 |
+
|
| 326 |
+
Ulysses' head-scatter all-to-all has to undo the uneven ``torch.chunk``
|
| 327 |
+
split, which means it needs the *global* sequence length -- information a
|
| 328 |
+
rank cannot recover from its own shard. The towers publish it here once per
|
| 329 |
+
forward instead of paying a ``dist.all_gather_object`` inside every single
|
| 330 |
+
attention call (~800 object collectives per step at 40 layers x 2 towers).
|
| 331 |
+
|
| 332 |
+
Values stay valid until the next forward overwrites them, so activation
|
| 333 |
+
checkpointing recompute during backward reads the correct lengths.
|
| 334 |
+
"""
|
| 335 |
+
|
| 336 |
+
__slots__ = ("visual", "audio")
|
| 337 |
+
|
| 338 |
+
def __init__(self):
|
| 339 |
+
self.visual = None
|
| 340 |
+
self.audio = None
|
| 341 |
+
|
| 342 |
+
def set(self, visual=None, audio=None):
|
| 343 |
+
if visual is not None:
|
| 344 |
+
self.visual = int(visual)
|
| 345 |
+
if audio is not None:
|
| 346 |
+
self.audio = int(audio)
|
| 347 |
+
|
| 348 |
+
def get(self, stream: str):
|
| 349 |
+
return getattr(self, stream, None)
|
| 350 |
+
|
| 351 |
+
def clear(self):
|
| 352 |
+
self.visual = None
|
| 353 |
+
self.audio = None
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
sp_seq_ctx = SPSequenceContext()
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
# ---------------------------------------------------------------------------
|
| 360 |
+
# Cross-rank data / RNG alignment (replaces hy_parallelism)
|
| 361 |
+
# ---------------------------------------------------------------------------
|
| 362 |
+
|
| 363 |
+
def _flatten_tensors(obj, out):
|
| 364 |
+
if isinstance(obj, torch.Tensor):
|
| 365 |
+
out.append(obj)
|
| 366 |
+
elif isinstance(obj, dict):
|
| 367 |
+
for value in obj.values():
|
| 368 |
+
_flatten_tensors(value, out)
|
| 369 |
+
elif isinstance(obj, (list, tuple)):
|
| 370 |
+
for value in obj:
|
| 371 |
+
_flatten_tensors(value, out)
|
| 372 |
+
return out
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
def _rebuild_with_tensors(obj, tensors, cursor):
|
| 376 |
+
if isinstance(obj, torch.Tensor):
|
| 377 |
+
value = tensors[cursor[0]]
|
| 378 |
+
cursor[0] += 1
|
| 379 |
+
return value
|
| 380 |
+
if isinstance(obj, dict):
|
| 381 |
+
return {k: _rebuild_with_tensors(v, tensors, cursor) for k, v in obj.items()}
|
| 382 |
+
if isinstance(obj, (list, tuple)):
|
| 383 |
+
rebuilt = [_rebuild_with_tensors(v, tensors, cursor) for v in obj]
|
| 384 |
+
return tuple(rebuilt) if isinstance(obj, tuple) else rebuilt
|
| 385 |
+
return obj
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def sync_data_for_sp(obj, parallel_dims: Optional[ParallelDims] = None, force_object: bool = False):
|
| 389 |
+
"""Make ``obj`` identical on every rank of the SP group (source = local rank 0).
|
| 390 |
+
|
| 391 |
+
Containers are walked recursively, so a whole dataloader batch can be passed
|
| 392 |
+
straight in. Non-tensor leaves go through ``broadcast_object_list``; tensors
|
| 393 |
+
are broadcast on the device.
|
| 394 |
+
|
| 395 |
+
The shapes are agreed on FIRST, in a single object broadcast, and any rank
|
| 396 |
+
whose local tensor disagrees reallocates before the data broadcast. That
|
| 397 |
+
matters because ``dist.broadcast`` demands matching shapes on every
|
| 398 |
+
participant: without the agreement step a rank that silently substituted a
|
| 399 |
+
different sample (the dataset retries on an I/O error, and an error need not
|
| 400 |
+
hit every rank) would corrupt the batch or hang the group -- the exact
|
| 401 |
+
failure this call exists to prevent.
|
| 402 |
+
"""
|
| 403 |
+
parallel_dims = parallel_dims or get_parallel_state()
|
| 404 |
+
if not parallel_dims.sp_enabled:
|
| 405 |
+
return obj
|
| 406 |
+
|
| 407 |
+
src = parallel_dims.sp_src_rank
|
| 408 |
+
group = parallel_dims.sp_group
|
| 409 |
+
|
| 410 |
+
if force_object or not isinstance(obj, (torch.Tensor, dict, list, tuple)):
|
| 411 |
+
holder = [obj]
|
| 412 |
+
dist.broadcast_object_list(holder, src=src, group=group)
|
| 413 |
+
return holder[0]
|
| 414 |
+
|
| 415 |
+
tensors = _flatten_tensors(obj, [])
|
| 416 |
+
if not tensors:
|
| 417 |
+
holder = [obj]
|
| 418 |
+
dist.broadcast_object_list(holder, src=src, group=group)
|
| 419 |
+
return holder[0]
|
| 420 |
+
|
| 421 |
+
specs = [(tuple(t.shape), t.dtype) for t in tensors]
|
| 422 |
+
holder = [specs]
|
| 423 |
+
dist.broadcast_object_list(holder, src=src, group=group)
|
| 424 |
+
specs = holder[0]
|
| 425 |
+
|
| 426 |
+
device = torch.device("cuda", torch.cuda.current_device())
|
| 427 |
+
synced = []
|
| 428 |
+
for tensor, (shape, dtype) in zip(tensors, specs):
|
| 429 |
+
target_device = tensor.device
|
| 430 |
+
if tuple(tensor.shape) != shape or tensor.dtype != dtype:
|
| 431 |
+
staged = torch.empty(shape, dtype=dtype, device=device)
|
| 432 |
+
else:
|
| 433 |
+
staged = tensor if tensor.is_cuda else tensor.to(device)
|
| 434 |
+
dist.broadcast(staged, src=src, group=group)
|
| 435 |
+
synced.append(staged if target_device.type == "cuda" else staged.to(target_device))
|
| 436 |
+
|
| 437 |
+
return _rebuild_with_tensors(obj, synced, [0])
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
def sync_random_states(step: int, base_seed: int = 0, dp_rank: int = 0):
|
| 441 |
+
"""Re-seed the RNGs so an SP group samples identical noise.
|
| 442 |
+
|
| 443 |
+
Derived purely from ``(base_seed, dp_rank, step)``: every SP rank computes
|
| 444 |
+
the same value locally, so there is no collective and no device sync, while
|
| 445 |
+
distinct DP groups still see distinct noise.
|
| 446 |
+
"""
|
| 447 |
+
seed = (int(base_seed) * 1_000_003 + int(dp_rank) * 10_007 + int(step)) % (2 ** 31 - 1)
|
| 448 |
+
random.seed(seed)
|
| 449 |
+
np.random.seed(seed)
|
| 450 |
+
torch.manual_seed(seed)
|
| 451 |
+
if torch.cuda.is_available():
|
| 452 |
+
torch.cuda.manual_seed_all(seed)
|
| 453 |
+
return seed
|
packages.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
ffmpeg
|
requirements.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.8.0
|
| 2 |
+
torchvision==0.23.0
|
| 3 |
+
diffusers==0.33.0
|
| 4 |
+
transformers==4.57.1
|
| 5 |
+
accelerate>=1.10.0
|
| 6 |
+
safetensors>=0.6.2
|
| 7 |
+
huggingface_hub[hf_xet]
|
| 8 |
+
einops>=0.8.1
|
| 9 |
+
loguru
|
| 10 |
+
ftfy
|
| 11 |
+
imageio[ffmpeg]>=2.37.0
|
| 12 |
+
numpy
|
| 13 |
+
scipy
|
| 14 |
+
sentencepiece
|