Text Generation
Transformers
Safetensors
English
Chinese
qwen3
ksa
ksa-v1
custom_code
long-context
conversational
text-generation-inference
Instructions to use OpenOneRec/KSA-1.9B-base with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use OpenOneRec/KSA-1.9B-base with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="OpenOneRec/KSA-1.9B-base", trust_remote_code=True) messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("OpenOneRec/KSA-1.9B-base", trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained("OpenOneRec/KSA-1.9B-base", trust_remote_code=True, device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=256) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use OpenOneRec/KSA-1.9B-base with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "OpenOneRec/KSA-1.9B-base" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "OpenOneRec/KSA-1.9B-base", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/OpenOneRec/KSA-1.9B-base
- SGLang
How to use OpenOneRec/KSA-1.9B-base with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "OpenOneRec/KSA-1.9B-base" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "OpenOneRec/KSA-1.9B-base", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "OpenOneRec/KSA-1.9B-base" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "OpenOneRec/KSA-1.9B-base", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use OpenOneRec/KSA-1.9B-base with Docker Model Runner:
docker model run hf.co/OpenOneRec/KSA-1.9B-base
Release KSA-1.9B-base weights and inference runtime
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- LICENSE +201 -0
- README.md +162 -0
- README_zh.md +156 -0
- assets/figures/mainmodel.png +3 -0
- config.json +43 -0
- generate.py +76 -0
- generation_config.json +8 -0
- kernel/deps/flash_attn/AUTHORS +5 -0
- kernel/deps/flash_attn/LICENSE +29 -0
- kernel/deps/flash_attn/__init__.py +4 -0
- kernel/deps/flash_attn/cute/__init__.py +21 -0
- kernel/deps/flash_attn/cute/ampere_helpers.py +103 -0
- kernel/deps/flash_attn/cute/barrier.py +71 -0
- kernel/deps/flash_attn/cute/blackwell_helpers.py +753 -0
- kernel/deps/flash_attn/cute/block_info.py +108 -0
- kernel/deps/flash_attn/cute/block_sparse_utils.py +1448 -0
- kernel/deps/flash_attn/cute/block_sparsity.py +440 -0
- kernel/deps/flash_attn/cute/compute_block_sparsity.py +378 -0
- kernel/deps/flash_attn/cute/copy_utils.py +340 -0
- kernel/deps/flash_attn/cute/cute_dsl_utils.py +144 -0
- kernel/deps/flash_attn/cute/fast_math.py +21 -0
- kernel/deps/flash_attn/cute/flash_bwd.py +1262 -0
- kernel/deps/flash_attn/cute/flash_bwd_postprocess.py +463 -0
- kernel/deps/flash_attn/cute/flash_bwd_preprocess.py +365 -0
- kernel/deps/flash_attn/cute/flash_bwd_sm100.py +0 -0
- kernel/deps/flash_attn/cute/flash_bwd_sm90.py +1708 -0
- kernel/deps/flash_attn/cute/flash_fwd.py +0 -0
- kernel/deps/flash_attn/cute/flash_fwd_combine.py +704 -0
- kernel/deps/flash_attn/cute/flash_fwd_sm100.py +0 -0
- kernel/deps/flash_attn/cute/hopper_helpers.py +101 -0
- kernel/deps/flash_attn/cute/interface.py +1762 -0
- kernel/deps/flash_attn/cute/mask.py +651 -0
- kernel/deps/flash_attn/cute/mma_sm100_desc.py +291 -0
- kernel/deps/flash_attn/cute/named_barrier.py +31 -0
- kernel/deps/flash_attn/cute/pack_gqa.py +164 -0
- kernel/deps/flash_attn/cute/paged_kv.py +214 -0
- kernel/deps/flash_attn/cute/pipeline.py +272 -0
- kernel/deps/flash_attn/cute/seqlen_info.py +138 -0
- kernel/deps/flash_attn/cute/softmax.py +582 -0
- kernel/deps/flash_attn/cute/tile_scheduler.py +719 -0
- kernel/deps/flash_attn/cute/utils.py +859 -0
- kernel/ksa_attn/__init__.py +2 -0
- kernel/ksa_attn/interface.py +996 -0
- kernel/ksa_attn/mask.py +253 -0
- merges.txt +0 -0
- model-00000-of-00001.safetensors +3 -0
- model.safetensors.index.json +274 -0
- modeling_qwen3.py +1570 -0
- requirements.txt +12 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
assets/figures/mainmodel.png filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [yyyy] [name of copyright owner]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
- zh
|
| 6 |
+
library_name: transformers
|
| 7 |
+
pipeline_tag: text-generation
|
| 8 |
+
tags:
|
| 9 |
+
- ksa
|
| 10 |
+
- ksa-v1
|
| 11 |
+
- custom_code
|
| 12 |
+
- long-context
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
<div align="center">
|
| 17 |
+
<h1>KSA V1 1.9B</h1>
|
| 18 |
+
<p><strong>Kwai Summary Attention for long-context modeling</strong></p>
|
| 19 |
+
<p>
|
| 20 |
+
<a href="https://arxiv.org/abs/2604.24432">Technical report</a> ·
|
| 21 |
+
<a href="https://github.com/Kuaishou-OneRec/KSA">Code</a> ·
|
| 22 |
+
<a href="LICENSE">Apache-2.0</a>
|
| 23 |
+
</p>
|
| 24 |
+
<p><a href="README.md">English</a> | <a href="README_zh.md">中文</a></p>
|
| 25 |
+
</div>
|
| 26 |
+
|
| 27 |
+
## 📖 Introduction
|
| 28 |
+
|
| 29 |
+
**KSA V1 1.9B** is our Hybrid KSA base model trained **from scratch**, with
|
| 30 |
+
128K context. Learnable Summary tokens compress distant text into persistent
|
| 31 |
+
long-range memory. Three KSA layers alternate with one Full Attention layer;
|
| 32 |
+
each Summary represents eight original text tokens.
|
| 33 |
+
|
| 34 |
+
This is the Hybrid KSA 1.9B backbone used to initialize KSA V2.
|
| 35 |
+
It has **no C-KSA compression or S-KSA Indexer** and is not instruction-tuned.
|
| 36 |
+
|
| 37 |
+
<p align="center"><img src="assets/figures/mainmodel.png" width="80%" alt="Hybrid KSA: Summary tokens interleaved with text, and KSA / Full Attention layers in a 3:1 layout." /></p>
|
| 38 |
+
|
| 39 |
+
## 🔥 News
|
| 40 |
+
|
| 41 |
+
- **2026-10-13** — [KSA V2 technical report](https://arxiv.org/abs/2610.18654) released.
|
| 42 |
+
- **2026-05-08** — [KSA-4B-base](https://huggingface.co/OpenOneRec/KSA-4B-base), our Qwen3-4B CPT variant, released.
|
| 43 |
+
- **2026-04-28** — [KSA technical report](https://arxiv.org/abs/2604.24432) and training code released.
|
| 44 |
+
|
| 45 |
+
## 🤖 Model configuration
|
| 46 |
+
|
| 47 |
+
This directory contains **KSA V1 1.9B**.
|
| 48 |
+
|
| 49 |
+
| Configuration | KSA V1 1.9B | KSA V1 4B |
|
| 50 |
+
| :--- | :--- | :--- |
|
| 51 |
+
| Training | From scratch | CPT from Qwen3-4B |
|
| 52 |
+
| Number of layers | 24 | 36 |
|
| 53 |
+
| Hidden size | 2048 | 2560 |
|
| 54 |
+
| Intermediate size | 6144 | 9728 |
|
| 55 |
+
| Attention heads (Q / KV) | 16 / 16 (MHA) | 32 / 8 (GQA) |
|
| 56 |
+
| Head dimension | 128 | 128 |
|
| 57 |
+
| Hybrid ratio (KSA : Full) | 3 : 1 | 3 : 1 |
|
| 58 |
+
| Maximum context (text tokens) | 131072 | 131072 |
|
| 59 |
+
| Summary chunk size | 8 | 8 |
|
| 60 |
+
| KSA sliding chunk number | 128 | 128 |
|
| 61 |
+
| Summary Q/K/V projections | Shared with text | Shared with text |
|
| 62 |
+
| RoPE base | 1000000 | 1000000 |
|
| 63 |
+
| Tied embeddings | False | True |
|
| 64 |
+
| Weight / inference precision | BF16 / BF16 | BF16 / BF16 |
|
| 65 |
+
|
| 66 |
+
## 📈 Long-context retrieval
|
| 67 |
+
|
| 68 |
+
RULER results from the KSA V1 report:
|
| 69 |
+
|
| 70 |
+
### 1.9B: From scratch
|
| 71 |
+
|
| 72 |
+
| Model | 4K | 8K | 16K | 32K | 64K | 128K | Mean |
|
| 73 |
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
| 74 |
+
| Full | 76.08 | 72.85 | 73.24 | 69.06 | 65.32 | 48.75 | 67.55 |
|
| 75 |
+
| Hybrid SWA | 74.54 | 71.69 | 69.54 | 67.86 | 63.03 | 56.64 | 67.22 |
|
| 76 |
+
| Hybrid SCA | 77.72 | 75.22 | 72.55 | 67.74 | 63.54 | 58.01 | 69.13 |
|
| 77 |
+
| Hybrid GDN | 79.83 | 76.01 | 74.04 | 70.41 | 69.39 | 59.87 | 71.59 |
|
| 78 |
+
| Pure KSA | 70.44 | 65.91 | 66.74 | 62.54 | 57.13 | 39.29 | 60.34 |
|
| 79 |
+
| Hybrid KSA | 80.65 | 73.35 | 74.07 | 72.30 | 69.95 | 65.35 | 72.61 |
|
| 80 |
+
|
| 81 |
+
### 4B: Continual pretraining
|
| 82 |
+
|
| 83 |
+
| Model | 4K | 8K | 16K | 32K | 64K | 128K | Mean |
|
| 84 |
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
| 85 |
+
| Full | 92.88 | 91.38 | 89.12 | 84.74 | 78.16 | 65.86 | 83.69 |
|
| 86 |
+
| Hybrid SWA | 91.30 | 88.03 | 82.87 | 78.94 | 73.88 | 66.27 | 80.22 |
|
| 87 |
+
| Hybrid SCA | 86.02 | 84.28 | 80.67 | 76.89 | 68.88 | 60.94 | 76.28 |
|
| 88 |
+
| Hybrid Linear | 86.39 | 83.86 | 78.06 | 76.48 | 73.50 | 67.98 | 77.71 |
|
| 89 |
+
| Pure KSA | 91.55 | 86.78 | 84.78 | 80.30 | 76.09 | 66.81 | 81.05 |
|
| 90 |
+
| Hybrid KSA | 92.97 | 90.53 | 88.86 | 86.65 | 76.04 | 71.67 | 84.45 |
|
| 91 |
+
|
| 92 |
+
## Quick start
|
| 93 |
+
|
| 94 |
+
Linux, Python 3.12 and H100/H800. Download the **entire model directory**,
|
| 95 |
+
including `kernel/`, and run from it:
|
| 96 |
+
|
| 97 |
+
```bash
|
| 98 |
+
python -m pip install torch==2.6.0 --index-url https://download.pytorch.org/whl/cu126
|
| 99 |
+
python -m pip install -r requirements.txt
|
| 100 |
+
|
| 101 |
+
CUDA_VISIBLE_DEVICES=0 python generate.py \
|
| 102 |
+
--prompt "The main advantage of long-context language models is" \
|
| 103 |
+
--max-new-tokens 128
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
Omit `--prompt` for interactive completion. The optional `--apply-chat-template`
|
| 107 |
+
only applies the tokenizer template; it does not add instruction tuning.
|
| 108 |
+
|
| 109 |
+
Transformers 5.0.0 and the bundled `ksa_attn` / FlashAttention CuTe sources are
|
| 110 |
+
used. No training checkout, kernel build or FlashAttention 2 CUDA extension
|
| 111 |
+
is required.
|
| 112 |
+
|
| 113 |
+
### Transformers usage
|
| 114 |
+
|
| 115 |
+
Set the bundled kernel paths before importing Transformers:
|
| 116 |
+
|
| 117 |
+
```bash
|
| 118 |
+
export PYTHONPATH="$PWD/kernel:$PWD/kernel/deps${PYTHONPATH:+:$PYTHONPATH}"
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
```python
|
| 122 |
+
import torch
|
| 123 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 124 |
+
|
| 125 |
+
tokenizer = AutoTokenizer.from_pretrained(".", local_files_only=True)
|
| 126 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 127 |
+
".",
|
| 128 |
+
trust_remote_code=True,
|
| 129 |
+
local_files_only=True,
|
| 130 |
+
dtype=torch.bfloat16,
|
| 131 |
+
device_map={"": "cuda:0"},
|
| 132 |
+
attn_implementation="sdpa",
|
| 133 |
+
).eval()
|
| 134 |
+
|
| 135 |
+
inputs = tokenizer("The main advantage of long-context models is", return_tensors="pt")
|
| 136 |
+
inputs = inputs.to(model.device)
|
| 137 |
+
with torch.inference_mode():
|
| 138 |
+
output = model.generate(
|
| 139 |
+
**inputs,
|
| 140 |
+
max_new_tokens=128,
|
| 141 |
+
do_sample=False,
|
| 142 |
+
pad_token_id=tokenizer.eos_token_id,
|
| 143 |
+
)
|
| 144 |
+
print(tokenizer.decode(output[0, inputs.input_ids.shape[1]:], skip_special_tokens=True))
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
## License and citation
|
| 148 |
+
|
| 149 |
+
Weights and our runtime: [Apache-2.0](LICENSE). Qwen-derived files retain their
|
| 150 |
+
copyright notices. Bundled FlashAttention CuTe retains its
|
| 151 |
+
[BSD-3-Clause license](kernel/deps/flash_attn/LICENSE) and
|
| 152 |
+
[AUTHORS](kernel/deps/flash_attn/AUTHORS).
|
| 153 |
+
|
| 154 |
+
```bibtex
|
| 155 |
+
@techreport{kwai2026ksa,
|
| 156 |
+
title = {Kwai Summary Attention Technical Report},
|
| 157 |
+
author = {OneRec Team},
|
| 158 |
+
year = {2026},
|
| 159 |
+
institution = {Kuaishou Technology},
|
| 160 |
+
url = {https://arxiv.org/abs/2604.24432}
|
| 161 |
+
}
|
| 162 |
+
```
|
README_zh.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
- zh
|
| 6 |
+
library_name: transformers
|
| 7 |
+
pipeline_tag: text-generation
|
| 8 |
+
tags:
|
| 9 |
+
- ksa
|
| 10 |
+
- ksa-v1
|
| 11 |
+
- custom_code
|
| 12 |
+
- long-context
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
<div align="center">
|
| 17 |
+
<h1>KSA V1 1.9B</h1>
|
| 18 |
+
<p><strong>Kwai Summary Attention:面向长上下文建模的高效注意力</strong></p>
|
| 19 |
+
<p>
|
| 20 |
+
<a href="https://arxiv.org/abs/2604.24432">技术报告</a> ·
|
| 21 |
+
<a href="https://github.com/Kuaishou-OneRec/KSA">代码</a> ·
|
| 22 |
+
<a href="LICENSE">Apache-2.0</a>
|
| 23 |
+
</p>
|
| 24 |
+
<p><a href="README.md">English</a> | <a href="README_zh.md">中文</a></p>
|
| 25 |
+
</div>
|
| 26 |
+
|
| 27 |
+
## 📖 简介
|
| 28 |
+
|
| 29 |
+
**KSA V1 1.9B** 是我们**从零训练**的 Hybrid KSA 基础模型,支持 128K 上下文。
|
| 30 |
+
可学习的 Summary Token 将远端原文压缩为持久的长程记忆。每 3 层 KSA 配置
|
| 31 |
+
1 层 Full Attention,每个 Summary 对应 8 个原文 token。
|
| 32 |
+
|
| 33 |
+
这也是 KSA V2 使用的 Hybrid KSA 1.9B 初始主干,**不含 C-KSA 二级压缩或 S-KSA Indexer**,
|
| 34 |
+
未经过指令微调。
|
| 35 |
+
|
| 36 |
+
<p align="center"><img src="assets/figures/mainmodel.png" width="80%" alt="Hybrid KSA:Summary 与原文交错排列,KSA 和 Full Attention 层按 3:1 配置。" /></p>
|
| 37 |
+
|
| 38 |
+
## 🔥 新闻
|
| 39 |
+
|
| 40 |
+
- **2026-10-13** —— [KSA V2 技术报告](https://arxiv.org/abs/2610.18654)发布。
|
| 41 |
+
- **2026-05-08** —— 基于 Qwen3-4B 继续预训练的 [KSA-4B-base](https://huggingface.co/OpenOneRec/KSA-4B-base) 发布。
|
| 42 |
+
- **2026-04-28** —— [KSA 技术报告](https://arxiv.org/abs/2604.24432)与训练代码发布。
|
| 43 |
+
|
| 44 |
+
## 🤖 模型配置
|
| 45 |
+
|
| 46 |
+
当前目录为 **KSA V1 1.9B**。
|
| 47 |
+
|
| 48 |
+
| 配置项 | KSA V1 1.9B | KSA V1 4B |
|
| 49 |
+
| :--- | :--- | :--- |
|
| 50 |
+
| 训练方式 | 从零训练 | 基于 Qwen3-4B 继续预训练 |
|
| 51 |
+
| 层数 | 24 | 36 |
|
| 52 |
+
| Hidden size | 2048 | 2560 |
|
| 53 |
+
| Intermediate size | 6144 | 9728 |
|
| 54 |
+
| Attention heads (Q / KV) | 16 / 16 (MHA) | 32 / 8 (GQA) |
|
| 55 |
+
| Head dimension | 128 | 128 |
|
| 56 |
+
| Hybrid 比例 (KSA : Full) | 3 : 1 | 3 : 1 |
|
| 57 |
+
| 最大上下文(原文 token) | 131072 | 131072 |
|
| 58 |
+
| Summary chunk size | 8 | 8 |
|
| 59 |
+
| KSA sliding chunk number | 128 | 128 |
|
| 60 |
+
| Summary Q/K/V 投影 | 与原文共享 | 与原文共享 |
|
| 61 |
+
| RoPE base | 1000000 | 1000000 |
|
| 62 |
+
| Tied embeddings | False | True |
|
| 63 |
+
| 权重/推理精度 | BF16 / BF16 | BF16 / BF16 |
|
| 64 |
+
|
| 65 |
+
## 📈 长上下文检索
|
| 66 |
+
|
| 67 |
+
KSA V1 报告中的 RULER 结果:
|
| 68 |
+
|
| 69 |
+
### 1.9B:从零训练
|
| 70 |
+
|
| 71 |
+
| 模型 | 4K | 8K | 16K | 32K | 64K | 128K | Mean |
|
| 72 |
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
| 73 |
+
| Full | 76.08 | 72.85 | 73.24 | 69.06 | 65.32 | 48.75 | 67.55 |
|
| 74 |
+
| Hybrid SWA | 74.54 | 71.69 | 69.54 | 67.86 | 63.03 | 56.64 | 67.22 |
|
| 75 |
+
| Hybrid SCA | 77.72 | 75.22 | 72.55 | 67.74 | 63.54 | 58.01 | 69.13 |
|
| 76 |
+
| Hybrid GDN | 79.83 | 76.01 | 74.04 | 70.41 | 69.39 | 59.87 | 71.59 |
|
| 77 |
+
| Pure KSA | 70.44 | 65.91 | 66.74 | 62.54 | 57.13 | 39.29 | 60.34 |
|
| 78 |
+
| Hybrid KSA | 80.65 | 73.35 | 74.07 | 72.30 | 69.95 | 65.35 | 72.61 |
|
| 79 |
+
|
| 80 |
+
### 4B:继续预训练
|
| 81 |
+
|
| 82 |
+
| 模型 | 4K | 8K | 16K | 32K | 64K | 128K | Mean |
|
| 83 |
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
| 84 |
+
| Full | 92.88 | 91.38 | 89.12 | 84.74 | 78.16 | 65.86 | 83.69 |
|
| 85 |
+
| Hybrid SWA | 91.30 | 88.03 | 82.87 | 78.94 | 73.88 | 66.27 | 80.22 |
|
| 86 |
+
| Hybrid SCA | 86.02 | 84.28 | 80.67 | 76.89 | 68.88 | 60.94 | 76.28 |
|
| 87 |
+
| Hybrid Linear | 86.39 | 83.86 | 78.06 | 76.48 | 73.50 | 67.98 | 77.71 |
|
| 88 |
+
| Pure KSA | 91.55 | 86.78 | 84.78 | 80.30 | 76.09 | 66.81 | 81.05 |
|
| 89 |
+
| Hybrid KSA | 92.97 | 90.53 | 88.86 | 86.65 | 76.04 | 71.67 | 84.45 |
|
| 90 |
+
|
| 91 |
+
## 快速开始
|
| 92 |
+
|
| 93 |
+
Linux、Python 3.12、H100/H800。下载**完整模型目录**(包含 `kernel/`),并在该目录下执行:
|
| 94 |
+
|
| 95 |
+
```bash
|
| 96 |
+
python -m pip install torch==2.6.0 --index-url https://download.pytorch.org/whl/cu126
|
| 97 |
+
python -m pip install -r requirements.txt
|
| 98 |
+
|
| 99 |
+
CUDA_VISIBLE_DEVICES=0 python generate.py \
|
| 100 |
+
--prompt "The main advantage of long-context language models is" \
|
| 101 |
+
--max-new-tokens 128
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
省略 `--prompt` 可进行交互式文本续写。`--apply-chat-template` 只应用 tokenizer 中的对话模板,不代表模型经过指令微调。
|
| 105 |
+
|
| 106 |
+
使用 Transformers 5.0.0 和随包提供的 `ksa_attn`/FlashAttention CuTe 源码,无需训练仓库、单独编译内核或安装 FlashAttention 2 CUDA 扩展。
|
| 107 |
+
|
| 108 |
+
### Transformers 用法
|
| 109 |
+
|
| 110 |
+
导入 Transformers 前设置随包内核路径:
|
| 111 |
+
|
| 112 |
+
```bash
|
| 113 |
+
export PYTHONPATH="$PWD/kernel:$PWD/kernel/deps${PYTHONPATH:+:$PYTHONPATH}"
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
```python
|
| 117 |
+
import torch
|
| 118 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 119 |
+
|
| 120 |
+
tokenizer = AutoTokenizer.from_pretrained(".", local_files_only=True)
|
| 121 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 122 |
+
".",
|
| 123 |
+
trust_remote_code=True,
|
| 124 |
+
local_files_only=True,
|
| 125 |
+
dtype=torch.bfloat16,
|
| 126 |
+
device_map={"": "cuda:0"},
|
| 127 |
+
attn_implementation="sdpa",
|
| 128 |
+
).eval()
|
| 129 |
+
|
| 130 |
+
inputs = tokenizer("The main advantage of long-context models is", return_tensors="pt")
|
| 131 |
+
inputs = inputs.to(model.device)
|
| 132 |
+
with torch.inference_mode():
|
| 133 |
+
output = model.generate(
|
| 134 |
+
**inputs,
|
| 135 |
+
max_new_tokens=128,
|
| 136 |
+
do_sample=False,
|
| 137 |
+
pad_token_id=tokenizer.eos_token_id,
|
| 138 |
+
)
|
| 139 |
+
print(tokenizer.decode(output[0, inputs.input_ids.shape[1]:], skip_special_tokens=True))
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
## 许可证与引用
|
| 143 |
+
|
| 144 |
+
模型权重及我们的推理代码采用 [Apache-2.0](LICENSE)。基于 Qwen 的文件保留原版权声明。
|
| 145 |
+
随包的 FlashAttention CuTe 保留其 [BSD-3-Clause 许可证](kernel/deps/flash_attn/LICENSE)
|
| 146 |
+
与 [AUTHORS](kernel/deps/flash_attn/AUTHORS)。
|
| 147 |
+
|
| 148 |
+
```bibtex
|
| 149 |
+
@techreport{kwai2026ksa,
|
| 150 |
+
title = {Kwai Summary Attention Technical Report},
|
| 151 |
+
author = {OneRec Team},
|
| 152 |
+
year = {2026},
|
| 153 |
+
institution = {Kuaishou Technology},
|
| 154 |
+
url = {https://arxiv.org/abs/2604.24432}
|
| 155 |
+
}
|
| 156 |
+
```
|
assets/figures/mainmodel.png
ADDED
|
Git LFS Details
|
config.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": ["Qwen3ForCausalLM"],
|
| 3 |
+
"auto_map": {
|
| 4 |
+
"AutoModel": "modeling_qwen3.Qwen3Model",
|
| 5 |
+
"AutoModelForCausalLM": "modeling_qwen3.Qwen3ForCausalLM"
|
| 6 |
+
},
|
| 7 |
+
"attention_bias": false,
|
| 8 |
+
"attention_dropout": 0.0,
|
| 9 |
+
"bos_token_id": 151643,
|
| 10 |
+
"eos_token_id": 151643,
|
| 11 |
+
"pad_token_id": 151643,
|
| 12 |
+
"dtype": "bfloat16",
|
| 13 |
+
"head_dim": 128,
|
| 14 |
+
"hidden_act": "silu",
|
| 15 |
+
"hidden_size": 2048,
|
| 16 |
+
"initializer_range": 0.02,
|
| 17 |
+
"intermediate_size": 6144,
|
| 18 |
+
"max_position_embeddings": 131072,
|
| 19 |
+
"max_window_layers": 24,
|
| 20 |
+
"mix_coeff": 0,
|
| 21 |
+
"model_type": "qwen3",
|
| 22 |
+
"num_attention_heads": 16,
|
| 23 |
+
"num_hidden_layers": 24,
|
| 24 |
+
"num_key_value_heads": 16,
|
| 25 |
+
"rms_norm_eps": 1e-6,
|
| 26 |
+
"rope_parameters": {"rope_theta": 1000000, "rope_type": "default"},
|
| 27 |
+
"sliding_window": null,
|
| 28 |
+
"summary_chunk_position_ids_type": "origin",
|
| 29 |
+
"summary_chunk_size": 8,
|
| 30 |
+
"summary_independent_attention_layernorm": false,
|
| 31 |
+
"summary_independent_parameters": true,
|
| 32 |
+
"summary_sliding_chunk_num": [128,128,128,16384,128,128,128,16384,128,128,128,16384,128,128,128,16384,128,128,128,16384,128,128,128,16384],
|
| 33 |
+
"summary_token_begin": 151936,
|
| 34 |
+
"summary_token_num": 1,
|
| 35 |
+
"summary_token_position_ids_type": "last_chunk_slice_right",
|
| 36 |
+
"tie_word_embeddings": false,
|
| 37 |
+
"transformers_version": "5.0.0",
|
| 38 |
+
"truncate_predict_nums": 151936,
|
| 39 |
+
"use_cache": true,
|
| 40 |
+
"use_sliding_window": false,
|
| 41 |
+
"use_summary_attention": true,
|
| 42 |
+
"vocab_size": 151937
|
| 43 |
+
}
|
generate.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
"""Generate text with a local KSA export and its bundled kernels."""
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
import sys
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def parse_args() -> argparse.Namespace:
|
| 11 |
+
parser = argparse.ArgumentParser(description="Interactively generate text with a local model.")
|
| 12 |
+
parser.add_argument("--model", type=Path, default=Path(__file__).resolve().parent)
|
| 13 |
+
parser.add_argument("--prompt", help="Omit for interactive text completion.")
|
| 14 |
+
parser.add_argument("--max-new-tokens", type=int, default=256)
|
| 15 |
+
parser.add_argument("--apply-chat-template", action="store_true")
|
| 16 |
+
return parser.parse_args()
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def main() -> None:
|
| 20 |
+
args = parse_args()
|
| 21 |
+
model_dir = args.model.resolve()
|
| 22 |
+
# Select the export's kernels before importing either runtime or Transformers.
|
| 23 |
+
for directory in (model_dir / "kernel/deps", model_dir / "kernel"):
|
| 24 |
+
sys.path.insert(0, str(directory))
|
| 25 |
+
|
| 26 |
+
import torch
|
| 27 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 28 |
+
|
| 29 |
+
tokenizer = AutoTokenizer.from_pretrained(model_dir, local_files_only=True)
|
| 30 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 31 |
+
model_dir,
|
| 32 |
+
trust_remote_code=True,
|
| 33 |
+
local_files_only=True,
|
| 34 |
+
dtype=torch.bfloat16,
|
| 35 |
+
device_map={"": "cuda:0"},
|
| 36 |
+
attn_implementation="sdpa",
|
| 37 |
+
).eval()
|
| 38 |
+
|
| 39 |
+
def complete(prompt: str) -> str:
|
| 40 |
+
if args.apply_chat_template:
|
| 41 |
+
prompt = tokenizer.apply_chat_template(
|
| 42 |
+
[{"role": "user", "content": prompt}],
|
| 43 |
+
tokenize=False,
|
| 44 |
+
add_generation_prompt=True,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
| 48 |
+
with torch.inference_mode():
|
| 49 |
+
output_ids = model.generate(
|
| 50 |
+
**inputs,
|
| 51 |
+
max_new_tokens=args.max_new_tokens,
|
| 52 |
+
do_sample=False,
|
| 53 |
+
pad_token_id=tokenizer.eos_token_id,
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
new_tokens = output_ids[0, inputs["input_ids"].shape[1] :]
|
| 57 |
+
return tokenizer.decode(new_tokens, skip_special_tokens=True)
|
| 58 |
+
|
| 59 |
+
if args.prompt is not None:
|
| 60 |
+
print(complete(args.prompt))
|
| 61 |
+
return
|
| 62 |
+
print("Enter a prompt, or type 'exit' to stop.")
|
| 63 |
+
while True:
|
| 64 |
+
try:
|
| 65 |
+
prompt = input("Prompt: ").strip()
|
| 66 |
+
except (EOFError, KeyboardInterrupt):
|
| 67 |
+
print()
|
| 68 |
+
break
|
| 69 |
+
if prompt.lower() in {"exit", "quit"}:
|
| 70 |
+
break
|
| 71 |
+
if prompt:
|
| 72 |
+
print(complete(prompt))
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
main()
|
generation_config.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token_id": 151643,
|
| 3 |
+
"eos_token_id": 151643,
|
| 4 |
+
"pad_token_id": 151643,
|
| 5 |
+
"do_sample": false,
|
| 6 |
+
"use_cache": true,
|
| 7 |
+
"transformers_version": "5.0.0"
|
| 8 |
+
}
|
kernel/deps/flash_attn/AUTHORS
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Tri Dao, tri@tridao.me
|
| 2 |
+
Jay Shah
|
| 3 |
+
Ted Zadouri
|
| 4 |
+
Markus Hoehnerbach
|
| 5 |
+
Vijay Thakkar
|
kernel/deps/flash_attn/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
BSD 3-Clause License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2022, the respective contributors, as shown by the AUTHORS file.
|
| 4 |
+
All rights reserved.
|
| 5 |
+
|
| 6 |
+
Redistribution and use in source and binary forms, with or without
|
| 7 |
+
modification, are permitted provided that the following conditions are met:
|
| 8 |
+
|
| 9 |
+
* Redistributions of source code must retain the above copyright notice, this
|
| 10 |
+
list of conditions and the following disclaimer.
|
| 11 |
+
|
| 12 |
+
* Redistributions in binary form must reproduce the above copyright notice,
|
| 13 |
+
this list of conditions and the following disclaimer in the documentation
|
| 14 |
+
and/or other materials provided with the distribution.
|
| 15 |
+
|
| 16 |
+
* Neither the name of the copyright holder nor the names of its
|
| 17 |
+
contributors may be used to endorse or promote products derived from
|
| 18 |
+
this software without specific prior written permission.
|
| 19 |
+
|
| 20 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 21 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 22 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 23 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 24 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 25 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 26 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 27 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 28 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 29 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
kernel/deps/flash_attn/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Bundled FlashAttention CuTe kernels for KSA V2 inference.
|
| 2 |
+
|
| 3 |
+
This package does not require the separate FlashAttention 2 extension.
|
| 4 |
+
"""
|
kernel/deps/flash_attn/cute/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Flash Attention CUTE (CUDA Template Engine) implementation."""
|
| 2 |
+
|
| 3 |
+
__version__ = "0.1.0"
|
| 4 |
+
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
|
| 7 |
+
from .interface import (
|
| 8 |
+
flash_attn_func,
|
| 9 |
+
flash_attn_varlen_func,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
from flash_attn.cute.cute_dsl_utils import cute_compile_patched
|
| 13 |
+
|
| 14 |
+
# Patch cute.compile to optionally dump SASS
|
| 15 |
+
cute.compile = cute_compile_patched
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
__all__ = [
|
| 19 |
+
"flash_attn_func",
|
| 20 |
+
"flash_attn_varlen_func",
|
| 21 |
+
]
|
kernel/deps/flash_attn/cute/ampere_helpers.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
from typing import Type, Callable, Optional
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def get_smem_layout_atom(dtype: Type[cutlass.Numeric], k_dim: int) -> cute.ComposedLayout:
|
| 9 |
+
dtype_byte = cutlass.const_expr(dtype.width // 8)
|
| 10 |
+
bytes_per_row = cutlass.const_expr(k_dim * dtype_byte)
|
| 11 |
+
smem_k_block_size = (
|
| 12 |
+
cutlass.const_expr(
|
| 13 |
+
128
|
| 14 |
+
if bytes_per_row % 128 == 0
|
| 15 |
+
else (64 if bytes_per_row % 64 == 0 else (32 if bytes_per_row % 32 == 0 else 16))
|
| 16 |
+
)
|
| 17 |
+
// dtype_byte
|
| 18 |
+
)
|
| 19 |
+
swizzle_bits = (
|
| 20 |
+
4
|
| 21 |
+
if smem_k_block_size == 128
|
| 22 |
+
else (3 if smem_k_block_size == 64 else (2 if smem_k_block_size == 32 else 1))
|
| 23 |
+
)
|
| 24 |
+
swizzle_base = 2 if dtype_byte == 4 else (3 if dtype_byte == 2 else 4)
|
| 25 |
+
return cute.make_composed_layout(
|
| 26 |
+
cute.make_swizzle(swizzle_bits, swizzle_base, swizzle_base),
|
| 27 |
+
0,
|
| 28 |
+
cute.make_ordered_layout(
|
| 29 |
+
(8 if cutlass.const_expr(k_dim % 32 == 0) else 16, smem_k_block_size), order=(1, 0)
|
| 30 |
+
),
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@cute.jit
|
| 35 |
+
def gemm(
|
| 36 |
+
tiled_mma: cute.TiledMma,
|
| 37 |
+
acc: cute.Tensor,
|
| 38 |
+
tCrA: cute.Tensor,
|
| 39 |
+
tCrB: cute.Tensor,
|
| 40 |
+
tCsA: cute.Tensor,
|
| 41 |
+
tCsB: cute.Tensor,
|
| 42 |
+
smem_thr_copy_A: cute.TiledCopy,
|
| 43 |
+
smem_thr_copy_B: cute.TiledCopy,
|
| 44 |
+
hook_fn: Optional[Callable] = None,
|
| 45 |
+
A_in_regs: cutlass.Constexpr[bool] = False,
|
| 46 |
+
B_in_regs: cutlass.Constexpr[bool] = False,
|
| 47 |
+
swap_AB: cutlass.Constexpr[bool] = False,
|
| 48 |
+
) -> None:
|
| 49 |
+
if cutlass.const_expr(swap_AB):
|
| 50 |
+
gemm(
|
| 51 |
+
tiled_mma,
|
| 52 |
+
acc,
|
| 53 |
+
tCrB,
|
| 54 |
+
tCrA,
|
| 55 |
+
tCsB,
|
| 56 |
+
tCsA,
|
| 57 |
+
smem_thr_copy_B,
|
| 58 |
+
smem_thr_copy_A,
|
| 59 |
+
hook_fn,
|
| 60 |
+
A_in_regs=B_in_regs,
|
| 61 |
+
B_in_regs=A_in_regs,
|
| 62 |
+
swap_AB=False,
|
| 63 |
+
)
|
| 64 |
+
else:
|
| 65 |
+
tCrA_copy_view = smem_thr_copy_A.retile(tCrA)
|
| 66 |
+
tCrB_copy_view = smem_thr_copy_B.retile(tCrB)
|
| 67 |
+
if cutlass.const_expr(not A_in_regs):
|
| 68 |
+
cute.copy(smem_thr_copy_A, tCsA[None, None, 0], tCrA_copy_view[None, None, 0])
|
| 69 |
+
if cutlass.const_expr(not B_in_regs):
|
| 70 |
+
cute.copy(smem_thr_copy_B, tCsB[None, None, 0], tCrB_copy_view[None, None, 0])
|
| 71 |
+
for k in cutlass.range_constexpr(cute.size(tCsA.shape[2])):
|
| 72 |
+
if k < cute.size(tCsA.shape[2]) - 1:
|
| 73 |
+
if cutlass.const_expr(not A_in_regs):
|
| 74 |
+
cute.copy(
|
| 75 |
+
smem_thr_copy_A, tCsA[None, None, k + 1], tCrA_copy_view[None, None, k + 1]
|
| 76 |
+
)
|
| 77 |
+
if cutlass.const_expr(not B_in_regs):
|
| 78 |
+
cute.copy(
|
| 79 |
+
smem_thr_copy_B, tCsB[None, None, k + 1], tCrB_copy_view[None, None, k + 1]
|
| 80 |
+
)
|
| 81 |
+
cute.gemm(tiled_mma, acc, tCrA[None, None, k], tCrB[None, None, k], acc)
|
| 82 |
+
if cutlass.const_expr(k == 0 and hook_fn is not None):
|
| 83 |
+
hook_fn()
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
@cute.jit
|
| 87 |
+
def gemm_rs(
|
| 88 |
+
tiled_mma: cute.TiledMma,
|
| 89 |
+
acc: cute.Tensor,
|
| 90 |
+
tCrA: cute.Tensor,
|
| 91 |
+
tCrB: cute.Tensor,
|
| 92 |
+
tCsB: cute.Tensor,
|
| 93 |
+
smem_thr_copy_B: cute.TiledCopy,
|
| 94 |
+
hook_fn: Optional[Callable] = None,
|
| 95 |
+
) -> None:
|
| 96 |
+
tCrB_copy_view = smem_thr_copy_B.retile(tCrB)
|
| 97 |
+
cute.copy(smem_thr_copy_B, tCsB[None, None, 0], tCrB_copy_view[None, None, 0])
|
| 98 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2])):
|
| 99 |
+
if cutlass.const_expr(k < cute.size(tCrA.shape[2]) - 1):
|
| 100 |
+
cute.copy(smem_thr_copy_B, tCsB[None, None, k + 1], tCrB_copy_view[None, None, k + 1])
|
| 101 |
+
cute.gemm(tiled_mma, acc, tCrA[None, None, k], tCrB[None, None, k], acc)
|
| 102 |
+
if cutlass.const_expr(k == 0 and hook_fn is not None):
|
| 103 |
+
hook_fn()
|
kernel/deps/flash_attn/cute/barrier.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cutlass
|
| 2 |
+
import cutlass.cute as cute
|
| 3 |
+
from cutlass import Int32
|
| 4 |
+
from cutlass.cutlass_dsl import T, dsl_user_op
|
| 5 |
+
from cutlass._mlir.dialects import llvm
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@dsl_user_op
|
| 9 |
+
def ld_acquire(lock_ptr: cute.Pointer, *, loc=None, ip=None) -> cutlass.Int32:
|
| 10 |
+
lock_ptr_i64 = lock_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 11 |
+
state = llvm.inline_asm(
|
| 12 |
+
T.i32(),
|
| 13 |
+
[lock_ptr_i64],
|
| 14 |
+
"ld.global.acquire.gpu.b32 $0, [$1];",
|
| 15 |
+
"=r,l",
|
| 16 |
+
has_side_effects=True,
|
| 17 |
+
is_align_stack=False,
|
| 18 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 19 |
+
)
|
| 20 |
+
return cutlass.Int32(state)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
@dsl_user_op
|
| 24 |
+
def red_relaxed(
|
| 25 |
+
lock_ptr: cute.Pointer, val: cutlass.Constexpr[Int32], *, loc=None, ip=None
|
| 26 |
+
) -> None:
|
| 27 |
+
lock_ptr_i64 = lock_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 28 |
+
llvm.inline_asm(
|
| 29 |
+
None,
|
| 30 |
+
[lock_ptr_i64, Int32(val).ir_value(loc=loc, ip=ip)],
|
| 31 |
+
"red.relaxed.gpu.global.add.s32 [$0], $1;",
|
| 32 |
+
"l,r",
|
| 33 |
+
has_side_effects=True,
|
| 34 |
+
is_align_stack=False,
|
| 35 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
@dsl_user_op
|
| 40 |
+
def red_release(
|
| 41 |
+
lock_ptr: cute.Pointer, val: cutlass.Constexpr[Int32], *, loc=None, ip=None
|
| 42 |
+
) -> None:
|
| 43 |
+
lock_ptr_i64 = lock_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 44 |
+
llvm.inline_asm(
|
| 45 |
+
None,
|
| 46 |
+
[lock_ptr_i64, Int32(val).ir_value(loc=loc, ip=ip)],
|
| 47 |
+
"red.release.gpu.global.add.s32 [$0], $1;",
|
| 48 |
+
"l,r",
|
| 49 |
+
has_side_effects=True,
|
| 50 |
+
is_align_stack=False,
|
| 51 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@cute.jit
|
| 56 |
+
def wait_eq(lock_ptr: cute.Pointer, thread_idx: int | Int32, flag_offset: int, val: Int32) -> None:
|
| 57 |
+
flag_ptr = lock_ptr + flag_offset
|
| 58 |
+
if thread_idx == 0:
|
| 59 |
+
read_val = Int32(0)
|
| 60 |
+
while read_val != val:
|
| 61 |
+
read_val = ld_acquire(flag_ptr)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@cute.jit
|
| 65 |
+
def arrive_inc(
|
| 66 |
+
lock_ptr: cute.Pointer, thread_idx: int | Int32, flag_offset: int, val: cutlass.Constexpr[Int32]
|
| 67 |
+
) -> None:
|
| 68 |
+
flag_ptr = lock_ptr + flag_offset
|
| 69 |
+
if thread_idx == 0:
|
| 70 |
+
red_release(flag_ptr, val)
|
| 71 |
+
# red_relaxed(flag_ptr, val)
|
kernel/deps/flash_attn/cute/blackwell_helpers.py
ADDED
|
@@ -0,0 +1,753 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
from typing import Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
from cutlass import Int32, Boolean, const_expr
|
| 7 |
+
from cutlass.cute.nvgpu import tcgen05
|
| 8 |
+
from cutlass._mlir.dialects import llvm
|
| 9 |
+
|
| 10 |
+
import flash_attn.cute.mma_sm100_desc as sm100_desc
|
| 11 |
+
from flash_attn.cute.utils import parse_swizzle_from_pointer
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@cute.jit
|
| 15 |
+
def gemm_w_idx(
|
| 16 |
+
tiled_mma: cute.TiledMma,
|
| 17 |
+
acc: cute.Tensor,
|
| 18 |
+
tCrA: cute.Tensor,
|
| 19 |
+
tCrB: cute.Tensor,
|
| 20 |
+
A_idx: Optional[Int32] = None,
|
| 21 |
+
B_idx: Optional[Int32] = None,
|
| 22 |
+
zero_init: bool | Boolean = False,
|
| 23 |
+
swap_AB: bool = False,
|
| 24 |
+
) -> None:
|
| 25 |
+
if const_expr(swap_AB):
|
| 26 |
+
return gemm_w_idx(
|
| 27 |
+
tiled_mma, acc, tCrB, tCrA, B_idx, A_idx, zero_init=zero_init, swap_AB=False
|
| 28 |
+
)
|
| 29 |
+
else:
|
| 30 |
+
rA = tCrA if const_expr(A_idx is None) else tCrA[None, None, None, A_idx]
|
| 31 |
+
rB = tCrB if const_expr(B_idx is None) else tCrB[None, None, None, B_idx]
|
| 32 |
+
mma_atom = cute.make_mma_atom(tiled_mma.op)
|
| 33 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2])):
|
| 34 |
+
mma_atom.set(tcgen05.Field.ACCUMULATE, not zero_init or k != 0)
|
| 35 |
+
cute.gemm(mma_atom, acc, rA[None, None, k], rB[None, None, k], acc)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@cute.jit
|
| 39 |
+
def gemm_ptx_w_idx(
|
| 40 |
+
tiled_mma: cute.TiledMma,
|
| 41 |
+
acc: cute.Tensor,
|
| 42 |
+
tCrA: cute.Tensor,
|
| 43 |
+
tCrB: cute.Tensor,
|
| 44 |
+
sA: Optional[cute.Tensor],
|
| 45 |
+
sB: cute.Tensor,
|
| 46 |
+
A_idx: Optional[Int32] = None,
|
| 47 |
+
B_idx: Optional[Int32] = None,
|
| 48 |
+
zero_init: bool | Boolean = False,
|
| 49 |
+
**kwargs,
|
| 50 |
+
) -> None:
|
| 51 |
+
rA = tCrA if const_expr(A_idx is None) else tCrA[None, None, None, A_idx]
|
| 52 |
+
rB = tCrB if const_expr(B_idx is None) else tCrB[None, None, None, B_idx]
|
| 53 |
+
sA_cur = None
|
| 54 |
+
if const_expr(sA is not None):
|
| 55 |
+
sA_cur = sA if const_expr(A_idx is None) else sA[None, None, None, A_idx]
|
| 56 |
+
sB_cur = sB if const_expr(B_idx is None) else sB[None, None, None, B_idx]
|
| 57 |
+
mma_atom = cute.make_mma_atom(tiled_mma.op)
|
| 58 |
+
acc_tmem_addr = acc.iterator.toint()
|
| 59 |
+
gemm_ptx_partial(
|
| 60 |
+
mma_atom.op, acc_tmem_addr, rA, rB, sA_cur, sB_cur, zero_init=zero_init, **kwargs
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@cute.jit
|
| 65 |
+
def gemm(
|
| 66 |
+
tiled_mma: cute.TiledMma,
|
| 67 |
+
acc: cute.Tensor,
|
| 68 |
+
tCrA: cute.Tensor,
|
| 69 |
+
tCrB: cute.Tensor,
|
| 70 |
+
zero_init: bool | Boolean = False,
|
| 71 |
+
) -> cute.TiledMma:
|
| 72 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2])):
|
| 73 |
+
tiled_mma.set(tcgen05.Field.ACCUMULATE, not zero_init or k != 0)
|
| 74 |
+
cute.gemm(tiled_mma, acc, tCrA[None, None, k], tCrB[None, None, k], acc)
|
| 75 |
+
return tiled_mma
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def i64_to_i32x2(i: int) -> Tuple[int, int]:
|
| 79 |
+
"""Convert a 64-bit integer to a tuple of two 32-bit integers."""
|
| 80 |
+
return i & 0xFFFF_FFFF, (i >> 32) & 0xFFFF_FFFF
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
@cute.jit
|
| 84 |
+
def gemm_ptx(
|
| 85 |
+
op: cute.nvgpu.tcgen05.mma.MmaOp,
|
| 86 |
+
acc: cute.Tensor,
|
| 87 |
+
tCrA: cute.Tensor,
|
| 88 |
+
tCrB: cute.Tensor,
|
| 89 |
+
sA: Optional[cute.Tensor],
|
| 90 |
+
sB: cute.Tensor,
|
| 91 |
+
zero_init: bool | Boolean = False,
|
| 92 |
+
) -> None:
|
| 93 |
+
is_ts = op.a_src == cute.nvgpu.tcgen05.OperandSource.TMEM
|
| 94 |
+
if const_expr(not is_ts):
|
| 95 |
+
assert sA is not None, "sA must be provided when a_src is not TMEM"
|
| 96 |
+
sA_layout = sA.layout if sA is not None else None
|
| 97 |
+
sB_layout = sB.layout
|
| 98 |
+
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 99 |
+
if const_expr(not is_ts):
|
| 100 |
+
sA_swizzle = parse_swizzle_from_pointer(sA.iterator)
|
| 101 |
+
smem_desc_base_a: int = const_expr(
|
| 102 |
+
sm100_desc.make_smem_desc_base(
|
| 103 |
+
cute.recast_layout(128, op.a_dtype.width, sA_layout[0]),
|
| 104 |
+
sA_swizzle,
|
| 105 |
+
sm100_desc.Major.K
|
| 106 |
+
if const_expr(op.a_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 107 |
+
else sm100_desc.Major.MN,
|
| 108 |
+
)
|
| 109 |
+
)
|
| 110 |
+
smem_desc_base_a_lo, smem_desc_a_hi = i64_to_i32x2(smem_desc_base_a)
|
| 111 |
+
smem_desc_base_a_lo = const_expr(smem_desc_base_a_lo)
|
| 112 |
+
smem_desc_a_hi = const_expr(smem_desc_a_hi)
|
| 113 |
+
else:
|
| 114 |
+
smem_desc_base_a = None
|
| 115 |
+
smem_desc_base_a_lo, smem_desc_a_hi = None, None
|
| 116 |
+
sB_swizzle = parse_swizzle_from_pointer(sB.iterator)
|
| 117 |
+
smem_desc_base_b: int = const_expr(
|
| 118 |
+
sm100_desc.make_smem_desc_base(
|
| 119 |
+
cute.recast_layout(128, op.b_dtype.width, sB_layout[0]),
|
| 120 |
+
sB_swizzle,
|
| 121 |
+
sm100_desc.Major.K
|
| 122 |
+
if const_expr(op.b_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 123 |
+
else sm100_desc.Major.MN,
|
| 124 |
+
)
|
| 125 |
+
)
|
| 126 |
+
smem_desc_base_b_lo, smem_desc_b_hi = i64_to_i32x2(smem_desc_base_b)
|
| 127 |
+
smem_desc_base_b_lo = const_expr(smem_desc_base_b_lo)
|
| 128 |
+
smem_desc_b_hi = const_expr(smem_desc_b_hi)
|
| 129 |
+
|
| 130 |
+
if const_expr(not is_ts):
|
| 131 |
+
smem_desc_start_a_lo = Int32(smem_desc_base_a_lo) | sm100_desc.make_smem_desc_start_addr(
|
| 132 |
+
sA[None, None, 0].iterator
|
| 133 |
+
)
|
| 134 |
+
else:
|
| 135 |
+
smem_desc_start_a_lo = None
|
| 136 |
+
smem_desc_start_b_lo = Int32(smem_desc_base_b_lo) | sm100_desc.make_smem_desc_start_addr(
|
| 137 |
+
sB[None, None, 0].iterator
|
| 138 |
+
)
|
| 139 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2])):
|
| 140 |
+
if const_expr(not is_ts):
|
| 141 |
+
smem_desc_a_lo = smem_desc_start_a_lo + (
|
| 142 |
+
(cute.crd2idx((0, 0, k), sA_layout) * sA.element_type.width // 8) >> 4
|
| 143 |
+
)
|
| 144 |
+
smem_desc_b_lo = smem_desc_start_b_lo + (
|
| 145 |
+
(cute.crd2idx((0, 0, k), sB_layout) * sB.element_type.width // 8) >> 4
|
| 146 |
+
)
|
| 147 |
+
# with cute.arch.elect_one():
|
| 148 |
+
# cute.printf("smem_desc_a_lo = {}, smem_desc_b_lo = {}", smem_desc_a_lo, smem_desc_b_lo)
|
| 149 |
+
# cute.printf("smem_desc_a_lo_correct = {}, smem_desc_b_lo_correct = {}", smem_desc_a_lo_correct, smem_desc_b_lo_correct)
|
| 150 |
+
with cute.arch.elect_one():
|
| 151 |
+
if const_expr(not is_ts):
|
| 152 |
+
llvm.inline_asm(
|
| 153 |
+
None,
|
| 154 |
+
[
|
| 155 |
+
acc.iterator.toint().ir_value(),
|
| 156 |
+
smem_desc_a_lo.ir_value(),
|
| 157 |
+
smem_desc_b_lo.ir_value(),
|
| 158 |
+
Int32(not zero_init or k != 0).ir_value(),
|
| 159 |
+
],
|
| 160 |
+
"{\n\t"
|
| 161 |
+
".reg .pred p;\n\t"
|
| 162 |
+
".reg .b64 smem_desc_a, smem_desc_b;\n\t"
|
| 163 |
+
".reg .b32 idesc;\n\t"
|
| 164 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 165 |
+
f"mov.b64 smem_desc_a, {{$1, {hex(smem_desc_a_hi)}}};\n\t"
|
| 166 |
+
f"mov.b64 smem_desc_b, {{$2, {hex(smem_desc_b_hi)}}};\n\t"
|
| 167 |
+
"setp.ne.b32 p, $3, 0;\n\t"
|
| 168 |
+
f"tcgen05.mma.cta_group::1.kind::f16 [$0], smem_desc_a, smem_desc_b, idesc, p;\n\t"
|
| 169 |
+
"}\n",
|
| 170 |
+
"r,r,r,r",
|
| 171 |
+
has_side_effects=True,
|
| 172 |
+
is_align_stack=False,
|
| 173 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 174 |
+
)
|
| 175 |
+
else:
|
| 176 |
+
llvm.inline_asm(
|
| 177 |
+
None,
|
| 178 |
+
[
|
| 179 |
+
acc.iterator.toint().ir_value(),
|
| 180 |
+
tCrA[None, None, k].iterator.toint().ir_value(),
|
| 181 |
+
smem_desc_b_lo.ir_value(),
|
| 182 |
+
Int32(not zero_init or k != 0).ir_value(),
|
| 183 |
+
],
|
| 184 |
+
"{\n\t"
|
| 185 |
+
".reg .pred p;\n\t"
|
| 186 |
+
".reg .b64 smem_desc_b;\n\t"
|
| 187 |
+
f"mov.b64 smem_desc_b, {{$2, {hex(smem_desc_b_hi)}}};\n\t"
|
| 188 |
+
"setp.ne.b32 p, $3, 0;\n\t"
|
| 189 |
+
f"tcgen05.mma.cta_group::1.kind::f16 [$0], [$1], smem_desc_b, {hex(idesc)}, p;\n\t"
|
| 190 |
+
"}\n",
|
| 191 |
+
"r,r,r,r",
|
| 192 |
+
has_side_effects=True,
|
| 193 |
+
is_align_stack=False,
|
| 194 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
@cute.jit
|
| 199 |
+
def gemm_ptx_loop(
|
| 200 |
+
op: cute.nvgpu.tcgen05.mma.MmaOp,
|
| 201 |
+
acc: cute.Tensor,
|
| 202 |
+
tCrA: cute.Tensor,
|
| 203 |
+
tCrB: cute.Tensor,
|
| 204 |
+
sA: Optional[cute.Tensor],
|
| 205 |
+
sB: cute.Tensor,
|
| 206 |
+
zero_init: bool | Boolean = False,
|
| 207 |
+
) -> None:
|
| 208 |
+
is_ts = op.a_src == cute.nvgpu.tcgen05.OperandSource.TMEM
|
| 209 |
+
if const_expr(not is_ts):
|
| 210 |
+
assert sA is not None, "sA must be provided when a_src is not TMEM"
|
| 211 |
+
sA_layout = sA.layout if sA is not None else tCrA.layout
|
| 212 |
+
sB_layout = sB.layout
|
| 213 |
+
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 214 |
+
if const_expr(not is_ts):
|
| 215 |
+
sA_swizzle = parse_swizzle_from_pointer(sA.iterator)
|
| 216 |
+
smem_desc_base_a: int = const_expr(
|
| 217 |
+
sm100_desc.make_smem_desc_base(
|
| 218 |
+
cute.recast_layout(128, op.a_dtype.width, sA_layout[0]),
|
| 219 |
+
sA_swizzle,
|
| 220 |
+
sm100_desc.Major.K
|
| 221 |
+
if const_expr(op.a_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 222 |
+
else sm100_desc.Major.MN,
|
| 223 |
+
)
|
| 224 |
+
)
|
| 225 |
+
smem_desc_base_a_lo, smem_desc_a_hi = i64_to_i32x2(smem_desc_base_a)
|
| 226 |
+
smem_desc_base_a_lo = const_expr(smem_desc_base_a_lo)
|
| 227 |
+
smem_desc_a_hi = const_expr(smem_desc_a_hi)
|
| 228 |
+
else:
|
| 229 |
+
smem_desc_base_a = None
|
| 230 |
+
smem_desc_base_a_lo, smem_desc_a_hi = None, None
|
| 231 |
+
sB_swizzle = parse_swizzle_from_pointer(sB.iterator)
|
| 232 |
+
smem_desc_base_b: int = const_expr(
|
| 233 |
+
sm100_desc.make_smem_desc_base(
|
| 234 |
+
cute.recast_layout(128, op.b_dtype.width, sB_layout[0]),
|
| 235 |
+
sB_swizzle,
|
| 236 |
+
sm100_desc.Major.K
|
| 237 |
+
if const_expr(op.b_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 238 |
+
else sm100_desc.Major.MN,
|
| 239 |
+
)
|
| 240 |
+
)
|
| 241 |
+
smem_desc_base_b_lo, smem_desc_b_hi = i64_to_i32x2(smem_desc_base_b)
|
| 242 |
+
smem_desc_base_b_lo = const_expr(smem_desc_base_b_lo)
|
| 243 |
+
smem_desc_b_hi = const_expr(smem_desc_b_hi)
|
| 244 |
+
|
| 245 |
+
if const_expr(not is_ts):
|
| 246 |
+
offset_a = [
|
| 247 |
+
(cute.crd2idx((0, 0, k), sA_layout) * sA.element_type.width // 8) >> 4
|
| 248 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2]))
|
| 249 |
+
]
|
| 250 |
+
else:
|
| 251 |
+
offset_a = [
|
| 252 |
+
cute.crd2idx((0, 0, k), sA_layout) * op.a_dtype.width // 32
|
| 253 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2]))
|
| 254 |
+
]
|
| 255 |
+
offset_a_diff = [
|
| 256 |
+
offset_a[k] - offset_a[k - 1] for k in cutlass.range_constexpr(1, cute.size(tCrA.shape[2]))
|
| 257 |
+
]
|
| 258 |
+
offset_b = [
|
| 259 |
+
(cute.crd2idx((0, 0, k), sB_layout) * sB.element_type.width // 8) >> 4
|
| 260 |
+
for k in cutlass.range_constexpr(cute.size(tCrB.shape[2]))
|
| 261 |
+
]
|
| 262 |
+
offset_b_diff = [
|
| 263 |
+
offset_b[k] - offset_b[k - 1] for k in cutlass.range_constexpr(1, cute.size(tCrB.shape[2]))
|
| 264 |
+
]
|
| 265 |
+
|
| 266 |
+
if const_expr(not is_ts):
|
| 267 |
+
smem_desc_start_a_lo = Int32(
|
| 268 |
+
smem_desc_base_a_lo | sm100_desc.make_smem_desc_start_addr(sA[None, None, 0].iterator)
|
| 269 |
+
)
|
| 270 |
+
else:
|
| 271 |
+
smem_desc_start_a_lo = None
|
| 272 |
+
smem_desc_start_b_lo = Int32(
|
| 273 |
+
smem_desc_base_b_lo | sm100_desc.make_smem_desc_start_addr(sB[None, None, 0].iterator)
|
| 274 |
+
)
|
| 275 |
+
pred_str = "p" if isinstance(zero_init, Boolean) else "0" if zero_init else "1"
|
| 276 |
+
if const_expr(not is_ts):
|
| 277 |
+
llvm.inline_asm(
|
| 278 |
+
None,
|
| 279 |
+
[
|
| 280 |
+
acc.iterator.toint().ir_value(),
|
| 281 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_a_lo)).ir_value(),
|
| 282 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 283 |
+
Int32(not zero_init).ir_value(),
|
| 284 |
+
],
|
| 285 |
+
"{\n\t"
|
| 286 |
+
".reg .pred leader_thread;\n\t"
|
| 287 |
+
".reg .pred p;\n\t"
|
| 288 |
+
".reg .b32 idesc;\n\t"
|
| 289 |
+
".reg .b32 smem_desc_a_lo, smem_desc_b_lo;\n\t"
|
| 290 |
+
".reg .b32 smem_desc_a_hi, smem_desc_b_hi;\n\t"
|
| 291 |
+
".reg .b64 smem_desc_a, smem_desc_b;\n\t"
|
| 292 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 293 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 294 |
+
"mov.b32 smem_desc_a_lo, $1;\n\t"
|
| 295 |
+
"mov.b32 smem_desc_b_lo, $2;\n\t"
|
| 296 |
+
f"mov.b32 smem_desc_a_hi, {hex(smem_desc_a_hi)};\n\t"
|
| 297 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 298 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 299 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 300 |
+
"setp.ne.b32 p, $3, 0;\n\t"
|
| 301 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [$0], smem_desc_a, smem_desc_b, idesc, {pred_str};\n\t"
|
| 302 |
+
+ "".join(
|
| 303 |
+
(
|
| 304 |
+
f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 305 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 306 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 307 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 308 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [$0], smem_desc_a, smem_desc_b, idesc, 1;\n\t"
|
| 309 |
+
)
|
| 310 |
+
for k in cutlass.range_constexpr(1, cute.size(tCrA.shape[2]))
|
| 311 |
+
)
|
| 312 |
+
+ "}\n",
|
| 313 |
+
"r,r,r,r",
|
| 314 |
+
has_side_effects=True,
|
| 315 |
+
is_align_stack=False,
|
| 316 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 317 |
+
)
|
| 318 |
+
else:
|
| 319 |
+
llvm.inline_asm(
|
| 320 |
+
None,
|
| 321 |
+
[
|
| 322 |
+
acc.iterator.toint().ir_value(),
|
| 323 |
+
Int32(tCrA[None, None, 0].iterator.toint()).ir_value(),
|
| 324 |
+
Int32(smem_desc_start_b_lo).ir_value(),
|
| 325 |
+
Int32(not zero_init).ir_value(),
|
| 326 |
+
],
|
| 327 |
+
"{\n\t"
|
| 328 |
+
".reg .pred leader_thread;\n\t"
|
| 329 |
+
".reg .pred p;\n\t"
|
| 330 |
+
".reg .b32 idesc;\n\t"
|
| 331 |
+
".reg .b32 tmem_a;\n\t"
|
| 332 |
+
".reg .b32 smem_desc_b_lo;\n\t"
|
| 333 |
+
".reg .b32 smem_desc_b_hi;\n\t"
|
| 334 |
+
".reg .b64 smem_desc_b;\n\t"
|
| 335 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 336 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 337 |
+
"mov.b32 tmem_a, $1;\n\t"
|
| 338 |
+
"mov.b32 smem_desc_b_lo, $2;\n\t"
|
| 339 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 340 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 341 |
+
"setp.ne.b32 p, $3, 0;\n\t"
|
| 342 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [$0], [tmem_a], smem_desc_b, idesc, {pred_str};\n\t"
|
| 343 |
+
+ "".join(
|
| 344 |
+
(
|
| 345 |
+
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 346 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 347 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 348 |
+
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [$0], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 349 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [$0], [tmem_a + {hex(offset_a[k])}], smem_desc_b, idesc, 1;\n\t"
|
| 350 |
+
)
|
| 351 |
+
for k in cutlass.range_constexpr(1, cute.size(tCrA.shape[2]))
|
| 352 |
+
)
|
| 353 |
+
+ "}\n",
|
| 354 |
+
"r,r,r,r",
|
| 355 |
+
has_side_effects=True,
|
| 356 |
+
is_align_stack=False,
|
| 357 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
@cute.jit
|
| 362 |
+
def gemm_ptx_partial(
|
| 363 |
+
op: cute.nvgpu.tcgen05.mma.MmaOp,
|
| 364 |
+
acc_tmem_addr: Int32,
|
| 365 |
+
tCrA: cute.Tensor,
|
| 366 |
+
tCrB: cute.Tensor,
|
| 367 |
+
sA: Optional[cute.Tensor],
|
| 368 |
+
sB: cute.Tensor,
|
| 369 |
+
mbar_ptr: Optional[cutlass.Pointer] = None,
|
| 370 |
+
mbar_phase: Optional[Int32] = None,
|
| 371 |
+
zero_init: bool | Boolean = False,
|
| 372 |
+
# sA_offset: Int32 = 0,
|
| 373 |
+
# acc_offset: Int32 = 0,
|
| 374 |
+
tA_addr: Optional[Int32] = None,
|
| 375 |
+
) -> None:
|
| 376 |
+
# acc_tmem_addr += acc_offset
|
| 377 |
+
is_ts = op.a_src == cute.nvgpu.tcgen05.OperandSource.TMEM
|
| 378 |
+
if const_expr(not is_ts):
|
| 379 |
+
assert sA is not None, "sA must be provided when a_src is not TMEM"
|
| 380 |
+
sA_layout = sA.layout if sA is not None else tCrA.layout
|
| 381 |
+
sB_layout = sB.layout
|
| 382 |
+
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 383 |
+
if const_expr(not is_ts):
|
| 384 |
+
sA_swizzle = parse_swizzle_from_pointer(sA.iterator)
|
| 385 |
+
smem_desc_base_a: int = const_expr(
|
| 386 |
+
sm100_desc.make_smem_desc_base(
|
| 387 |
+
cute.recast_layout(128, op.a_dtype.width, sA_layout[0]),
|
| 388 |
+
sA_swizzle,
|
| 389 |
+
sm100_desc.Major.K
|
| 390 |
+
if const_expr(op.a_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 391 |
+
else sm100_desc.Major.MN,
|
| 392 |
+
)
|
| 393 |
+
)
|
| 394 |
+
smem_desc_base_a_lo, smem_desc_a_hi = i64_to_i32x2(smem_desc_base_a)
|
| 395 |
+
smem_desc_base_a_lo = const_expr(smem_desc_base_a_lo)
|
| 396 |
+
smem_desc_a_hi = const_expr(smem_desc_a_hi)
|
| 397 |
+
else:
|
| 398 |
+
smem_desc_base_a = None
|
| 399 |
+
smem_desc_base_a_lo, smem_desc_a_hi = None, None
|
| 400 |
+
sB_swizzle = parse_swizzle_from_pointer(sB.iterator)
|
| 401 |
+
smem_desc_base_b: int = const_expr(
|
| 402 |
+
sm100_desc.make_smem_desc_base(
|
| 403 |
+
cute.recast_layout(128, op.b_dtype.width, sB_layout[0]),
|
| 404 |
+
sB_swizzle,
|
| 405 |
+
sm100_desc.Major.K
|
| 406 |
+
if const_expr(op.b_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 407 |
+
else sm100_desc.Major.MN,
|
| 408 |
+
)
|
| 409 |
+
)
|
| 410 |
+
smem_desc_base_b_lo, smem_desc_b_hi = i64_to_i32x2(smem_desc_base_b)
|
| 411 |
+
smem_desc_base_b_lo = const_expr(smem_desc_base_b_lo)
|
| 412 |
+
smem_desc_b_hi = const_expr(smem_desc_b_hi)
|
| 413 |
+
|
| 414 |
+
tCrA_layout = (
|
| 415 |
+
tCrA.layout
|
| 416 |
+
if const_expr(not is_ts)
|
| 417 |
+
else cute.recast_layout(32, tCrA.element_type.width, tCrA.layout)
|
| 418 |
+
)
|
| 419 |
+
offset_a = [cute.crd2idx((0, 0, k), tCrA_layout) for k in range(cute.size(tCrA.shape[2]))]
|
| 420 |
+
offset_a_diff = [offset_a[k] - offset_a[k - 1] for k in range(1, cute.size(tCrA.shape[2]))]
|
| 421 |
+
offset_b = [cute.crd2idx((0, 0, k), tCrB.layout) for k in range(cute.size(tCrB.shape[2]))]
|
| 422 |
+
offset_b_diff = [offset_b[k] - offset_b[k - 1] for k in range(1, cute.size(tCrB.shape[2]))]
|
| 423 |
+
|
| 424 |
+
if const_expr(not is_ts):
|
| 425 |
+
smem_desc_start_a_lo = Int32(
|
| 426 |
+
smem_desc_base_a_lo | sm100_desc.make_smem_desc_start_addr(sA[None, None, 0].iterator)
|
| 427 |
+
)
|
| 428 |
+
# ) + sA_offset
|
| 429 |
+
else:
|
| 430 |
+
smem_desc_start_a_lo = None
|
| 431 |
+
smem_desc_start_b_lo = Int32(
|
| 432 |
+
smem_desc_base_b_lo | sm100_desc.make_smem_desc_start_addr(sB[None, None, 0].iterator)
|
| 433 |
+
)
|
| 434 |
+
pred_str = "p" if isinstance(zero_init, Boolean) else "0" if zero_init else "1"
|
| 435 |
+
if const_expr(not is_ts):
|
| 436 |
+
assert mbar_ptr is None, "mbar_ptr must be None when a_src is not TMEM"
|
| 437 |
+
llvm.inline_asm(
|
| 438 |
+
None,
|
| 439 |
+
[
|
| 440 |
+
# acc.iterator.toint().ir_value(),
|
| 441 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_a_lo)).ir_value(),
|
| 442 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 443 |
+
Int32(not zero_init).ir_value(),
|
| 444 |
+
Int32(cute.arch.make_warp_uniform(acc_tmem_addr)).ir_value(),
|
| 445 |
+
],
|
| 446 |
+
"{\n\t"
|
| 447 |
+
".reg .pred leader_thread;\n\t"
|
| 448 |
+
".reg .pred p;\n\t"
|
| 449 |
+
".reg .b32 idesc;\n\t"
|
| 450 |
+
".reg .b32 tmem_acc;\n\t"
|
| 451 |
+
".reg .b32 smem_desc_a_lo_start, smem_desc_b_lo_start;\n\t"
|
| 452 |
+
".reg .b32 smem_desc_a_lo, smem_desc_b_lo;\n\t"
|
| 453 |
+
".reg .b32 smem_desc_a_hi, smem_desc_b_hi;\n\t"
|
| 454 |
+
".reg .b64 smem_desc_a, smem_desc_b;\n\t"
|
| 455 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 456 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 457 |
+
# f"mov.b32 tmem_acc, {hex(acc_tmem_addr)};\n\t"
|
| 458 |
+
f"mov.b32 tmem_acc, $3;\n\t"
|
| 459 |
+
"mov.b32 smem_desc_a_lo_start, $0;\n\t"
|
| 460 |
+
"mov.b32 smem_desc_b_lo_start, $1;\n\t"
|
| 461 |
+
f"mov.b32 smem_desc_a_hi, {hex(smem_desc_a_hi)};\n\t"
|
| 462 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 463 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo_start, smem_desc_a_hi}};\n\t"
|
| 464 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 465 |
+
"setp.ne.b32 p, $2, 0;\n\t"
|
| 466 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], smem_desc_a, smem_desc_b, idesc, {pred_str};\n\t"
|
| 467 |
+
+ "".join(
|
| 468 |
+
(
|
| 469 |
+
# f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 470 |
+
# f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 471 |
+
f"add.u32 smem_desc_a_lo, smem_desc_a_lo_start, {hex(offset_a[k])};\n\t"
|
| 472 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 473 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 474 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 475 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], smem_desc_a, smem_desc_b, idesc, 1;\n\t"
|
| 476 |
+
)
|
| 477 |
+
for k in range(1, cute.size(tCrA.shape[2]))
|
| 478 |
+
)
|
| 479 |
+
+ "}\n",
|
| 480 |
+
# "r,r,r",
|
| 481 |
+
"r,r,r,r",
|
| 482 |
+
has_side_effects=True,
|
| 483 |
+
is_align_stack=False,
|
| 484 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 485 |
+
)
|
| 486 |
+
else:
|
| 487 |
+
# For TS gemm, somehow tCrA.iterator.toint() returns 0 no matter what, so we need to
|
| 488 |
+
# explicitly pass in the tA_addr for correctness.
|
| 489 |
+
tA_addr = tCrA[None, None, 0].iterator.toint() if tA_addr is None else tA_addr
|
| 490 |
+
input_args = [
|
| 491 |
+
# Int32(cute.arch.make_warp_uniform(tCrA[None, None, 0].iterator.toint())).ir_value(),
|
| 492 |
+
Int32(cute.arch.make_warp_uniform(tA_addr)).ir_value(),
|
| 493 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 494 |
+
Int32(not zero_init).ir_value(),
|
| 495 |
+
Int32(cute.arch.make_warp_uniform(acc_tmem_addr)).ir_value(),
|
| 496 |
+
]
|
| 497 |
+
if const_expr(mbar_ptr is not None):
|
| 498 |
+
assert mbar_phase is not None, "mbar_phase must be provided when mbar_ptr is not None"
|
| 499 |
+
input_args.append(mbar_ptr.toint().ir_value())
|
| 500 |
+
input_args.append(Int32(mbar_phase).ir_value())
|
| 501 |
+
mbar_wait_str = (
|
| 502 |
+
".reg .pred P1; \n\t"
|
| 503 |
+
"LAB_WAIT: \n\t"
|
| 504 |
+
"mbarrier.try_wait.parity.shared::cta.b64 P1, [$4], $5, 10000000; \n\t"
|
| 505 |
+
"@P1 bra DONE; \n\t"
|
| 506 |
+
"bra LAB_WAIT; \n\t"
|
| 507 |
+
"DONE: \n\t"
|
| 508 |
+
)
|
| 509 |
+
else:
|
| 510 |
+
mbar_wait_str = ""
|
| 511 |
+
llvm.inline_asm(
|
| 512 |
+
None,
|
| 513 |
+
# [
|
| 514 |
+
# # acc.iterator.toint().ir_value(),
|
| 515 |
+
# Int32(tCrA[None, None, 0].iterator.toint()).ir_value(),
|
| 516 |
+
# Int32(smem_desc_start_b_lo).ir_value(),
|
| 517 |
+
# Int32(not zero_init).ir_value(),
|
| 518 |
+
# ],
|
| 519 |
+
input_args,
|
| 520 |
+
"{\n\t"
|
| 521 |
+
".reg .pred leader_thread;\n\t"
|
| 522 |
+
".reg .pred p;\n\t"
|
| 523 |
+
".reg .b32 idesc;\n\t"
|
| 524 |
+
".reg .b32 tmem_acc;\n\t"
|
| 525 |
+
".reg .b32 tmem_a;\n\t"
|
| 526 |
+
".reg .b32 smem_desc_b_lo_start;\n\t"
|
| 527 |
+
".reg .b32 smem_desc_b_lo;\n\t"
|
| 528 |
+
".reg .b32 smem_desc_b_hi;\n\t"
|
| 529 |
+
".reg .b64 smem_desc_b;\n\t"
|
| 530 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 531 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 532 |
+
# f"mov.b32 tmem_acc, {hex(acc_tmem_addr)};\n\t"
|
| 533 |
+
f"mov.b32 tmem_acc, $3;\n\t"
|
| 534 |
+
f"mov.b32 tmem_a, $0;\n\t"
|
| 535 |
+
f"mov.b32 smem_desc_b_lo_start, $1;\n\t"
|
| 536 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 537 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 538 |
+
"setp.ne.b32 p, $2, 0;\n\t"
|
| 539 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], [tmem_a], smem_desc_b, idesc, {pred_str};\n\t"
|
| 540 |
+
+ "".join(
|
| 541 |
+
(
|
| 542 |
+
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 543 |
+
# f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 544 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 545 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 546 |
+
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 547 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], [tmem_a + {hex(offset_a[k])}], smem_desc_b, idesc, 1;\n\t"
|
| 548 |
+
)
|
| 549 |
+
for k in range(
|
| 550 |
+
1,
|
| 551 |
+
cute.size(tCrA.shape[2])
|
| 552 |
+
if const_expr(mbar_ptr is None)
|
| 553 |
+
else cute.size(tCrA.shape[2]) // 4 * 3,
|
| 554 |
+
)
|
| 555 |
+
)
|
| 556 |
+
+ mbar_wait_str
|
| 557 |
+
+ (
|
| 558 |
+
"".join(
|
| 559 |
+
(
|
| 560 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 561 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 562 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], [tmem_a + {hex(offset_a[k])}], smem_desc_b, idesc, 1;\n\t"
|
| 563 |
+
)
|
| 564 |
+
for k in range(cute.size(tCrA.shape[2]) // 4 * 3, cute.size(tCrA.shape[2]))
|
| 565 |
+
)
|
| 566 |
+
if const_expr(mbar_ptr is not None)
|
| 567 |
+
else ""
|
| 568 |
+
)
|
| 569 |
+
+ "}\n",
|
| 570 |
+
"r,r,r,r" if const_expr(mbar_ptr is None) else "r,r,r,r,r,r",
|
| 571 |
+
has_side_effects=True,
|
| 572 |
+
is_align_stack=False,
|
| 573 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
@cute.jit
|
| 578 |
+
def gemm_ptx_partial1(
|
| 579 |
+
op: cute.nvgpu.tcgen05.mma.MmaOp,
|
| 580 |
+
acc_tmem_addr: cutlass.Constexpr[int],
|
| 581 |
+
tCrA: cute.Tensor,
|
| 582 |
+
tCrB: cute.Tensor,
|
| 583 |
+
sA_base_addr_for_desc: Int32,
|
| 584 |
+
sA_addr_offset_for_desc: cutlass.Constexpr[int],
|
| 585 |
+
sA_stage: Int32,
|
| 586 |
+
sB_base_addr_for_desc: Int32,
|
| 587 |
+
sB_addr_offset_for_desc: cutlass.Constexpr[int],
|
| 588 |
+
sB_stage: Int32,
|
| 589 |
+
sA_layout: Optional[cute.Layout],
|
| 590 |
+
sB_layout: Optional[cute.Layout],
|
| 591 |
+
sA_swizzle: Optional[cute.Swizzle],
|
| 592 |
+
sB_swizzle: cute.Swizzle,
|
| 593 |
+
zero_init: bool | Boolean = False,
|
| 594 |
+
) -> None:
|
| 595 |
+
is_ts = op.a_src == cute.nvgpu.tcgen05.OperandSource.TMEM
|
| 596 |
+
if const_expr(not is_ts):
|
| 597 |
+
assert sA_layout is not None, "sA_layout must be provided when a_src is not TMEM"
|
| 598 |
+
assert sA_swizzle is not None, "sA_swizzle must be provided when a_src is not TMEM"
|
| 599 |
+
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 600 |
+
if const_expr(not is_ts):
|
| 601 |
+
smem_desc_base_a: int = const_expr(
|
| 602 |
+
sm100_desc.make_smem_desc_base(
|
| 603 |
+
cute.recast_layout(128, op.a_dtype.width, sA_layout[0]),
|
| 604 |
+
sA_swizzle,
|
| 605 |
+
sm100_desc.Major.K
|
| 606 |
+
if const_expr(op.a_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 607 |
+
else sm100_desc.Major.MN,
|
| 608 |
+
)
|
| 609 |
+
)
|
| 610 |
+
smem_desc_base_a_lo, smem_desc_a_hi = i64_to_i32x2(smem_desc_base_a)
|
| 611 |
+
smem_desc_base_a_lo = const_expr(smem_desc_base_a_lo)
|
| 612 |
+
smem_desc_a_hi = const_expr(smem_desc_a_hi)
|
| 613 |
+
else:
|
| 614 |
+
smem_desc_base_a = None
|
| 615 |
+
smem_desc_base_a_lo, smem_desc_a_hi = None, None
|
| 616 |
+
smem_desc_base_b: int = const_expr(
|
| 617 |
+
sm100_desc.make_smem_desc_base(
|
| 618 |
+
cute.recast_layout(128, op.b_dtype.width, sB_layout[0]),
|
| 619 |
+
sB_swizzle,
|
| 620 |
+
sm100_desc.Major.K
|
| 621 |
+
if const_expr(op.b_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 622 |
+
else sm100_desc.Major.MN,
|
| 623 |
+
)
|
| 624 |
+
)
|
| 625 |
+
smem_desc_base_b_lo, smem_desc_b_hi = i64_to_i32x2(smem_desc_base_b)
|
| 626 |
+
smem_desc_base_b_lo = const_expr(smem_desc_base_b_lo)
|
| 627 |
+
smem_desc_b_hi = const_expr(smem_desc_b_hi)
|
| 628 |
+
mask = [Int32(0)] * 4
|
| 629 |
+
|
| 630 |
+
if const_expr(not is_ts):
|
| 631 |
+
offset_a = [
|
| 632 |
+
(cute.crd2idx((0, 0, k), sA_layout) * op.a_dtype.width // 8) >> 4
|
| 633 |
+
for k in range(cute.size(tCrA.shape[2]))
|
| 634 |
+
]
|
| 635 |
+
else:
|
| 636 |
+
offset_a = [
|
| 637 |
+
cute.crd2idx((0, 0, k), sA_layout) * op.a_dtype.width // 32
|
| 638 |
+
for k in range(cute.size(tCrA.shape[2]))
|
| 639 |
+
]
|
| 640 |
+
offset_a_diff = [offset_a[k] - offset_a[k - 1] for k in range(1, cute.size(tCrA.shape[2]))]
|
| 641 |
+
offset_b = [
|
| 642 |
+
(cute.crd2idx((0, 0, k), sB_layout) * op.b_dtype.width // 8) >> 4
|
| 643 |
+
for k in range(cute.size(tCrB.shape[2]))
|
| 644 |
+
]
|
| 645 |
+
offset_b_diff = [offset_b[k] - offset_b[k - 1] for k in range(1, cute.size(tCrB.shape[2]))]
|
| 646 |
+
|
| 647 |
+
if const_expr(not is_ts):
|
| 648 |
+
# smem_desc_start_a_lo = Int32(smem_desc_base_a_lo | sm100_desc.make_smem_desc_start_addr(sA[None, None, 0].iterator))
|
| 649 |
+
smem_desc_start_a_lo = const_expr(smem_desc_base_a_lo)
|
| 650 |
+
else:
|
| 651 |
+
smem_desc_start_a_lo = None
|
| 652 |
+
# smem_desc_start_b_lo = Int32(smem_desc_base_b_lo | sm100_desc.make_smem_desc_start_addr(sB[None, None, 0].iterator))
|
| 653 |
+
smem_desc_start_b_lo = const_expr(smem_desc_base_b_lo)
|
| 654 |
+
pred_str = "p" if isinstance(zero_init, Boolean) else "0" if zero_init else "1"
|
| 655 |
+
if const_expr(not is_ts):
|
| 656 |
+
llvm.inline_asm(
|
| 657 |
+
None,
|
| 658 |
+
[
|
| 659 |
+
# acc.iterator.toint().ir_value(),
|
| 660 |
+
# Int32(cute.arch.make_warp_uniform(smem_desc_start_a_lo)).ir_value(),
|
| 661 |
+
Int32(sA_base_addr_for_desc).ir_value(),
|
| 662 |
+
Int32(sA_stage).ir_value(),
|
| 663 |
+
# Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 664 |
+
Int32(sB_base_addr_for_desc).ir_value(),
|
| 665 |
+
Int32(sB_stage).ir_value(),
|
| 666 |
+
Int32(not zero_init).ir_value(),
|
| 667 |
+
mask[0].ir_value(),
|
| 668 |
+
mask[1].ir_value(),
|
| 669 |
+
mask[2].ir_value(),
|
| 670 |
+
mask[3].ir_value(),
|
| 671 |
+
],
|
| 672 |
+
"{\n\t"
|
| 673 |
+
".reg .pred leader_thread;\n\t"
|
| 674 |
+
".reg .pred p;\n\t"
|
| 675 |
+
".reg .b32 idesc;\n\t"
|
| 676 |
+
".reg .b32 tmem_acc;\n\t"
|
| 677 |
+
".reg .b32 smem_desc_a_lo, smem_desc_b_lo;\n\t"
|
| 678 |
+
".reg .b32 smem_desc_a_hi, smem_desc_b_hi;\n\t"
|
| 679 |
+
".reg .b64 smem_desc_a, smem_desc_b;\n\t"
|
| 680 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 681 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 682 |
+
f"mov.b32 tmem_acc, {hex(acc_tmem_addr)};\n\t"
|
| 683 |
+
# "mov.b32 smem_desc_a_lo, $0;\n\t"
|
| 684 |
+
# f"add.u32 smem_desc_a_lo, $0, {hex(smem_desc_start_a_lo)};\n\t"
|
| 685 |
+
f"mad.lo.u32 smem_desc_a_lo, $1, {hex(sA_addr_offset_for_desc)}, $0;\n\t"
|
| 686 |
+
# "mov.b32 smem_desc_b_lo, $2;\n\t"
|
| 687 |
+
f"mad.lo.u32 smem_desc_b_lo, $3, {hex(sB_addr_offset_for_desc)}, $2;\n\t"
|
| 688 |
+
f"mov.b32 smem_desc_a_hi, {hex(smem_desc_a_hi)};\n\t"
|
| 689 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 690 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 691 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 692 |
+
"setp.ne.b32 p, $4, 0;\n\t"
|
| 693 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], smem_desc_a, smem_desc_b, idesc, {{$5, $6, $7, $8}}, {pred_str};\n\t"
|
| 694 |
+
+ "".join(
|
| 695 |
+
(
|
| 696 |
+
f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 697 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 698 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 699 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 700 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], smem_desc_a, smem_desc_b, idesc, {{$5, $6, $7, $8}}, 1;\n\t"
|
| 701 |
+
)
|
| 702 |
+
for k in range(1, cute.size(tCrA.shape[2]))
|
| 703 |
+
)
|
| 704 |
+
+ "}\n",
|
| 705 |
+
"r,r,r,r,r,r,r,r,r",
|
| 706 |
+
has_side_effects=True,
|
| 707 |
+
is_align_stack=False,
|
| 708 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 709 |
+
)
|
| 710 |
+
else:
|
| 711 |
+
llvm.inline_asm(
|
| 712 |
+
None,
|
| 713 |
+
[
|
| 714 |
+
# acc.iterator.toint().ir_value(),
|
| 715 |
+
Int32(tCrA[None, None, 0].iterator.toint()).ir_value(),
|
| 716 |
+
Int32(smem_desc_start_b_lo).ir_value(),
|
| 717 |
+
Int32(not zero_init).ir_value(),
|
| 718 |
+
mask[0].ir_value(),
|
| 719 |
+
mask[1].ir_value(),
|
| 720 |
+
mask[2].ir_value(),
|
| 721 |
+
mask[3].ir_value(),
|
| 722 |
+
],
|
| 723 |
+
"{\n\t"
|
| 724 |
+
".reg .pred leader_thread;\n\t"
|
| 725 |
+
".reg .pred p;\n\t"
|
| 726 |
+
".reg .b32 idesc;\n\t"
|
| 727 |
+
".reg .b32 tmem_a;\n\t"
|
| 728 |
+
".reg .b32 smem_desc_b_lo;\n\t"
|
| 729 |
+
".reg .b32 smem_desc_b_hi;\n\t"
|
| 730 |
+
".reg .b64 smem_desc_b;\n\t"
|
| 731 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 732 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 733 |
+
f"mov.b32 tmem_a, $1;\n\t"
|
| 734 |
+
f"mov.b32 smem_desc_b_lo, $2;\n\t"
|
| 735 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 736 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 737 |
+
"setp.ne.b32 p, $3, 0;\n\t"
|
| 738 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [$0], [tmem_a], smem_desc_b, idesc, {{$4, $5, $6, $7}}, {pred_str};\n\t"
|
| 739 |
+
+ "".join(
|
| 740 |
+
(
|
| 741 |
+
f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 742 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 743 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 744 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [$0], [tmem_a], smem_desc_b, idesc, {{$4, $5, $6, $7}}, 1;\n\t"
|
| 745 |
+
)
|
| 746 |
+
for k in range(1, cute.size(tCrA.shape[2]))
|
| 747 |
+
)
|
| 748 |
+
+ "}\n",
|
| 749 |
+
"r,r,r,r,r,r,r,r",
|
| 750 |
+
has_side_effects=True,
|
| 751 |
+
is_align_stack=False,
|
| 752 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 753 |
+
)
|
kernel/deps/flash_attn/cute/block_info.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
from typing import Tuple, Optional
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
import cutlass
|
| 6 |
+
import cutlass.cute as cute
|
| 7 |
+
from cutlass import Int32, const_expr
|
| 8 |
+
|
| 9 |
+
from flash_attn.cute.seqlen_info import SeqlenInfoQK
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass(frozen=True)
|
| 13 |
+
class BlockInfo:
|
| 14 |
+
tile_m: cutlass.Constexpr[int]
|
| 15 |
+
tile_n: cutlass.Constexpr[int]
|
| 16 |
+
is_causal: cutlass.Constexpr[bool]
|
| 17 |
+
is_local: cutlass.Constexpr[bool] = False
|
| 18 |
+
is_split_kv: cutlass.Constexpr[bool] = False
|
| 19 |
+
window_size_left: Optional[Int32] = None
|
| 20 |
+
window_size_right: Optional[Int32] = None
|
| 21 |
+
qhead_per_kvhead_packgqa: cutlass.Constexpr[int] = 1
|
| 22 |
+
|
| 23 |
+
@cute.jit
|
| 24 |
+
def get_n_block_min_max(
|
| 25 |
+
self,
|
| 26 |
+
seqlen_info: SeqlenInfoQK,
|
| 27 |
+
m_block: Int32,
|
| 28 |
+
split_idx: cutlass.Int32 = 0,
|
| 29 |
+
num_splits: cutlass.Int32 = 1,
|
| 30 |
+
) -> Tuple[Int32, Int32]:
|
| 31 |
+
n_block_max = cute.ceil_div(seqlen_info.seqlen_k, self.tile_n)
|
| 32 |
+
if const_expr(self.is_causal or (self.is_local and self.window_size_right is not None)):
|
| 33 |
+
m_idx_max = (m_block + 1) * self.tile_m
|
| 34 |
+
if const_expr(self.qhead_per_kvhead_packgqa > 1):
|
| 35 |
+
m_idx_max = cute.ceil_div(m_idx_max, self.qhead_per_kvhead_packgqa)
|
| 36 |
+
n_idx = m_idx_max + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 37 |
+
n_idx_right = n_idx if const_expr(self.is_causal) else n_idx + self.window_size_right
|
| 38 |
+
n_block_max = min(n_block_max, cute.ceil_div(n_idx_right, self.tile_n))
|
| 39 |
+
n_block_min = 0
|
| 40 |
+
if const_expr(self.is_local and self.window_size_left is not None):
|
| 41 |
+
m_idx_min = m_block * self.tile_m
|
| 42 |
+
if const_expr(self.qhead_per_kvhead_packgqa > 1):
|
| 43 |
+
m_idx_min = m_idx_min // self.qhead_per_kvhead_packgqa
|
| 44 |
+
n_idx = m_idx_min + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 45 |
+
n_idx_left = n_idx - self.window_size_left
|
| 46 |
+
n_block_min = cutlass.max(n_idx_left // self.tile_n, 0)
|
| 47 |
+
if cutlass.const_expr(self.is_split_kv):
|
| 48 |
+
num_n_blocks_per_split = (
|
| 49 |
+
cutlass.Int32(0)
|
| 50 |
+
if n_block_max <= n_block_min
|
| 51 |
+
else (n_block_max - n_block_min + num_splits - 1) // num_splits
|
| 52 |
+
)
|
| 53 |
+
n_block_min = n_block_min + split_idx * num_n_blocks_per_split
|
| 54 |
+
n_block_max = cutlass.min(n_block_min + num_n_blocks_per_split, n_block_max)
|
| 55 |
+
return n_block_min, n_block_max
|
| 56 |
+
|
| 57 |
+
@cute.jit
|
| 58 |
+
def get_m_block_min_max(self, seqlen_info: SeqlenInfoQK, n_block: Int32) -> Tuple[Int32, Int32]:
|
| 59 |
+
m_block_max = cute.ceil_div(seqlen_info.seqlen_q, self.tile_m)
|
| 60 |
+
m_block_min = 0
|
| 61 |
+
if const_expr(self.is_causal or (self.is_local and self.window_size_right is not None)):
|
| 62 |
+
n_idx_min = n_block * self.tile_n
|
| 63 |
+
m_idx = n_idx_min + seqlen_info.seqlen_q - seqlen_info.seqlen_k
|
| 64 |
+
m_idx_right = m_idx if const_expr(self.is_causal) else m_idx - self.window_size_right
|
| 65 |
+
m_block_min = max(m_block_min, m_idx_right // self.tile_m)
|
| 66 |
+
if const_expr(self.is_local and self.window_size_left is not None):
|
| 67 |
+
n_idx_max = (n_block + 1) * self.tile_n
|
| 68 |
+
m_idx = n_idx_max + seqlen_info.seqlen_q - seqlen_info.seqlen_k
|
| 69 |
+
m_idx_left = m_idx + self.window_size_left
|
| 70 |
+
m_block_max = min(m_block_max, cute.ceil_div(m_idx_left, self.tile_m))
|
| 71 |
+
return m_block_min, m_block_max
|
| 72 |
+
|
| 73 |
+
@cute.jit
|
| 74 |
+
def get_n_block_min_causal_local_mask(
|
| 75 |
+
self,
|
| 76 |
+
seqlen_info: SeqlenInfoQK,
|
| 77 |
+
m_block: Int32,
|
| 78 |
+
n_block_min: Int32,
|
| 79 |
+
) -> Int32:
|
| 80 |
+
"""If we have separate iterations with causal or local masking at the start, where do we stop"""
|
| 81 |
+
m_idx_min = m_block * self.tile_m
|
| 82 |
+
if const_expr(self.qhead_per_kvhead_packgqa > 1):
|
| 83 |
+
m_idx_min = m_idx_min // self.qhead_per_kvhead_packgqa
|
| 84 |
+
n_idx = m_idx_min + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 85 |
+
n_idx_right = (
|
| 86 |
+
n_idx
|
| 87 |
+
if const_expr(not self.is_local or self.window_size_right is None)
|
| 88 |
+
else n_idx + self.window_size_right
|
| 89 |
+
)
|
| 90 |
+
return cutlass.max(n_block_min, n_idx_right // self.tile_n)
|
| 91 |
+
|
| 92 |
+
@cute.jit
|
| 93 |
+
def get_n_block_min_before_local_mask(
|
| 94 |
+
self,
|
| 95 |
+
seqlen_info: SeqlenInfoQK,
|
| 96 |
+
m_block: Int32,
|
| 97 |
+
n_block_min: Int32,
|
| 98 |
+
) -> Int32:
|
| 99 |
+
"""If we have separate iterations with local masking at the end, where do we stop the non-masked iterations"""
|
| 100 |
+
if const_expr(not self.is_local or self.window_size_left is None):
|
| 101 |
+
return n_block_min
|
| 102 |
+
else:
|
| 103 |
+
m_idx_max = (m_block + 1) * self.tile_m
|
| 104 |
+
if const_expr(self.qhead_per_kvhead_packgqa > 1):
|
| 105 |
+
m_idx_max = cute.ceil_div(m_idx_max, self.qhead_per_kvhead_packgqa)
|
| 106 |
+
n_idx = m_idx_max + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 107 |
+
n_idx_left = n_idx - self.window_size_left
|
| 108 |
+
return cutlass.max(n_block_min, cute.ceil_div(n_idx_left, self.tile_n))
|
kernel/deps/flash_attn/cute/block_sparse_utils.py
ADDED
|
@@ -0,0 +1,1448 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Block-sparse runtime utilities for CUTE DSL kernels.
|
| 3 |
+
|
| 4 |
+
This module contains runtime execution functions for block-sparse attention kernels.
|
| 5 |
+
These utilities are used by CUTE DSL kernels to produce and consume block-sparse loads.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from typing import Callable, Optional
|
| 9 |
+
from functools import partial
|
| 10 |
+
import math
|
| 11 |
+
import cutlass
|
| 12 |
+
import cutlass.cute as cute
|
| 13 |
+
from cutlass import Float32, Int32, const_expr
|
| 14 |
+
|
| 15 |
+
# Import data structures from block_sparsity
|
| 16 |
+
from flash_attn.cute.block_sparsity import BlockSparseTensors
|
| 17 |
+
from flash_attn.cute import utils
|
| 18 |
+
from flash_attn.cute import copy_utils
|
| 19 |
+
from flash_attn.cute.named_barrier import NamedBarrierBwd
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@cute.jit
|
| 23 |
+
def load_block_list(
|
| 24 |
+
block_indices: cute.Tensor,
|
| 25 |
+
block_count,
|
| 26 |
+
load_q_with_first: cutlass.Constexpr,
|
| 27 |
+
first_block_preloaded: cutlass.Constexpr,
|
| 28 |
+
kv_producer_state,
|
| 29 |
+
load_Q,
|
| 30 |
+
load_K,
|
| 31 |
+
load_V,
|
| 32 |
+
pipeline_k,
|
| 33 |
+
pipeline_v,
|
| 34 |
+
use_tma_q: cutlass.Constexpr,
|
| 35 |
+
tma_q_bytes: cutlass.Constexpr,
|
| 36 |
+
intra_wg_overlap: cutlass.Constexpr,
|
| 37 |
+
):
|
| 38 |
+
"""Iterate over the sparse blocks and load K, V (and Q) into the pipeline.
|
| 39 |
+
for the intra_wg_overlap case, we overlap the loads of K and V. And this
|
| 40 |
+
means we need to pipeline the last V load from the partial block case,
|
| 41 |
+
with the loads for the full blocks. Set first_block_preloaded when the
|
| 42 |
+
caller has already issued the first K load for the list.
|
| 43 |
+
|
| 44 |
+
Note:
|
| 45 |
+
we iterate along the block_n indices in reverse.
|
| 46 |
+
|
| 47 |
+
Returns:
|
| 48 |
+
Updated kv_producer_state after processing the block list.
|
| 49 |
+
|
| 50 |
+
"""
|
| 51 |
+
if block_count > 0:
|
| 52 |
+
if const_expr(not intra_wg_overlap):
|
| 53 |
+
# Peel first iteration: the first block may need to load Q alongside K,
|
| 54 |
+
# Parameters are already Constexpr, so no need to wrap in const_expr()
|
| 55 |
+
n_block_first = block_indices[block_count - 1]
|
| 56 |
+
extra_tx = tma_q_bytes if const_expr(load_q_with_first) and const_expr(use_tma_q) else 0
|
| 57 |
+
pipeline_k.producer_acquire(kv_producer_state, extra_tx_count=extra_tx)
|
| 58 |
+
|
| 59 |
+
if const_expr(load_q_with_first and use_tma_q):
|
| 60 |
+
load_Q(tma_bar_ptr=pipeline_k.producer_get_barrier(kv_producer_state))
|
| 61 |
+
|
| 62 |
+
load_K(src_idx=n_block_first, producer_state=kv_producer_state)
|
| 63 |
+
pipeline_v.producer_acquire(kv_producer_state)
|
| 64 |
+
load_V(src_idx=n_block_first, producer_state=kv_producer_state)
|
| 65 |
+
kv_producer_state.advance()
|
| 66 |
+
|
| 67 |
+
for offset in cutlass.range(1, block_count):
|
| 68 |
+
n_block = block_indices[block_count - 1 - offset]
|
| 69 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
| 70 |
+
load_K(src_idx=n_block, producer_state=kv_producer_state)
|
| 71 |
+
pipeline_v.producer_acquire(kv_producer_state)
|
| 72 |
+
load_V(src_idx=n_block, producer_state=kv_producer_state)
|
| 73 |
+
kv_producer_state.advance()
|
| 74 |
+
else:
|
| 75 |
+
n_block_first = block_indices[block_count - 1]
|
| 76 |
+
if const_expr(not first_block_preloaded):
|
| 77 |
+
extra_tx = (
|
| 78 |
+
tma_q_bytes if const_expr(load_q_with_first) and const_expr(use_tma_q) else 0
|
| 79 |
+
)
|
| 80 |
+
pipeline_k.producer_acquire(kv_producer_state, extra_tx_count=extra_tx)
|
| 81 |
+
|
| 82 |
+
if const_expr(load_q_with_first and use_tma_q):
|
| 83 |
+
load_Q(tma_bar_ptr=pipeline_k.producer_get_barrier(kv_producer_state))
|
| 84 |
+
|
| 85 |
+
load_K(src_idx=n_block_first, producer_state=kv_producer_state)
|
| 86 |
+
|
| 87 |
+
for idx in cutlass.range(block_count - 1, unroll=1):
|
| 88 |
+
n_block_prev = block_indices[block_count - 1 - idx]
|
| 89 |
+
n_block = block_indices[block_count - 2 - idx]
|
| 90 |
+
kv_producer_state_prev = kv_producer_state.clone()
|
| 91 |
+
kv_producer_state.advance()
|
| 92 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
| 93 |
+
load_K(src_idx=n_block, producer_state=kv_producer_state)
|
| 94 |
+
pipeline_v.producer_acquire(kv_producer_state_prev)
|
| 95 |
+
load_V(src_idx=n_block_prev, producer_state=kv_producer_state_prev)
|
| 96 |
+
|
| 97 |
+
return kv_producer_state
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
@cute.jit
|
| 101 |
+
def finish_overlap_v_load(
|
| 102 |
+
block_indices: cute.Tensor,
|
| 103 |
+
block_count,
|
| 104 |
+
load_V,
|
| 105 |
+
pipeline_v,
|
| 106 |
+
kv_producer_state,
|
| 107 |
+
):
|
| 108 |
+
"""Load the final V block after overlapped K/V loads."""
|
| 109 |
+
if block_count > 0:
|
| 110 |
+
n_block_last = block_indices[0]
|
| 111 |
+
pipeline_v.producer_acquire(kv_producer_state)
|
| 112 |
+
load_V(src_idx=n_block_last, producer_state=kv_producer_state)
|
| 113 |
+
kv_producer_state.advance()
|
| 114 |
+
|
| 115 |
+
return kv_producer_state
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
@cute.jit
|
| 119 |
+
def sparse_tensor_m_block(
|
| 120 |
+
m_block,
|
| 121 |
+
qhead_per_kvhead: cutlass.Constexpr[int],
|
| 122 |
+
q_subtile_factor: cutlass.Constexpr[int],
|
| 123 |
+
):
|
| 124 |
+
"""Map packed m_block indices to block-sparse tensor indices."""
|
| 125 |
+
block = m_block
|
| 126 |
+
if const_expr(qhead_per_kvhead != 1):
|
| 127 |
+
block = block // qhead_per_kvhead
|
| 128 |
+
if const_expr(q_subtile_factor != 1):
|
| 129 |
+
block = block // q_subtile_factor
|
| 130 |
+
return block
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
@cute.jit
|
| 134 |
+
def produce_block_sparse_loads(
|
| 135 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 136 |
+
batch_idx,
|
| 137 |
+
head_idx,
|
| 138 |
+
m_block,
|
| 139 |
+
kv_producer_state,
|
| 140 |
+
load_Q,
|
| 141 |
+
load_K,
|
| 142 |
+
load_V,
|
| 143 |
+
pipeline_k,
|
| 144 |
+
pipeline_v,
|
| 145 |
+
use_tma_q: cutlass.Constexpr,
|
| 146 |
+
tma_q_bytes: cutlass.Constexpr,
|
| 147 |
+
intra_wg_overlap: cutlass.Constexpr,
|
| 148 |
+
qhead_per_kvhead: cutlass.Constexpr[int] = 1,
|
| 149 |
+
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 150 |
+
):
|
| 151 |
+
"""Iterate over the mask and full block lists for a single tile.
|
| 152 |
+
|
| 153 |
+
The masked (partial) list may leave the last V load pending when intra-warp-group
|
| 154 |
+
overlap is enabled. The first full block must consume that pending V while
|
| 155 |
+
issuing its own K load on the next pipeline stage.
|
| 156 |
+
|
| 157 |
+
In the intra-wg-overlap path, the last masked block leaves its V copy in flight
|
| 158 |
+
while we advance the producer state to start the next full K. Either the full list
|
| 159 |
+
overlaps that pending V load, or, if no full blocks exist, we explicitly drain it.
|
| 160 |
+
|
| 161 |
+
Args:
|
| 162 |
+
qhead_per_kvhead: Pack-GQA factor. When > 1, m_block is in packed space and
|
| 163 |
+
must be converted to unpacked for sparse tensor indexing.
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx = blocksparse_tensors
|
| 167 |
+
|
| 168 |
+
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 169 |
+
|
| 170 |
+
curr_mask_block_cnt = mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 171 |
+
curr_mask_block_idx = mask_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 172 |
+
|
| 173 |
+
if const_expr(full_block_cnt is not None):
|
| 174 |
+
curr_full_block_cnt = full_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 175 |
+
curr_full_block_idx = full_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 176 |
+
else:
|
| 177 |
+
curr_full_block_cnt = Int32(0)
|
| 178 |
+
curr_full_block_idx = None
|
| 179 |
+
|
| 180 |
+
mask_empty = curr_mask_block_cnt == 0
|
| 181 |
+
full_empty = curr_full_block_cnt == 0
|
| 182 |
+
|
| 183 |
+
if mask_empty:
|
| 184 |
+
# No masked blocks: the full list owns the initial Q+K load.
|
| 185 |
+
kv_producer_state = load_block_list(
|
| 186 |
+
curr_full_block_idx,
|
| 187 |
+
curr_full_block_cnt,
|
| 188 |
+
load_q_with_first=True,
|
| 189 |
+
first_block_preloaded=False,
|
| 190 |
+
kv_producer_state=kv_producer_state,
|
| 191 |
+
load_Q=load_Q,
|
| 192 |
+
load_K=load_K,
|
| 193 |
+
load_V=load_V,
|
| 194 |
+
pipeline_k=pipeline_k,
|
| 195 |
+
pipeline_v=pipeline_v,
|
| 196 |
+
use_tma_q=use_tma_q,
|
| 197 |
+
tma_q_bytes=tma_q_bytes,
|
| 198 |
+
intra_wg_overlap=intra_wg_overlap,
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
if const_expr(intra_wg_overlap) and curr_full_block_cnt > 0:
|
| 202 |
+
kv_producer_state = finish_overlap_v_load(
|
| 203 |
+
curr_full_block_idx,
|
| 204 |
+
curr_full_block_cnt,
|
| 205 |
+
load_V,
|
| 206 |
+
pipeline_v,
|
| 207 |
+
kv_producer_state,
|
| 208 |
+
)
|
| 209 |
+
else:
|
| 210 |
+
# Masked blocks present: load Q together with the first masked K so consumers can
|
| 211 |
+
# start immediately. When overlap is disabled this fully drains the list.
|
| 212 |
+
kv_producer_state = load_block_list(
|
| 213 |
+
curr_mask_block_idx,
|
| 214 |
+
curr_mask_block_cnt,
|
| 215 |
+
load_q_with_first=True,
|
| 216 |
+
first_block_preloaded=False,
|
| 217 |
+
kv_producer_state=kv_producer_state,
|
| 218 |
+
load_Q=load_Q,
|
| 219 |
+
load_K=load_K,
|
| 220 |
+
load_V=load_V,
|
| 221 |
+
pipeline_k=pipeline_k,
|
| 222 |
+
pipeline_v=pipeline_v,
|
| 223 |
+
use_tma_q=use_tma_q,
|
| 224 |
+
tma_q_bytes=tma_q_bytes,
|
| 225 |
+
intra_wg_overlap=intra_wg_overlap,
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
if full_empty:
|
| 229 |
+
if const_expr(intra_wg_overlap):
|
| 230 |
+
kv_producer_state = finish_overlap_v_load(
|
| 231 |
+
curr_mask_block_idx,
|
| 232 |
+
curr_mask_block_cnt,
|
| 233 |
+
load_V,
|
| 234 |
+
pipeline_v,
|
| 235 |
+
kv_producer_state,
|
| 236 |
+
)
|
| 237 |
+
else:
|
| 238 |
+
if const_expr(intra_wg_overlap):
|
| 239 |
+
# Bridge the masked list to the full list by overlapping the pending masked V
|
| 240 |
+
# with the first full K load.
|
| 241 |
+
n_block_mask_last = curr_mask_block_idx[0]
|
| 242 |
+
n_block_full_first = curr_full_block_idx[curr_full_block_cnt - 1]
|
| 243 |
+
kv_producer_state_prev = kv_producer_state.clone()
|
| 244 |
+
kv_producer_state.advance()
|
| 245 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
| 246 |
+
load_K(src_idx=n_block_full_first, producer_state=kv_producer_state)
|
| 247 |
+
pipeline_v.producer_acquire(kv_producer_state_prev)
|
| 248 |
+
load_V(src_idx=n_block_mask_last, producer_state=kv_producer_state_prev)
|
| 249 |
+
|
| 250 |
+
kv_producer_state = load_block_list(
|
| 251 |
+
curr_full_block_idx,
|
| 252 |
+
curr_full_block_cnt,
|
| 253 |
+
load_q_with_first=False,
|
| 254 |
+
first_block_preloaded=True,
|
| 255 |
+
kv_producer_state=kv_producer_state,
|
| 256 |
+
load_Q=load_Q,
|
| 257 |
+
load_K=load_K,
|
| 258 |
+
load_V=load_V,
|
| 259 |
+
pipeline_k=pipeline_k,
|
| 260 |
+
pipeline_v=pipeline_v,
|
| 261 |
+
use_tma_q=use_tma_q,
|
| 262 |
+
tma_q_bytes=tma_q_bytes,
|
| 263 |
+
intra_wg_overlap=intra_wg_overlap,
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
kv_producer_state = finish_overlap_v_load(
|
| 267 |
+
curr_full_block_idx,
|
| 268 |
+
curr_full_block_cnt,
|
| 269 |
+
load_V,
|
| 270 |
+
pipeline_v,
|
| 271 |
+
kv_producer_state,
|
| 272 |
+
)
|
| 273 |
+
else:
|
| 274 |
+
# Non-overlap path with both lists: run the full list normally (skipping the Q
|
| 275 |
+
# reload because the masked list already issued it).
|
| 276 |
+
kv_producer_state = load_block_list(
|
| 277 |
+
curr_full_block_idx,
|
| 278 |
+
curr_full_block_cnt,
|
| 279 |
+
load_q_with_first=False,
|
| 280 |
+
first_block_preloaded=False,
|
| 281 |
+
kv_producer_state=kv_producer_state,
|
| 282 |
+
load_Q=load_Q,
|
| 283 |
+
load_K=load_K,
|
| 284 |
+
load_V=load_V,
|
| 285 |
+
pipeline_k=pipeline_k,
|
| 286 |
+
pipeline_v=pipeline_v,
|
| 287 |
+
use_tma_q=use_tma_q,
|
| 288 |
+
tma_q_bytes=tma_q_bytes,
|
| 289 |
+
intra_wg_overlap=intra_wg_overlap,
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
return kv_producer_state
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
@cute.jit
|
| 296 |
+
def consume_block_sparse_loads(
|
| 297 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 298 |
+
batch_idx,
|
| 299 |
+
head_idx,
|
| 300 |
+
m_block,
|
| 301 |
+
seqlen,
|
| 302 |
+
kv_consumer_state,
|
| 303 |
+
mma_pv_fn,
|
| 304 |
+
mma_one_n_block,
|
| 305 |
+
process_first_half_block,
|
| 306 |
+
process_last_half_block,
|
| 307 |
+
mask_fn,
|
| 308 |
+
score_mod_fn,
|
| 309 |
+
O_should_accumulate,
|
| 310 |
+
mask_mod,
|
| 311 |
+
fastdiv_mods,
|
| 312 |
+
intra_wg_overlap: cutlass.Constexpr,
|
| 313 |
+
warp_scheduler_barrier_sync: Callable,
|
| 314 |
+
warp_scheduler_barrier_arrive: Callable,
|
| 315 |
+
qhead_per_kvhead: cutlass.Constexpr[int] = 1,
|
| 316 |
+
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 317 |
+
):
|
| 318 |
+
"""Consume the mask and full block lists for a single tile on the consumer side.
|
| 319 |
+
|
| 320 |
+
Mirrors `produce_block_sparse_loads` so that the consumer pipeline uses
|
| 321 |
+
the same sparse tensor indexing.
|
| 322 |
+
|
| 323 |
+
Args:
|
| 324 |
+
qhead_per_kvhead: Pack-GQA factor. When > 1, m_block is in packed space and
|
| 325 |
+
must be converted to unpacked for sparse tensor indexing.
|
| 326 |
+
"""
|
| 327 |
+
|
| 328 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx = blocksparse_tensors
|
| 329 |
+
|
| 330 |
+
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 331 |
+
|
| 332 |
+
curr_mask_block_cnt = mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 333 |
+
curr_mask_block_idx = mask_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 334 |
+
curr_full_block_cnt = full_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 335 |
+
curr_full_block_idx = full_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 336 |
+
|
| 337 |
+
processed_any = curr_mask_block_cnt + curr_full_block_cnt > 0
|
| 338 |
+
|
| 339 |
+
if const_expr(not intra_wg_overlap):
|
| 340 |
+
if curr_mask_block_cnt > 0:
|
| 341 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1]
|
| 342 |
+
warp_scheduler_barrier_sync()
|
| 343 |
+
kv_consumer_state = mma_one_n_block(
|
| 344 |
+
kv_consumer_state,
|
| 345 |
+
n_block=mask_n_block,
|
| 346 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 347 |
+
mask_fn=partial(
|
| 348 |
+
mask_fn,
|
| 349 |
+
mask_mod=mask_mod,
|
| 350 |
+
mask_seqlen=True,
|
| 351 |
+
fastdiv_mods=fastdiv_mods if cutlass.const_expr(mask_mod is not None) else None,
|
| 352 |
+
),
|
| 353 |
+
is_first_n_block=True,
|
| 354 |
+
)
|
| 355 |
+
O_should_accumulate = True
|
| 356 |
+
for i in cutlass.range(1, curr_mask_block_cnt):
|
| 357 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1 - i]
|
| 358 |
+
kv_consumer_state = mma_one_n_block(
|
| 359 |
+
kv_consumer_state,
|
| 360 |
+
n_block=mask_n_block,
|
| 361 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 362 |
+
mask_fn=partial(mask_fn, mask_mod=mask_mod, mask_seqlen=False),
|
| 363 |
+
is_first_n_block=False,
|
| 364 |
+
)
|
| 365 |
+
O_should_accumulate = True
|
| 366 |
+
if curr_full_block_cnt == 0:
|
| 367 |
+
warp_scheduler_barrier_arrive()
|
| 368 |
+
|
| 369 |
+
if curr_full_block_cnt > 0:
|
| 370 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1]
|
| 371 |
+
if curr_mask_block_cnt == 0:
|
| 372 |
+
warp_scheduler_barrier_sync()
|
| 373 |
+
kv_consumer_state = mma_one_n_block(
|
| 374 |
+
kv_consumer_state,
|
| 375 |
+
n_block=full_n_block,
|
| 376 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 377 |
+
mask_fn=partial(mask_fn, mask_seqlen=True),
|
| 378 |
+
is_first_n_block=True,
|
| 379 |
+
)
|
| 380 |
+
O_should_accumulate = True
|
| 381 |
+
for i in cutlass.range(1, curr_full_block_cnt):
|
| 382 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1 - i]
|
| 383 |
+
kv_consumer_state = mma_one_n_block(
|
| 384 |
+
kv_consumer_state,
|
| 385 |
+
n_block=full_n_block,
|
| 386 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 387 |
+
mask_fn=partial(mask_fn, mask_seqlen=False),
|
| 388 |
+
is_first_n_block=False,
|
| 389 |
+
)
|
| 390 |
+
O_should_accumulate = True
|
| 391 |
+
else:
|
| 392 |
+
kv_consumer_state = mma_one_n_block(
|
| 393 |
+
kv_consumer_state,
|
| 394 |
+
n_block=full_n_block,
|
| 395 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 396 |
+
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=True),
|
| 397 |
+
is_first_n_block=False,
|
| 398 |
+
)
|
| 399 |
+
O_should_accumulate = True
|
| 400 |
+
for i in cutlass.range(1, curr_full_block_cnt):
|
| 401 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1 - i]
|
| 402 |
+
kv_consumer_state = mma_one_n_block(
|
| 403 |
+
kv_consumer_state,
|
| 404 |
+
n_block=full_n_block,
|
| 405 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 406 |
+
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=False),
|
| 407 |
+
is_first_n_block=False,
|
| 408 |
+
)
|
| 409 |
+
O_should_accumulate = True
|
| 410 |
+
warp_scheduler_barrier_arrive()
|
| 411 |
+
else:
|
| 412 |
+
if curr_mask_block_cnt > 0:
|
| 413 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1]
|
| 414 |
+
kv_consumer_state = process_first_half_block(
|
| 415 |
+
n_block=mask_n_block,
|
| 416 |
+
seqlen=seqlen,
|
| 417 |
+
kv_consumer_state=kv_consumer_state,
|
| 418 |
+
mask_fn=partial(
|
| 419 |
+
mask_fn,
|
| 420 |
+
mask_mod=mask_mod,
|
| 421 |
+
mask_seqlen=True,
|
| 422 |
+
fastdiv_mods=fastdiv_mods if cutlass.const_expr(mask_mod is not None) else None,
|
| 423 |
+
),
|
| 424 |
+
score_mod_fn=score_mod_fn,
|
| 425 |
+
is_first_block=True,
|
| 426 |
+
)
|
| 427 |
+
for i in cutlass.range(1, curr_mask_block_cnt):
|
| 428 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1 - i]
|
| 429 |
+
kv_consumer_state = mma_one_n_block(
|
| 430 |
+
kv_consumer_state,
|
| 431 |
+
n_block=mask_n_block,
|
| 432 |
+
seqlen=seqlen,
|
| 433 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 434 |
+
mask_fn=partial(mask_fn, mask_mod=mask_mod, mask_seqlen=False),
|
| 435 |
+
)
|
| 436 |
+
O_should_accumulate = True
|
| 437 |
+
|
| 438 |
+
if curr_full_block_cnt > 0:
|
| 439 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1]
|
| 440 |
+
if curr_mask_block_cnt == 0:
|
| 441 |
+
kv_consumer_state = process_first_half_block(
|
| 442 |
+
n_block=full_n_block,
|
| 443 |
+
seqlen=seqlen,
|
| 444 |
+
kv_consumer_state=kv_consumer_state,
|
| 445 |
+
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=True),
|
| 446 |
+
score_mod_fn=score_mod_fn,
|
| 447 |
+
is_first_block=True,
|
| 448 |
+
)
|
| 449 |
+
else:
|
| 450 |
+
kv_consumer_state = mma_one_n_block(
|
| 451 |
+
kv_consumer_state,
|
| 452 |
+
n_block=full_n_block,
|
| 453 |
+
seqlen=seqlen,
|
| 454 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 455 |
+
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=True),
|
| 456 |
+
)
|
| 457 |
+
O_should_accumulate = True
|
| 458 |
+
for i in cutlass.range(1, curr_full_block_cnt):
|
| 459 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1 - i]
|
| 460 |
+
kv_consumer_state = mma_one_n_block(
|
| 461 |
+
kv_consumer_state,
|
| 462 |
+
n_block=full_n_block,
|
| 463 |
+
seqlen=seqlen,
|
| 464 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 465 |
+
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=False),
|
| 466 |
+
)
|
| 467 |
+
O_should_accumulate = True
|
| 468 |
+
|
| 469 |
+
if curr_mask_block_cnt + curr_full_block_cnt > 0:
|
| 470 |
+
kv_consumer_state = process_last_half_block(
|
| 471 |
+
kv_consumer_state=kv_consumer_state,
|
| 472 |
+
zero_init=not O_should_accumulate,
|
| 473 |
+
)
|
| 474 |
+
O_should_accumulate = True
|
| 475 |
+
|
| 476 |
+
return kv_consumer_state, O_should_accumulate, processed_any
|
| 477 |
+
|
| 478 |
+
|
| 479 |
+
@cute.jit
|
| 480 |
+
def load_block_list_sm100(
|
| 481 |
+
block_indices: cute.Tensor,
|
| 482 |
+
block_count,
|
| 483 |
+
load_q_with_first: cutlass.Constexpr,
|
| 484 |
+
m_block,
|
| 485 |
+
q_stage: cutlass.Constexpr,
|
| 486 |
+
kv_producer_state,
|
| 487 |
+
load_Q,
|
| 488 |
+
load_K,
|
| 489 |
+
load_V,
|
| 490 |
+
pipeline_kv,
|
| 491 |
+
):
|
| 492 |
+
"""SM100 version of load_block_list (no intra_wg_overlap, no extra_tx_count)."""
|
| 493 |
+
if block_count > 0:
|
| 494 |
+
# First iteration: load Q alongside K if requested
|
| 495 |
+
n_block_first = block_indices[block_count - 1]
|
| 496 |
+
|
| 497 |
+
if const_expr(load_q_with_first):
|
| 498 |
+
# SM100 loads Q0 and optionally Q1
|
| 499 |
+
load_Q(block=q_stage * m_block + 0, stage=0)
|
| 500 |
+
if const_expr(q_stage == 2):
|
| 501 |
+
load_Q(block=q_stage * m_block + 1, stage=1)
|
| 502 |
+
|
| 503 |
+
# SM100 doesn't use producer_acquire for pipeline_kv in load path
|
| 504 |
+
# The pipeline barriers are handled inside load_KV
|
| 505 |
+
load_K(block=n_block_first, producer_state=kv_producer_state, page_idx=None)
|
| 506 |
+
kv_producer_state.advance()
|
| 507 |
+
load_V(block=n_block_first, producer_state=kv_producer_state, page_idx=None)
|
| 508 |
+
kv_producer_state.advance()
|
| 509 |
+
|
| 510 |
+
# Remaining blocks
|
| 511 |
+
for offset in cutlass.range(1, block_count):
|
| 512 |
+
n_block = block_indices[block_count - 1 - offset]
|
| 513 |
+
load_K(block=n_block, producer_state=kv_producer_state, page_idx=None)
|
| 514 |
+
kv_producer_state.advance()
|
| 515 |
+
load_V(block=n_block, producer_state=kv_producer_state, page_idx=None)
|
| 516 |
+
kv_producer_state.advance()
|
| 517 |
+
|
| 518 |
+
return kv_producer_state
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
# SM100-specific tile processor using SM100 helpers
|
| 522 |
+
@cute.jit
|
| 523 |
+
def produce_block_sparse_loads_sm100(
|
| 524 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 525 |
+
batch_idx,
|
| 526 |
+
head_idx,
|
| 527 |
+
m_block,
|
| 528 |
+
kv_producer_state,
|
| 529 |
+
load_Q,
|
| 530 |
+
load_K,
|
| 531 |
+
load_V,
|
| 532 |
+
pipeline_kv,
|
| 533 |
+
q_stage: cutlass.Constexpr,
|
| 534 |
+
q_producer_phase: Int32,
|
| 535 |
+
qhead_per_kvhead: cutlass.Constexpr,
|
| 536 |
+
q_subtile_factor: cutlass.Constexpr,
|
| 537 |
+
):
|
| 538 |
+
"""SM100 entry point for sparse block iteration.
|
| 539 |
+
|
| 540 |
+
SM100 uses PipelineTmaUmma which doesn't support extra_tx_count, so we use
|
| 541 |
+
simplified block processing that just calls producer_acquire without extras.
|
| 542 |
+
|
| 543 |
+
Args:
|
| 544 |
+
m_block: which tile of m we are processing
|
| 545 |
+
qhead_per_kvhead: Constexpr pack factor
|
| 546 |
+
"""
|
| 547 |
+
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 548 |
+
|
| 549 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx = blocksparse_tensors
|
| 550 |
+
|
| 551 |
+
curr_mask_block_cnt = mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 552 |
+
curr_mask_block_idx = mask_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 553 |
+
|
| 554 |
+
if const_expr(full_block_cnt is not None):
|
| 555 |
+
curr_full_block_cnt = full_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 556 |
+
curr_full_block_idx = full_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 557 |
+
else:
|
| 558 |
+
curr_full_block_cnt = Int32(0)
|
| 559 |
+
curr_full_block_idx = None
|
| 560 |
+
|
| 561 |
+
mask_empty = curr_mask_block_cnt == 0
|
| 562 |
+
full_empty = curr_full_block_cnt == 0
|
| 563 |
+
|
| 564 |
+
q_phase_flipped = False
|
| 565 |
+
|
| 566 |
+
if mask_empty:
|
| 567 |
+
# No masked blocks: process full list with Q loading
|
| 568 |
+
kv_producer_state = load_block_list_sm100(
|
| 569 |
+
curr_full_block_idx,
|
| 570 |
+
curr_full_block_cnt,
|
| 571 |
+
load_q_with_first=True,
|
| 572 |
+
m_block=m_block,
|
| 573 |
+
q_stage=q_stage,
|
| 574 |
+
kv_producer_state=kv_producer_state,
|
| 575 |
+
load_Q=load_Q,
|
| 576 |
+
load_K=load_K,
|
| 577 |
+
load_V=load_V,
|
| 578 |
+
pipeline_kv=pipeline_kv,
|
| 579 |
+
)
|
| 580 |
+
q_phase_flipped = not full_empty
|
| 581 |
+
else:
|
| 582 |
+
# Process masked blocks with Q loading
|
| 583 |
+
kv_producer_state = load_block_list_sm100(
|
| 584 |
+
curr_mask_block_idx,
|
| 585 |
+
curr_mask_block_cnt,
|
| 586 |
+
load_q_with_first=True,
|
| 587 |
+
m_block=m_block,
|
| 588 |
+
q_stage=q_stage,
|
| 589 |
+
kv_producer_state=kv_producer_state,
|
| 590 |
+
load_Q=load_Q,
|
| 591 |
+
load_K=load_K,
|
| 592 |
+
load_V=load_V,
|
| 593 |
+
pipeline_kv=pipeline_kv,
|
| 594 |
+
)
|
| 595 |
+
q_phase_flipped = True
|
| 596 |
+
|
| 597 |
+
if not full_empty:
|
| 598 |
+
# Process full blocks without Q loading
|
| 599 |
+
kv_producer_state = load_block_list_sm100(
|
| 600 |
+
curr_full_block_idx,
|
| 601 |
+
curr_full_block_cnt,
|
| 602 |
+
load_q_with_first=False,
|
| 603 |
+
m_block=m_block,
|
| 604 |
+
q_stage=q_stage,
|
| 605 |
+
kv_producer_state=kv_producer_state,
|
| 606 |
+
load_Q=load_Q,
|
| 607 |
+
load_K=load_K,
|
| 608 |
+
load_V=load_V,
|
| 609 |
+
pipeline_kv=pipeline_kv,
|
| 610 |
+
)
|
| 611 |
+
|
| 612 |
+
if q_phase_flipped:
|
| 613 |
+
q_producer_phase ^= 1
|
| 614 |
+
|
| 615 |
+
return kv_producer_state, q_producer_phase
|
| 616 |
+
|
| 617 |
+
|
| 618 |
+
@cute.jit
|
| 619 |
+
def get_total_block_count(
|
| 620 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 621 |
+
batch_idx,
|
| 622 |
+
head_idx,
|
| 623 |
+
m_block,
|
| 624 |
+
qhead_per_kvhead: cutlass.Constexpr,
|
| 625 |
+
q_subtile_factor: cutlass.Constexpr,
|
| 626 |
+
):
|
| 627 |
+
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 628 |
+
|
| 629 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx = blocksparse_tensors
|
| 630 |
+
if const_expr(full_block_cnt is not None):
|
| 631 |
+
return (
|
| 632 |
+
mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 633 |
+
+ full_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 634 |
+
)
|
| 635 |
+
else:
|
| 636 |
+
return mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 637 |
+
|
| 638 |
+
|
| 639 |
+
@cute.jit
|
| 640 |
+
def handle_block_sparse_empty_tile_correction_sm100(
|
| 641 |
+
tidx: Int32,
|
| 642 |
+
q_stage: cutlass.Constexpr,
|
| 643 |
+
m_block_size: cutlass.Constexpr,
|
| 644 |
+
qhead_per_kvhead,
|
| 645 |
+
pack_gqa: cutlass.Constexpr,
|
| 646 |
+
is_split_kv: cutlass.Constexpr,
|
| 647 |
+
learnable_sink,
|
| 648 |
+
mLSE,
|
| 649 |
+
seqlen,
|
| 650 |
+
m_block: Int32,
|
| 651 |
+
head_idx: Int32,
|
| 652 |
+
batch_idx: Int32,
|
| 653 |
+
split_idx: Int32,
|
| 654 |
+
sScale: cute.Tensor,
|
| 655 |
+
stats: list,
|
| 656 |
+
correction_epilogue: Callable,
|
| 657 |
+
thr_mma_pv: cute.core.ThrMma,
|
| 658 |
+
tOtOs: tuple[cute.Tensor],
|
| 659 |
+
sO: cute.Tensor,
|
| 660 |
+
mbar_ptr,
|
| 661 |
+
mbar_softmax_corr_full_offset: Int32,
|
| 662 |
+
mbar_softmax_corr_empty_offset: Int32,
|
| 663 |
+
mbar_P_full_O_rescaled_offset: Int32,
|
| 664 |
+
mbar_P_full_2_offset: Int32,
|
| 665 |
+
mbar_corr_epi_full_offset: Int32,
|
| 666 |
+
mbar_corr_epi_empty_offset: Int32,
|
| 667 |
+
softmax_corr_consumer_phase: Int32,
|
| 668 |
+
o_corr_consumer_phase: Int32,
|
| 669 |
+
corr_epi_producer_phase: Int32,
|
| 670 |
+
softmax_scale_log2: Float32,
|
| 671 |
+
mO_cur: Optional[cute.Tensor] = None,
|
| 672 |
+
gO: Optional[cute.Tensor] = None,
|
| 673 |
+
gmem_tiled_copy_O: Optional[cute.TiledCopy] = None,
|
| 674 |
+
):
|
| 675 |
+
"""Handle the block-sparse case where a tile is fully masked:
|
| 676 |
+
* zero staged results
|
| 677 |
+
* seed stats
|
| 678 |
+
* satisfy the usual barrier protocol so downstream warps continue to make progress.
|
| 679 |
+
"""
|
| 680 |
+
LOG2_E = Float32(math.log2(math.e))
|
| 681 |
+
|
| 682 |
+
for stage in cutlass.range_constexpr(q_stage):
|
| 683 |
+
row_sum_value = Float32(1.0)
|
| 684 |
+
row_max_value = (
|
| 685 |
+
-Float32.inf if const_expr(mLSE is not None or learnable_sink is not None) else None
|
| 686 |
+
)
|
| 687 |
+
if const_expr(learnable_sink is not None):
|
| 688 |
+
sink_val = -Float32.inf
|
| 689 |
+
if const_expr(not pack_gqa):
|
| 690 |
+
sink_val = Float32(learnable_sink[head_idx])
|
| 691 |
+
elif tidx < m_block_size:
|
| 692 |
+
q_head_idx = (
|
| 693 |
+
(q_stage * m_block + stage) * m_block_size + tidx
|
| 694 |
+
) % qhead_per_kvhead + head_idx * qhead_per_kvhead
|
| 695 |
+
sink_val = Float32(learnable_sink[q_head_idx])
|
| 696 |
+
if sink_val != -Float32.inf and (const_expr(not is_split_kv) or split_idx == 0):
|
| 697 |
+
if row_max_value == -Float32.inf:
|
| 698 |
+
row_max_value = sink_val * (LOG2_E / softmax_scale_log2)
|
| 699 |
+
row_sum_value = Float32(1.0)
|
| 700 |
+
else:
|
| 701 |
+
row_sum_value = row_sum_value + utils.exp2f(
|
| 702 |
+
sink_val * LOG2_E - row_max_value * softmax_scale_log2
|
| 703 |
+
)
|
| 704 |
+
if tidx < m_block_size:
|
| 705 |
+
scale_row_idx = tidx + stage * m_block_size
|
| 706 |
+
sScale[scale_row_idx] = row_sum_value
|
| 707 |
+
if const_expr(mLSE is not None or learnable_sink is not None):
|
| 708 |
+
sScale[scale_row_idx + m_block_size * 2] = row_max_value
|
| 709 |
+
acc_flag = row_sum_value == Float32(0.0) or row_sum_value != row_sum_value
|
| 710 |
+
stats[stage] = (row_sum_value, row_max_value, acc_flag)
|
| 711 |
+
|
| 712 |
+
cute.arch.mbarrier_wait(
|
| 713 |
+
mbar_ptr + mbar_softmax_corr_full_offset + stage,
|
| 714 |
+
softmax_corr_consumer_phase,
|
| 715 |
+
)
|
| 716 |
+
cute.arch.mbarrier_arrive(mbar_ptr + mbar_softmax_corr_empty_offset + stage)
|
| 717 |
+
|
| 718 |
+
if const_expr(gmem_tiled_copy_O is None):
|
| 719 |
+
cute.arch.mbarrier_wait(
|
| 720 |
+
mbar_ptr + mbar_corr_epi_empty_offset + stage,
|
| 721 |
+
corr_epi_producer_phase,
|
| 722 |
+
)
|
| 723 |
+
correction_epilogue(
|
| 724 |
+
thr_mma_pv,
|
| 725 |
+
tOtOs[stage],
|
| 726 |
+
tidx,
|
| 727 |
+
stage,
|
| 728 |
+
m_block,
|
| 729 |
+
seqlen.seqlen_q,
|
| 730 |
+
Float32(0.0), # zero scale ensures empty tile writes zeros into staged outputs
|
| 731 |
+
sO[None, None, stage],
|
| 732 |
+
mO_cur,
|
| 733 |
+
gO,
|
| 734 |
+
gmem_tiled_copy_O,
|
| 735 |
+
)
|
| 736 |
+
if const_expr(gmem_tiled_copy_O is None):
|
| 737 |
+
cute.arch.mbarrier_arrive(mbar_ptr + mbar_corr_epi_full_offset + stage)
|
| 738 |
+
cute.arch.mbarrier_arrive(mbar_ptr + mbar_P_full_O_rescaled_offset + stage)
|
| 739 |
+
cute.arch.mbarrier_arrive(mbar_ptr + mbar_P_full_2_offset + stage)
|
| 740 |
+
|
| 741 |
+
softmax_corr_consumer_phase ^= 1
|
| 742 |
+
o_corr_consumer_phase ^= 1
|
| 743 |
+
corr_epi_producer_phase ^= 1
|
| 744 |
+
|
| 745 |
+
return (
|
| 746 |
+
softmax_corr_consumer_phase,
|
| 747 |
+
o_corr_consumer_phase,
|
| 748 |
+
corr_epi_producer_phase,
|
| 749 |
+
)
|
| 750 |
+
|
| 751 |
+
|
| 752 |
+
@cute.jit
|
| 753 |
+
def softmax_block_sparse_sm100(
|
| 754 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 755 |
+
batch_idx,
|
| 756 |
+
head_idx,
|
| 757 |
+
m_block,
|
| 758 |
+
softmax_step: Callable,
|
| 759 |
+
mask_fn: Callable,
|
| 760 |
+
mask_fn_none: Callable,
|
| 761 |
+
mma_si_consumer_phase: Int32,
|
| 762 |
+
si_corr_producer_phase: Int32,
|
| 763 |
+
s0_s1_sequence_phase: Int32,
|
| 764 |
+
mbar_ptr,
|
| 765 |
+
mbar_softmax_corr_full_offset: Int32,
|
| 766 |
+
mbar_softmax_corr_empty_offset: Int32,
|
| 767 |
+
mbar_P_full_O_rescaled_offset: Int32,
|
| 768 |
+
mbar_P_full_2_offset: Int32,
|
| 769 |
+
q_stage: cutlass.Constexpr,
|
| 770 |
+
stage_idx: Int32,
|
| 771 |
+
check_m_boundary: bool,
|
| 772 |
+
qhead_per_kvhead: cutlass.Constexpr,
|
| 773 |
+
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 774 |
+
):
|
| 775 |
+
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 776 |
+
|
| 777 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx = blocksparse_tensors
|
| 778 |
+
|
| 779 |
+
curr_mask_block_cnt = mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 780 |
+
curr_mask_block_idx = mask_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 781 |
+
|
| 782 |
+
if const_expr(full_block_cnt is not None):
|
| 783 |
+
curr_full_block_cnt = full_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 784 |
+
curr_full_block_idx = full_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 785 |
+
else:
|
| 786 |
+
curr_full_block_cnt = Int32(0)
|
| 787 |
+
curr_full_block_idx = None
|
| 788 |
+
|
| 789 |
+
total_block_cnt = curr_mask_block_cnt + curr_full_block_cnt
|
| 790 |
+
|
| 791 |
+
if total_block_cnt == 0:
|
| 792 |
+
cute.arch.mbarrier_arrive(mbar_ptr + mbar_softmax_corr_full_offset + stage_idx)
|
| 793 |
+
cute.arch.mbarrier_arrive(mbar_ptr + mbar_P_full_O_rescaled_offset + stage_idx)
|
| 794 |
+
cute.arch.mbarrier_arrive(mbar_ptr + mbar_P_full_2_offset + stage_idx)
|
| 795 |
+
cute.arch.mbarrier_arrive(mbar_ptr + mbar_softmax_corr_empty_offset + stage_idx)
|
| 796 |
+
else:
|
| 797 |
+
if curr_mask_block_cnt > 0:
|
| 798 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1]
|
| 799 |
+
(
|
| 800 |
+
mma_si_consumer_phase,
|
| 801 |
+
si_corr_producer_phase,
|
| 802 |
+
s0_s1_sequence_phase,
|
| 803 |
+
) = softmax_step(
|
| 804 |
+
mma_si_consumer_phase,
|
| 805 |
+
si_corr_producer_phase,
|
| 806 |
+
s0_s1_sequence_phase,
|
| 807 |
+
mask_n_block,
|
| 808 |
+
is_first=True,
|
| 809 |
+
mask_fn=partial(mask_fn, mask_seqlen=True, check_q_boundary=check_m_boundary),
|
| 810 |
+
)
|
| 811 |
+
for i in cutlass.range(1, curr_mask_block_cnt):
|
| 812 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1 - i]
|
| 813 |
+
(
|
| 814 |
+
mma_si_consumer_phase,
|
| 815 |
+
si_corr_producer_phase,
|
| 816 |
+
s0_s1_sequence_phase,
|
| 817 |
+
) = softmax_step(
|
| 818 |
+
mma_si_consumer_phase,
|
| 819 |
+
si_corr_producer_phase,
|
| 820 |
+
s0_s1_sequence_phase,
|
| 821 |
+
mask_n_block,
|
| 822 |
+
mask_fn=partial(mask_fn, mask_seqlen=False, check_q_boundary=check_m_boundary),
|
| 823 |
+
)
|
| 824 |
+
|
| 825 |
+
if curr_full_block_cnt > 0:
|
| 826 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1]
|
| 827 |
+
if curr_mask_block_cnt == 0:
|
| 828 |
+
(
|
| 829 |
+
mma_si_consumer_phase,
|
| 830 |
+
si_corr_producer_phase,
|
| 831 |
+
s0_s1_sequence_phase,
|
| 832 |
+
) = softmax_step(
|
| 833 |
+
mma_si_consumer_phase,
|
| 834 |
+
si_corr_producer_phase,
|
| 835 |
+
s0_s1_sequence_phase,
|
| 836 |
+
full_n_block,
|
| 837 |
+
is_first=True,
|
| 838 |
+
mask_fn=partial(
|
| 839 |
+
mask_fn_none, mask_seqlen=True, check_q_boundary=check_m_boundary
|
| 840 |
+
),
|
| 841 |
+
)
|
| 842 |
+
else:
|
| 843 |
+
(
|
| 844 |
+
mma_si_consumer_phase,
|
| 845 |
+
si_corr_producer_phase,
|
| 846 |
+
s0_s1_sequence_phase,
|
| 847 |
+
) = softmax_step(
|
| 848 |
+
mma_si_consumer_phase,
|
| 849 |
+
si_corr_producer_phase,
|
| 850 |
+
s0_s1_sequence_phase,
|
| 851 |
+
full_n_block,
|
| 852 |
+
is_first=False,
|
| 853 |
+
mask_fn=partial(
|
| 854 |
+
mask_fn_none, mask_seqlen=False, check_q_boundary=check_m_boundary
|
| 855 |
+
),
|
| 856 |
+
)
|
| 857 |
+
for i in cutlass.range(1, curr_full_block_cnt):
|
| 858 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1 - i]
|
| 859 |
+
(
|
| 860 |
+
mma_si_consumer_phase,
|
| 861 |
+
si_corr_producer_phase,
|
| 862 |
+
s0_s1_sequence_phase,
|
| 863 |
+
) = softmax_step(
|
| 864 |
+
mma_si_consumer_phase,
|
| 865 |
+
si_corr_producer_phase,
|
| 866 |
+
s0_s1_sequence_phase,
|
| 867 |
+
full_n_block,
|
| 868 |
+
mask_fn=partial(
|
| 869 |
+
mask_fn_none, mask_seqlen=False, check_q_boundary=check_m_boundary
|
| 870 |
+
),
|
| 871 |
+
)
|
| 872 |
+
|
| 873 |
+
return (
|
| 874 |
+
mma_si_consumer_phase,
|
| 875 |
+
si_corr_producer_phase,
|
| 876 |
+
s0_s1_sequence_phase,
|
| 877 |
+
total_block_cnt == 0,
|
| 878 |
+
)
|
| 879 |
+
|
| 880 |
+
|
| 881 |
+
# =============================================================================
|
| 882 |
+
# Backward-specific block-sparse helpers (SM100)
|
| 883 |
+
# =============================================================================
|
| 884 |
+
#
|
| 885 |
+
# In backward, iteration is transposed compared to forward:
|
| 886 |
+
# - Forward: outer loop over m_blocks (Q tiles), inner loop over n_blocks (KV tiles)
|
| 887 |
+
# - Backward: outer loop over n_blocks (KV tiles), inner loop over m_blocks (Q tiles)
|
| 888 |
+
#
|
| 889 |
+
# The backward block-sparse tensors use "Q direction" indexing:
|
| 890 |
+
# - q_block_cnt[batch, head, n_block] → count of m_blocks to process for this KV tile
|
| 891 |
+
# - q_block_idx[batch, head, n_block, :] → indices of m_blocks to process
|
| 892 |
+
#
|
| 893 |
+
|
| 894 |
+
|
| 895 |
+
@cute.jit
|
| 896 |
+
def get_total_q_block_count_bwd(
|
| 897 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 898 |
+
batch_idx,
|
| 899 |
+
head_idx,
|
| 900 |
+
n_block,
|
| 901 |
+
subtile_factor: cutlass.Constexpr = 1,
|
| 902 |
+
m_block_max: int = 0,
|
| 903 |
+
):
|
| 904 |
+
"""Count total tile iterations for given n_block (KV tile) in backward."""
|
| 905 |
+
q_block_cnt, _, full_block_cnt, _ = blocksparse_tensors
|
| 906 |
+
total = q_block_cnt[batch_idx, head_idx, n_block]
|
| 907 |
+
if const_expr(full_block_cnt is not None):
|
| 908 |
+
total = total + full_block_cnt[batch_idx, head_idx, n_block]
|
| 909 |
+
return total * subtile_factor
|
| 910 |
+
|
| 911 |
+
|
| 912 |
+
@cute.jit
|
| 913 |
+
def produce_block_sparse_q_loads_bwd_sm100(
|
| 914 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 915 |
+
batch_idx,
|
| 916 |
+
head_idx,
|
| 917 |
+
n_block,
|
| 918 |
+
# Pipeline states (will be returned after advancing)
|
| 919 |
+
producer_state_Q_LSE,
|
| 920 |
+
producer_state_dO_dPsum,
|
| 921 |
+
# Pipelines
|
| 922 |
+
pipeline_Q,
|
| 923 |
+
pipeline_LSE,
|
| 924 |
+
pipeline_dO,
|
| 925 |
+
pipeline_dPsum,
|
| 926 |
+
# Load functions
|
| 927 |
+
load_K,
|
| 928 |
+
load_V,
|
| 929 |
+
load_Q,
|
| 930 |
+
load_dO,
|
| 931 |
+
copy_stats,
|
| 932 |
+
# Global tensors for LSE/dPsum
|
| 933 |
+
gLSE,
|
| 934 |
+
sLSE,
|
| 935 |
+
gdPsum,
|
| 936 |
+
sdPsum,
|
| 937 |
+
# TMA copy bytes for extra_tx_count
|
| 938 |
+
tma_copy_bytes_K,
|
| 939 |
+
tma_copy_bytes_V,
|
| 940 |
+
# Flags for which loads to perform
|
| 941 |
+
should_load_Q: cutlass.Constexpr,
|
| 942 |
+
should_load_dO: cutlass.Constexpr,
|
| 943 |
+
# Subtiling factor and bounds
|
| 944 |
+
subtile_factor: cutlass.Constexpr = 1,
|
| 945 |
+
m_block_max: int = 0,
|
| 946 |
+
):
|
| 947 |
+
"""SM100 backward block sparse loading with subtiling.
|
| 948 |
+
|
| 949 |
+
Returns updated (producer_state_Q_LSE, producer_state_dO_dPsum).
|
| 950 |
+
First iteration loads K/V alongside Q/dO; subsequent iterations load only Q/dO.
|
| 951 |
+
"""
|
| 952 |
+
(
|
| 953 |
+
curr_q_cnt,
|
| 954 |
+
curr_q_idx,
|
| 955 |
+
curr_full_cnt,
|
| 956 |
+
curr_full_idx,
|
| 957 |
+
loop_count,
|
| 958 |
+
) = get_block_sparse_iteration_info_bwd(
|
| 959 |
+
blocksparse_tensors, batch_idx, head_idx, n_block, subtile_factor, m_block_max
|
| 960 |
+
)
|
| 961 |
+
|
| 962 |
+
for iter_idx in cutlass.range(loop_count, unroll=1):
|
| 963 |
+
m_block, _ = get_m_block_from_iter_bwd(
|
| 964 |
+
iter_idx,
|
| 965 |
+
curr_q_cnt,
|
| 966 |
+
curr_q_idx,
|
| 967 |
+
curr_full_cnt,
|
| 968 |
+
curr_full_idx,
|
| 969 |
+
subtile_factor,
|
| 970 |
+
m_block_max,
|
| 971 |
+
)
|
| 972 |
+
m_block_safe = m_block
|
| 973 |
+
if m_block_max > 0:
|
| 974 |
+
m_block_safe = cutlass.min(m_block, m_block_max - 1)
|
| 975 |
+
|
| 976 |
+
if iter_idx == 0:
|
| 977 |
+
# First block: load K/V alongside Q/dO
|
| 978 |
+
if const_expr(should_load_Q):
|
| 979 |
+
pipeline_Q.producer_acquire(producer_state_Q_LSE, extra_tx_count=tma_copy_bytes_K)
|
| 980 |
+
load_K(tma_bar_ptr=pipeline_Q.producer_get_barrier(producer_state_Q_LSE))
|
| 981 |
+
load_Q(m_block_safe, producer_state=producer_state_Q_LSE)
|
| 982 |
+
pipeline_Q.producer_commit(producer_state_Q_LSE)
|
| 983 |
+
pipeline_LSE.producer_acquire(producer_state_Q_LSE)
|
| 984 |
+
with cute.arch.elect_one():
|
| 985 |
+
copy_stats(
|
| 986 |
+
gLSE[None, m_block_safe],
|
| 987 |
+
sLSE[None, producer_state_Q_LSE.index],
|
| 988 |
+
mbar_ptr=pipeline_LSE.producer_get_barrier(producer_state_Q_LSE),
|
| 989 |
+
)
|
| 990 |
+
producer_state_Q_LSE.advance()
|
| 991 |
+
if const_expr(should_load_dO):
|
| 992 |
+
pipeline_dO.producer_acquire(
|
| 993 |
+
producer_state_dO_dPsum, extra_tx_count=tma_copy_bytes_V
|
| 994 |
+
)
|
| 995 |
+
load_V(tma_bar_ptr=pipeline_dO.producer_get_barrier(producer_state_dO_dPsum))
|
| 996 |
+
load_dO(m_block_safe, producer_state=producer_state_dO_dPsum)
|
| 997 |
+
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
| 998 |
+
pipeline_dPsum.producer_acquire(producer_state_dO_dPsum)
|
| 999 |
+
with cute.arch.elect_one():
|
| 1000 |
+
copy_stats(
|
| 1001 |
+
gdPsum[None, m_block_safe],
|
| 1002 |
+
sdPsum[None, producer_state_dO_dPsum.index],
|
| 1003 |
+
mbar_ptr=pipeline_dPsum.producer_get_barrier(producer_state_dO_dPsum),
|
| 1004 |
+
)
|
| 1005 |
+
producer_state_dO_dPsum.advance()
|
| 1006 |
+
else:
|
| 1007 |
+
# Subsequent blocks: just load Q/dO (K/V already loaded)
|
| 1008 |
+
if const_expr(should_load_Q):
|
| 1009 |
+
pipeline_Q.producer_acquire(producer_state_Q_LSE)
|
| 1010 |
+
load_Q(m_block_safe, producer_state=producer_state_Q_LSE)
|
| 1011 |
+
pipeline_Q.producer_commit(producer_state_Q_LSE)
|
| 1012 |
+
pipeline_LSE.producer_acquire(producer_state_Q_LSE)
|
| 1013 |
+
with cute.arch.elect_one():
|
| 1014 |
+
copy_stats(
|
| 1015 |
+
gLSE[None, m_block_safe],
|
| 1016 |
+
sLSE[None, producer_state_Q_LSE.index],
|
| 1017 |
+
mbar_ptr=pipeline_LSE.producer_get_barrier(producer_state_Q_LSE),
|
| 1018 |
+
)
|
| 1019 |
+
producer_state_Q_LSE.advance()
|
| 1020 |
+
if const_expr(should_load_dO):
|
| 1021 |
+
pipeline_dO.producer_acquire(producer_state_dO_dPsum)
|
| 1022 |
+
load_dO(m_block_safe, producer_state=producer_state_dO_dPsum)
|
| 1023 |
+
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
| 1024 |
+
pipeline_dPsum.producer_acquire(producer_state_dO_dPsum)
|
| 1025 |
+
with cute.arch.elect_one():
|
| 1026 |
+
copy_stats(
|
| 1027 |
+
gdPsum[None, m_block_safe],
|
| 1028 |
+
sdPsum[None, producer_state_dO_dPsum.index],
|
| 1029 |
+
mbar_ptr=pipeline_dPsum.producer_get_barrier(producer_state_dO_dPsum),
|
| 1030 |
+
)
|
| 1031 |
+
producer_state_dO_dPsum.advance()
|
| 1032 |
+
|
| 1033 |
+
return producer_state_Q_LSE, producer_state_dO_dPsum
|
| 1034 |
+
|
| 1035 |
+
|
| 1036 |
+
@cute.jit
|
| 1037 |
+
def get_block_sparse_iteration_info_bwd(
|
| 1038 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 1039 |
+
batch_idx,
|
| 1040 |
+
head_idx,
|
| 1041 |
+
n_block,
|
| 1042 |
+
subtile_factor: cutlass.Constexpr = 1,
|
| 1043 |
+
m_block_max: int = 0,
|
| 1044 |
+
):
|
| 1045 |
+
"""Extract block-sparse iteration info for backward pass.
|
| 1046 |
+
|
| 1047 |
+
Returns (curr_q_cnt, curr_q_idx, curr_full_cnt, curr_full_idx, total_count).
|
| 1048 |
+
"""
|
| 1049 |
+
q_cnt, q_idx, full_cnt, full_idx = blocksparse_tensors
|
| 1050 |
+
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1051 |
+
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1052 |
+
|
| 1053 |
+
if const_expr(full_cnt is not None):
|
| 1054 |
+
curr_full_cnt = full_cnt[batch_idx, head_idx, n_block]
|
| 1055 |
+
curr_full_idx = full_idx[batch_idx, head_idx, n_block, None]
|
| 1056 |
+
else:
|
| 1057 |
+
curr_full_cnt = Int32(0)
|
| 1058 |
+
curr_full_idx = None
|
| 1059 |
+
|
| 1060 |
+
sparse_block_count = curr_q_cnt
|
| 1061 |
+
if const_expr(full_cnt is not None):
|
| 1062 |
+
sparse_block_count = sparse_block_count + curr_full_cnt
|
| 1063 |
+
total_count = sparse_block_count * subtile_factor
|
| 1064 |
+
|
| 1065 |
+
return curr_q_cnt, curr_q_idx, curr_full_cnt, curr_full_idx, total_count
|
| 1066 |
+
|
| 1067 |
+
|
| 1068 |
+
@cute.jit
|
| 1069 |
+
def get_m_block_from_iter_bwd(
|
| 1070 |
+
iter_idx,
|
| 1071 |
+
curr_q_cnt,
|
| 1072 |
+
curr_q_idx: cute.Tensor,
|
| 1073 |
+
curr_full_cnt,
|
| 1074 |
+
curr_full_idx: Optional[cute.Tensor],
|
| 1075 |
+
subtile_factor: cutlass.Constexpr = 1,
|
| 1076 |
+
m_block_max: int = 0,
|
| 1077 |
+
):
|
| 1078 |
+
"""Derive m_block index and is_full_block flag from iteration index.
|
| 1079 |
+
|
| 1080 |
+
Returns (m_block, is_full_block):
|
| 1081 |
+
- m_block: The actual Q-tile block index
|
| 1082 |
+
- is_full_block: True if this is a full block (no mask_mod needed)
|
| 1083 |
+
"""
|
| 1084 |
+
sparse_iter_idx = iter_idx // subtile_factor
|
| 1085 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1086 |
+
|
| 1087 |
+
sparse_m_block = Int32(0)
|
| 1088 |
+
is_full_block = False
|
| 1089 |
+
if const_expr(curr_full_idx is not None):
|
| 1090 |
+
if sparse_iter_idx < curr_q_cnt:
|
| 1091 |
+
sparse_m_block = curr_q_idx[sparse_iter_idx]
|
| 1092 |
+
else:
|
| 1093 |
+
sparse_m_block = curr_full_idx[sparse_iter_idx - curr_q_cnt]
|
| 1094 |
+
is_full_block = True
|
| 1095 |
+
else:
|
| 1096 |
+
sparse_m_block = curr_q_idx[sparse_iter_idx]
|
| 1097 |
+
|
| 1098 |
+
return sparse_m_block * subtile_factor + subtile_offset, is_full_block
|
| 1099 |
+
|
| 1100 |
+
|
| 1101 |
+
@cute.jit
|
| 1102 |
+
def _load_q_do_block_sm90(
|
| 1103 |
+
m_block,
|
| 1104 |
+
producer_state_Q,
|
| 1105 |
+
producer_state_dO,
|
| 1106 |
+
pipeline_Q,
|
| 1107 |
+
pipeline_dO,
|
| 1108 |
+
load_K,
|
| 1109 |
+
load_V,
|
| 1110 |
+
load_Q,
|
| 1111 |
+
load_dO,
|
| 1112 |
+
load_LSE,
|
| 1113 |
+
load_dPsum,
|
| 1114 |
+
tma_copy_bytes_K,
|
| 1115 |
+
tma_copy_bytes_V,
|
| 1116 |
+
Q_stage_eq_dO_stage: cutlass.Constexpr,
|
| 1117 |
+
load_kv: bool,
|
| 1118 |
+
):
|
| 1119 |
+
"""Load one Q/dO block, optionally loading K/V on first iteration."""
|
| 1120 |
+
if load_kv:
|
| 1121 |
+
pipeline_Q.producer_acquire(producer_state_Q, extra_tx_count=tma_copy_bytes_K)
|
| 1122 |
+
load_K(tma_bar_ptr=pipeline_Q.producer_get_barrier(producer_state_Q))
|
| 1123 |
+
else:
|
| 1124 |
+
pipeline_Q.producer_acquire(producer_state_Q)
|
| 1125 |
+
load_Q(m_block, producer_state=producer_state_Q)
|
| 1126 |
+
with cute.arch.elect_one():
|
| 1127 |
+
load_LSE(m_block, producer_state=producer_state_Q)
|
| 1128 |
+
|
| 1129 |
+
producer_state_dO_cur = (
|
| 1130 |
+
producer_state_dO if const_expr(not Q_stage_eq_dO_stage) else producer_state_Q
|
| 1131 |
+
)
|
| 1132 |
+
if load_kv:
|
| 1133 |
+
pipeline_dO.producer_acquire(producer_state_dO_cur, extra_tx_count=tma_copy_bytes_V)
|
| 1134 |
+
load_V(tma_bar_ptr=pipeline_dO.producer_get_barrier(producer_state_dO_cur))
|
| 1135 |
+
else:
|
| 1136 |
+
pipeline_dO.producer_acquire(producer_state_dO_cur)
|
| 1137 |
+
load_dO(m_block, producer_state=producer_state_dO_cur)
|
| 1138 |
+
with cute.arch.elect_one():
|
| 1139 |
+
load_dPsum(m_block, producer_state=producer_state_dO_cur)
|
| 1140 |
+
|
| 1141 |
+
producer_state_Q.advance()
|
| 1142 |
+
producer_state_dO.advance()
|
| 1143 |
+
return producer_state_Q, producer_state_dO
|
| 1144 |
+
|
| 1145 |
+
|
| 1146 |
+
@cute.jit
|
| 1147 |
+
def produce_block_sparse_q_loads_bwd_sm90(
|
| 1148 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 1149 |
+
batch_idx,
|
| 1150 |
+
head_idx,
|
| 1151 |
+
n_block,
|
| 1152 |
+
producer_state_Q,
|
| 1153 |
+
producer_state_dO,
|
| 1154 |
+
pipeline_Q,
|
| 1155 |
+
pipeline_dO,
|
| 1156 |
+
load_K,
|
| 1157 |
+
load_V,
|
| 1158 |
+
load_Q,
|
| 1159 |
+
load_dO,
|
| 1160 |
+
load_LSE,
|
| 1161 |
+
load_dPsum,
|
| 1162 |
+
tma_copy_bytes_K,
|
| 1163 |
+
tma_copy_bytes_V,
|
| 1164 |
+
Q_stage_eq_dO_stage: cutlass.Constexpr,
|
| 1165 |
+
subtile_factor: cutlass.Constexpr,
|
| 1166 |
+
m_block_max: int,
|
| 1167 |
+
):
|
| 1168 |
+
"""SM90 backward block sparse loading with separate partial/full loops.
|
| 1169 |
+
|
| 1170 |
+
K/V are loaded with the first valid block. Iterates partial blocks first,
|
| 1171 |
+
then full blocks, matching consumer order.
|
| 1172 |
+
|
| 1173 |
+
Returns updated (producer_state_Q, producer_state_dO).
|
| 1174 |
+
"""
|
| 1175 |
+
q_cnt, q_idx, full_cnt, full_idx = blocksparse_tensors
|
| 1176 |
+
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1177 |
+
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1178 |
+
|
| 1179 |
+
if const_expr(full_cnt is not None):
|
| 1180 |
+
curr_full_cnt = full_cnt[batch_idx, head_idx, n_block]
|
| 1181 |
+
curr_full_idx = full_idx[batch_idx, head_idx, n_block, None]
|
| 1182 |
+
else:
|
| 1183 |
+
curr_full_cnt = Int32(0)
|
| 1184 |
+
curr_full_idx = None
|
| 1185 |
+
|
| 1186 |
+
kv_loaded = False
|
| 1187 |
+
|
| 1188 |
+
for iter_idx in cutlass.range(curr_q_cnt * subtile_factor, unroll=1):
|
| 1189 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1190 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1191 |
+
m_block = curr_q_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1192 |
+
|
| 1193 |
+
if m_block < m_block_max:
|
| 1194 |
+
producer_state_Q, producer_state_dO = _load_q_do_block_sm90(
|
| 1195 |
+
m_block,
|
| 1196 |
+
producer_state_Q,
|
| 1197 |
+
producer_state_dO,
|
| 1198 |
+
pipeline_Q,
|
| 1199 |
+
pipeline_dO,
|
| 1200 |
+
load_K,
|
| 1201 |
+
load_V,
|
| 1202 |
+
load_Q,
|
| 1203 |
+
load_dO,
|
| 1204 |
+
load_LSE,
|
| 1205 |
+
load_dPsum,
|
| 1206 |
+
tma_copy_bytes_K,
|
| 1207 |
+
tma_copy_bytes_V,
|
| 1208 |
+
Q_stage_eq_dO_stage,
|
| 1209 |
+
load_kv=not kv_loaded,
|
| 1210 |
+
)
|
| 1211 |
+
kv_loaded = True
|
| 1212 |
+
|
| 1213 |
+
if const_expr(full_cnt is not None):
|
| 1214 |
+
for iter_idx in cutlass.range(curr_full_cnt * subtile_factor, unroll=1):
|
| 1215 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1216 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1217 |
+
m_block = curr_full_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1218 |
+
|
| 1219 |
+
if m_block < m_block_max:
|
| 1220 |
+
producer_state_Q, producer_state_dO = _load_q_do_block_sm90(
|
| 1221 |
+
m_block,
|
| 1222 |
+
producer_state_Q,
|
| 1223 |
+
producer_state_dO,
|
| 1224 |
+
pipeline_Q,
|
| 1225 |
+
pipeline_dO,
|
| 1226 |
+
load_K,
|
| 1227 |
+
load_V,
|
| 1228 |
+
load_Q,
|
| 1229 |
+
load_dO,
|
| 1230 |
+
load_LSE,
|
| 1231 |
+
load_dPsum,
|
| 1232 |
+
tma_copy_bytes_K,
|
| 1233 |
+
tma_copy_bytes_V,
|
| 1234 |
+
Q_stage_eq_dO_stage,
|
| 1235 |
+
load_kv=not kv_loaded,
|
| 1236 |
+
)
|
| 1237 |
+
kv_loaded = True
|
| 1238 |
+
|
| 1239 |
+
return producer_state_Q, producer_state_dO
|
| 1240 |
+
|
| 1241 |
+
|
| 1242 |
+
@cute.jit
|
| 1243 |
+
def consume_block_sparse_mma_bwd_sm90(
|
| 1244 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 1245 |
+
batch_idx,
|
| 1246 |
+
head_idx,
|
| 1247 |
+
n_block,
|
| 1248 |
+
consumer_state_Q,
|
| 1249 |
+
consumer_state_dO,
|
| 1250 |
+
mma_one_m_block_fn,
|
| 1251 |
+
mask,
|
| 1252 |
+
mask_mod,
|
| 1253 |
+
is_causal: cutlass.Constexpr,
|
| 1254 |
+
is_local: cutlass.Constexpr,
|
| 1255 |
+
thr_mma_SdP,
|
| 1256 |
+
softmax_scale,
|
| 1257 |
+
seqlen,
|
| 1258 |
+
subtile_factor: cutlass.Constexpr,
|
| 1259 |
+
m_block_max: int,
|
| 1260 |
+
aux_tensors=None,
|
| 1261 |
+
fastdiv_mods=(None, None),
|
| 1262 |
+
):
|
| 1263 |
+
"""SM90 backward block sparse MMA consumption with separate partial/full loops.
|
| 1264 |
+
|
| 1265 |
+
Partial blocks are processed first (with mask_mod applied), then full blocks
|
| 1266 |
+
(without mask_mod). This ensures mask_mod is only applied where needed.
|
| 1267 |
+
|
| 1268 |
+
Returns updated (consumer_state_Q, consumer_state_dO).
|
| 1269 |
+
"""
|
| 1270 |
+
q_cnt, q_idx, full_cnt, full_idx = blocksparse_tensors
|
| 1271 |
+
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1272 |
+
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1273 |
+
|
| 1274 |
+
if const_expr(full_cnt is not None):
|
| 1275 |
+
curr_full_cnt = full_cnt[batch_idx, head_idx, n_block]
|
| 1276 |
+
curr_full_idx = full_idx[batch_idx, head_idx, n_block, None]
|
| 1277 |
+
else:
|
| 1278 |
+
curr_full_cnt = Int32(0)
|
| 1279 |
+
curr_full_idx = None
|
| 1280 |
+
|
| 1281 |
+
dKV_accumulate = False
|
| 1282 |
+
|
| 1283 |
+
mask_fn_partial = partial(
|
| 1284 |
+
mask.apply_mask,
|
| 1285 |
+
batch_idx=batch_idx,
|
| 1286 |
+
head_idx=head_idx,
|
| 1287 |
+
n_block=n_block,
|
| 1288 |
+
thr_mma=thr_mma_SdP,
|
| 1289 |
+
mask_seqlen=True,
|
| 1290 |
+
mask_causal=is_causal,
|
| 1291 |
+
mask_local=is_local,
|
| 1292 |
+
mask_mod=mask_mod,
|
| 1293 |
+
aux_tensors=aux_tensors,
|
| 1294 |
+
fastdiv_mods=fastdiv_mods,
|
| 1295 |
+
)
|
| 1296 |
+
|
| 1297 |
+
mask_fn_full = partial(
|
| 1298 |
+
mask.apply_mask,
|
| 1299 |
+
batch_idx=batch_idx,
|
| 1300 |
+
head_idx=head_idx,
|
| 1301 |
+
n_block=n_block,
|
| 1302 |
+
thr_mma=thr_mma_SdP,
|
| 1303 |
+
mask_seqlen=True,
|
| 1304 |
+
mask_causal=is_causal,
|
| 1305 |
+
mask_local=is_local,
|
| 1306 |
+
aux_tensors=aux_tensors,
|
| 1307 |
+
fastdiv_mods=fastdiv_mods,
|
| 1308 |
+
)
|
| 1309 |
+
|
| 1310 |
+
for iter_idx in cutlass.range(curr_q_cnt * subtile_factor, unroll=1):
|
| 1311 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1312 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1313 |
+
m_block = curr_q_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1314 |
+
|
| 1315 |
+
if m_block < m_block_max:
|
| 1316 |
+
consumer_state_Q, consumer_state_dO = mma_one_m_block_fn(
|
| 1317 |
+
m_block,
|
| 1318 |
+
consumer_state_Q,
|
| 1319 |
+
consumer_state_dO,
|
| 1320 |
+
mask_fn=mask_fn_partial,
|
| 1321 |
+
dKV_accumulate=dKV_accumulate,
|
| 1322 |
+
thr_mma_SdP=thr_mma_SdP,
|
| 1323 |
+
batch_idx=batch_idx,
|
| 1324 |
+
head_idx=head_idx,
|
| 1325 |
+
n_block=n_block,
|
| 1326 |
+
softmax_scale=softmax_scale,
|
| 1327 |
+
seqlen=seqlen,
|
| 1328 |
+
aux_tensors=aux_tensors,
|
| 1329 |
+
fastdiv_mods=fastdiv_mods,
|
| 1330 |
+
)
|
| 1331 |
+
dKV_accumulate = True
|
| 1332 |
+
|
| 1333 |
+
if const_expr(full_cnt is not None):
|
| 1334 |
+
for iter_idx in cutlass.range(curr_full_cnt * subtile_factor, unroll=1):
|
| 1335 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1336 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1337 |
+
m_block = curr_full_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1338 |
+
|
| 1339 |
+
if m_block < m_block_max:
|
| 1340 |
+
consumer_state_Q, consumer_state_dO = mma_one_m_block_fn(
|
| 1341 |
+
m_block,
|
| 1342 |
+
consumer_state_Q,
|
| 1343 |
+
consumer_state_dO,
|
| 1344 |
+
mask_fn=mask_fn_full,
|
| 1345 |
+
dKV_accumulate=dKV_accumulate,
|
| 1346 |
+
thr_mma_SdP=thr_mma_SdP,
|
| 1347 |
+
batch_idx=batch_idx,
|
| 1348 |
+
head_idx=head_idx,
|
| 1349 |
+
n_block=n_block,
|
| 1350 |
+
softmax_scale=softmax_scale,
|
| 1351 |
+
seqlen=seqlen,
|
| 1352 |
+
aux_tensors=aux_tensors,
|
| 1353 |
+
fastdiv_mods=fastdiv_mods,
|
| 1354 |
+
)
|
| 1355 |
+
dKV_accumulate = True
|
| 1356 |
+
|
| 1357 |
+
return consumer_state_Q, consumer_state_dO
|
| 1358 |
+
|
| 1359 |
+
|
| 1360 |
+
@cute.jit
|
| 1361 |
+
def _store_one_dQaccum_sm90(
|
| 1362 |
+
m_block,
|
| 1363 |
+
sdQaccum: cute.Tensor,
|
| 1364 |
+
gdQaccum: cute.Tensor,
|
| 1365 |
+
num_mma_warp_groups: cutlass.Constexpr,
|
| 1366 |
+
num_threads_per_warp_group: cutlass.Constexpr,
|
| 1367 |
+
tma_copy_bytes_dQ,
|
| 1368 |
+
):
|
| 1369 |
+
"""Store dQaccum for a single m_block."""
|
| 1370 |
+
for warp_group_idx in cutlass.range_constexpr(num_mma_warp_groups):
|
| 1371 |
+
cute.arch.barrier(
|
| 1372 |
+
barrier_id=int(NamedBarrierBwd.dQFullWG0) + warp_group_idx,
|
| 1373 |
+
number_of_threads=num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1374 |
+
)
|
| 1375 |
+
with cute.arch.elect_one():
|
| 1376 |
+
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1377 |
+
sdQaccum[None, warp_group_idx].iterator,
|
| 1378 |
+
gdQaccum[None, warp_group_idx, m_block].iterator,
|
| 1379 |
+
tma_copy_bytes_dQ,
|
| 1380 |
+
)
|
| 1381 |
+
cute.arch.cp_async_bulk_commit_group()
|
| 1382 |
+
for warp_group_idx in cutlass.range_constexpr(num_mma_warp_groups):
|
| 1383 |
+
cute.arch.cp_async_bulk_wait_group(num_mma_warp_groups - 1 - warp_group_idx, read=True)
|
| 1384 |
+
cute.arch.barrier_arrive(
|
| 1385 |
+
barrier_id=int(NamedBarrierBwd.dQEmptyWG0) + warp_group_idx,
|
| 1386 |
+
number_of_threads=num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1387 |
+
)
|
| 1388 |
+
|
| 1389 |
+
|
| 1390 |
+
@cute.jit
|
| 1391 |
+
def dQaccum_store_block_sparse_bwd_sm90(
|
| 1392 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 1393 |
+
batch_idx,
|
| 1394 |
+
head_idx,
|
| 1395 |
+
n_block,
|
| 1396 |
+
sdQaccum: cute.Tensor,
|
| 1397 |
+
gdQaccum: cute.Tensor,
|
| 1398 |
+
subtile_factor: cutlass.Constexpr,
|
| 1399 |
+
m_block_max: int,
|
| 1400 |
+
num_mma_warp_groups: cutlass.Constexpr,
|
| 1401 |
+
num_threads_per_warp_group: cutlass.Constexpr,
|
| 1402 |
+
tma_copy_bytes_dQ,
|
| 1403 |
+
):
|
| 1404 |
+
"""SM90 backward block sparse dQaccum store with separate partial/full loops.
|
| 1405 |
+
|
| 1406 |
+
Iterates partial blocks first, then full blocks, matching producer/consumer order.
|
| 1407 |
+
"""
|
| 1408 |
+
q_cnt, q_idx, full_cnt, full_idx = blocksparse_tensors
|
| 1409 |
+
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1410 |
+
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1411 |
+
|
| 1412 |
+
if const_expr(full_cnt is not None):
|
| 1413 |
+
curr_full_cnt = full_cnt[batch_idx, head_idx, n_block]
|
| 1414 |
+
curr_full_idx = full_idx[batch_idx, head_idx, n_block, None]
|
| 1415 |
+
else:
|
| 1416 |
+
curr_full_cnt = Int32(0)
|
| 1417 |
+
curr_full_idx = None
|
| 1418 |
+
|
| 1419 |
+
for iter_idx in cutlass.range(curr_q_cnt * subtile_factor, unroll=1):
|
| 1420 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1421 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1422 |
+
m_block = curr_q_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1423 |
+
|
| 1424 |
+
if m_block < m_block_max:
|
| 1425 |
+
_store_one_dQaccum_sm90(
|
| 1426 |
+
m_block,
|
| 1427 |
+
sdQaccum,
|
| 1428 |
+
gdQaccum,
|
| 1429 |
+
num_mma_warp_groups,
|
| 1430 |
+
num_threads_per_warp_group,
|
| 1431 |
+
tma_copy_bytes_dQ,
|
| 1432 |
+
)
|
| 1433 |
+
|
| 1434 |
+
if const_expr(full_cnt is not None):
|
| 1435 |
+
for iter_idx in cutlass.range(curr_full_cnt * subtile_factor, unroll=1):
|
| 1436 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1437 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1438 |
+
m_block = curr_full_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1439 |
+
|
| 1440 |
+
if m_block < m_block_max:
|
| 1441 |
+
_store_one_dQaccum_sm90(
|
| 1442 |
+
m_block,
|
| 1443 |
+
sdQaccum,
|
| 1444 |
+
gdQaccum,
|
| 1445 |
+
num_mma_warp_groups,
|
| 1446 |
+
num_threads_per_warp_group,
|
| 1447 |
+
tma_copy_bytes_dQ,
|
| 1448 |
+
)
|
kernel/deps/flash_attn/cute/block_sparsity.py
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Block-sparsity utilities for FlexAttention
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from typing import Callable, NamedTuple, Tuple
|
| 6 |
+
|
| 7 |
+
import cutlass.cute as cute
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from flash_attn.cute.cute_dsl_utils import get_broadcast_dims, to_cute_tensor
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def ceildiv(a: int, b: int) -> int:
|
| 14 |
+
return (a + b - 1) // b
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class BlockSparseTensors(NamedTuple):
|
| 18 |
+
mask_block_cnt: cute.Tensor
|
| 19 |
+
mask_block_idx: cute.Tensor
|
| 20 |
+
full_block_cnt: cute.Tensor | None
|
| 21 |
+
full_block_idx: cute.Tensor | None
|
| 22 |
+
|
| 23 |
+
def __new_from_mlir_values__(self, values):
|
| 24 |
+
if len(values) == 2:
|
| 25 |
+
values = (*values, None, None)
|
| 26 |
+
return BlockSparseTensors(*values)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class BlockSparseTensorsTorch(NamedTuple):
|
| 30 |
+
mask_block_cnt: torch.Tensor
|
| 31 |
+
mask_block_idx: torch.Tensor
|
| 32 |
+
full_block_cnt: torch.Tensor | None = None
|
| 33 |
+
full_block_idx: torch.Tensor | None = None
|
| 34 |
+
block_size: tuple[int, int] | None = None
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _expand_sparsity_tensor(
|
| 38 |
+
tensor: torch.Tensor,
|
| 39 |
+
expected_shape: Tuple[int, ...],
|
| 40 |
+
tensor_name: str,
|
| 41 |
+
context: str | None,
|
| 42 |
+
hint: str | Callable[[], str] | None,
|
| 43 |
+
) -> torch.Tensor:
|
| 44 |
+
"""Check if we need to expand the tensor to expected shape, and do so if possible."""
|
| 45 |
+
needs_expand = tensor.shape != expected_shape
|
| 46 |
+
if not needs_expand:
|
| 47 |
+
return tensor
|
| 48 |
+
can_expand = all(map(lambda cur, tgt: cur == tgt or cur == 1, tensor.shape, expected_shape))
|
| 49 |
+
if not can_expand:
|
| 50 |
+
context_clause = f" ({context})" if context else ""
|
| 51 |
+
resolved_hint = hint() if callable(hint) else hint
|
| 52 |
+
hint_clause = f" Hint: {resolved_hint}" if resolved_hint else ""
|
| 53 |
+
raise ValueError(
|
| 54 |
+
f"{tensor_name}{context_clause} with shape {tensor.shape} cannot be expanded to expected shape {expected_shape}."
|
| 55 |
+
f"{hint_clause}"
|
| 56 |
+
)
|
| 57 |
+
return tensor.expand(*expected_shape)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _check_and_expand_block(
|
| 61 |
+
name: str,
|
| 62 |
+
cnt: torch.Tensor | None,
|
| 63 |
+
idx: torch.Tensor | None,
|
| 64 |
+
expected_count_shape: Tuple[int, int, int],
|
| 65 |
+
expected_index_shape: Tuple[int, int, int, int],
|
| 66 |
+
context: str | None,
|
| 67 |
+
hint: str | Callable[[], str] | None,
|
| 68 |
+
) -> Tuple[torch.Tensor | None, torch.Tensor | None]:
|
| 69 |
+
if (cnt is None) != (idx is None):
|
| 70 |
+
raise ValueError(
|
| 71 |
+
f"{name}_block_cnt and {name}_block_idx must both be provided or both be None"
|
| 72 |
+
)
|
| 73 |
+
if cnt is None or idx is None:
|
| 74 |
+
return None, None
|
| 75 |
+
if cnt.dtype != torch.int32 or idx.dtype != torch.int32:
|
| 76 |
+
raise ValueError(f"{name}_block tensors must have dtype torch.int32")
|
| 77 |
+
if cnt.device != idx.device:
|
| 78 |
+
raise ValueError(f"{name}_block_cnt and {name}_block_idx must be on the same device")
|
| 79 |
+
if not cnt.is_cuda or not idx.is_cuda:
|
| 80 |
+
raise ValueError(f"{name}_block tensors must live on CUDA")
|
| 81 |
+
expanded_cnt = _expand_sparsity_tensor(
|
| 82 |
+
cnt, expected_count_shape, f"{name}_block_cnt", context, hint
|
| 83 |
+
)
|
| 84 |
+
expanded_idx = _expand_sparsity_tensor(
|
| 85 |
+
idx, expected_index_shape, f"{name}_block_idx", context, hint
|
| 86 |
+
)
|
| 87 |
+
return expanded_cnt, expanded_idx
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def get_block_sparse_expected_shapes(
|
| 91 |
+
batch_size: int,
|
| 92 |
+
num_head: int,
|
| 93 |
+
seqlen_q: int,
|
| 94 |
+
seqlen_k: int,
|
| 95 |
+
m_block_size: int,
|
| 96 |
+
n_block_size: int,
|
| 97 |
+
q_stage: int,
|
| 98 |
+
) -> Tuple[Tuple[int, int, int], Tuple[int, int, int, int]]:
|
| 99 |
+
"""Return (expected_count_shape, expected_index_shape) for block sparse normalization."""
|
| 100 |
+
m_block_size_effective = q_stage * m_block_size
|
| 101 |
+
expected_m_blocks = ceildiv(seqlen_q, m_block_size_effective)
|
| 102 |
+
expected_n_blocks = ceildiv(seqlen_k, n_block_size)
|
| 103 |
+
expected_count_shape = (batch_size, num_head, expected_m_blocks)
|
| 104 |
+
expected_index_shape = (batch_size, num_head, expected_m_blocks, expected_n_blocks)
|
| 105 |
+
return expected_count_shape, expected_index_shape
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def infer_block_sparse_expected_shapes(
|
| 109 |
+
tensors: BlockSparseTensorsTorch,
|
| 110 |
+
*,
|
| 111 |
+
batch_size: int,
|
| 112 |
+
num_head: int,
|
| 113 |
+
seqlen_q: int,
|
| 114 |
+
seqlen_k: int,
|
| 115 |
+
m_block_size: int,
|
| 116 |
+
n_block_size: int,
|
| 117 |
+
q_stage: int,
|
| 118 |
+
context: str,
|
| 119 |
+
sparse_block_size_q: int | None = None,
|
| 120 |
+
sparse_block_size_kv: int | None = None,
|
| 121 |
+
) -> Tuple[Tuple[int, int, int], Tuple[int, int, int, int], int]:
|
| 122 |
+
"""Infer shapes and scaling for block-sparse tensors.
|
| 123 |
+
|
| 124 |
+
Expectations:
|
| 125 |
+
- mask_block_cnt is (B, H, M) and mask_block_idx is (B, H, M, N).
|
| 126 |
+
- Batch/head dims may be 1 for broadcast, or match the requested sizes.
|
| 127 |
+
- sparse_block_size_kv must match tile_n.
|
| 128 |
+
- sparse_block_size_q must be a multiple of q_stage * tile_m.
|
| 129 |
+
- If sparse_block_size_q is omitted and seqlen_q/num_m_blocks is ambiguous,
|
| 130 |
+
the caller must provide block_size to disambiguate. TODO will make this required in a future PR.
|
| 131 |
+
"""
|
| 132 |
+
base_m_block = q_stage * m_block_size
|
| 133 |
+
base_n_block = n_block_size
|
| 134 |
+
if sparse_block_size_kv is None:
|
| 135 |
+
sparse_block_size_kv = base_n_block
|
| 136 |
+
if sparse_block_size_kv != base_n_block:
|
| 137 |
+
raise ValueError(f"Block sparse tensors{context} require BLOCK_SIZE_KV={base_n_block}.")
|
| 138 |
+
if tensors.mask_block_idx is None:
|
| 139 |
+
raise ValueError("mask_block_cnt and mask_block_idx must be provided for block sparsity.")
|
| 140 |
+
num_m_blocks = tensors.mask_block_idx.shape[2]
|
| 141 |
+
|
| 142 |
+
if sparse_block_size_q is None:
|
| 143 |
+
min_block_size = ceildiv(seqlen_q, num_m_blocks)
|
| 144 |
+
if num_m_blocks == 1:
|
| 145 |
+
max_block_size = seqlen_q
|
| 146 |
+
else:
|
| 147 |
+
max_block_size = (seqlen_q - 1) // (num_m_blocks - 1)
|
| 148 |
+
if max_block_size != min_block_size and base_m_block != 1:
|
| 149 |
+
raise ValueError(
|
| 150 |
+
f"Block sparse tensors{context} require explicit sparse_block_size[0] "
|
| 151 |
+
f"to disambiguate block size for seqlen_q={seqlen_q} and num_m_blocks={num_m_blocks}."
|
| 152 |
+
)
|
| 153 |
+
sparse_block_size_q = min_block_size
|
| 154 |
+
|
| 155 |
+
if sparse_block_size_q % base_m_block != 0:
|
| 156 |
+
raise ValueError(
|
| 157 |
+
f"Block sparse tensors{context} have block size {sparse_block_size_q}, "
|
| 158 |
+
f"which must be a multiple of {base_m_block}."
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
expected_m_blocks = ceildiv(seqlen_q, sparse_block_size_q)
|
| 162 |
+
expected_n_blocks = ceildiv(seqlen_k, sparse_block_size_kv)
|
| 163 |
+
q_subtile_factor = sparse_block_size_q // base_m_block
|
| 164 |
+
expected_count_shape = (batch_size, num_head, expected_m_blocks)
|
| 165 |
+
expected_index_shape = (batch_size, num_head, expected_m_blocks, expected_n_blocks)
|
| 166 |
+
|
| 167 |
+
mask_block_cnt = tensors.mask_block_cnt
|
| 168 |
+
mask_block_idx = tensors.mask_block_idx
|
| 169 |
+
if mask_block_cnt is None or mask_block_idx is None:
|
| 170 |
+
raise ValueError("mask_block_cnt and mask_block_idx must be provided for block sparsity.")
|
| 171 |
+
if mask_block_cnt.ndim != 3 or mask_block_idx.ndim != 4:
|
| 172 |
+
raise ValueError(
|
| 173 |
+
f"Block sparse tensors{context} must have shapes (B, H, M) and (B, H, M, N)."
|
| 174 |
+
)
|
| 175 |
+
for dim_name, cur, tgt in (
|
| 176 |
+
("batch", mask_block_cnt.shape[0], expected_count_shape[0]),
|
| 177 |
+
("head", mask_block_cnt.shape[1], expected_count_shape[1]),
|
| 178 |
+
):
|
| 179 |
+
if cur != tgt and cur != 1:
|
| 180 |
+
raise ValueError(f"Block sparse tensors{context} {dim_name} dim must be {tgt} or 1.")
|
| 181 |
+
for dim_name, cur, tgt in (
|
| 182 |
+
("batch", mask_block_idx.shape[0], expected_index_shape[0]),
|
| 183 |
+
("head", mask_block_idx.shape[1], expected_index_shape[1]),
|
| 184 |
+
):
|
| 185 |
+
if cur != tgt and cur != 1:
|
| 186 |
+
raise ValueError(f"Block sparse tensors{context} {dim_name} dim must be {tgt} or 1.")
|
| 187 |
+
if mask_block_cnt.shape[2] != mask_block_idx.shape[2]:
|
| 188 |
+
raise ValueError(f"Block sparse tensors{context} must share the same m-block dimension.")
|
| 189 |
+
if mask_block_idx.shape[3] != expected_n_blocks:
|
| 190 |
+
raise ValueError(
|
| 191 |
+
f"Block sparse tensors{context} n-block dimension must be {expected_n_blocks}."
|
| 192 |
+
)
|
| 193 |
+
if expected_m_blocks != num_m_blocks:
|
| 194 |
+
raise ValueError(
|
| 195 |
+
f"Block sparse tensors{context} m-block dimension {num_m_blocks} does not match "
|
| 196 |
+
f"sparse_block_size_q={sparse_block_size_q}. "
|
| 197 |
+
f"Set BlockSparseTensorsTorch.block_size to match the BlockMask BLOCK_SIZE."
|
| 198 |
+
)
|
| 199 |
+
return expected_count_shape, expected_index_shape, q_subtile_factor
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def get_block_sparse_expected_shapes_bwd(
|
| 203 |
+
batch_size: int,
|
| 204 |
+
num_head: int,
|
| 205 |
+
seqlen_q: int,
|
| 206 |
+
seqlen_k: int,
|
| 207 |
+
m_block_size: int,
|
| 208 |
+
n_block_size: int,
|
| 209 |
+
subtile_factor: int,
|
| 210 |
+
) -> Tuple[Tuple[int, int, int], Tuple[int, int, int, int]]:
|
| 211 |
+
"""Return (expected_count_shape, expected_index_shape) for backward block sparse normalization.
|
| 212 |
+
|
| 213 |
+
Backward uses Q-direction indexing (transposed from forward), where shapes are
|
| 214 |
+
indexed by N-blocks first, then M-blocks. The sparse_block_size_q is determined
|
| 215 |
+
by subtile_factor * m_block_size.
|
| 216 |
+
"""
|
| 217 |
+
sparse_block_size_q = subtile_factor * m_block_size
|
| 218 |
+
expected_m_blocks = ceildiv(seqlen_q, sparse_block_size_q)
|
| 219 |
+
expected_n_blocks = ceildiv(seqlen_k, n_block_size)
|
| 220 |
+
expected_count_shape = (batch_size, num_head, expected_n_blocks)
|
| 221 |
+
expected_index_shape = (batch_size, num_head, expected_n_blocks, expected_m_blocks)
|
| 222 |
+
return expected_count_shape, expected_index_shape
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def normalize_block_sparse_tensors(
|
| 226 |
+
tensors: BlockSparseTensorsTorch,
|
| 227 |
+
*,
|
| 228 |
+
expected_count_shape: Tuple[int, int, int],
|
| 229 |
+
expected_index_shape: Tuple[int, int, int, int],
|
| 230 |
+
context: str | None = None,
|
| 231 |
+
hint: str | Callable[[], str] | None = None,
|
| 232 |
+
) -> BlockSparseTensorsTorch:
|
| 233 |
+
if tensors.mask_block_cnt is None or tensors.mask_block_idx is None:
|
| 234 |
+
raise ValueError("mask_block_cnt and mask_block_idx must be provided for block sparsity.")
|
| 235 |
+
|
| 236 |
+
mask_cnt, mask_idx = _check_and_expand_block(
|
| 237 |
+
"mask",
|
| 238 |
+
tensors.mask_block_cnt,
|
| 239 |
+
tensors.mask_block_idx,
|
| 240 |
+
expected_count_shape,
|
| 241 |
+
expected_index_shape,
|
| 242 |
+
context,
|
| 243 |
+
hint,
|
| 244 |
+
)
|
| 245 |
+
if mask_cnt is None or mask_idx is None:
|
| 246 |
+
raise ValueError("mask_block_cnt and mask_block_idx must be provided for block sparsity.")
|
| 247 |
+
|
| 248 |
+
full_cnt, full_idx = _check_and_expand_block(
|
| 249 |
+
"full",
|
| 250 |
+
tensors.full_block_cnt,
|
| 251 |
+
tensors.full_block_idx,
|
| 252 |
+
expected_count_shape,
|
| 253 |
+
expected_index_shape,
|
| 254 |
+
context,
|
| 255 |
+
hint,
|
| 256 |
+
)
|
| 257 |
+
if full_cnt is not None and mask_cnt.device != full_cnt.device:
|
| 258 |
+
raise ValueError("All block sparse tensors must be on the same device")
|
| 259 |
+
|
| 260 |
+
return BlockSparseTensorsTorch(
|
| 261 |
+
mask_block_cnt=mask_cnt,
|
| 262 |
+
mask_block_idx=mask_idx,
|
| 263 |
+
full_block_cnt=full_cnt,
|
| 264 |
+
full_block_idx=full_idx,
|
| 265 |
+
block_size=tensors.block_size,
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def is_block_sparsity_enabled(tensors: BlockSparseTensorsTorch) -> bool:
|
| 270 |
+
return any(t is not None for t in (tensors.full_block_cnt, tensors.mask_block_cnt))
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def get_block_sparse_broadcast_pattern(
|
| 274 |
+
tensors: BlockSparseTensorsTorch,
|
| 275 |
+
) -> Tuple[Tuple[bool, ...], ...] | None:
|
| 276 |
+
"""Return broadcast pattern for block sparse tensors by checking actual strides.
|
| 277 |
+
|
| 278 |
+
Returns a tuple of broadcast patterns (one per tensor) where each pattern
|
| 279 |
+
is a tuple of bools indicating which dims have stride=0.
|
| 280 |
+
This is used in compile keys to ensure kernels are recompiled when
|
| 281 |
+
broadcast patterns change, since CuTe's mark_layout_dynamic() keeps
|
| 282 |
+
stride=0 as static.
|
| 283 |
+
|
| 284 |
+
The tensors should already be expanded/normalized before calling this function.
|
| 285 |
+
|
| 286 |
+
Returns None if block sparsity is not enabled.
|
| 287 |
+
"""
|
| 288 |
+
if not is_block_sparsity_enabled(tensors):
|
| 289 |
+
return None
|
| 290 |
+
|
| 291 |
+
patterns = []
|
| 292 |
+
for tensor in (
|
| 293 |
+
tensors.mask_block_cnt,
|
| 294 |
+
tensors.mask_block_idx,
|
| 295 |
+
tensors.full_block_cnt,
|
| 296 |
+
tensors.full_block_idx,
|
| 297 |
+
):
|
| 298 |
+
if tensor is not None:
|
| 299 |
+
patterns.append(get_broadcast_dims(tensor))
|
| 300 |
+
else:
|
| 301 |
+
patterns.append(None)
|
| 302 |
+
return tuple(patterns)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def normalize_block_sparse_config(
|
| 306 |
+
tensors: BlockSparseTensorsTorch,
|
| 307 |
+
*,
|
| 308 |
+
batch_size: int,
|
| 309 |
+
num_head: int,
|
| 310 |
+
seqlen_q: int,
|
| 311 |
+
seqlen_k: int,
|
| 312 |
+
block_size: tuple[int, int],
|
| 313 |
+
q_stage: int,
|
| 314 |
+
) -> tuple[BlockSparseTensorsTorch, Tuple[Tuple[bool, ...], ...] | None, int]:
|
| 315 |
+
m_block_size, n_block_size = block_size
|
| 316 |
+
if tensors.block_size is None:
|
| 317 |
+
sparse_block_size_q, sparse_block_size_kv = q_stage * m_block_size, n_block_size
|
| 318 |
+
else:
|
| 319 |
+
sparse_block_size_q, sparse_block_size_kv = tensors.block_size
|
| 320 |
+
if sparse_block_size_kv != n_block_size:
|
| 321 |
+
raise ValueError(
|
| 322 |
+
f"Block sparsity requires sparse_block_size[1]={n_block_size} to match tile_n."
|
| 323 |
+
)
|
| 324 |
+
expected_count_shape, expected_index_shape, q_subtile_factor = (
|
| 325 |
+
infer_block_sparse_expected_shapes(
|
| 326 |
+
tensors,
|
| 327 |
+
batch_size=batch_size,
|
| 328 |
+
num_head=num_head,
|
| 329 |
+
seqlen_q=seqlen_q,
|
| 330 |
+
seqlen_k=seqlen_k,
|
| 331 |
+
m_block_size=m_block_size,
|
| 332 |
+
n_block_size=n_block_size,
|
| 333 |
+
q_stage=q_stage,
|
| 334 |
+
context="forward",
|
| 335 |
+
sparse_block_size_q=sparse_block_size_q,
|
| 336 |
+
sparse_block_size_kv=sparse_block_size_kv,
|
| 337 |
+
)
|
| 338 |
+
)
|
| 339 |
+
normalized_tensors = normalize_block_sparse_tensors(
|
| 340 |
+
tensors,
|
| 341 |
+
expected_count_shape=expected_count_shape,
|
| 342 |
+
expected_index_shape=expected_index_shape,
|
| 343 |
+
)
|
| 344 |
+
return (
|
| 345 |
+
normalized_tensors,
|
| 346 |
+
get_block_sparse_broadcast_pattern(normalized_tensors),
|
| 347 |
+
q_subtile_factor,
|
| 348 |
+
)
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
def normalize_block_sparse_config_bwd(
|
| 352 |
+
tensors: BlockSparseTensorsTorch,
|
| 353 |
+
*,
|
| 354 |
+
batch_size: int,
|
| 355 |
+
num_head: int,
|
| 356 |
+
seqlen_q: int,
|
| 357 |
+
seqlen_k: int,
|
| 358 |
+
block_size: tuple[int, int],
|
| 359 |
+
subtile_factor: int,
|
| 360 |
+
) -> tuple[BlockSparseTensorsTorch, Tuple[Tuple[bool, ...], ...] | None]:
|
| 361 |
+
m_block_size, n_block_size = block_size
|
| 362 |
+
if tensors.block_size is None:
|
| 363 |
+
sparse_block_size_q, sparse_block_size_kv = subtile_factor * m_block_size, n_block_size
|
| 364 |
+
else:
|
| 365 |
+
sparse_block_size_q, sparse_block_size_kv = tensors.block_size
|
| 366 |
+
if sparse_block_size_q != subtile_factor * m_block_size:
|
| 367 |
+
raise ValueError(
|
| 368 |
+
f"Block sparsity expects sparse_block_size_q={subtile_factor * m_block_size} "
|
| 369 |
+
f"for subtile_factor={subtile_factor}."
|
| 370 |
+
)
|
| 371 |
+
if sparse_block_size_kv != n_block_size:
|
| 372 |
+
raise ValueError(
|
| 373 |
+
f"Block sparsity expects sparse_block_size[1]={n_block_size} to match tile_n."
|
| 374 |
+
)
|
| 375 |
+
expected_count_shape, expected_index_shape = get_block_sparse_expected_shapes_bwd(
|
| 376 |
+
batch_size,
|
| 377 |
+
num_head,
|
| 378 |
+
seqlen_q,
|
| 379 |
+
seqlen_k,
|
| 380 |
+
m_block_size,
|
| 381 |
+
n_block_size,
|
| 382 |
+
subtile_factor,
|
| 383 |
+
)
|
| 384 |
+
normalized_tensors = normalize_block_sparse_tensors(
|
| 385 |
+
tensors,
|
| 386 |
+
expected_count_shape=expected_count_shape,
|
| 387 |
+
expected_index_shape=expected_index_shape,
|
| 388 |
+
context="_flash_attn_bwd",
|
| 389 |
+
hint=lambda: (
|
| 390 |
+
f"Backward expects Q-direction block-sparse tensors (q_mask_cnt/q_mask_idx, "
|
| 391 |
+
f"and optionally full_q_cnt/full_q_idx). Regenerate the backward BlockMask with "
|
| 392 |
+
f"BLOCK_SIZE=({subtile_factor * m_block_size}, {n_block_size})."
|
| 393 |
+
),
|
| 394 |
+
)
|
| 395 |
+
return normalized_tensors, get_block_sparse_broadcast_pattern(normalized_tensors)
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
def to_cute_block_sparse_tensors(
|
| 399 |
+
tensors: BlockSparseTensorsTorch, enable_tvm_ffi: bool = True
|
| 400 |
+
) -> BlockSparseTensors | None:
|
| 401 |
+
"""Convert torch block sparsity tensors to CuTe tensors, optionally for tvm ffi"""
|
| 402 |
+
if not is_block_sparsity_enabled(tensors):
|
| 403 |
+
return None
|
| 404 |
+
(
|
| 405 |
+
mask_block_cnt,
|
| 406 |
+
mask_block_idx,
|
| 407 |
+
full_block_cnt,
|
| 408 |
+
full_block_idx,
|
| 409 |
+
*_,
|
| 410 |
+
) = tensors
|
| 411 |
+
|
| 412 |
+
(
|
| 413 |
+
mask_block_cnt_tensor,
|
| 414 |
+
mask_block_idx_tensor,
|
| 415 |
+
) = [
|
| 416 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=-1, enable_tvm_ffi=enable_tvm_ffi)
|
| 417 |
+
for t in (mask_block_cnt, mask_block_idx)
|
| 418 |
+
]
|
| 419 |
+
(
|
| 420 |
+
full_block_cnt_tensor,
|
| 421 |
+
full_block_idx_tensor,
|
| 422 |
+
) = [
|
| 423 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=-1, enable_tvm_ffi=enable_tvm_ffi)
|
| 424 |
+
if t is not None
|
| 425 |
+
else None
|
| 426 |
+
for t in (full_block_cnt, full_block_idx)
|
| 427 |
+
]
|
| 428 |
+
|
| 429 |
+
return BlockSparseTensors(
|
| 430 |
+
mask_block_cnt_tensor,
|
| 431 |
+
mask_block_idx_tensor,
|
| 432 |
+
full_block_cnt_tensor,
|
| 433 |
+
full_block_idx_tensor,
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
def fast_sampling(mask_mod):
|
| 438 |
+
"""Convenience decorator to mark mask_mod as safe for 5-point fast sampling"""
|
| 439 |
+
mask_mod.use_fast_sampling = True
|
| 440 |
+
return mask_mod
|
kernel/deps/flash_attn/cute/compute_block_sparsity.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from functools import partial
|
| 2 |
+
from typing import Callable, Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
import torch
|
| 7 |
+
from cutlass import Boolean, Int8, Int32, const_expr
|
| 8 |
+
|
| 9 |
+
from flash_attn.cute.block_sparsity import (
|
| 10 |
+
BlockSparseTensors,
|
| 11 |
+
BlockSparseTensorsTorch,
|
| 12 |
+
to_cute_block_sparse_tensors,
|
| 13 |
+
)
|
| 14 |
+
from flash_attn.cute.utils import hash_callable, scalar_to_ssa, ssa_to_scalar
|
| 15 |
+
from flash_attn.cute.seqlen_info import SeqlenInfoQK
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class BlockSparsityKernel:
|
| 19 |
+
"""Block sparsity kernel for FlexAttention.
|
| 20 |
+
|
| 21 |
+
This kernel computes `mask_mod` for every token of each block
|
| 22 |
+
to determine if an n block is full, masked, or neither.
|
| 23 |
+
|
| 24 |
+
Writes block counts and indices to a BlockSparseTensors object.
|
| 25 |
+
|
| 26 |
+
When use_fast_sampling=True, uses 5-point sampling (4 corners + center)
|
| 27 |
+
which is much faster but only suitable for masks where this is sufficient.
|
| 28 |
+
|
| 29 |
+
TODO:
|
| 30 |
+
- optimize mask_mod evaluation
|
| 31 |
+
- varlen support
|
| 32 |
+
- transposed tensors for bwd pass
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
def __init__(
|
| 36 |
+
self,
|
| 37 |
+
mask_mod: Callable,
|
| 38 |
+
tile_mn: Tuple[int, int],
|
| 39 |
+
compute_full_blocks: bool = True,
|
| 40 |
+
use_aux_tensors: bool = False,
|
| 41 |
+
use_fast_sampling: bool = False,
|
| 42 |
+
):
|
| 43 |
+
self.mask_mod = mask_mod
|
| 44 |
+
self.tile_mn = tile_mn
|
| 45 |
+
self.compute_full_blocks = compute_full_blocks
|
| 46 |
+
self.use_aux_tensors = use_aux_tensors
|
| 47 |
+
self.use_fast_sampling = use_fast_sampling
|
| 48 |
+
|
| 49 |
+
@cute.jit
|
| 50 |
+
def __call__(
|
| 51 |
+
self,
|
| 52 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 53 |
+
seqlen_q: Int32,
|
| 54 |
+
seqlen_k: Int32,
|
| 55 |
+
aux_tensors: Optional[list] = None,
|
| 56 |
+
):
|
| 57 |
+
self.mask_cnt, self.mask_idx, self.full_cnt, self.full_idx = blocksparse_tensors
|
| 58 |
+
|
| 59 |
+
if const_expr(self.compute_full_blocks):
|
| 60 |
+
assert self.full_cnt is not None and self.full_idx is not None, (
|
| 61 |
+
"full block tensors must be provided when computing full blocks"
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
batch_size, num_heads, num_m_blocks, num_n_blocks = self.mask_idx.shape
|
| 65 |
+
# launch 1 CTA per m block
|
| 66 |
+
grid = [num_m_blocks, num_heads, batch_size]
|
| 67 |
+
|
| 68 |
+
if const_expr(self.use_fast_sampling):
|
| 69 |
+
num_threads = 5
|
| 70 |
+
self.num_warps = 1
|
| 71 |
+
else:
|
| 72 |
+
num_threads = self.tile_mn[0]
|
| 73 |
+
self.num_warps = (num_threads + 32 - 1) // 32
|
| 74 |
+
|
| 75 |
+
self.kernel(
|
| 76 |
+
self.mask_cnt,
|
| 77 |
+
self.mask_idx,
|
| 78 |
+
self.full_cnt,
|
| 79 |
+
self.full_idx,
|
| 80 |
+
num_n_blocks,
|
| 81 |
+
seqlen_q,
|
| 82 |
+
seqlen_k,
|
| 83 |
+
aux_tensors,
|
| 84 |
+
).launch(grid=grid, block=[num_threads, 1, 1])
|
| 85 |
+
|
| 86 |
+
@cute.kernel
|
| 87 |
+
def kernel(
|
| 88 |
+
self,
|
| 89 |
+
mask_cnt: cute.Tensor,
|
| 90 |
+
mask_idx: cute.Tensor,
|
| 91 |
+
full_cnt: cute.Tensor,
|
| 92 |
+
full_idx: cute.Tensor,
|
| 93 |
+
num_n_blocks: Int32,
|
| 94 |
+
seqlen_q: Int32,
|
| 95 |
+
seqlen_k: Int32,
|
| 96 |
+
aux_tensors: Optional[list] = None,
|
| 97 |
+
):
|
| 98 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 99 |
+
warp_idx = cute.arch.warp_idx()
|
| 100 |
+
lane_id = cute.arch.lane_idx()
|
| 101 |
+
m_block, head_idx, batch_idx = cute.arch.block_idx()
|
| 102 |
+
|
| 103 |
+
ssa = partial(scalar_to_ssa, dtype=Int32)
|
| 104 |
+
|
| 105 |
+
seqlen = SeqlenInfoQK.create(
|
| 106 |
+
batch_idx,
|
| 107 |
+
seqlen_q,
|
| 108 |
+
seqlen_k,
|
| 109 |
+
mCuSeqlensQ=None,
|
| 110 |
+
mCuSeqlensK=None,
|
| 111 |
+
mSeqUsedQ=None,
|
| 112 |
+
mSeqUsedK=None,
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
@cute.struct
|
| 116 |
+
class SharedStorage:
|
| 117 |
+
reduction_buffer_smem: cute.struct.Align[
|
| 118 |
+
cute.struct.MemRange[cutlass.Int8, 2 * self.num_warps], 1024
|
| 119 |
+
]
|
| 120 |
+
|
| 121 |
+
smem = cutlass.utils.SmemAllocator()
|
| 122 |
+
storage = smem.allocate(SharedStorage, 16)
|
| 123 |
+
|
| 124 |
+
reduction_buffer = storage.reduction_buffer_smem.get_tensor(
|
| 125 |
+
cute.make_layout((self.num_warps, 2))
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
num_mask_blocks = Int32(0)
|
| 129 |
+
num_full_blocks = Int32(0)
|
| 130 |
+
|
| 131 |
+
for n_block in cutlass.range(num_n_blocks, unroll_full=True):
|
| 132 |
+
m_base = m_block * self.tile_mn[0]
|
| 133 |
+
n_base = n_block * self.tile_mn[1]
|
| 134 |
+
|
| 135 |
+
if const_expr(self.use_fast_sampling):
|
| 136 |
+
# Fast path: 5-point sampling (4 corners + center)
|
| 137 |
+
# Clamps OOB indices to nearest in bounds.
|
| 138 |
+
thread_result = Boolean(False)
|
| 139 |
+
thread_is_valid = Boolean(False)
|
| 140 |
+
q_idx = Int32(0)
|
| 141 |
+
kv_idx = Int32(0)
|
| 142 |
+
|
| 143 |
+
if tidx == 0:
|
| 144 |
+
# Top-left corner (0, 0); always in bounds
|
| 145 |
+
q_idx = m_base
|
| 146 |
+
kv_idx = n_base
|
| 147 |
+
elif tidx == 1:
|
| 148 |
+
# Top-right corner
|
| 149 |
+
q_idx = m_base
|
| 150 |
+
kv_idx = cutlass.min(n_base + self.tile_mn[1] - 1, seqlen_k - 1)
|
| 151 |
+
elif tidx == 2:
|
| 152 |
+
# Bottom-left corner
|
| 153 |
+
q_idx = cutlass.min(m_base + self.tile_mn[0] - 1, seqlen_q - 1)
|
| 154 |
+
kv_idx = n_base
|
| 155 |
+
elif tidx == 3:
|
| 156 |
+
# Bottom-right corner
|
| 157 |
+
q_idx = cutlass.min(m_base + self.tile_mn[0] - 1, seqlen_q - 1)
|
| 158 |
+
kv_idx = cutlass.min(n_base + self.tile_mn[1] - 1, seqlen_k - 1)
|
| 159 |
+
elif tidx == 4:
|
| 160 |
+
# Center point
|
| 161 |
+
q_idx = m_base + (cutlass.min(seqlen_q - m_base, self.tile_mn[0])) // 2
|
| 162 |
+
kv_idx = n_base + (cutlass.min(seqlen_k - n_base, self.tile_mn[1])) // 2
|
| 163 |
+
else:
|
| 164 |
+
thread_is_valid = Boolean(False)
|
| 165 |
+
|
| 166 |
+
# Check bounds and determine if this thread has a valid index pair
|
| 167 |
+
if tidx < 5 and q_idx < seqlen_q and kv_idx < seqlen_k:
|
| 168 |
+
thread_is_valid = Boolean(True)
|
| 169 |
+
q_idx_ssa = ssa(q_idx)
|
| 170 |
+
kv_idx_ssa = ssa(kv_idx)
|
| 171 |
+
thread_result = ssa_to_scalar(
|
| 172 |
+
self.mask_mod(
|
| 173 |
+
ssa(batch_idx),
|
| 174 |
+
ssa(head_idx),
|
| 175 |
+
q_idx_ssa,
|
| 176 |
+
kv_idx_ssa,
|
| 177 |
+
seqlen,
|
| 178 |
+
aux_tensors,
|
| 179 |
+
)
|
| 180 |
+
)
|
| 181 |
+
else:
|
| 182 |
+
thread_is_valid = Boolean(False)
|
| 183 |
+
|
| 184 |
+
# Use vote_any_sync to see if any valid thread found unmasked or masked
|
| 185 |
+
# Only count results from threads that checked valid indices
|
| 186 |
+
has_unmasked = cute.arch.vote_any_sync(thread_result & thread_is_valid)
|
| 187 |
+
has_masked = cute.arch.vote_any_sync((Boolean(not thread_result)) & thread_is_valid)
|
| 188 |
+
|
| 189 |
+
else:
|
| 190 |
+
# Full path: check all elements in the block
|
| 191 |
+
# Track if this thread's row has any masked or unmasked elements
|
| 192 |
+
thread_has_unmasked = Boolean(False)
|
| 193 |
+
thread_has_masked = Boolean(False)
|
| 194 |
+
thread_is_valid = Boolean(False)
|
| 195 |
+
|
| 196 |
+
# Each thread handles 1 row
|
| 197 |
+
q_idx = m_base + tidx
|
| 198 |
+
kv_idx = Int32(0)
|
| 199 |
+
if tidx < self.tile_mn[0] and q_idx < seqlen_q:
|
| 200 |
+
thread_is_valid = Boolean(True)
|
| 201 |
+
q_idx_ssa = ssa(q_idx)
|
| 202 |
+
|
| 203 |
+
# Loop over all columns in this row
|
| 204 |
+
for c in cutlass.range(self.tile_mn[1], unroll_full=True):
|
| 205 |
+
kv_idx = n_base + c
|
| 206 |
+
kv_idx_ssa = ssa(kv_idx)
|
| 207 |
+
|
| 208 |
+
# Only check elements within valid sequence bounds
|
| 209 |
+
if kv_idx < seqlen_k:
|
| 210 |
+
# Direct scalar call
|
| 211 |
+
mask_val = ssa_to_scalar(
|
| 212 |
+
self.mask_mod(
|
| 213 |
+
ssa(batch_idx),
|
| 214 |
+
ssa(head_idx),
|
| 215 |
+
q_idx_ssa,
|
| 216 |
+
kv_idx_ssa,
|
| 217 |
+
seqlen,
|
| 218 |
+
aux_tensors,
|
| 219 |
+
)
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
# Update tracking flags
|
| 223 |
+
if mask_val:
|
| 224 |
+
thread_has_unmasked = Boolean(True)
|
| 225 |
+
else:
|
| 226 |
+
thread_has_masked = Boolean(True)
|
| 227 |
+
|
| 228 |
+
# Block-level reduction to combine results across all threads
|
| 229 |
+
# Only count votes from threads that checked valid indices
|
| 230 |
+
warp_has_unmasked_mask = cute.arch.vote_any_sync(
|
| 231 |
+
thread_has_unmasked & thread_is_valid
|
| 232 |
+
)
|
| 233 |
+
warp_has_masked_mask = cute.arch.vote_any_sync(thread_has_masked & thread_is_valid)
|
| 234 |
+
|
| 235 |
+
# lane 0 writes the ballot mask to shared memory
|
| 236 |
+
lane_id = tidx % 32
|
| 237 |
+
if lane_id == 0:
|
| 238 |
+
# Store as Int8
|
| 239 |
+
reduction_buffer[warp_idx, 0] = Int8(1) if warp_has_unmasked_mask else Int8(0)
|
| 240 |
+
reduction_buffer[warp_idx, 1] = Int8(1) if warp_has_masked_mask else Int8(0)
|
| 241 |
+
|
| 242 |
+
cute.arch.sync_threads()
|
| 243 |
+
|
| 244 |
+
# Thread 0 ORs all warp results together
|
| 245 |
+
has_unmasked = Boolean(False)
|
| 246 |
+
has_masked = Boolean(False)
|
| 247 |
+
if tidx == 0:
|
| 248 |
+
for w in cutlass.range(self.num_warps):
|
| 249 |
+
if reduction_buffer[w, 0]:
|
| 250 |
+
has_unmasked = Boolean(True)
|
| 251 |
+
if reduction_buffer[w, 1]:
|
| 252 |
+
has_masked = Boolean(True)
|
| 253 |
+
|
| 254 |
+
# Only thread 0 updates the output arrays (common to both paths)
|
| 255 |
+
if tidx == 0:
|
| 256 |
+
# Block classification based on what we found:
|
| 257 |
+
# - If has_masked and has_unmasked: partial block (needs masking)
|
| 258 |
+
# - If only has_unmasked: full block (no masking needed)
|
| 259 |
+
# - If only has_masked: skip this block entirely
|
| 260 |
+
is_partial = Boolean(has_masked and has_unmasked)
|
| 261 |
+
is_full = Boolean(has_unmasked and (not has_masked))
|
| 262 |
+
|
| 263 |
+
if is_partial:
|
| 264 |
+
mask_idx[batch_idx, head_idx, m_block, num_mask_blocks] = n_block
|
| 265 |
+
num_mask_blocks += 1
|
| 266 |
+
elif is_full and const_expr(self.compute_full_blocks):
|
| 267 |
+
full_idx[batch_idx, head_idx, m_block, num_full_blocks] = n_block
|
| 268 |
+
num_full_blocks += 1
|
| 269 |
+
|
| 270 |
+
# Only thread 0 writes back the counts
|
| 271 |
+
if tidx == 0:
|
| 272 |
+
mask_cnt[batch_idx, head_idx, m_block] = num_mask_blocks
|
| 273 |
+
if const_expr(self.compute_full_blocks):
|
| 274 |
+
full_cnt[batch_idx, head_idx, m_block] = num_full_blocks
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def compute_block_sparsity(
|
| 278 |
+
tile_m,
|
| 279 |
+
tile_n,
|
| 280 |
+
batch_size,
|
| 281 |
+
num_heads,
|
| 282 |
+
seqlen_q,
|
| 283 |
+
seqlen_k,
|
| 284 |
+
mask_mod: Callable,
|
| 285 |
+
aux_tensors: Optional[list], # list[cute.Tensor]
|
| 286 |
+
device,
|
| 287 |
+
compute_full_blocks: bool = True,
|
| 288 |
+
use_fast_sampling: bool = False,
|
| 289 |
+
) -> Tuple[BlockSparseTensors, BlockSparseTensorsTorch]:
|
| 290 |
+
"""
|
| 291 |
+
Computes block sparsity for a given `mask_mod`.
|
| 292 |
+
|
| 293 |
+
Args:
|
| 294 |
+
tile_m: The tile size for the m dimension.
|
| 295 |
+
tile_n: The tile size for the n dimension.
|
| 296 |
+
batch_size: The batch size.
|
| 297 |
+
num_heads: The number of heads.
|
| 298 |
+
seqlen_q: The sequence length for the query.
|
| 299 |
+
seqlen_k: The sequence length for the key.
|
| 300 |
+
mask_mod: The `mask_mod` callable to use.
|
| 301 |
+
aux_tensors: A list of auxiliary tensors.
|
| 302 |
+
device: The device to use.
|
| 303 |
+
compute_full_blocks: Whether to compute full blocks. If False, only partially-masked blocks are computed.
|
| 304 |
+
use_fast_sampling: Whether to use 5-point sampling (4 corners + center). This is much faster, but only suitable for masks where this check is sufficient.
|
| 305 |
+
|
| 306 |
+
Returns:
|
| 307 |
+
A tuple of `BlockSparseTensors` and `BlockSparseTensorsTorch`.
|
| 308 |
+
"""
|
| 309 |
+
# Check if mask_mod is marked as suitable for 5-point fast sampling
|
| 310 |
+
use_fast_sampling = getattr(mask_mod, "use_fast_sampling", use_fast_sampling)
|
| 311 |
+
|
| 312 |
+
num_m_blocks = (seqlen_q + tile_m - 1) // tile_m
|
| 313 |
+
num_n_blocks = (seqlen_k + tile_n - 1) // tile_n
|
| 314 |
+
|
| 315 |
+
mask_block_cnt = torch.zeros(
|
| 316 |
+
(batch_size, num_heads, num_m_blocks), device=device, dtype=torch.int32
|
| 317 |
+
)
|
| 318 |
+
mask_block_idx = torch.zeros(
|
| 319 |
+
(batch_size, num_heads, num_m_blocks, num_n_blocks), device=device, dtype=torch.int32
|
| 320 |
+
)
|
| 321 |
+
full_block_cnt = (
|
| 322 |
+
torch.zeros((batch_size, num_heads, num_m_blocks), device=device, dtype=torch.int32)
|
| 323 |
+
if compute_full_blocks
|
| 324 |
+
else None
|
| 325 |
+
)
|
| 326 |
+
full_block_idx = (
|
| 327 |
+
torch.zeros(
|
| 328 |
+
(batch_size, num_heads, num_m_blocks, num_n_blocks), device=device, dtype=torch.int32
|
| 329 |
+
)
|
| 330 |
+
if compute_full_blocks
|
| 331 |
+
else None
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
blocksparse_tensors_torch = BlockSparseTensorsTorch(
|
| 335 |
+
mask_block_cnt=mask_block_cnt,
|
| 336 |
+
mask_block_idx=mask_block_idx,
|
| 337 |
+
full_block_cnt=full_block_cnt,
|
| 338 |
+
full_block_idx=full_block_idx,
|
| 339 |
+
block_size=(tile_m, tile_n),
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
mask_mod_hash = hash_callable(mask_mod)
|
| 343 |
+
blocksparse_tensors = to_cute_block_sparse_tensors(
|
| 344 |
+
blocksparse_tensors_torch, enable_tvm_ffi=True
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
compile_key = (
|
| 348 |
+
tile_m,
|
| 349 |
+
tile_n,
|
| 350 |
+
mask_mod_hash,
|
| 351 |
+
compute_full_blocks,
|
| 352 |
+
aux_tensors is not None,
|
| 353 |
+
use_fast_sampling,
|
| 354 |
+
)
|
| 355 |
+
if compile_key not in compute_block_sparsity.compile_cache:
|
| 356 |
+
kernel = BlockSparsityKernel(
|
| 357 |
+
mask_mod,
|
| 358 |
+
tile_mn=(tile_m, tile_n),
|
| 359 |
+
compute_full_blocks=compute_full_blocks,
|
| 360 |
+
use_aux_tensors=aux_tensors is not None,
|
| 361 |
+
use_fast_sampling=use_fast_sampling,
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
compute_block_sparsity.compile_cache[compile_key] = cute.compile(
|
| 365 |
+
kernel, blocksparse_tensors, seqlen_q, seqlen_k, aux_tensors, options="--enable-tvm-ffi"
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
compute_block_sparsity.compile_cache[compile_key](
|
| 369 |
+
blocksparse_tensors_torch[:4],
|
| 370 |
+
seqlen_q,
|
| 371 |
+
seqlen_k,
|
| 372 |
+
aux_tensors,
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
return blocksparse_tensors, blocksparse_tensors_torch
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
compute_block_sparsity.compile_cache = {}
|
kernel/deps/flash_attn/cute/copy_utils.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Wentao Guo, Ted Zadouri, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Optional, Type, Callable
|
| 5 |
+
|
| 6 |
+
import cutlass
|
| 7 |
+
import cutlass.cute as cute
|
| 8 |
+
from cutlass import Float32, Int32, const_expr
|
| 9 |
+
from cutlass.cute.nvgpu import cpasync
|
| 10 |
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
| 11 |
+
from cutlass.cutlass_dsl import T, dsl_user_op
|
| 12 |
+
from cutlass._mlir.dialects import llvm
|
| 13 |
+
import cutlass.pipeline
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dsl_user_op
|
| 17 |
+
def cvt_copy(
|
| 18 |
+
atom: cute.CopyAtom,
|
| 19 |
+
src: cute.Tensor,
|
| 20 |
+
dst: cute.Tensor,
|
| 21 |
+
*,
|
| 22 |
+
pred: Optional[cute.Tensor] = None,
|
| 23 |
+
loc=None,
|
| 24 |
+
ip=None,
|
| 25 |
+
**kwargs,
|
| 26 |
+
) -> None:
|
| 27 |
+
assert isinstance(src.iterator, cute.Pointer) and src.memspace == cute.AddressSpace.rmem
|
| 28 |
+
if const_expr(src.element_type != dst.element_type):
|
| 29 |
+
src_cvt = cute.make_fragment_like(src, dst.element_type, loc=loc, ip=ip)
|
| 30 |
+
src_cvt.store(src.load().to(dst.element_type))
|
| 31 |
+
src = src_cvt
|
| 32 |
+
cute.copy(atom, src, dst, pred=pred, loc=loc, ip=ip, **kwargs)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@dsl_user_op
|
| 36 |
+
def load_s2r(src: cute.Tensor, *, loc=None, ip=None) -> cute.Tensor:
|
| 37 |
+
dst = cute.make_fragment_like(src, src.element_type, loc=loc, ip=ip)
|
| 38 |
+
cute.autovec_copy(src, dst, loc=loc, ip=ip)
|
| 39 |
+
return dst
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@dsl_user_op
|
| 43 |
+
def get_copy_atom(
|
| 44 |
+
dtype: Type[cutlass.Numeric], num_copy_elems: int, is_async: bool = False, *, loc=None, ip=None
|
| 45 |
+
) -> cute.CopyAtom:
|
| 46 |
+
num_copy_bits = const_expr(min(128, num_copy_elems * dtype.width))
|
| 47 |
+
copy_op = cpasync.CopyG2SOp() if is_async else cute.nvgpu.CopyUniversalOp()
|
| 48 |
+
return cute.make_copy_atom(copy_op, dtype, num_bits_per_copy=num_copy_bits)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@dsl_user_op
|
| 52 |
+
def make_tmem_copy(
|
| 53 |
+
tmem_copy_atom: cute.CopyAtom, num_wg: int = 1, *, loc=None, ip=None
|
| 54 |
+
) -> cute.CopyAtom:
|
| 55 |
+
num_dp, num_bits, num_rep, _ = sm100_utils.get_tmem_copy_properties(tmem_copy_atom)
|
| 56 |
+
assert num_dp == 32
|
| 57 |
+
assert num_bits == 32
|
| 58 |
+
tiler_mn = (cute.make_layout((128 * num_rep * num_wg // 32, 32), stride=(32, 1)),)
|
| 59 |
+
layout_tv = cute.make_layout(
|
| 60 |
+
((32, 4, num_wg), (num_rep, 32)), stride=((0, 1, 4 * num_rep), (4, 4 * num_rep * num_wg))
|
| 61 |
+
)
|
| 62 |
+
return cute.make_tiled_copy(tmem_copy_atom, layout_tv, tiler_mn)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@dsl_user_op
|
| 66 |
+
def copy(
|
| 67 |
+
src: cute.Tensor,
|
| 68 |
+
dst: cute.Tensor,
|
| 69 |
+
*,
|
| 70 |
+
pred: Optional[cute.Tensor] = None,
|
| 71 |
+
num_copy_elems: int = 1,
|
| 72 |
+
is_async: bool = False,
|
| 73 |
+
loc=None,
|
| 74 |
+
ip=None,
|
| 75 |
+
**kwargs,
|
| 76 |
+
) -> None:
|
| 77 |
+
copy_atom = get_copy_atom(src.element_type, num_copy_elems, is_async)
|
| 78 |
+
cute.copy(copy_atom, src, dst, pred=pred, loc=loc, ip=ip, **kwargs)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def tiled_copy_1d(
|
| 82 |
+
dtype: Type[cutlass.Numeric], num_threads: int, num_copy_elems: int = 1, is_async: bool = False
|
| 83 |
+
) -> cute.TiledCopy:
|
| 84 |
+
num_copy_bits = num_copy_elems * dtype.width
|
| 85 |
+
copy_op = cpasync.CopyG2SOp() if is_async else cute.nvgpu.CopyUniversalOp()
|
| 86 |
+
copy_atom = cute.make_copy_atom(copy_op, dtype, num_bits_per_copy=num_copy_bits)
|
| 87 |
+
thr_layout = cute.make_layout(num_threads)
|
| 88 |
+
val_layout = cute.make_layout(num_copy_elems)
|
| 89 |
+
return cute.make_tiled_copy_tv(copy_atom, thr_layout, val_layout)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def tiled_copy_2d(
|
| 93 |
+
dtype: Type[cutlass.Numeric], major_mode_size: int, num_threads: int, is_async: bool = False
|
| 94 |
+
) -> cute.TiledCopy:
|
| 95 |
+
num_copy_bits = math.gcd(major_mode_size, 128 // dtype.width) * dtype.width
|
| 96 |
+
copy_elems = num_copy_bits // dtype.width
|
| 97 |
+
copy_op = cpasync.CopyG2SOp() if is_async else cute.nvgpu.CopyUniversalOp()
|
| 98 |
+
copy_atom = cute.make_copy_atom(copy_op, dtype, num_bits_per_copy=num_copy_bits)
|
| 99 |
+
gmem_threads_per_row = major_mode_size // copy_elems
|
| 100 |
+
assert num_threads % gmem_threads_per_row == 0
|
| 101 |
+
thr_layout = cute.make_ordered_layout(
|
| 102 |
+
(num_threads // gmem_threads_per_row, gmem_threads_per_row),
|
| 103 |
+
order=(1, 0),
|
| 104 |
+
)
|
| 105 |
+
val_layout = cute.make_layout((1, copy_elems))
|
| 106 |
+
return cute.make_tiled_copy_tv(copy_atom, thr_layout, val_layout)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
@dsl_user_op
|
| 110 |
+
def atomic_add_fp32x4(
|
| 111 |
+
a: Float32, b: Float32, c: Float32, d: Float32, gmem_ptr: cute.Pointer, *, loc=None, ip=None
|
| 112 |
+
) -> None:
|
| 113 |
+
gmem_ptr_i64 = gmem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 114 |
+
# cache_hint = cutlass.Int64(0x12F0000000000000)
|
| 115 |
+
llvm.inline_asm(
|
| 116 |
+
None,
|
| 117 |
+
[
|
| 118 |
+
gmem_ptr_i64,
|
| 119 |
+
Float32(a).ir_value(loc=loc, ip=ip),
|
| 120 |
+
Float32(b).ir_value(loc=loc, ip=ip),
|
| 121 |
+
Float32(c).ir_value(loc=loc, ip=ip),
|
| 122 |
+
Float32(d).ir_value(loc=loc, ip=ip),
|
| 123 |
+
],
|
| 124 |
+
# [gmem_ptr_i64, Float32(a).ir_value(loc=loc, ip=ip), cache_hint.ir_value()],
|
| 125 |
+
"{\n\t"
|
| 126 |
+
# ".reg .b128 abcd;\n\t"
|
| 127 |
+
# "mov.b128 abcd, {$1, $2, $3, $4};\n\t"
|
| 128 |
+
".reg .v4 .f32 abcd;\n\t"
|
| 129 |
+
# "mov.b128 abcd, {$1, $2, $3, $4};\n\t"
|
| 130 |
+
"mov.f32 abcd.x, $1;\n\t"
|
| 131 |
+
"mov.f32 abcd.y, $2;\n\t"
|
| 132 |
+
"mov.f32 abcd.z, $3;\n\t"
|
| 133 |
+
"mov.f32 abcd.w, $4;\n\t"
|
| 134 |
+
"red.global.add.v4.f32 [$0], abcd;\n\t"
|
| 135 |
+
# "red.global.add.L2::cache_hint.v4.f32 [$0], abcd, 0x14F0000000000000;\n\t"
|
| 136 |
+
"}\n",
|
| 137 |
+
# "red.global.add.L2::cache_hint.f32 [$0], $1, 0x12F0000000000000;",
|
| 138 |
+
# "red.global.add.L2::cache_hint.f32 [$0], $1, $2;",
|
| 139 |
+
"l,f,f,f,f",
|
| 140 |
+
# "l,f,l",
|
| 141 |
+
has_side_effects=True,
|
| 142 |
+
is_align_stack=False,
|
| 143 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
@dsl_user_op
|
| 148 |
+
def set_block_rank(
|
| 149 |
+
smem_ptr: cute.Pointer, peer_cta_rank_in_cluster: Int32, *, loc=None, ip=None
|
| 150 |
+
) -> Int32:
|
| 151 |
+
"""Map the given smem pointer to the address at another CTA rank in the cluster."""
|
| 152 |
+
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 153 |
+
return Int32(
|
| 154 |
+
llvm.inline_asm(
|
| 155 |
+
T.i32(),
|
| 156 |
+
[smem_ptr_i32, peer_cta_rank_in_cluster.ir_value()],
|
| 157 |
+
"mapa.shared::cluster.u32 $0, $1, $2;",
|
| 158 |
+
"=r,r,r",
|
| 159 |
+
has_side_effects=False,
|
| 160 |
+
is_align_stack=False,
|
| 161 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 162 |
+
)
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
@dsl_user_op
|
| 167 |
+
def store_shared_remote_fp32x4(
|
| 168 |
+
a: Float32,
|
| 169 |
+
b: Float32,
|
| 170 |
+
c: Float32,
|
| 171 |
+
d: Float32,
|
| 172 |
+
smem_ptr: cute.Pointer,
|
| 173 |
+
mbar_ptr: cute.Pointer,
|
| 174 |
+
peer_cta_rank_in_cluster: Int32,
|
| 175 |
+
*,
|
| 176 |
+
loc=None,
|
| 177 |
+
ip=None,
|
| 178 |
+
) -> None:
|
| 179 |
+
remote_smem_ptr_i32 = set_block_rank(
|
| 180 |
+
smem_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
| 181 |
+
).ir_value()
|
| 182 |
+
remote_mbar_ptr_i32 = set_block_rank(
|
| 183 |
+
mbar_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
| 184 |
+
).ir_value()
|
| 185 |
+
llvm.inline_asm(
|
| 186 |
+
None,
|
| 187 |
+
[
|
| 188 |
+
remote_smem_ptr_i32,
|
| 189 |
+
remote_mbar_ptr_i32,
|
| 190 |
+
Float32(a).ir_value(loc=loc, ip=ip),
|
| 191 |
+
Float32(b).ir_value(loc=loc, ip=ip),
|
| 192 |
+
Float32(c).ir_value(loc=loc, ip=ip),
|
| 193 |
+
Float32(d).ir_value(loc=loc, ip=ip),
|
| 194 |
+
],
|
| 195 |
+
"{\n\t"
|
| 196 |
+
".reg .v4 .f32 abcd;\n\t"
|
| 197 |
+
"mov.f32 abcd.x, $2;\n\t"
|
| 198 |
+
"mov.f32 abcd.y, $3;\n\t"
|
| 199 |
+
"mov.f32 abcd.z, $4;\n\t"
|
| 200 |
+
"mov.f32 abcd.w, $5;\n\t"
|
| 201 |
+
"st.async.shared::cluster.mbarrier::complete_tx::bytes.v4.f32 [$0], abcd, [$1];\n\t"
|
| 202 |
+
"}\n",
|
| 203 |
+
"r,r,f,f,f,f",
|
| 204 |
+
has_side_effects=True,
|
| 205 |
+
is_align_stack=False,
|
| 206 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
@dsl_user_op
|
| 211 |
+
def cpasync_bulk_g2s(
|
| 212 |
+
gmem_ptr: cute.Pointer,
|
| 213 |
+
smem_ptr: cute.Pointer,
|
| 214 |
+
tma_bar_ptr: cute.Pointer,
|
| 215 |
+
size: int | Int32,
|
| 216 |
+
*,
|
| 217 |
+
loc=None,
|
| 218 |
+
ip=None,
|
| 219 |
+
):
|
| 220 |
+
gmem_ptr_i64 = gmem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 221 |
+
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 222 |
+
mbar_ptr_i32 = tma_bar_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 223 |
+
llvm.inline_asm(
|
| 224 |
+
None,
|
| 225 |
+
[gmem_ptr_i64, smem_ptr_i32, mbar_ptr_i32, Int32(size).ir_value()],
|
| 226 |
+
"cp.async.bulk.shared::cta.global.mbarrier::complete_tx::bytes [$1], [$0], $3, [$2];",
|
| 227 |
+
"l,r,r,r",
|
| 228 |
+
has_side_effects=True,
|
| 229 |
+
is_align_stack=False,
|
| 230 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
@dsl_user_op
|
| 235 |
+
def cpasync_reduce_bulk_add_f32(
|
| 236 |
+
smem_ptr: cute.Pointer,
|
| 237 |
+
gmem_ptr: cute.Pointer,
|
| 238 |
+
store_bytes: int | Int32,
|
| 239 |
+
*,
|
| 240 |
+
loc=None,
|
| 241 |
+
ip=None,
|
| 242 |
+
):
|
| 243 |
+
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 244 |
+
# cache_hint = cutlass.Int64(0x14F0000000000000) # EVICT_LAST
|
| 245 |
+
llvm.inline_asm(
|
| 246 |
+
None,
|
| 247 |
+
[gmem_ptr.llvm_ptr, smem_ptr_i32, Int32(store_bytes).ir_value()],
|
| 248 |
+
"cp.reduce.async.bulk.global.shared::cta.bulk_group.add.f32 [$0], [$1], $2;",
|
| 249 |
+
"l,r,r",
|
| 250 |
+
# [gmem_ptr.llvm_ptr, smem_ptr_i32, Int32(store_bytes).ir_value(), cache_hint.ir_value()],
|
| 251 |
+
# "cp.reduce.async.bulk.global.shared::cta.bulk_group.L2::cache_hint.add.f32 [$0], [$1], $2, $3;",
|
| 252 |
+
# "l,r,r,l",
|
| 253 |
+
has_side_effects=True,
|
| 254 |
+
is_align_stack=False,
|
| 255 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
def cpasync_bulk_get_copy_fn(
|
| 260 |
+
src_tensor: cute.Tensor,
|
| 261 |
+
dst_tensor: cute.Tensor,
|
| 262 |
+
single_stage: bool = False,
|
| 263 |
+
**kwargs,
|
| 264 |
+
) -> Callable:
|
| 265 |
+
# src_is_smem = const_expr(
|
| 266 |
+
# isinstance(src_tensor.iterator, cute.Pointer)
|
| 267 |
+
# and src_tensor.memspace == cute.AddressSpace.smem
|
| 268 |
+
# )
|
| 269 |
+
group_rank_src = const_expr(cute.rank(src_tensor) - (1 if not single_stage else 0))
|
| 270 |
+
group_rank_dst = const_expr(cute.rank(dst_tensor) - (1 if not single_stage else 0))
|
| 271 |
+
# ((atom_v, rest_v), STAGE), ((atom_v, rest_v), RestK)
|
| 272 |
+
src = cute.group_modes(src_tensor, 0, group_rank_src)
|
| 273 |
+
dst = cute.group_modes(dst_tensor, 0, group_rank_dst)
|
| 274 |
+
|
| 275 |
+
def copy_bulk(src_idx, dst_idx, **new_kwargs):
|
| 276 |
+
size = const_expr(cute.size(src.shape[:-1]) * src.element_type.width // 8)
|
| 277 |
+
cpasync_bulk_g2s(
|
| 278 |
+
src[None, src_idx].iterator,
|
| 279 |
+
dst[None, dst_idx].iterator,
|
| 280 |
+
size=size,
|
| 281 |
+
**new_kwargs,
|
| 282 |
+
**kwargs,
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
def copy_bulk_single_stage(**new_kwargs):
|
| 286 |
+
size = const_expr(cute.size(src.shape) * src.element_type.width // 8)
|
| 287 |
+
cpasync_bulk_g2s(src.iterator, dst.iterator, size=size, **new_kwargs, **kwargs)
|
| 288 |
+
|
| 289 |
+
return copy_bulk if const_expr(not single_stage) else copy_bulk_single_stage
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def tma_get_copy_fn(
|
| 293 |
+
atom: cute.CopyAtom,
|
| 294 |
+
cta_coord: cute.Coord,
|
| 295 |
+
cta_layout: cute.Layout,
|
| 296 |
+
src_tensor: cute.Tensor,
|
| 297 |
+
dst_tensor: cute.Tensor,
|
| 298 |
+
filter_zeros: bool = False,
|
| 299 |
+
single_stage: bool = False,
|
| 300 |
+
**kwargs,
|
| 301 |
+
) -> Callable:
|
| 302 |
+
src_is_smem = const_expr(
|
| 303 |
+
isinstance(src_tensor.iterator, cute.Pointer)
|
| 304 |
+
and src_tensor.memspace == cute.AddressSpace.smem
|
| 305 |
+
)
|
| 306 |
+
smem_tensor, gmem_tensor = (src_tensor, dst_tensor) if src_is_smem else (dst_tensor, src_tensor)
|
| 307 |
+
group_rank_smem = const_expr(cute.rank(smem_tensor) - (1 if not single_stage else 0))
|
| 308 |
+
group_rank_gmem = const_expr(cute.rank(gmem_tensor) - (1 if not single_stage else 0))
|
| 309 |
+
# ((atom_v, rest_v), STAGE), ((atom_v, rest_v), RestK)
|
| 310 |
+
s, g = cpasync.tma_partition(
|
| 311 |
+
atom,
|
| 312 |
+
cta_coord,
|
| 313 |
+
cta_layout,
|
| 314 |
+
cute.group_modes(smem_tensor, 0, group_rank_smem),
|
| 315 |
+
cute.group_modes(gmem_tensor, 0, group_rank_gmem),
|
| 316 |
+
)
|
| 317 |
+
if const_expr(filter_zeros):
|
| 318 |
+
s = cute.filter_zeros(s)
|
| 319 |
+
g = cute.filter_zeros(g)
|
| 320 |
+
src, dst = (s, g) if src_is_smem else (g, s)
|
| 321 |
+
|
| 322 |
+
def copy_tma(src_idx, dst_idx, **new_kwargs):
|
| 323 |
+
cute.copy(atom, src[None, src_idx], dst[None, dst_idx], **new_kwargs, **kwargs)
|
| 324 |
+
|
| 325 |
+
def copy_tma_single_stage(**new_kwargs):
|
| 326 |
+
cute.copy(atom, src, dst, **new_kwargs, **kwargs)
|
| 327 |
+
|
| 328 |
+
return (copy_tma if const_expr(not single_stage) else copy_tma_single_stage), s, g
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
def tma_producer_copy_fn(copy: Callable, pipeline: cutlass.pipeline.PipelineAsync):
|
| 332 |
+
def copy_fn(src_idx, producer_state: cutlass.pipeline.PipelineState, **new_kwargs):
|
| 333 |
+
copy(
|
| 334 |
+
src_idx=src_idx,
|
| 335 |
+
dst_idx=producer_state.index,
|
| 336 |
+
tma_bar_ptr=pipeline.producer_get_barrier(producer_state),
|
| 337 |
+
**new_kwargs,
|
| 338 |
+
)
|
| 339 |
+
|
| 340 |
+
return copy_fn
|
kernel/deps/flash_attn/cute/cute_dsl_utils.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
import pathlib
|
| 5 |
+
from typing import Tuple
|
| 6 |
+
from functools import partial, lru_cache
|
| 7 |
+
from dataclasses import dataclass, fields
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
try:
|
| 12 |
+
from triton.tools.disasm import extract
|
| 13 |
+
except ImportError:
|
| 14 |
+
extract = None
|
| 15 |
+
|
| 16 |
+
import cutlass
|
| 17 |
+
import cutlass.cute as cute
|
| 18 |
+
from cutlass.base_dsl.typing import JitArgument
|
| 19 |
+
from cutlass.cutlass_dsl import NumericMeta
|
| 20 |
+
from cutlass.cute.runtime import from_dlpack
|
| 21 |
+
|
| 22 |
+
StaticTypes = (cutlass.Constexpr, NumericMeta, int, bool, str, float, type(None))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
load_cubin_module_data_og = cutlass.base_dsl.runtime.cuda.load_cubin_module_data
|
| 26 |
+
cute_compile_og = cute.compile
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
torch2cute_dtype_map = {
|
| 30 |
+
torch.float16: cutlass.Float16,
|
| 31 |
+
torch.bfloat16: cutlass.BFloat16,
|
| 32 |
+
torch.float32: cutlass.Float32,
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@lru_cache
|
| 37 |
+
def get_max_active_clusters(cluster_size):
|
| 38 |
+
return cutlass.utils.HardwareInfo().get_max_active_clusters(cluster_size=cluster_size)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@lru_cache
|
| 42 |
+
def get_device_capacity(device: torch.device = None) -> Tuple[int, int]:
|
| 43 |
+
return torch.cuda.get_device_capability(device)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@dataclass
|
| 47 |
+
class ParamsBase:
|
| 48 |
+
def __extract_mlir_values__(self):
|
| 49 |
+
all_fields = [getattr(self, field.name) for field in fields(self)]
|
| 50 |
+
non_constexpr_fields = [f for f in all_fields if not isinstance(f, StaticTypes)]
|
| 51 |
+
values, self._values_pos = [], []
|
| 52 |
+
for obj in non_constexpr_fields:
|
| 53 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 54 |
+
values += obj_values
|
| 55 |
+
self._values_pos.append(len(obj_values))
|
| 56 |
+
return values
|
| 57 |
+
|
| 58 |
+
def __new_from_mlir_values__(self, values):
|
| 59 |
+
all_fields = {field.name: getattr(self, field.name) for field in fields(self)}
|
| 60 |
+
constexpr_fields = {n: f for n, f in all_fields.items() if isinstance(f, StaticTypes)}
|
| 61 |
+
non_constexpr_fields = {
|
| 62 |
+
n: f for n, f in all_fields.items() if not isinstance(f, StaticTypes)
|
| 63 |
+
}
|
| 64 |
+
for (name, field), n_items in zip(non_constexpr_fields.items(), self._values_pos):
|
| 65 |
+
non_constexpr_fields[name] = cutlass.new_from_mlir_values(field, values[:n_items])
|
| 66 |
+
values = values[n_items:]
|
| 67 |
+
return self.__class__(**non_constexpr_fields, **constexpr_fields)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
@dataclass
|
| 71 |
+
class ArgumentsBase(JitArgument):
|
| 72 |
+
def __c_pointers__(self):
|
| 73 |
+
all_fields = [getattr(self, field.name) for field in fields(self)]
|
| 74 |
+
non_constexpr_fields = [f for f in all_fields if not isinstance(f, StaticTypes)]
|
| 75 |
+
c_ptrs = []
|
| 76 |
+
for obj in non_constexpr_fields:
|
| 77 |
+
if hasattr(obj, "__c_pointers__"):
|
| 78 |
+
c_ptrs.extend(obj.__c_pointers__())
|
| 79 |
+
return c_ptrs
|
| 80 |
+
|
| 81 |
+
def __get_mlir_types__(self):
|
| 82 |
+
all_fields = [getattr(self, field.name) for field in fields(self)]
|
| 83 |
+
non_constexpr_fields = [f for f in all_fields if not isinstance(f, StaticTypes)]
|
| 84 |
+
types, self._values_pos = [], []
|
| 85 |
+
for obj in non_constexpr_fields:
|
| 86 |
+
if hasattr(obj, "__get_mlir_types__"):
|
| 87 |
+
obj_types = obj.__get_mlir_types__()
|
| 88 |
+
types.extend(obj_types)
|
| 89 |
+
self._values_pos.append(len(obj_types))
|
| 90 |
+
else:
|
| 91 |
+
self._values_pos.append(0)
|
| 92 |
+
return types
|
| 93 |
+
|
| 94 |
+
def __new_from_mlir_values__(self, values):
|
| 95 |
+
all_fields = {field.name: getattr(self, field.name) for field in fields(self)}
|
| 96 |
+
constexpr_fields = {n: f for n, f in all_fields.items() if isinstance(f, StaticTypes)}
|
| 97 |
+
non_constexpr_fields = {
|
| 98 |
+
n: f for n, f in all_fields.items() if not isinstance(f, StaticTypes)
|
| 99 |
+
}
|
| 100 |
+
for (name, field), n_items in zip(non_constexpr_fields.items(), self._values_pos):
|
| 101 |
+
non_constexpr_fields[name] = cutlass.new_from_mlir_values(field, values[:n_items])
|
| 102 |
+
values = values[n_items:]
|
| 103 |
+
return self.__class__(**non_constexpr_fields, **constexpr_fields)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def load_cubin_module_data_patched(cubin_data, filepath):
|
| 107 |
+
pathlib.Path(filepath).write_bytes(cubin_data)
|
| 108 |
+
return load_cubin_module_data_og(cubin_data)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def cute_compile_patched(*args, **kwargs):
|
| 112 |
+
"""A patched version of cute.compile that dump the SASS to a file if CUTE_CUBIN_PATH is set."""
|
| 113 |
+
cubin_path = os.getenv("CUTE_CUBIN_PATH", None)
|
| 114 |
+
if cubin_path is not None:
|
| 115 |
+
cutlass.base_dsl.runtime.cuda.load_cubin_module_data = partial(
|
| 116 |
+
load_cubin_module_data_patched, filepath=cubin_path
|
| 117 |
+
)
|
| 118 |
+
output = cute_compile_og(*args, **kwargs)
|
| 119 |
+
if cubin_path is not None:
|
| 120 |
+
cutlass.base_dsl.runtime.cuda.load_cubin_module_data = load_cubin_module_data_og
|
| 121 |
+
if extract is not None:
|
| 122 |
+
sass = extract(cubin_path, None)
|
| 123 |
+
pathlib.Path(cubin_path).with_suffix(".annotated.sass").write_text(sass)
|
| 124 |
+
return output
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def to_cute_tensor(t, assumed_align=16, leading_dim=-1, fully_dynamic=False, enable_tvm_ffi=True):
|
| 128 |
+
"""Convert torch tensor to cute tensor for TVM FFI. leading_dim=-1 defaults to t.ndim-1."""
|
| 129 |
+
tensor = from_dlpack(t.detach(), assumed_align=assumed_align, enable_tvm_ffi=enable_tvm_ffi)
|
| 130 |
+
if fully_dynamic:
|
| 131 |
+
return tensor.mark_layout_dynamic()
|
| 132 |
+
if leading_dim == -1:
|
| 133 |
+
leading_dim = t.ndim - 1
|
| 134 |
+
return tensor.mark_layout_dynamic(leading_dim=leading_dim)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def get_broadcast_dims(tensor: torch.Tensor) -> Tuple[bool, ...]:
|
| 138 |
+
"""Return tuple of bools indicating which dims have stride=0 (broadcast).
|
| 139 |
+
|
| 140 |
+
This is useful for compile keys since CuTe's mark_layout_dynamic() keeps
|
| 141 |
+
stride=0 as static, meaning kernels compiled with different broadcast
|
| 142 |
+
patterns are not interchangeable.
|
| 143 |
+
"""
|
| 144 |
+
return tuple(s == 0 for s in tensor.stride())
|
kernel/deps/flash_attn/cute/fast_math.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import cutlass
|
| 4 |
+
import cutlass.cute as cute
|
| 5 |
+
from cutlass import Int32
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@cute.jit
|
| 9 |
+
def clz(x: Int32) -> Int32:
|
| 10 |
+
# for i in cutlass.range_constexpr(32):
|
| 11 |
+
# if (1 << (31 - i)) & x:
|
| 12 |
+
# return Int32(i)
|
| 13 |
+
# return Int32(32)
|
| 14 |
+
# Early exit is not supported yet
|
| 15 |
+
res = Int32(32)
|
| 16 |
+
done = False
|
| 17 |
+
for i in cutlass.range(32):
|
| 18 |
+
if ((1 << (31 - i)) & x) and not done:
|
| 19 |
+
res = Int32(i)
|
| 20 |
+
done = True
|
| 21 |
+
return res
|
kernel/deps/flash_attn/cute/flash_bwd.py
ADDED
|
@@ -0,0 +1,1262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
# A reimplementation of https://github.com/Dao-AILab/flash-attention/blob/main/hopper/mainloop_bwd_sm80.hpp
|
| 3 |
+
# from Cutlass C++ to Cute-DSL.
|
| 4 |
+
import math
|
| 5 |
+
from types import SimpleNamespace
|
| 6 |
+
from typing import Type, Callable, Optional
|
| 7 |
+
from functools import partial
|
| 8 |
+
|
| 9 |
+
import cuda.bindings.driver as cuda
|
| 10 |
+
|
| 11 |
+
import cutlass
|
| 12 |
+
import cutlass.cute as cute
|
| 13 |
+
from cutlass.cute.nvgpu import cpasync, warp
|
| 14 |
+
from cutlass import Float32, Int32
|
| 15 |
+
import cutlass.utils as utils_basic
|
| 16 |
+
|
| 17 |
+
from flash_attn.cute import ampere_helpers as sm80_utils
|
| 18 |
+
from flash_attn.cute import utils
|
| 19 |
+
from flash_attn.cute.mask import AttentionMask
|
| 20 |
+
from flash_attn.cute.seqlen_info import SeqlenInfoQK
|
| 21 |
+
from flash_attn.cute.tile_scheduler import ParamsBase, SingleTileScheduler, SingleTileVarlenScheduler, TileSchedulerArguments
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class FlashAttentionBackwardSm80:
|
| 25 |
+
def __init__(
|
| 26 |
+
self,
|
| 27 |
+
dtype: Type[cutlass.Numeric],
|
| 28 |
+
head_dim: int,
|
| 29 |
+
head_dim_v: Optional[int] = None,
|
| 30 |
+
qhead_per_kvhead: int = 1,
|
| 31 |
+
m_block_size: int = 64,
|
| 32 |
+
n_block_size: int = 128,
|
| 33 |
+
num_stages_Q: int = 2,
|
| 34 |
+
num_stages_dO: int = 2,
|
| 35 |
+
num_threads: int = 256,
|
| 36 |
+
pack_gqa: bool = False,
|
| 37 |
+
is_causal: bool = False,
|
| 38 |
+
SdP_swapAB: bool = False,
|
| 39 |
+
dKV_swapAB: bool = False,
|
| 40 |
+
dQ_swapAB: bool = False,
|
| 41 |
+
AtomLayoutMSdP: int = 1,
|
| 42 |
+
AtomLayoutNdKV: int = 8,
|
| 43 |
+
AtomLayoutMdQ: int = 1,
|
| 44 |
+
V_in_regs: bool = False,
|
| 45 |
+
):
|
| 46 |
+
"""Initializes the configuration for a flash attention v2 kernel.
|
| 47 |
+
|
| 48 |
+
All contiguous dimensions must be at least 16 bytes aligned which indicates the head dimension
|
| 49 |
+
should be a multiple of 8.
|
| 50 |
+
|
| 51 |
+
:param head_dim: head dimension
|
| 52 |
+
:type head_dim: int
|
| 53 |
+
:param m_block_size: m block size
|
| 54 |
+
:type m_block_size: int
|
| 55 |
+
:param n_block_size: n block size
|
| 56 |
+
:type n_block_size: int
|
| 57 |
+
:param num_threads: number of threads
|
| 58 |
+
:type num_threads: int
|
| 59 |
+
:param is_causal: is causal
|
| 60 |
+
"""
|
| 61 |
+
self.dtype = dtype
|
| 62 |
+
# padding head_dim to a multiple of 16 as k_block_size
|
| 63 |
+
hdim_multiple_of = 32
|
| 64 |
+
self.head_dim_padded = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 65 |
+
head_dim_v = head_dim_v if head_dim_v is not None else head_dim
|
| 66 |
+
self.same_hdim_kv = head_dim == head_dim_v
|
| 67 |
+
self.head_dim_v_padded = int(math.ceil(head_dim_v / hdim_multiple_of) * hdim_multiple_of)
|
| 68 |
+
# Can save registers (and hence be faster) if we don't have to check hdim predication
|
| 69 |
+
self.check_hdim_oob = head_dim != self.head_dim_padded
|
| 70 |
+
self.check_hdim_v_oob = head_dim_v != self.head_dim_v_padded
|
| 71 |
+
self.qhead_per_kvhead = qhead_per_kvhead
|
| 72 |
+
self.m_block_size = m_block_size
|
| 73 |
+
self.n_block_size = n_block_size
|
| 74 |
+
self.num_threads = num_threads
|
| 75 |
+
self.pack_gqa = pack_gqa
|
| 76 |
+
self.is_causal = is_causal
|
| 77 |
+
self.num_stages_Q = num_stages_Q
|
| 78 |
+
self.num_stages_dO = num_stages_dO
|
| 79 |
+
self.SdP_swapAB = SdP_swapAB
|
| 80 |
+
self.dKV_swapAB = dKV_swapAB
|
| 81 |
+
self.dQ_swapAB = dQ_swapAB
|
| 82 |
+
self.AtomLayoutMSdP = AtomLayoutMSdP
|
| 83 |
+
self.AtomLayoutNdKV = AtomLayoutNdKV
|
| 84 |
+
self.AtomLayoutMdQ = AtomLayoutMdQ
|
| 85 |
+
num_mma_warps = self.num_threads // cute.arch.WARP_SIZE
|
| 86 |
+
self.Mma_dKV_is_RS = AtomLayoutMSdP == 1 and AtomLayoutNdKV == num_mma_warps and SdP_swapAB and not dKV_swapAB
|
| 87 |
+
self.V_in_regs = V_in_regs
|
| 88 |
+
self.share_QV_smem = V_in_regs
|
| 89 |
+
|
| 90 |
+
@staticmethod
|
| 91 |
+
def can_implement(
|
| 92 |
+
dtype, head_dim, head_dim_v, m_block_size, n_block_size, num_stages_Q, num_stages_dO,
|
| 93 |
+
num_threads, is_causal,
|
| 94 |
+
V_in_regs=False
|
| 95 |
+
) -> bool:
|
| 96 |
+
"""Check if the kernel can be implemented with the given parameters.
|
| 97 |
+
|
| 98 |
+
:param dtype: data type
|
| 99 |
+
:type dtype: cutlass.Numeric
|
| 100 |
+
:param head_dim: head dimension
|
| 101 |
+
:type head_dim: int
|
| 102 |
+
:param m_block_size: m block size
|
| 103 |
+
:type m_block_size: int
|
| 104 |
+
:param n_block_size: n block size
|
| 105 |
+
:type n_block_size: int
|
| 106 |
+
:param num_threads: number of threads
|
| 107 |
+
:type num_threads: int
|
| 108 |
+
:param is_causal: is causal
|
| 109 |
+
:type is_causal: bool
|
| 110 |
+
|
| 111 |
+
:return: True if the kernel can be implemented, False otherwise
|
| 112 |
+
:rtype: bool
|
| 113 |
+
"""
|
| 114 |
+
if dtype not in [cutlass.Float16, cutlass.BFloat16]:
|
| 115 |
+
return False
|
| 116 |
+
if head_dim % 8 != 0:
|
| 117 |
+
return False
|
| 118 |
+
if head_dim_v % 8 != 0:
|
| 119 |
+
return False
|
| 120 |
+
if n_block_size % 16 != 0:
|
| 121 |
+
return False
|
| 122 |
+
if num_threads % 32 != 0:
|
| 123 |
+
return False
|
| 124 |
+
# Check if block size setting is out of shared memory capacity
|
| 125 |
+
# Shared memory usage: Q tile + (K tile + V tile) where K and V use the same tile size
|
| 126 |
+
smem_usage_Q = m_block_size * head_dim * num_stages_Q * 2
|
| 127 |
+
smem_usage_dO = m_block_size * head_dim_v * num_stages_dO * 2
|
| 128 |
+
smem_usage_K = n_block_size * head_dim * 2
|
| 129 |
+
smem_usage_V = n_block_size * head_dim_v * 2
|
| 130 |
+
smem_usage_QV = (smem_usage_Q + smem_usage_V) if not V_in_regs else max(smem_usage_Q, smem_usage_V)
|
| 131 |
+
smem_usage = smem_usage_QV + smem_usage_dO + smem_usage_K
|
| 132 |
+
smem_capacity = utils_basic.get_smem_capacity_in_bytes("sm_80")
|
| 133 |
+
if smem_usage > smem_capacity:
|
| 134 |
+
return False
|
| 135 |
+
return True
|
| 136 |
+
|
| 137 |
+
def _check_type(
|
| 138 |
+
self,
|
| 139 |
+
mQ_type: Type[cutlass.Numeric],
|
| 140 |
+
mK_type: Type[cutlass.Numeric],
|
| 141 |
+
mV_type: Type[cutlass.Numeric],
|
| 142 |
+
mdO_type: Type[cutlass.Numeric],
|
| 143 |
+
mLSE_type: Type[cutlass.Numeric],
|
| 144 |
+
mdPsum_type: Type[cutlass.Numeric],
|
| 145 |
+
mdQaccum_type: Type[cutlass.Numeric],
|
| 146 |
+
mdK_type: Type[cutlass.Numeric],
|
| 147 |
+
mdV_type: Type[cutlass.Numeric],
|
| 148 |
+
mCuSeqlensQ_type: Type[cutlass.Numeric] | None,
|
| 149 |
+
mCuSeqlensK_type: Type[cutlass.Numeric] | None,
|
| 150 |
+
mSeqUsedQ_type: Type[cutlass.Numeric] | None,
|
| 151 |
+
mSeqUsedK_type: Type[cutlass.Numeric] | None,
|
| 152 |
+
):
|
| 153 |
+
if cutlass.const_expr(not (mQ_type == mK_type == mV_type == mdO_type)):
|
| 154 |
+
raise TypeError("All tensors must have the same data type")
|
| 155 |
+
if cutlass.const_expr(self.qhead_per_kvhead == 1):
|
| 156 |
+
if cutlass.const_expr(not (mdK_type == mdV_type == mQ_type)):
|
| 157 |
+
raise TypeError("mdK and mdV tensors must have the same data type as mQ")
|
| 158 |
+
else:
|
| 159 |
+
if cutlass.const_expr(not (mdK_type == mdV_type == cutlass.Float32)):
|
| 160 |
+
raise TypeError("mdKaccum and mdVaccum tensors must have the data type Float32")
|
| 161 |
+
if cutlass.const_expr(not mQ_type in [cutlass.Float16, cutlass.BFloat16]):
|
| 162 |
+
raise TypeError("Only Float16 or BFloat16 is supported")
|
| 163 |
+
if cutlass.const_expr(not mLSE_type in [cutlass.Float32]):
|
| 164 |
+
raise TypeError("LSE tensor must be Float32")
|
| 165 |
+
if cutlass.const_expr(not mdPsum_type in [cutlass.Float32]):
|
| 166 |
+
raise TypeError("dPsum tensor must be Float32")
|
| 167 |
+
if cutlass.const_expr(not mdQaccum_type in [cutlass.Float32]):
|
| 168 |
+
raise TypeError("dQaccum tensor must be Float32")
|
| 169 |
+
if cutlass.const_expr(mCuSeqlensQ_type not in [None, cutlass.Int32]):
|
| 170 |
+
raise TypeError("cuSeqlensQ tensor must be Int32")
|
| 171 |
+
if cutlass.const_expr(mCuSeqlensK_type not in [None, cutlass.Int32]):
|
| 172 |
+
raise TypeError("cuSeqlensK tensor must be Int32")
|
| 173 |
+
if cutlass.const_expr(mSeqUsedQ_type not in [None, cutlass.Int32]):
|
| 174 |
+
raise TypeError("SeqUsedQ tensor must be Int32")
|
| 175 |
+
if cutlass.const_expr(mSeqUsedK_type not in [None, cutlass.Int32]):
|
| 176 |
+
raise TypeError("SeqUsedK tensor must be Int32")
|
| 177 |
+
assert mQ_type == self.dtype
|
| 178 |
+
|
| 179 |
+
def _setup_attributes(self):
|
| 180 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 181 |
+
# Shared memory layout: Q/K/V
|
| 182 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 183 |
+
sQ_layout_atom = sm80_utils.get_smem_layout_atom(self.dtype, self.head_dim_padded)
|
| 184 |
+
self.sQ_layout = cute.tile_to_shape(
|
| 185 |
+
sQ_layout_atom, (self.m_block_size, self.head_dim_padded, self.num_stages_Q), (0, 1, 2),
|
| 186 |
+
)
|
| 187 |
+
sK_layout_atom = sQ_layout_atom
|
| 188 |
+
self.sK_layout = cute.tile_to_shape(
|
| 189 |
+
sK_layout_atom, (self.n_block_size, self.head_dim_padded), (0, 1),
|
| 190 |
+
)
|
| 191 |
+
sV_layout_atom = sm80_utils.get_smem_layout_atom(self.dtype, self.head_dim_v_padded)
|
| 192 |
+
self.sV_layout = cute.tile_to_shape(
|
| 193 |
+
sV_layout_atom, (self.n_block_size, self.head_dim_v_padded), (0, 1),
|
| 194 |
+
)
|
| 195 |
+
sdO_layout_atom = sV_layout_atom
|
| 196 |
+
self.sdO_layout = cute.tile_to_shape(
|
| 197 |
+
sdO_layout_atom, (self.m_block_size, self.head_dim_v_padded, self.num_stages_dO), (0, 1, 2),
|
| 198 |
+
)
|
| 199 |
+
# TODO: do we set swizzle to be 3 here explicitly?
|
| 200 |
+
sPdS_layout_atom = sm80_utils.get_smem_layout_atom(self.dtype, self.n_block_size)
|
| 201 |
+
self.sPdS_layout = cute.tile_to_shape(
|
| 202 |
+
sPdS_layout_atom, (self.m_block_size, self.n_block_size), (0, 1),
|
| 203 |
+
)
|
| 204 |
+
# We set stride to be multiple of 64 so that if ShuffleLSE, even if threads read from sLSE but out of bounds,
|
| 205 |
+
# it's still a valid smem address.
|
| 206 |
+
self.sLSE_layout = cute.make_layout(
|
| 207 |
+
(self.m_block_size, self.num_stages_Q),
|
| 208 |
+
stride=(1, cute.round_up(self.m_block_size, 64)),
|
| 209 |
+
)
|
| 210 |
+
sLSEMma_layout = cute.make_layout(
|
| 211 |
+
(self.m_block_size, self.n_block_size, self.num_stages_Q),
|
| 212 |
+
stride=(1, 0, cute.round_up(self.m_block_size, 64)),
|
| 213 |
+
)
|
| 214 |
+
sLSEMma_layout_transposed = cute.make_layout(
|
| 215 |
+
(self.n_block_size, self.m_block_size, self.num_stages_Q),
|
| 216 |
+
stride=(0, 1, cute.round_up(self.m_block_size, 64)),
|
| 217 |
+
)
|
| 218 |
+
self.sLSEMma_layout = sLSEMma_layout if not self.SdP_swapAB else sLSEMma_layout_transposed
|
| 219 |
+
|
| 220 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 221 |
+
# GMEM Tiled copy:
|
| 222 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 223 |
+
# Thread layouts for copies
|
| 224 |
+
universal_copy_bits = 128
|
| 225 |
+
async_copy_elems = universal_copy_bits // self.dtype.width
|
| 226 |
+
# atom_async_copy: async copy atom for QKV load
|
| 227 |
+
atom_async_copy = cute.make_copy_atom(
|
| 228 |
+
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.GLOBAL),
|
| 229 |
+
self.dtype,
|
| 230 |
+
num_bits_per_copy=universal_copy_bits,
|
| 231 |
+
)
|
| 232 |
+
# atom_universal_copy: universal copy atom for O store
|
| 233 |
+
atom_universal_copy = cute.make_copy_atom(
|
| 234 |
+
cute.nvgpu.CopyUniversalOp(), self.dtype, num_bits_per_copy=universal_copy_bits,
|
| 235 |
+
)
|
| 236 |
+
# tQK_layout: thread layout for QK load
|
| 237 |
+
tQK_shape_dim_1 = sQ_layout_atom.outer.shape[1] // async_copy_elems
|
| 238 |
+
assert self.num_threads % tQK_shape_dim_1 == 0, "num_threads must be divisible by tQK_shape_dim_1"
|
| 239 |
+
tQK_layout = cute.make_ordered_layout(
|
| 240 |
+
(self.num_threads // tQK_shape_dim_1, tQK_shape_dim_1), order=(1, 0),
|
| 241 |
+
)
|
| 242 |
+
# Do we need to check if we overshot kBlockM when we load Q?
|
| 243 |
+
self.is_even_m_smem_q = self.m_block_size % tQK_layout.shape[0] == 0
|
| 244 |
+
# Do we need to check if we overshot kBlockN when we load K?
|
| 245 |
+
self.is_even_n_smem_k = self.n_block_size % tQK_layout.shape[0] == 0
|
| 246 |
+
tVdO_shape_dim_1 = sV_layout_atom.outer.shape[1] // async_copy_elems
|
| 247 |
+
assert self.num_threads % tVdO_shape_dim_1 == 0, "num_threads must be divisible by tVdO_shape_dim_1"
|
| 248 |
+
tVdO_layout = cute.make_ordered_layout(
|
| 249 |
+
(self.num_threads // tVdO_shape_dim_1, tVdO_shape_dim_1), order=(1, 0),
|
| 250 |
+
)
|
| 251 |
+
# Do we need to check if we overshot kBlockN when we load V?
|
| 252 |
+
self.is_even_n_smem_v = self.n_block_size % tVdO_layout.shape[0] == 0
|
| 253 |
+
self.is_even_m_smem_do = self.m_block_size % tVdO_layout.shape[0] == 0
|
| 254 |
+
|
| 255 |
+
# Value layouts for copies
|
| 256 |
+
vQKVdO_layout = cute.make_layout((1, async_copy_elems))
|
| 257 |
+
|
| 258 |
+
# gmem_tiled_copy_QK: tiled copy for QK load
|
| 259 |
+
self.gmem_tiled_copy_QK = cute.make_tiled_copy_tv(atom_async_copy, tQK_layout, vQKVdO_layout)
|
| 260 |
+
self.gmem_tiled_copy_VdO = cute.make_tiled_copy_tv(atom_async_copy, tVdO_layout, vQKVdO_layout)
|
| 261 |
+
self.gmem_tiled_copy_dK = cute.make_tiled_copy_tv(atom_universal_copy, tQK_layout, vQKVdO_layout)
|
| 262 |
+
self.gmem_tiled_copy_dV = cute.make_tiled_copy_tv(atom_universal_copy, tVdO_layout, vQKVdO_layout)
|
| 263 |
+
async_copy_elems_accum = universal_copy_bits // cutlass.Float32.width
|
| 264 |
+
|
| 265 |
+
# I think we wouldn't require this with smarter padding
|
| 266 |
+
if cutlass.const_expr(not self.varlen_q):
|
| 267 |
+
async_copy_elems_accum = universal_copy_bits // cutlass.Float32.width
|
| 268 |
+
atom_async_copy_accum = cute.make_copy_atom(
|
| 269 |
+
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.GLOBAL),
|
| 270 |
+
cutlass.Float32,
|
| 271 |
+
num_bits_per_copy=universal_copy_bits,
|
| 272 |
+
)
|
| 273 |
+
else:
|
| 274 |
+
async_copy_elems_accum = 1
|
| 275 |
+
atom_async_copy_accum = cute.make_copy_atom(
|
| 276 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 277 |
+
cutlass.Float32,
|
| 278 |
+
num_bits_per_copy=cutlass.Float32.width,
|
| 279 |
+
)
|
| 280 |
+
self.gmem_tiled_copy_LSE = cute.make_tiled_copy_tv(
|
| 281 |
+
atom_async_copy_accum,
|
| 282 |
+
cute.make_layout(self.num_threads),
|
| 283 |
+
cute.make_layout(async_copy_elems_accum),
|
| 284 |
+
)
|
| 285 |
+
self.gmem_tiled_copy_dQaccum = cute.make_tiled_copy_tv(
|
| 286 |
+
cute.make_copy_atom(
|
| 287 |
+
cute.nvgpu.CopyUniversalOp(), cutlass.Float32, num_bits_per_copy=cutlass.Float32.width
|
| 288 |
+
),
|
| 289 |
+
cute.make_layout(self.num_threads),
|
| 290 |
+
cute.make_layout(1)
|
| 291 |
+
)
|
| 292 |
+
if cutlass.const_expr(self.qhead_per_kvhead > 1):
|
| 293 |
+
self.gmem_tiled_copy_dK = self.gmem_tiled_copy_dQaccum
|
| 294 |
+
self.gmem_tiled_copy_dV = self.gmem_tiled_copy_dQaccum
|
| 295 |
+
|
| 296 |
+
def _get_tiled_mma(self):
|
| 297 |
+
num_mma_warps = self.num_threads // 32
|
| 298 |
+
AtomLayoutSdP = (self.AtomLayoutMSdP, num_mma_warps // self.AtomLayoutMSdP, 1) if cutlass.const_expr(not self.SdP_swapAB) else (num_mma_warps // self.AtomLayoutMSdP, self.AtomLayoutMSdP, 1)
|
| 299 |
+
tiled_mma_sdp = cute.make_tiled_mma(
|
| 300 |
+
warp.MmaF16BF16Op(self.dtype, cutlass.Float32, (16, 8, 16)),
|
| 301 |
+
AtomLayoutSdP,
|
| 302 |
+
permutation_mnk=(AtomLayoutSdP[0] * 16, AtomLayoutSdP[1] * 16, 16),
|
| 303 |
+
)
|
| 304 |
+
AtomLayoutdKV = (self.AtomLayoutNdKV, num_mma_warps // self.AtomLayoutNdKV, 1) if cutlass.const_expr(not self.dKV_swapAB) else (num_mma_warps // self.AtomLayoutNdKV, self.AtomLayoutNdKV, 1)
|
| 305 |
+
tiled_mma_dkv = cute.make_tiled_mma(
|
| 306 |
+
warp.MmaF16BF16Op(self.dtype, cutlass.Float32, (16, 8, 16)),
|
| 307 |
+
AtomLayoutdKV,
|
| 308 |
+
permutation_mnk=(AtomLayoutdKV[0] * 16, AtomLayoutdKV[1] * 16, 16),
|
| 309 |
+
)
|
| 310 |
+
AtomLayoutdQ = (self.AtomLayoutMdQ, num_mma_warps // self.AtomLayoutMdQ, 1) if cutlass.const_expr(not self.dQ_swapAB) else (num_mma_warps // self.AtomLayoutMdQ, self.AtomLayoutMdQ, 1)
|
| 311 |
+
tiled_mma_dq = cute.make_tiled_mma(
|
| 312 |
+
warp.MmaF16BF16Op(self.dtype, cutlass.Float32, (16, 8, 16)),
|
| 313 |
+
AtomLayoutdQ,
|
| 314 |
+
permutation_mnk=(AtomLayoutdQ[0] * 16, AtomLayoutdQ[1] * 16, 16),
|
| 315 |
+
)
|
| 316 |
+
return tiled_mma_sdp, tiled_mma_dkv, tiled_mma_dq
|
| 317 |
+
|
| 318 |
+
def _get_shared_storage_cls(self):
|
| 319 |
+
sQ_struct, sK_struct, sV_struct, sdO_struct = [
|
| 320 |
+
cute.struct.Align[cute.struct.MemRange[self.dtype, cute.cosize(layout)], 1024]
|
| 321 |
+
for layout in (self.sQ_layout, self.sK_layout, self.sV_layout, self.sdO_layout)
|
| 322 |
+
]
|
| 323 |
+
cosize_sQV = max(cute.cosize(self.sQ_layout), cute.cosize(self.sV_layout))
|
| 324 |
+
sQV_struct = cute.struct.Align[cute.struct.MemRange[self.dtype, cosize_sQV], 1024]
|
| 325 |
+
sLSE_struct, sdPsum_struct = [
|
| 326 |
+
cute.struct.Align[cute.struct.MemRange[cutlass.Float32, cute.cosize(layout)], 128]
|
| 327 |
+
for layout in (self.sLSE_layout, self.sLSE_layout)
|
| 328 |
+
]
|
| 329 |
+
sP_struct, sdS_struct = [
|
| 330 |
+
cute.struct.Align[cute.struct.MemRange[self.dtype, cute.cosize(layout)], 128]
|
| 331 |
+
for layout in (self.sPdS_layout, self.sPdS_layout)
|
| 332 |
+
]
|
| 333 |
+
|
| 334 |
+
@cute.struct
|
| 335 |
+
class SharedStorageSeparateQV:
|
| 336 |
+
sK: sK_struct
|
| 337 |
+
sV: sV_struct
|
| 338 |
+
sQ: sQ_struct
|
| 339 |
+
sdO: sdO_struct
|
| 340 |
+
sLSE: sLSE_struct
|
| 341 |
+
sdPsum: sdPsum_struct
|
| 342 |
+
sP: sP_struct
|
| 343 |
+
sdS: sdS_struct
|
| 344 |
+
# TODO: the case where there's no sP
|
| 345 |
+
|
| 346 |
+
@cute.struct
|
| 347 |
+
class SharedStorageSharedQV:
|
| 348 |
+
sK: sK_struct
|
| 349 |
+
sV: sV_struct
|
| 350 |
+
sQ: sQV_struct
|
| 351 |
+
sdO: sdO_struct
|
| 352 |
+
sLSE: sLSE_struct
|
| 353 |
+
sdPsum: sdPsum_struct
|
| 354 |
+
sP: sP_struct
|
| 355 |
+
sdS: sdS_struct
|
| 356 |
+
|
| 357 |
+
return SharedStorageSeparateQV if cutlass.const_expr(not self.share_QV_smem) else SharedStorageSharedQV
|
| 358 |
+
|
| 359 |
+
@cute.jit
|
| 360 |
+
def __call__(
|
| 361 |
+
self,
|
| 362 |
+
mQ: cute.Tensor,
|
| 363 |
+
mK: cute.Tensor,
|
| 364 |
+
mV: cute.Tensor,
|
| 365 |
+
mdO: cute.Tensor,
|
| 366 |
+
mLSE: cute.Tensor,
|
| 367 |
+
mdPsum: cute.Tensor,
|
| 368 |
+
mdQaccum: cute.Tensor,
|
| 369 |
+
mdK: cute.Tensor,
|
| 370 |
+
mdV: cute.Tensor,
|
| 371 |
+
softmax_scale: cutlass.Float32,
|
| 372 |
+
stream: cuda.CUstream,
|
| 373 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 374 |
+
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 375 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 376 |
+
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 377 |
+
softcap: Float32 | float | None = None,
|
| 378 |
+
window_size_left: Int32 | int | None = None,
|
| 379 |
+
window_size_right: Int32 | int | None = None,
|
| 380 |
+
mdQ_semaphore: Optional[cute.Tensor] = None,
|
| 381 |
+
):
|
| 382 |
+
assert mdQ_semaphore is None, "semaphore not supported yet"
|
| 383 |
+
# Get the data type and check if it is fp16 or bf16
|
| 384 |
+
self._check_type(*(t.element_type if t is not None else None
|
| 385 |
+
for t in (mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV, mCuSeqlensQ, mCuSeqlensK, mSeqUsedQ, mSeqUsedK)))
|
| 386 |
+
# Assume all strides are divisible by 128 bits except the last stride
|
| 387 |
+
# Skip cute.assume() for stride=0 (broadcast dims from expand() are Python ints)
|
| 388 |
+
new_stride = lambda t: (*(cute.assume(s, divby=128 // t.element_type.width) if not isinstance(s, int) or s != 0 else s for s in t.stride[:-1]), t.stride[-1])
|
| 389 |
+
mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV = [cute.make_tensor(t.iterator, cute.make_layout(t.shape, stride=new_stride(t))) if t is not None else None for t in (mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV)]
|
| 390 |
+
self.varlen_q = (mCuSeqlensQ is not None)
|
| 391 |
+
self._setup_attributes()
|
| 392 |
+
SharedStorage = self._get_shared_storage_cls()
|
| 393 |
+
tiled_mma_sdp, tiled_mma_dkv, tiled_mma_dq = self._get_tiled_mma()
|
| 394 |
+
|
| 395 |
+
num_head = mQ.shape[1] if cutlass.const_expr(mCuSeqlensQ is not None) else mQ.shape[2]
|
| 396 |
+
|
| 397 |
+
if cutlass.const_expr(mCuSeqlensK is not None):
|
| 398 |
+
TileScheduler = SingleTileVarlenScheduler
|
| 399 |
+
num_batch = mCuSeqlensK.shape[0] - 1
|
| 400 |
+
else:
|
| 401 |
+
TileScheduler = SingleTileScheduler
|
| 402 |
+
num_batch = mK.shape[0]
|
| 403 |
+
|
| 404 |
+
# Uses seqlen k, etc. since main bwd kernel's blocks are over n
|
| 405 |
+
tile_sched_args = TileSchedulerArguments(
|
| 406 |
+
num_block=cute.ceil_div(mK.shape[1], self.n_block_size),
|
| 407 |
+
num_head=num_head,
|
| 408 |
+
num_batch=num_batch,
|
| 409 |
+
num_splits=1,
|
| 410 |
+
seqlen_k=0,
|
| 411 |
+
headdim=mK.shape[2],
|
| 412 |
+
headdim_v=mV.shape[2],
|
| 413 |
+
total_q=mK.shape[0],
|
| 414 |
+
tile_shape_mn=(self.n_block_size, self.m_block_size),
|
| 415 |
+
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if cutlass.const_expr(self.pack_gqa) else 1,
|
| 416 |
+
mCuSeqlensQ=mCuSeqlensK,
|
| 417 |
+
mSeqUsedQ=mSeqUsedK,
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 421 |
+
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 422 |
+
|
| 423 |
+
softmax_scale_log2 = softmax_scale * math.log2(math.e)
|
| 424 |
+
self.kernel(
|
| 425 |
+
mQ,
|
| 426 |
+
mK,
|
| 427 |
+
mV,
|
| 428 |
+
mdO,
|
| 429 |
+
mLSE,
|
| 430 |
+
mdPsum,
|
| 431 |
+
mdQaccum,
|
| 432 |
+
mdK,
|
| 433 |
+
mdV,
|
| 434 |
+
mCuSeqlensQ,
|
| 435 |
+
mCuSeqlensK,
|
| 436 |
+
mSeqUsedQ,
|
| 437 |
+
mSeqUsedK,
|
| 438 |
+
softmax_scale,
|
| 439 |
+
softmax_scale_log2,
|
| 440 |
+
self.sQ_layout,
|
| 441 |
+
self.sK_layout,
|
| 442 |
+
self.sV_layout,
|
| 443 |
+
self.sdO_layout,
|
| 444 |
+
self.sPdS_layout,
|
| 445 |
+
self.sLSE_layout,
|
| 446 |
+
self.sLSEMma_layout,
|
| 447 |
+
self.gmem_tiled_copy_QK,
|
| 448 |
+
self.gmem_tiled_copy_VdO,
|
| 449 |
+
self.gmem_tiled_copy_dK,
|
| 450 |
+
self.gmem_tiled_copy_dV,
|
| 451 |
+
self.gmem_tiled_copy_LSE,
|
| 452 |
+
self.gmem_tiled_copy_dQaccum,
|
| 453 |
+
tiled_mma_sdp,
|
| 454 |
+
tiled_mma_dkv,
|
| 455 |
+
tiled_mma_dq,
|
| 456 |
+
SharedStorage,
|
| 457 |
+
tile_sched_params,
|
| 458 |
+
TileScheduler,
|
| 459 |
+
).launch(
|
| 460 |
+
grid=grid_dim,
|
| 461 |
+
block=[self.num_threads, 1, 1],
|
| 462 |
+
smem=SharedStorage.size_in_bytes(),
|
| 463 |
+
stream=stream,
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
@cute.kernel
|
| 467 |
+
def kernel(
|
| 468 |
+
self,
|
| 469 |
+
mQ: cute.Tensor,
|
| 470 |
+
mK: cute.Tensor,
|
| 471 |
+
mV: cute.Tensor,
|
| 472 |
+
mdO: cute.Tensor,
|
| 473 |
+
mLSE: cute.Tensor,
|
| 474 |
+
mdPsum: cute.Tensor,
|
| 475 |
+
mdQaccum: cute.Tensor,
|
| 476 |
+
mdK: cute.Tensor,
|
| 477 |
+
mdV: cute.Tensor,
|
| 478 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 479 |
+
mCuSeqlensK: Optional[cute.Tensor],
|
| 480 |
+
mSeqUsedQ: Optional[cute.Tensor],
|
| 481 |
+
mSeqUsedK: Optional[cute.Tensor],
|
| 482 |
+
softmax_scale: cutlass.Float32,
|
| 483 |
+
softmax_scale_log2: cutlass.Float32,
|
| 484 |
+
sQ_layout: cute.ComposedLayout,
|
| 485 |
+
sK_layout: cute.ComposedLayout,
|
| 486 |
+
sV_layout: cute.ComposedLayout,
|
| 487 |
+
sdO_layout: cute.ComposedLayout,
|
| 488 |
+
sPdS_layout: cute.ComposedLayout,
|
| 489 |
+
sLSE_layout: cute.Layout,
|
| 490 |
+
sLSEMma_layout: cute.Layout,
|
| 491 |
+
gmem_tiled_copy_QK: cute.TiledCopy,
|
| 492 |
+
gmem_tiled_copy_VdO: cute.TiledCopy,
|
| 493 |
+
gmem_tiled_copy_dK: cute.TiledCopy,
|
| 494 |
+
gmem_tiled_copy_dV: cute.TiledCopy,
|
| 495 |
+
gmem_tiled_copy_LSE: cute.TiledCopy,
|
| 496 |
+
gmem_tiled_copy_dQaccum: cute.TiledCopy,
|
| 497 |
+
tiled_mma_sdp: cute.TiledMma,
|
| 498 |
+
tiled_mma_dkv: cute.TiledMma,
|
| 499 |
+
tiled_mma_dq: cute.TiledMma,
|
| 500 |
+
SharedStorage: cutlass.Constexpr,
|
| 501 |
+
tile_sched_params: ParamsBase,
|
| 502 |
+
TileScheduler: cutlass.Constexpr[Callable],
|
| 503 |
+
):
|
| 504 |
+
# Thread index, block index
|
| 505 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 506 |
+
|
| 507 |
+
tile_scheduler = TileScheduler.create(tile_sched_params)
|
| 508 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 509 |
+
|
| 510 |
+
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 511 |
+
|
| 512 |
+
if work_tile.is_valid_tile:
|
| 513 |
+
seqlen = SeqlenInfoQK.create(batch_idx, mQ.shape[1], mK.shape[1], mCuSeqlensQ=mCuSeqlensQ, mCuSeqlensK=mCuSeqlensK, mSeqUsedQ=mSeqUsedQ, mSeqUsedK=mSeqUsedK)
|
| 514 |
+
|
| 515 |
+
m_block_max = cute.ceil_div(seqlen.seqlen_q, self.m_block_size)
|
| 516 |
+
m_block_min = 0
|
| 517 |
+
if cutlass.const_expr(self.is_causal):
|
| 518 |
+
m_block_min = max(
|
| 519 |
+
(n_block * self.n_block_size + seqlen.seqlen_q - seqlen.seqlen_k) // self.m_block_size,
|
| 520 |
+
m_block_min,
|
| 521 |
+
)
|
| 522 |
+
# TODO: return early if m_block_max == 0
|
| 523 |
+
|
| 524 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 525 |
+
# Get the appropriate tiles for this thread block.
|
| 526 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 527 |
+
blkQ_shape = (self.m_block_size, self.head_dim_padded)
|
| 528 |
+
blkK_shape = (self.n_block_size, self.head_dim_padded)
|
| 529 |
+
blkV_shape = (self.n_block_size, self.head_dim_v_padded)
|
| 530 |
+
blkdO_shape = (self.m_block_size, self.head_dim_v_padded)
|
| 531 |
+
|
| 532 |
+
if cutlass.const_expr(not seqlen.has_cu_seqlens_q):
|
| 533 |
+
mQ_cur = mQ[batch_idx, None, head_idx, None]
|
| 534 |
+
mLSE_cur = mLSE[batch_idx, head_idx, None]
|
| 535 |
+
mdO_cur = mdO[batch_idx, None, head_idx, None]
|
| 536 |
+
mdPsum_cur = mdPsum[batch_idx, head_idx, None]
|
| 537 |
+
mdQaccum_cur = mdQaccum[batch_idx, head_idx, None]
|
| 538 |
+
else:
|
| 539 |
+
padded_offset_q = seqlen.offset_q + batch_idx * self.m_block_size
|
| 540 |
+
mQ_cur = cute.domain_offset((seqlen.offset_q, 0), mQ[None, head_idx, None])
|
| 541 |
+
mLSE_cur = cute.domain_offset((padded_offset_q,), mLSE[head_idx, None])
|
| 542 |
+
mdO_cur = cute.domain_offset((seqlen.offset_q, 0), mdO[None, head_idx, None])
|
| 543 |
+
mdPsum_cur = cute.domain_offset((padded_offset_q,), mdPsum[head_idx, None])
|
| 544 |
+
mdQaccum_cur = cute.domain_offset((padded_offset_q * self.head_dim_padded,), mdQaccum[head_idx, None])
|
| 545 |
+
head_idx_kv = head_idx // self.qhead_per_kvhead if cutlass.const_expr(not self.pack_gqa) else head_idx
|
| 546 |
+
|
| 547 |
+
if cutlass.const_expr(not seqlen.has_cu_seqlens_k):
|
| 548 |
+
mK_cur, mV_cur = [t[batch_idx, None, head_idx_kv, None] for t in (mK, mV)]
|
| 549 |
+
else:
|
| 550 |
+
mK_cur, mV_cur = [cute.domain_offset((seqlen.offset_k, 0), t[None, head_idx_kv, None]) for t in (mK, mV)]
|
| 551 |
+
|
| 552 |
+
# (m_block_size, head_dim, m_block)
|
| 553 |
+
gQ = cute.local_tile(mQ_cur, blkQ_shape, (None, 0))
|
| 554 |
+
# (n_block_size, head_dim)
|
| 555 |
+
gK = cute.local_tile(mK_cur, blkK_shape, (n_block, 0))
|
| 556 |
+
# (n_block_size, head_dim_v)
|
| 557 |
+
gV = cute.local_tile(mV_cur, blkV_shape, (n_block, 0))
|
| 558 |
+
# (m_block_size, head_dim_v, m_block)
|
| 559 |
+
gdO = cute.local_tile(mdO_cur, blkdO_shape, (None, 0))
|
| 560 |
+
gLSE = cute.local_tile(mLSE_cur, (self.m_block_size,), (None,))
|
| 561 |
+
gdPsum = cute.local_tile(mdPsum_cur, (self.m_block_size,), (None,))
|
| 562 |
+
gdQaccum = cute.local_tile(mdQaccum_cur, (self.m_block_size * self.head_dim_padded,), (None,))
|
| 563 |
+
|
| 564 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 565 |
+
# Get shared memory buffer
|
| 566 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 567 |
+
smem = cutlass.utils.SmemAllocator()
|
| 568 |
+
storage = smem.allocate(SharedStorage)
|
| 569 |
+
sQ = storage.sQ.get_tensor(sQ_layout)
|
| 570 |
+
sK = storage.sK.get_tensor(sK_layout)
|
| 571 |
+
if cutlass.const_expr(not self.share_QV_smem):
|
| 572 |
+
sV = storage.sV.get_tensor(sV_layout)
|
| 573 |
+
else:
|
| 574 |
+
sV = cute.make_tensor(cute.recast_ptr(sQ.iterator, dtype=self.dtype), sV_layout)
|
| 575 |
+
sdO = storage.sdO.get_tensor(sdO_layout)
|
| 576 |
+
sP = storage.sP.get_tensor(sPdS_layout)
|
| 577 |
+
sdS = storage.sdS.get_tensor(sPdS_layout)
|
| 578 |
+
sLSE = storage.sLSE.get_tensor(sLSE_layout)
|
| 579 |
+
sdPsum = storage.sdPsum.get_tensor(sLSE_layout)
|
| 580 |
+
sLSEMma = storage.sLSE.get_tensor(sLSEMma_layout)
|
| 581 |
+
sdPsumMma = storage.sdPsum.get_tensor(sLSEMma_layout)
|
| 582 |
+
|
| 583 |
+
# Transpose view of tensors for tiled mma
|
| 584 |
+
sQt, sdOt, sKt, sPt, sdSt = [utils.transpose_view(t) for t in (sQ, sdO, sK, sP, sdS)]
|
| 585 |
+
|
| 586 |
+
gmem_thr_copy_QK = gmem_tiled_copy_QK.get_slice(tidx)
|
| 587 |
+
gmem_thr_copy_VdO = gmem_tiled_copy_VdO.get_slice(tidx)
|
| 588 |
+
gmem_thr_copy_lse = gmem_tiled_copy_LSE.get_slice(tidx)
|
| 589 |
+
gmem_thr_copy_dQaccum = gmem_tiled_copy_dQaccum.get_slice(tidx)
|
| 590 |
+
# (CPY_Atom, CPY_M, CPY_K, m_block)
|
| 591 |
+
tQgQ = gmem_thr_copy_QK.partition_S(gQ)
|
| 592 |
+
tQsQ = gmem_thr_copy_QK.partition_D(sQ)
|
| 593 |
+
# (CPY_Atom, CPY_N, CPY_K)
|
| 594 |
+
tKgK = gmem_thr_copy_QK.partition_S(gK)
|
| 595 |
+
tKsK = gmem_thr_copy_QK.partition_D(sK)
|
| 596 |
+
# (CPY_Atom, CPY_N, CPY_K)
|
| 597 |
+
tVgV = gmem_thr_copy_VdO.partition_S(gV)
|
| 598 |
+
tVsV = gmem_thr_copy_VdO.partition_D(sV)
|
| 599 |
+
# (CPY_Atom, CPY_M, CPY_K, m_block)
|
| 600 |
+
tdOgdO = gmem_thr_copy_VdO.partition_S(gdO)
|
| 601 |
+
tdOsdO = gmem_thr_copy_VdO.partition_D(sdO)
|
| 602 |
+
tLSEgLSE = gmem_thr_copy_lse.partition_S(gLSE)
|
| 603 |
+
tLSEsLSE = gmem_thr_copy_lse.partition_D(sLSE)
|
| 604 |
+
tLSEgdPsum = gmem_thr_copy_lse.partition_S(gdPsum)
|
| 605 |
+
tLSEsdPsum = gmem_thr_copy_lse.partition_D(sdPsum)
|
| 606 |
+
tdQgdQaccum = gmem_thr_copy_dQaccum.partition_S(gdQaccum)
|
| 607 |
+
|
| 608 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 609 |
+
# Tile MMA compute thread partitions and allocate accumulators
|
| 610 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 611 |
+
thr_mma_sdp = tiled_mma_sdp.get_slice(tidx)
|
| 612 |
+
thr_mma_dkv = tiled_mma_dkv.get_slice(tidx)
|
| 613 |
+
thr_mma_dq = tiled_mma_dq.get_slice(tidx)
|
| 614 |
+
acc_shape_dK = thr_mma_dkv.partition_shape_C((self.n_block_size, self.head_dim_padded))
|
| 615 |
+
acc_shape_dV = thr_mma_dkv.partition_shape_C((self.n_block_size, self.head_dim_v_padded))
|
| 616 |
+
acc_dK = cute.make_fragment(acc_shape_dK, cutlass.Float32)
|
| 617 |
+
acc_dV = cute.make_fragment(acc_shape_dV, cutlass.Float32)
|
| 618 |
+
acc_dK.fill(0.0)
|
| 619 |
+
acc_dV.fill(0.0)
|
| 620 |
+
|
| 621 |
+
tSrQ = utils.mma_make_fragment_A(sQ[None, None, 0], thr_mma_sdp, swapAB=self.SdP_swapAB)
|
| 622 |
+
tSrK = utils.mma_make_fragment_B(sK, thr_mma_sdp, swapAB=self.SdP_swapAB)
|
| 623 |
+
tdPrdO = utils.mma_make_fragment_A(sdO[None, None, 0], thr_mma_sdp, swapAB=self.SdP_swapAB)
|
| 624 |
+
tdPrV = utils.mma_make_fragment_B(sV, thr_mma_sdp, swapAB=self.SdP_swapAB)
|
| 625 |
+
tdVrP = utils.mma_make_fragment_A(sPt, thr_mma_dkv, swapAB=self.dKV_swapAB)
|
| 626 |
+
tdVrdO = utils.mma_make_fragment_B(sdOt[None, None, 0], thr_mma_dkv, swapAB=self.dKV_swapAB)
|
| 627 |
+
tdKrdS = utils.mma_make_fragment_A(sdSt, thr_mma_dkv, swapAB=self.dKV_swapAB)
|
| 628 |
+
tdKrQ = utils.mma_make_fragment_B(sQt[None, None, 0], thr_mma_dkv, swapAB=self.dKV_swapAB)
|
| 629 |
+
tdQrdS = utils.mma_make_fragment_A(sdS, thr_mma_dq, swapAB=self.dQ_swapAB)
|
| 630 |
+
tdQrK = utils.mma_make_fragment_B(sKt, thr_mma_dq, swapAB=self.dQ_swapAB)
|
| 631 |
+
|
| 632 |
+
LSEslice = (None, 0, None) if cutlass.const_expr(not self.SdP_swapAB) else (0, None, None)
|
| 633 |
+
tSsLSEMma = utils.make_acc_tensor_mn_view(thr_mma_sdp.partition_C(sLSEMma))[LSEslice]
|
| 634 |
+
tSsdPsumMma = utils.make_acc_tensor_mn_view(thr_mma_sdp.partition_C(sdPsumMma))[LSEslice]
|
| 635 |
+
|
| 636 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 637 |
+
# Smem copy atom tiling
|
| 638 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 639 |
+
smem_copy_atom = cute.make_copy_atom(
|
| 640 |
+
warp.LdMatrix8x8x16bOp(transpose=False, num_matrices=4), self.dtype,
|
| 641 |
+
)
|
| 642 |
+
smem_copy_atom_transposed = cute.make_copy_atom(
|
| 643 |
+
warp.LdMatrix8x8x16bOp(transpose=True, num_matrices=4), self.dtype,
|
| 644 |
+
)
|
| 645 |
+
smem_thr_copy_QdO = utils.make_tiled_copy_A(
|
| 646 |
+
smem_copy_atom, tiled_mma_sdp, swapAB=self.SdP_swapAB
|
| 647 |
+
).get_slice(tidx)
|
| 648 |
+
smem_thr_copy_KV = utils.make_tiled_copy_B(
|
| 649 |
+
smem_copy_atom, tiled_mma_sdp, swapAB=self.SdP_swapAB
|
| 650 |
+
).get_slice(tidx)
|
| 651 |
+
# TODO: should this be smem_copy_atom_transposed?
|
| 652 |
+
smem_thr_copy_PdSt = utils.make_tiled_copy_A(
|
| 653 |
+
smem_copy_atom_transposed, tiled_mma_dkv, swapAB=self.dKV_swapAB
|
| 654 |
+
).get_slice(tidx)
|
| 655 |
+
smem_thr_copy_QdOt = utils.make_tiled_copy_B(
|
| 656 |
+
smem_copy_atom_transposed, tiled_mma_dkv, swapAB=self.dKV_swapAB
|
| 657 |
+
).get_slice(tidx)
|
| 658 |
+
smem_thr_copy_dS = utils.make_tiled_copy_A(
|
| 659 |
+
smem_copy_atom, tiled_mma_dq, swapAB=self.dQ_swapAB
|
| 660 |
+
).get_slice(tidx)
|
| 661 |
+
smem_thr_copy_Kt = utils.make_tiled_copy_B(
|
| 662 |
+
smem_copy_atom_transposed, tiled_mma_dq, swapAB=self.dQ_swapAB
|
| 663 |
+
).get_slice(tidx)
|
| 664 |
+
# TODO: what's the number of bits? What if SdP_swapAB
|
| 665 |
+
r2s_thr_copy_PdS = cute.make_tiled_copy_C(
|
| 666 |
+
cute.make_copy_atom(
|
| 667 |
+
cute.nvgpu.CopyUniversalOp(), self.dtype, num_bits_per_copy=2 * self.dtype.width
|
| 668 |
+
),
|
| 669 |
+
tiled_mma_sdp,
|
| 670 |
+
).get_slice(tidx)
|
| 671 |
+
|
| 672 |
+
tSsQ = smem_thr_copy_QdO.partition_S(sQ)
|
| 673 |
+
tdPsdO = smem_thr_copy_QdO.partition_S(sdO)
|
| 674 |
+
tSsK = smem_thr_copy_KV.partition_S(sK)
|
| 675 |
+
tdPsV = smem_thr_copy_KV.partition_S(sV)
|
| 676 |
+
tdVsPt = smem_thr_copy_PdSt.partition_S(sPt)
|
| 677 |
+
tdKsdSt = smem_thr_copy_PdSt.partition_S(sdSt)
|
| 678 |
+
tdVsdOt = smem_thr_copy_QdOt.partition_S(sdOt)
|
| 679 |
+
tdKsQt = smem_thr_copy_QdOt.partition_S(sQt)
|
| 680 |
+
tdQsdS = smem_thr_copy_dS.partition_S(sdS)
|
| 681 |
+
tdQsKt = smem_thr_copy_Kt.partition_S(sKt)
|
| 682 |
+
tPsP = r2s_thr_copy_PdS.partition_D(sP)
|
| 683 |
+
tdSsdS = r2s_thr_copy_PdS.partition_D(sdS)
|
| 684 |
+
|
| 685 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 686 |
+
# Predicate: Mark indices that need to copy when problem_shape isn't a multiple
|
| 687 |
+
# of tile_shape
|
| 688 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 689 |
+
# Construct identity layout for KV
|
| 690 |
+
cQ = cute.make_identity_tensor((self.m_block_size, self.head_dim_padded))
|
| 691 |
+
tQcQ = gmem_thr_copy_QK.partition_S(cQ)
|
| 692 |
+
t0QcQ = gmem_thr_copy_QK.get_slice(0).partition_S(cQ)
|
| 693 |
+
if cutlass.const_expr(self.head_dim_padded == self.head_dim_v_padded):
|
| 694 |
+
tdOcdO = tQcQ
|
| 695 |
+
t0dOcdO = t0QcQ
|
| 696 |
+
else:
|
| 697 |
+
cdO = cute.make_identity_tensor((self.m_block_size, self.head_dim_v_padded))
|
| 698 |
+
tdOcdO = gmem_thr_copy_VdO.partition_S(cdO)
|
| 699 |
+
t0dOcdO = gmem_thr_copy_VdO.get_slice(0).partition_S(cdO)
|
| 700 |
+
cLSE = cute.make_identity_tensor((self.m_block_size,))
|
| 701 |
+
tLSEcLSE = gmem_thr_copy_lse.partition_S(cLSE)
|
| 702 |
+
|
| 703 |
+
# Allocate predicate tensors for m and n, here we only allocate the tile of k, and
|
| 704 |
+
# use "if" on the mn dimension.
|
| 705 |
+
# This is to reduce register pressure and gets 2-3% performance gain.
|
| 706 |
+
|
| 707 |
+
d_head = mQ.shape[cute.rank(mQ) - 1]
|
| 708 |
+
d_head_v = mdO.shape[cute.rank(mdO) - 1]
|
| 709 |
+
|
| 710 |
+
tQpQ = utils.predicate_k(tQcQ, limit=d_head)
|
| 711 |
+
if cutlass.const_expr(self.same_hdim_kv):
|
| 712 |
+
tdOpdO = tQpQ
|
| 713 |
+
else:
|
| 714 |
+
tdOpdO = utils.predicate_k(tdOcdO, limit=d_head_v)
|
| 715 |
+
|
| 716 |
+
# group parameters for compute_one_m_block
|
| 717 |
+
mma_params = SimpleNamespace(
|
| 718 |
+
thr_mma_sdp=thr_mma_sdp, thr_mma_dkv=thr_mma_dkv, thr_mma_dq=thr_mma_dq,
|
| 719 |
+
tSrQ=tSrQ, tSrK=tSrK, tdPrdO=tdPrdO, tdPrV=tdPrV,
|
| 720 |
+
tdVrP=tdVrP, tdVrdO=tdVrdO, tdKrdS=tdKrdS, tdKrQ=tdKrQ,
|
| 721 |
+
tdQrdS=tdQrdS, tdQrK=tdQrK,
|
| 722 |
+
acc_dK=acc_dK, acc_dV=acc_dV,
|
| 723 |
+
)
|
| 724 |
+
smem_copy_params = SimpleNamespace(
|
| 725 |
+
smem_thr_copy_QdO=smem_thr_copy_QdO,
|
| 726 |
+
smem_thr_copy_KV=smem_thr_copy_KV,
|
| 727 |
+
smem_thr_copy_PdSt=smem_thr_copy_PdSt,
|
| 728 |
+
smem_thr_copy_QdOt=smem_thr_copy_QdOt,
|
| 729 |
+
smem_thr_copy_dS=smem_thr_copy_dS,
|
| 730 |
+
smem_thr_copy_Kt=smem_thr_copy_Kt,
|
| 731 |
+
r2s_thr_copy_PdS=r2s_thr_copy_PdS,
|
| 732 |
+
tSsQ=tSsQ, tSsK=tSsK, tdPsdO=tdPsdO, tdPsV=tdPsV,
|
| 733 |
+
tSsLSEMma=tSsLSEMma, tSsdPsumMma=tSsdPsumMma,
|
| 734 |
+
tPsP=tPsP, tdSsdS=tdSsdS,
|
| 735 |
+
tdVsPt=tdVsPt, tdVsdOt=tdVsdOt, tdKsdSt=tdKsdSt, tdKsQt=tdKsQt,
|
| 736 |
+
tdQsdS=tdQsdS, tdQsKt=tdQsKt,
|
| 737 |
+
)
|
| 738 |
+
gmem_copy_params = SimpleNamespace(
|
| 739 |
+
gmem_thr_copy_dQaccum=gmem_thr_copy_dQaccum, tdQgdQaccum=tdQgdQaccum
|
| 740 |
+
)
|
| 741 |
+
load_Q_LSE = partial(
|
| 742 |
+
self.load_Q_LSE, gmem_tiled_copy_QK, gmem_tiled_copy_LSE,
|
| 743 |
+
tQgQ, tQsQ, tQcQ, t0QcQ, tQpQ,
|
| 744 |
+
tLSEgLSE, tLSEsLSE, tLSEcLSE, seqlen=seqlen.seqlen_q
|
| 745 |
+
)
|
| 746 |
+
load_dO_dPsum = partial(
|
| 747 |
+
self.load_dO_dPsum, gmem_tiled_copy_VdO, gmem_tiled_copy_LSE,
|
| 748 |
+
tdOgdO, tdOsdO, tdOcdO, t0dOcdO, tdOpdO,
|
| 749 |
+
tLSEgdPsum, tLSEsdPsum, tLSEcLSE, seqlen=seqlen.seqlen_q
|
| 750 |
+
)
|
| 751 |
+
compute_one_m_block = partial(
|
| 752 |
+
self.compute_one_m_block, mma_params=mma_params,
|
| 753 |
+
smem_copy_params=smem_copy_params, gmem_copy_params=gmem_copy_params,
|
| 754 |
+
load_Q_LSE=load_Q_LSE, load_dO_dPsum=load_dO_dPsum,
|
| 755 |
+
m_block_max=m_block_max,
|
| 756 |
+
softmax_scale_log2=softmax_scale_log2,
|
| 757 |
+
)
|
| 758 |
+
|
| 759 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 760 |
+
# Prologue
|
| 761 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 762 |
+
# Start async loads of the last mn-tile, where we take care of the mn residue
|
| 763 |
+
self.load_V(gmem_thr_copy_VdO, tVgV, tVsV, n_block, seqlen=seqlen.seqlen_k,
|
| 764 |
+
headdim=d_head_v)
|
| 765 |
+
if cutlass.const_expr(self.V_in_regs):
|
| 766 |
+
cute.arch.cp_async_commit_group()
|
| 767 |
+
self.load_K(gmem_thr_copy_QK, tKgK, tKsK, n_block, seqlen=seqlen.seqlen_k,
|
| 768 |
+
headdim=d_head)
|
| 769 |
+
cute.arch.cp_async_commit_group()
|
| 770 |
+
|
| 771 |
+
if cutlass.const_expr(self.V_in_regs):
|
| 772 |
+
cute.arch.cp_async_wait_group(1)
|
| 773 |
+
cute.arch.barrier()
|
| 774 |
+
tdPrV_copy_view = smem_thr_copy_KV.retile(tdPrV)
|
| 775 |
+
cute.copy(smem_thr_copy_KV, tdPsV, tdPrV_copy_view)
|
| 776 |
+
# Sync to avoid loading Q to smem_q, which overlaps with smem_v
|
| 777 |
+
cute.arch.barrier()
|
| 778 |
+
|
| 779 |
+
m_block = m_block_min
|
| 780 |
+
assert self.num_stages_Q >= self.num_stages_dO
|
| 781 |
+
for stage in cutlass.range_constexpr(self.num_stages_Q):
|
| 782 |
+
if cutlass.const_expr(self.num_stages_Q == 1 or stage < self.num_stages_Q - 1):
|
| 783 |
+
if stage == 0 or m_block + stage < m_block_max:
|
| 784 |
+
load_Q_LSE(m_block + stage, smem_pipe_write_q=stage)
|
| 785 |
+
cute.arch.cp_async_commit_group()
|
| 786 |
+
if cutlass.const_expr(stage < self.num_stages_dO):
|
| 787 |
+
if stage == 0 or m_block + stage < m_block_max:
|
| 788 |
+
load_dO_dPsum(m_block + stage, smem_pipe_write_q=stage)
|
| 789 |
+
cute.arch.cp_async_commit_group()
|
| 790 |
+
|
| 791 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 792 |
+
# Mainloop
|
| 793 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 794 |
+
# Start processing of the first n-block.
|
| 795 |
+
mask = AttentionMask(self.m_block_size, self.n_block_size, seqlen.seqlen_q, seqlen.seqlen_k)
|
| 796 |
+
mask_fn = partial(
|
| 797 |
+
mask.apply_mask, n_block=n_block, thr_mma=thr_mma_sdp,
|
| 798 |
+
mask_seqlen=True, mask_causal=self.is_causal
|
| 799 |
+
)
|
| 800 |
+
smem_pipe_read_q = cutlass.Int32(0)
|
| 801 |
+
smem_pipe_read_do = cutlass.Int32(0)
|
| 802 |
+
smem_pipe_write_q = cutlass.Int32(self.num_stages_Q - 1)
|
| 803 |
+
smem_pipe_write_do = cutlass.Int32(0)
|
| 804 |
+
for m_tile in cutlass.range(m_block_min, m_block_max, unroll=1):
|
| 805 |
+
compute_one_m_block(
|
| 806 |
+
m_tile, smem_pipe_read_q, smem_pipe_read_do, smem_pipe_write_q, smem_pipe_write_do,
|
| 807 |
+
mask_fn=mask_fn,
|
| 808 |
+
)
|
| 809 |
+
smem_pipe_read_q = self.advance_pipeline(smem_pipe_read_q, self.num_stages_Q)
|
| 810 |
+
smem_pipe_read_do = self.advance_pipeline(smem_pipe_read_do, self.num_stages_dO)
|
| 811 |
+
smem_pipe_write_q = self.advance_pipeline(smem_pipe_write_q, self.num_stages_Q)
|
| 812 |
+
smem_pipe_write_do = self.advance_pipeline(smem_pipe_write_do, self.num_stages_dO)
|
| 813 |
+
|
| 814 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 815 |
+
# Epilogue
|
| 816 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 817 |
+
# If GQA, we scale dK in the postprocessing kernel instead
|
| 818 |
+
if cutlass.const_expr(self.qhead_per_kvhead == 1):
|
| 819 |
+
acc_dK.store(acc_dK.load() * softmax_scale)
|
| 820 |
+
# reuse sK and sV data iterator
|
| 821 |
+
sdK = cute.make_tensor(sK.iterator, sK_layout)
|
| 822 |
+
sdV = cute.make_tensor(sV.iterator, sV_layout)
|
| 823 |
+
self.epilogue(
|
| 824 |
+
acc_dK, acc_dV, mdK, mdV, sdK, sdV,
|
| 825 |
+
gmem_tiled_copy_dK, gmem_tiled_copy_dV, tiled_mma_dkv,
|
| 826 |
+
tidx, n_block, head_idx, batch_idx, seqlen, d_head, d_head_v
|
| 827 |
+
)
|
| 828 |
+
|
| 829 |
+
@cute.jit
|
| 830 |
+
def compute_one_m_block(
|
| 831 |
+
self,
|
| 832 |
+
m_block: cutlass.Int32,
|
| 833 |
+
smem_pipe_read_q: cutlass.Int32,
|
| 834 |
+
smem_pipe_read_do: cutlass.Int32,
|
| 835 |
+
smem_pipe_write_q: cutlass.Int32,
|
| 836 |
+
smem_pipe_write_do: cutlass.Int32,
|
| 837 |
+
mma_params: SimpleNamespace,
|
| 838 |
+
smem_copy_params: SimpleNamespace,
|
| 839 |
+
gmem_copy_params: SimpleNamespace,
|
| 840 |
+
load_Q_LSE: Callable,
|
| 841 |
+
load_dO_dPsum: Callable,
|
| 842 |
+
m_block_max: cutlass.Int32,
|
| 843 |
+
softmax_scale_log2: cutlass.Float32,
|
| 844 |
+
mask_fn: Optional[Callable] = None,
|
| 845 |
+
):
|
| 846 |
+
def load_Q_next():
|
| 847 |
+
m_block_next = m_block + (self.num_stages_Q - 1 if cutlass.const_expr(self.num_stages_Q > 1) else 1)
|
| 848 |
+
if m_block_next < m_block_max:
|
| 849 |
+
load_Q_LSE(m_block_next, smem_pipe_write_q)
|
| 850 |
+
cute.arch.cp_async_commit_group()
|
| 851 |
+
|
| 852 |
+
def load_dO_next():
|
| 853 |
+
if m_block + self.num_stages_dO < m_block_max:
|
| 854 |
+
load_dO_dPsum(m_block + self.num_stages_dO, smem_pipe_write_do)
|
| 855 |
+
cute.arch.cp_async_commit_group()
|
| 856 |
+
|
| 857 |
+
# MMA S
|
| 858 |
+
acc_shape_SdP = mma_params.thr_mma_sdp.partition_shape_C(
|
| 859 |
+
(self.m_block_size, self.n_block_size) if cutlass.const_expr(not self.SdP_swapAB) else (self.n_block_size, self.m_block_size)
|
| 860 |
+
)
|
| 861 |
+
acc_S = cute.make_fragment(acc_shape_SdP, cutlass.Float32)
|
| 862 |
+
acc_S.fill(0.0)
|
| 863 |
+
cute.arch.cp_async_wait_group(1 if cutlass.const_expr(self.num_stages_Q > 1) else 0)
|
| 864 |
+
cute.arch.barrier()
|
| 865 |
+
sm80_utils.gemm(
|
| 866 |
+
mma_params.thr_mma_sdp, acc_S, mma_params.tSrQ, mma_params.tSrK,
|
| 867 |
+
smem_copy_params.tSsQ[None, None, None, smem_pipe_read_q if cutlass.const_expr(self.num_stages_Q > 1) else 0],
|
| 868 |
+
smem_copy_params.tSsK,
|
| 869 |
+
smem_copy_params.smem_thr_copy_QdO, smem_copy_params.smem_thr_copy_KV,
|
| 870 |
+
swap_AB=self.SdP_swapAB,
|
| 871 |
+
)
|
| 872 |
+
tLSErLSE = cute.make_fragment_like(smem_copy_params.tSsLSEMma[None, 0])
|
| 873 |
+
cute.autovec_copy(
|
| 874 |
+
smem_copy_params.tSsLSEMma[None, smem_pipe_read_q if cutlass.const_expr(self.num_stages_Q > 1) else 0], tLSErLSE
|
| 875 |
+
)
|
| 876 |
+
if cutlass.const_expr(mask_fn is not None):
|
| 877 |
+
mask_fn(acc_S, m_block=m_block)
|
| 878 |
+
acc_S_mn = utils.make_acc_tensor_mn_view(acc_S)
|
| 879 |
+
bidx = 0
|
| 880 |
+
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_S_mn)
|
| 881 |
+
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == 1: cute.print_tensor(tLSErLSE)
|
| 882 |
+
assert cute.size(acc_S_mn, mode=[0]) == cute.size(tLSErLSE)
|
| 883 |
+
for r in cutlass.range(cute.size(acc_S_mn, mode=[0]), unroll_full=True):
|
| 884 |
+
acc_S_mn[r, None].store(utils.exp2f(acc_S_mn[r, None].load() * softmax_scale_log2 - tLSErLSE[r]))
|
| 885 |
+
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_S_mn)
|
| 886 |
+
|
| 887 |
+
# MMA dP
|
| 888 |
+
acc_dP = cute.make_fragment(acc_shape_SdP, cutlass.Float32)
|
| 889 |
+
acc_dP.fill(0.0)
|
| 890 |
+
cute.arch.cp_async_wait_group(1 if cutlass.const_expr(self.num_stages_dO > 1) else 0)
|
| 891 |
+
cute.arch.barrier()
|
| 892 |
+
sm80_utils.gemm(
|
| 893 |
+
mma_params.thr_mma_sdp, acc_dP, mma_params.tdPrdO, mma_params.tdPrV,
|
| 894 |
+
smem_copy_params.tdPsdO[None, None, None, smem_pipe_read_do if cutlass.const_expr(self.num_stages_dO > 1) else 0],
|
| 895 |
+
smem_copy_params.tdPsV,
|
| 896 |
+
smem_copy_params.smem_thr_copy_QdO, smem_copy_params.smem_thr_copy_KV,
|
| 897 |
+
hook_fn=load_Q_next if cutlass.const_expr(self.num_stages_Q > 1) else None,
|
| 898 |
+
swap_AB=self.SdP_swapAB,
|
| 899 |
+
)
|
| 900 |
+
tLSErdPsum = cute.make_fragment_like(smem_copy_params.tSsdPsumMma[None, 0])
|
| 901 |
+
cute.autovec_copy(
|
| 902 |
+
smem_copy_params.tSsdPsumMma[None, smem_pipe_read_do if cutlass.const_expr(self.num_stages_dO > 1) else 0], tLSErdPsum
|
| 903 |
+
)
|
| 904 |
+
acc_dP_mn = utils.make_acc_tensor_mn_view(acc_dP)
|
| 905 |
+
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_dP_mn)
|
| 906 |
+
assert cute.size(acc_dP_mn, mode=[0]) == cute.size(tLSErdPsum)
|
| 907 |
+
for r in cutlass.range(cute.size(acc_dP_mn, mode=[0]), unroll_full=True):
|
| 908 |
+
acc_dP_mn[r, None].store(acc_S_mn[r, None].load() * (acc_dP_mn[r, None].load() - tLSErdPsum[r]))
|
| 909 |
+
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_dP_mn)
|
| 910 |
+
rP = cute.make_fragment_like(acc_S, self.dtype)
|
| 911 |
+
rP.store(acc_S.load().to(self.dtype))
|
| 912 |
+
if cutlass.const_expr(not self.Mma_dKV_is_RS):
|
| 913 |
+
tPrP = smem_copy_params.r2s_thr_copy_PdS.retile(rP) # ((Atom,AtomNum), MMA_N, MMA_N)
|
| 914 |
+
cute.copy(smem_copy_params.r2s_thr_copy_PdS, tPrP, smem_copy_params.tPsP)
|
| 915 |
+
rdS = cute.make_fragment_like(acc_dP, self.dtype)
|
| 916 |
+
rdS.store(acc_dP.load().to(self.dtype))
|
| 917 |
+
if cutlass.const_expr(not self.Mma_dKV_is_RS):
|
| 918 |
+
cute.arch.barrier() # Make sure P is written
|
| 919 |
+
# For hdim 64, It's faster to write to smem_dS first before the dV gemm
|
| 920 |
+
if cutlass.const_expr(not self.Mma_dKV_is_RS):
|
| 921 |
+
tdSrdS = smem_copy_params.r2s_thr_copy_PdS.retile(rdS)
|
| 922 |
+
cute.copy(smem_copy_params.r2s_thr_copy_PdS, tdSrdS, smem_copy_params.tdSsdS)
|
| 923 |
+
if cutlass.const_expr(self.Mma_dKV_is_RS):
|
| 924 |
+
tdVrP = cute.make_tensor(rP.iterator, utils.convert_layout_acc_frgA(rP.layout))
|
| 925 |
+
else:
|
| 926 |
+
tdVrP = mma_params.tdVrP
|
| 927 |
+
|
| 928 |
+
# MMA dK
|
| 929 |
+
sm80_utils.gemm(
|
| 930 |
+
mma_params.thr_mma_dkv, mma_params.acc_dV, tdVrP, mma_params.tdVrdO,
|
| 931 |
+
smem_copy_params.tdVsPt,
|
| 932 |
+
smem_copy_params.tdVsdOt[None, None, None, smem_pipe_read_do if cutlass.const_expr(self.num_stages_dO > 1) else 0],
|
| 933 |
+
smem_copy_params.smem_thr_copy_PdSt, smem_copy_params.smem_thr_copy_QdOt,
|
| 934 |
+
A_in_regs=self.Mma_dKV_is_RS,
|
| 935 |
+
swap_AB=self.dKV_swapAB,
|
| 936 |
+
)
|
| 937 |
+
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(mma_params.acc_dV)
|
| 938 |
+
cute.arch.barrier() # Make sure dS is written
|
| 939 |
+
|
| 940 |
+
# MMA dQ
|
| 941 |
+
def dQ_mma(hook_fn):
|
| 942 |
+
acc_shape_dQ = mma_params.thr_mma_dq.partition_shape_C(
|
| 943 |
+
(self.m_block_size, self.head_dim_padded) if cutlass.const_expr(not self.dQ_swapAB) else (self.head_dim_padded, self.m_block_size)
|
| 944 |
+
)
|
| 945 |
+
acc_dQ = cute.make_fragment(acc_shape_dQ, cutlass.Float32)
|
| 946 |
+
acc_dQ.fill(0.0)
|
| 947 |
+
sm80_utils.gemm(
|
| 948 |
+
mma_params.thr_mma_dq, acc_dQ, mma_params.tdQrdS, mma_params.tdQrK,
|
| 949 |
+
smem_copy_params.tdQsdS, smem_copy_params.tdQsKt,
|
| 950 |
+
smem_copy_params.smem_thr_copy_dS, smem_copy_params.smem_thr_copy_Kt,
|
| 951 |
+
swap_AB=self.dQ_swapAB,
|
| 952 |
+
hook_fn=hook_fn
|
| 953 |
+
)
|
| 954 |
+
# ((1, 1), num_elements)
|
| 955 |
+
acc_dQ_atomic = gmem_copy_params.gmem_thr_copy_dQaccum.retile(acc_dQ)
|
| 956 |
+
tdQgdQaccum_atomic = gmem_copy_params.tdQgdQaccum[None, None, m_block]
|
| 957 |
+
assert cute.size(acc_dQ_atomic) == cute.size(tdQgdQaccum_atomic)
|
| 958 |
+
for i in cutlass.range(cute.size(acc_dQ_atomic), unroll_full=True):
|
| 959 |
+
utils.atomic_add_fp32(acc_dQ_atomic[i], utils.elem_pointer(tdQgdQaccum_atomic, i))
|
| 960 |
+
# utils.atomic_add_fp32(acc_dQ[i], tdQgdQaccum_atomic.iterator + i * tdQgdQaccum_atomic.stride[1])
|
| 961 |
+
# if cute.arch.thread_idx()[0] == 64 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_dQ)
|
| 962 |
+
|
| 963 |
+
# If num_stages_Q == 1, we want to do Mma_dK first so we can start loading Q for the next iteration
|
| 964 |
+
if cutlass.const_expr(self.num_stages_Q > 1):
|
| 965 |
+
dQ_mma(load_dO_next)
|
| 966 |
+
|
| 967 |
+
# MMA dK
|
| 968 |
+
if cutlass.const_expr(self.Mma_dKV_is_RS):
|
| 969 |
+
tdKrdS = cute.make_tensor(rdS.iterator, utils.convert_layout_acc_frgA(rdS.layout))
|
| 970 |
+
else:
|
| 971 |
+
tdKrdS = mma_params.tdKrdS
|
| 972 |
+
sm80_utils.gemm(
|
| 973 |
+
mma_params.thr_mma_dkv, mma_params.acc_dK, tdKrdS, mma_params.tdKrQ,
|
| 974 |
+
smem_copy_params.tdKsdSt,
|
| 975 |
+
smem_copy_params.tdKsQt[None, None, None, smem_pipe_read_q if cutlass.const_expr(self.num_stages_Q > 1) else 0],
|
| 976 |
+
smem_copy_params.smem_thr_copy_PdSt, smem_copy_params.smem_thr_copy_QdOt,
|
| 977 |
+
A_in_regs=self.Mma_dKV_is_RS,
|
| 978 |
+
swap_AB=self.dKV_swapAB,
|
| 979 |
+
hook_fn=load_dO_next if cutlass.const_expr(self.num_stages_Q == 1) else None,
|
| 980 |
+
)
|
| 981 |
+
# if cute.arch.thread_idx()[0] == 0: cute.print_tensor(mma_params.acc_dK)
|
| 982 |
+
if cutlass.const_expr(self.num_stages_Q == 1):
|
| 983 |
+
cute.arch.barrier()
|
| 984 |
+
dQ_mma(load_Q_next)
|
| 985 |
+
|
| 986 |
+
@cute.jit
|
| 987 |
+
def epilogue(
|
| 988 |
+
self,
|
| 989 |
+
acc_dK: cute.Tensor,
|
| 990 |
+
acc_dV: cute.Tensor,
|
| 991 |
+
mdK: cute.Tensor,
|
| 992 |
+
mdV: cute.Tensor,
|
| 993 |
+
sdK: cute.Tensor,
|
| 994 |
+
sdV: cute.Tensor,
|
| 995 |
+
gmem_tiled_copy_dK: cute.TiledCopy,
|
| 996 |
+
gmem_tiled_copy_dV: cute.TiledCopy,
|
| 997 |
+
tiled_mma: cute.TiledMma,
|
| 998 |
+
tidx: cutlass.Int32,
|
| 999 |
+
n_block: cutlass.Int32,
|
| 1000 |
+
num_head: cutlass.Int32,
|
| 1001 |
+
batch_size: cutlass.Int32,
|
| 1002 |
+
seqlen: SeqlenInfoQK,
|
| 1003 |
+
d_head: cutlass.Int32,
|
| 1004 |
+
d_head_v: cutlass.Int32
|
| 1005 |
+
):
|
| 1006 |
+
rdV = cute.make_fragment_like(acc_dV, self.dtype)
|
| 1007 |
+
rdV.store(acc_dV.load().to(self.dtype))
|
| 1008 |
+
rdK = cute.make_fragment_like(acc_dK, self.dtype)
|
| 1009 |
+
rdK.store(acc_dK.load().to(self.dtype))
|
| 1010 |
+
gmem_thr_copy_dK = gmem_tiled_copy_dK.get_slice(tidx)
|
| 1011 |
+
gmem_thr_copy_dV = gmem_tiled_copy_dV.get_slice(tidx)
|
| 1012 |
+
|
| 1013 |
+
batch_idx = batch_size
|
| 1014 |
+
head_idx_kv = num_head // self.qhead_per_kvhead if cutlass.const_expr(not self.pack_gqa) else num_head
|
| 1015 |
+
|
| 1016 |
+
if cutlass.const_expr(self.qhead_per_kvhead == 1):
|
| 1017 |
+
# Make sure all threads have finished reading K and V, otherwise we get racy dQ
|
| 1018 |
+
# because smem_q could be changed.
|
| 1019 |
+
cute.arch.barrier()
|
| 1020 |
+
# smem copy atom for dKV
|
| 1021 |
+
smem_copy_atom_dKV = cute.make_copy_atom(
|
| 1022 |
+
cute.nvgpu.CopyUniversalOp(), self.dtype, num_bits_per_copy=2 * self.dtype.width
|
| 1023 |
+
)
|
| 1024 |
+
smem_thr_copy_dKV = cute.make_tiled_copy_C(smem_copy_atom_dKV, tiled_mma).get_slice(tidx)
|
| 1025 |
+
taccdVrdV = smem_thr_copy_dKV.retile(rdV)
|
| 1026 |
+
taccdKrdK = smem_thr_copy_dKV.retile(rdK)
|
| 1027 |
+
taccdVsdV = smem_thr_copy_dKV.partition_D(sdV)
|
| 1028 |
+
taccdKsdK = smem_thr_copy_dKV.partition_D(sdK)
|
| 1029 |
+
# copy acc O from rmem to smem with the smem copy atom
|
| 1030 |
+
cute.copy(smem_copy_atom_dKV, taccdVrdV, taccdVsdV)
|
| 1031 |
+
cute.copy(smem_copy_atom_dKV, taccdKrdK, taccdKsdK)
|
| 1032 |
+
|
| 1033 |
+
|
| 1034 |
+
if cutlass.const_expr(not seqlen.has_cu_seqlens_k):
|
| 1035 |
+
mdK_cur, mdV_cur = [t[batch_idx, None, head_idx_kv, None] for t in (mdK, mdV)]
|
| 1036 |
+
else:
|
| 1037 |
+
mdK_cur, mdV_cur = [cute.domain_offset((seqlen.offset_k, 0), t[None, head_idx_kv, None]) for t in (mdK, mdV)]
|
| 1038 |
+
|
| 1039 |
+
blkdK_shape = (self.n_block_size, self.head_dim_padded)
|
| 1040 |
+
blkdV_shape = (self.n_block_size, self.head_dim_v_padded)
|
| 1041 |
+
gdK = cute.local_tile(mdK_cur, blkdK_shape, (n_block, 0))
|
| 1042 |
+
gdV = cute.local_tile(mdV_cur, blkdV_shape, (n_block, 0))
|
| 1043 |
+
tdKsdK = gmem_thr_copy_dK.partition_S(sdK)
|
| 1044 |
+
tdKgdK = gmem_thr_copy_dK.partition_D(gdK)
|
| 1045 |
+
tdVsdV = gmem_thr_copy_dV.partition_S(sdV)
|
| 1046 |
+
tdVgdV = gmem_thr_copy_dV.partition_D(gdV)
|
| 1047 |
+
tdKrdK = cute.make_fragment_like(tdKgdK, self.dtype)
|
| 1048 |
+
tdVrdV = cute.make_fragment_like(tdVgdV, self.dtype)
|
| 1049 |
+
# sync before all smem stores are done.
|
| 1050 |
+
cute.arch.barrier()
|
| 1051 |
+
# load acc dK and dV from smem to rmem for wider vectorization
|
| 1052 |
+
# Need to check OOB when reading from smem if kBlockN isn't evenly tiled
|
| 1053 |
+
# TODO
|
| 1054 |
+
cute.autovec_copy(tdKsdK, tdKrdK)
|
| 1055 |
+
cute.autovec_copy(tdVsdV, tdVrdV)
|
| 1056 |
+
|
| 1057 |
+
cdK = cute.make_identity_tensor((self.n_block_size, self.head_dim_padded))
|
| 1058 |
+
tdKcdK = gmem_thr_copy_dK.partition_S(cdK)
|
| 1059 |
+
t0dKcdK = gmem_tiled_copy_dK.get_slice(0).partition_S(cdK)
|
| 1060 |
+
if cutlass.const_expr(self.head_dim_padded == self.head_dim_v_padded):
|
| 1061 |
+
tdVcdV = tdKcdK
|
| 1062 |
+
t0dVcdV = t0dKcdK
|
| 1063 |
+
else:
|
| 1064 |
+
cdV = cute.make_identity_tensor((self.n_block_size, self.head_dim_v_padded))
|
| 1065 |
+
tdVcdV = gmem_thr_copy_dV.partition_S(cdV)
|
| 1066 |
+
t0dVcdV = gmem_tiled_copy_dV.get_slice(0).partition_S(cdV)
|
| 1067 |
+
tdKpdK = utils.predicate_k(tdKcdK, limit=d_head)
|
| 1068 |
+
if cutlass.const_expr(self.same_hdim_kv):
|
| 1069 |
+
tdVpdV = tdKpdK
|
| 1070 |
+
else:
|
| 1071 |
+
tdVpdV = utils.predicate_k(tdVcdV, limit=d_head_v)
|
| 1072 |
+
# copy acc dK and acc_dV from rmem to gmem
|
| 1073 |
+
for rest_m in cutlass.range_constexpr(cute.size(tdKrdK.shape[1])):
|
| 1074 |
+
if t0dKcdK[0, rest_m, 0][0] < seqlen.seqlen_k - n_block * self.n_block_size - tdKcdK[0][0]:
|
| 1075 |
+
cute.copy(
|
| 1076 |
+
gmem_tiled_copy_dK,
|
| 1077 |
+
tdKrdK[None, rest_m, None],
|
| 1078 |
+
tdKgdK[None, rest_m, None],
|
| 1079 |
+
pred=tdKpdK[None, rest_m, None] if cutlass.const_expr(self.check_hdim_oob) else None,
|
| 1080 |
+
)
|
| 1081 |
+
for rest_m in cutlass.range_constexpr(cute.size(tdVrdV.shape[1])):
|
| 1082 |
+
if t0dVcdV[0, rest_m, 0][0] < seqlen.seqlen_k - n_block * self.n_block_size - tdVcdV[0][0]:
|
| 1083 |
+
cute.copy(
|
| 1084 |
+
gmem_tiled_copy_dV,
|
| 1085 |
+
tdVrdV[None, rest_m, None],
|
| 1086 |
+
tdVgdV[None, rest_m, None],
|
| 1087 |
+
pred=tdVpdV[None, rest_m, None] if cutlass.const_expr(self.check_hdim_v_oob) else None,
|
| 1088 |
+
)
|
| 1089 |
+
|
| 1090 |
+
else: # qhead_per_kvhead > 1, do atomic add
|
| 1091 |
+
# For Sm90, we need to sync to avoid racy writes to smem_q
|
| 1092 |
+
# For Sm80, we don't need to sync since we're not touching smem
|
| 1093 |
+
head_idx_kv = num_head // self.qhead_per_kvhead if cutlass.const_expr(not self.pack_gqa) else num_head
|
| 1094 |
+
|
| 1095 |
+
if cutlass.const_expr(not seqlen.has_cu_seqlens_k):
|
| 1096 |
+
mdK_cur, mdV_cur = [t[batch_idx, head_idx_kv, None] for t in (mdK, mdV)]
|
| 1097 |
+
else:
|
| 1098 |
+
padded_offset_k = seqlen.offset_k + batch_idx * self.n_block_size
|
| 1099 |
+
mdK_cur = cute.domain_offset((padded_offset_k * self.head_dim_padded,), mdK[head_idx_kv, None])
|
| 1100 |
+
mdV_cur = cute.domain_offset((padded_offset_k * self.head_dim_v_padded,), mdV[head_idx_kv, None])
|
| 1101 |
+
|
| 1102 |
+
gdV = cute.local_tile(mdV_cur, (self.n_block_size * self.head_dim_v_padded,), (n_block,))
|
| 1103 |
+
gdK = cute.local_tile(mdK_cur, (self.n_block_size * self.head_dim_padded,), (n_block,))
|
| 1104 |
+
tdVgdVaccum = gmem_thr_copy_dV.partition_S(gdV)
|
| 1105 |
+
tdKgdKaccum = gmem_thr_copy_dK.partition_S(gdK)
|
| 1106 |
+
acc_dV_atomic = gmem_thr_copy_dV.retile(acc_dV)
|
| 1107 |
+
acc_dK_atomic = gmem_thr_copy_dK.retile(acc_dK)
|
| 1108 |
+
assert cute.size(acc_dV_atomic) == cute.size(tdVgdVaccum)
|
| 1109 |
+
assert cute.size(acc_dK_atomic) == cute.size(tdKgdKaccum)
|
| 1110 |
+
for i in cutlass.range(cute.size(acc_dV_atomic), unroll_full=True):
|
| 1111 |
+
utils.atomic_add_fp32(acc_dV_atomic[i], utils.elem_pointer(tdVgdVaccum, i))
|
| 1112 |
+
for i in cutlass.range(cute.size(acc_dK_atomic), unroll_full=True):
|
| 1113 |
+
utils.atomic_add_fp32(acc_dK_atomic[i], utils.elem_pointer(tdKgdKaccum, i))
|
| 1114 |
+
|
| 1115 |
+
@cute.jit
|
| 1116 |
+
def advance_pipeline(self, pipeline_index, num_stages: cutlass.Constexpr):
|
| 1117 |
+
return pipeline_index + 1 if pipeline_index < num_stages - 1 else 0
|
| 1118 |
+
|
| 1119 |
+
@cute.jit
|
| 1120 |
+
def load_K(
|
| 1121 |
+
self,
|
| 1122 |
+
gmem_thr_copy: cute.TiledCopy,
|
| 1123 |
+
tKgK: cute.Tensor,
|
| 1124 |
+
tKsK: cute.Tensor,
|
| 1125 |
+
block: cutlass.Int32,
|
| 1126 |
+
seqlen: cutlass.Int32,
|
| 1127 |
+
headdim: cutlass.Int32,
|
| 1128 |
+
):
|
| 1129 |
+
cK = cute.make_identity_tensor((self.n_block_size, self.head_dim_padded))
|
| 1130 |
+
tKcK = gmem_thr_copy.partition_S(cK)
|
| 1131 |
+
t0KcK = gmem_thr_copy.get_slice(0).partition_S(cK)
|
| 1132 |
+
tKpK = utils.predicate_k(tKcK, limit=headdim)
|
| 1133 |
+
for n in cutlass.range_constexpr(cute.size(tKsK.shape[1])):
|
| 1134 |
+
# If kBlockN doesn't evenly divide the tiled copy, only the last `n` needs to be checked
|
| 1135 |
+
if self.is_even_n_smem_k or n < cute.size(tKsK.shape[1]) - 1 or tKcK[0, n, 0][0] < self.n_block_size:
|
| 1136 |
+
# Instead of using tKcK, we using t0KcK and subtract the offset from the limit
|
| 1137 |
+
# (seqlen - block * kBlockN). This is because the entries of t0KcK are known at compile time.
|
| 1138 |
+
predicate_n = t0KcK[0, n, 0][0] < seqlen - block * self.n_block_size - tKcK[0][0]
|
| 1139 |
+
predicate = cute.make_fragment_like(tKpK[None, 0, None])
|
| 1140 |
+
for k in cutlass.range_constexpr(cute.size(predicate.shape[1])):
|
| 1141 |
+
for i in cutlass.range_constexpr(cute.size(predicate.shape[0])):
|
| 1142 |
+
predicate[i, k] = (tKpK[i, n, k] if cutlass.const_expr(self.check_hdim_oob) else True) and predicate_n
|
| 1143 |
+
cute.copy(
|
| 1144 |
+
gmem_thr_copy, tKgK[None, n, None], tKsK[None, n, None], pred=predicate,
|
| 1145 |
+
)
|
| 1146 |
+
# We need to clear the sK smem tiles since we'll use sKt for mma_dq
|
| 1147 |
+
|
| 1148 |
+
@cute.jit
|
| 1149 |
+
def load_V(
|
| 1150 |
+
self,
|
| 1151 |
+
gmem_thr_copy: cute.TiledCopy,
|
| 1152 |
+
tVgV: cute.Tensor,
|
| 1153 |
+
tVsV: cute.Tensor,
|
| 1154 |
+
block: cutlass.Int32,
|
| 1155 |
+
seqlen: cutlass.Int32,
|
| 1156 |
+
headdim: cutlass.Int32,
|
| 1157 |
+
):
|
| 1158 |
+
cV = cute.make_identity_tensor((self.n_block_size, self.head_dim_v_padded))
|
| 1159 |
+
tVcV = gmem_thr_copy.partition_S(cV)
|
| 1160 |
+
t0VcV = gmem_thr_copy.get_slice(0).partition_S(cV)
|
| 1161 |
+
tVpV = utils.predicate_k(tVcV, limit=headdim)
|
| 1162 |
+
for n in cutlass.range_constexpr(cute.size(tVsV.shape[1])):
|
| 1163 |
+
# If kBlockN doesn't evenly divide the tiled copy, only the last `n` needs to be checked
|
| 1164 |
+
if self.is_even_n_smem_v or n < cute.size(tVsV.shape[1]) - 1 or tVcV[0, n, 0][0] < self.n_block_size:
|
| 1165 |
+
# Instead of using tVcV, we using t0VcV and subtract the offset from the limit
|
| 1166 |
+
# (seqlen - block * kBlockN). This is because the entries of t0VcV are known at compile time.
|
| 1167 |
+
predicate_n = t0VcV[0, n, 0][0] < seqlen - block * self.n_block_size - tVcV[0][0]
|
| 1168 |
+
predicate = cute.make_fragment_like(tVpV[None, 0, None])
|
| 1169 |
+
for k in cutlass.range_constexpr(cute.size(predicate.shape[1])):
|
| 1170 |
+
for i in cutlass.range_constexpr(cute.size(predicate.shape[0])):
|
| 1171 |
+
predicate[i, k] = (tVpV[i, n, k] if cutlass.const_expr(self.check_hdim_oob) else True) and predicate_n
|
| 1172 |
+
cute.copy(
|
| 1173 |
+
gmem_thr_copy, tVgV[None, n, None], tVsV[None, n, None], pred=predicate,
|
| 1174 |
+
)
|
| 1175 |
+
|
| 1176 |
+
@cute.jit
|
| 1177 |
+
def load_Q_LSE(
|
| 1178 |
+
self,
|
| 1179 |
+
gmem_tiled_copy_Q: cute.TiledCopy,
|
| 1180 |
+
gmem_tiled_copy_LSE: cute.TiledCopy,
|
| 1181 |
+
tQgQ: cute.Tensor,
|
| 1182 |
+
tQsQ: cute.Tensor,
|
| 1183 |
+
tQcQ: cute.Tensor,
|
| 1184 |
+
t0QcQ: cute.Tensor,
|
| 1185 |
+
tQpQ: cute.Tensor,
|
| 1186 |
+
tLSEgLSE: cute.Tensor,
|
| 1187 |
+
tLSEsLSE: cute.Tensor,
|
| 1188 |
+
tLSEcLSE: cute.Tensor,
|
| 1189 |
+
block: cutlass.Int32,
|
| 1190 |
+
smem_pipe_write_q: cutlass.Int32,
|
| 1191 |
+
seqlen: cutlass.Int32,
|
| 1192 |
+
):
|
| 1193 |
+
for m in cutlass.range_constexpr(cute.size(tQsQ.shape[1])):
|
| 1194 |
+
# If kBlockM doesn't evenly divide the tiled copy, only the last `m` needs to be checked
|
| 1195 |
+
if self.is_even_m_smem_q or m < cute.size(tQsQ.shape[1]) - 1 or tQcQ[0, m, 0][0] < self.m_block_size:
|
| 1196 |
+
# Instead of using tQcQ, we using t0QcQ and subtract the offset from the limit
|
| 1197 |
+
# (seqlen - block * kBlockM). This is because the entries of t0QcQ are known at compile time.
|
| 1198 |
+
predicate_m = t0QcQ[0, m, 0][0] < seqlen - block * self.m_block_size - tQcQ[0][0]
|
| 1199 |
+
predicate = cute.make_fragment_like(tQpQ[None, 0, None])
|
| 1200 |
+
for k in cutlass.range_constexpr(cute.size(predicate.shape[1])):
|
| 1201 |
+
for i in cutlass.range_constexpr(cute.size(predicate.shape[0])):
|
| 1202 |
+
predicate[i, k] = (tQpQ[i, m, k] if cutlass.const_expr(self.check_hdim_oob) else True) and predicate_m
|
| 1203 |
+
cute.copy(
|
| 1204 |
+
gmem_tiled_copy_Q,
|
| 1205 |
+
tQgQ[None, m, None, block],
|
| 1206 |
+
tQsQ[None, m, None, smem_pipe_write_q if cutlass.const_expr(self.num_stages_Q) > 1 else 0],
|
| 1207 |
+
pred=predicate,
|
| 1208 |
+
)
|
| 1209 |
+
# We need to clear the sQ smem tiles since we'll use sQt for mma_dK
|
| 1210 |
+
# We made sure LSE length is padded so we read `kBlockM` elements so that all
|
| 1211 |
+
# elements in sLSE are filled. Without this we might have uninitialized sLSE values.
|
| 1212 |
+
for m in cutlass.range_constexpr(cute.size(tLSEsLSE.shape[1])):
|
| 1213 |
+
if tLSEcLSE[0, m][0] < self.m_block_size:
|
| 1214 |
+
cute.copy(
|
| 1215 |
+
gmem_tiled_copy_LSE,
|
| 1216 |
+
tLSEgLSE[None, m, block],
|
| 1217 |
+
tLSEsLSE[None, m, smem_pipe_write_q if cutlass.const_expr(self.num_stages_Q > 1) else 0],
|
| 1218 |
+
)
|
| 1219 |
+
|
| 1220 |
+
@cute.jit
|
| 1221 |
+
def load_dO_dPsum(
|
| 1222 |
+
self,
|
| 1223 |
+
gmem_tiled_copy_dO: cute.TiledCopy,
|
| 1224 |
+
gmem_tiled_copy_dPsum: cute.TiledCopy,
|
| 1225 |
+
tdOgdO: cute.Tensor,
|
| 1226 |
+
tdOsdO: cute.Tensor,
|
| 1227 |
+
tdOcdO: cute.Tensor,
|
| 1228 |
+
t0dOcdO: cute.Tensor,
|
| 1229 |
+
tdOpdO: cute.Tensor,
|
| 1230 |
+
tdPsumgdPsum: cute.Tensor,
|
| 1231 |
+
tdPsumsdPsum: cute.Tensor,
|
| 1232 |
+
tdPsumcdPsum: cute.Tensor,
|
| 1233 |
+
block: cutlass.Int32,
|
| 1234 |
+
smem_pipe_write_q: cutlass.Int32,
|
| 1235 |
+
seqlen: cutlass.Int32,
|
| 1236 |
+
):
|
| 1237 |
+
for m in cutlass.range_constexpr(cute.size(tdOsdO.shape[1])):
|
| 1238 |
+
# If kBlockM doesn't evenly divide the tiled copy, only the last `m` needs to be checked
|
| 1239 |
+
if self.is_even_m_smem_do or m < cute.size(tdOsdO.shape[1]) - 1 or tdOcdO[0, m, 0][0] < self.m_block_size:
|
| 1240 |
+
# Instead of using tdOcdO, we using t0dOcdO and subtract the offset from the limit
|
| 1241 |
+
# (seqlen - block * kBlockM). This is because the entries of t0dOcdO are known at compile time.
|
| 1242 |
+
predicate_m = t0dOcdO[0, m, 0][0] < seqlen - block * self.m_block_size - tdOcdO[0][0]
|
| 1243 |
+
predicate = cute.make_fragment_like(tdOpdO[None, 0, None])
|
| 1244 |
+
for k in cutlass.range_constexpr(cute.size(predicate.shape[1])):
|
| 1245 |
+
for i in cutlass.range_constexpr(cute.size(predicate.shape[0])):
|
| 1246 |
+
predicate[i, k] = (tdOpdO[i, m, k] if cutlass.const_expr(self.check_hdim_oob) else True) and predicate_m
|
| 1247 |
+
cute.copy(
|
| 1248 |
+
gmem_tiled_copy_dO,
|
| 1249 |
+
tdOgdO[None, m, None, block],
|
| 1250 |
+
tdOsdO[None, m, None, smem_pipe_write_q if cutlass.const_expr(self.num_stages_dO > 1) else 0],
|
| 1251 |
+
pred=predicate,
|
| 1252 |
+
)
|
| 1253 |
+
# We need to clear the sQ smem tiles since we'll use sQt for mma_dK
|
| 1254 |
+
# We made sure LSE length is padded so we read `kBlockM` elements so that all
|
| 1255 |
+
# elements in sLSE are filled. Without this we might have uninitialized sLSE values.
|
| 1256 |
+
for m in cutlass.range_constexpr(cute.size(tdPsumgdPsum.shape[1])):
|
| 1257 |
+
if tdPsumcdPsum[0, m][0] < self.m_block_size:
|
| 1258 |
+
cute.copy(
|
| 1259 |
+
gmem_tiled_copy_dPsum,
|
| 1260 |
+
tdPsumgdPsum[None, m, block],
|
| 1261 |
+
tdPsumsdPsum[None, m, smem_pipe_write_q if cutlass.const_expr(self.num_stages_dO > 1) else 0],
|
| 1262 |
+
)
|
kernel/deps/flash_attn/cute/flash_bwd_postprocess.py
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
# A reimplementation of https://github.com/Dao-AILab/flash-attention/blob/main/hopper/flash_bwd_postprocess_kernel.h
|
| 3 |
+
# from Cutlass C++ to Cute-DSL.
|
| 4 |
+
import math
|
| 5 |
+
from typing import Callable, Optional, Type, Literal
|
| 6 |
+
|
| 7 |
+
import cuda.bindings.driver as cuda
|
| 8 |
+
|
| 9 |
+
import cutlass
|
| 10 |
+
import cutlass.cute as cute
|
| 11 |
+
import cutlass.utils.hopper_helpers as sm90_utils_basic
|
| 12 |
+
import cutlass.utils.blackwell_helpers as sm100_utils_basic
|
| 13 |
+
from cutlass.cute.nvgpu import cpasync, warp, warpgroup
|
| 14 |
+
from cutlass import Float32, const_expr
|
| 15 |
+
from cutlass.utils import LayoutEnum
|
| 16 |
+
|
| 17 |
+
from flash_attn.cute import utils
|
| 18 |
+
from flash_attn.cute import copy_utils
|
| 19 |
+
from flash_attn.cute import ampere_helpers as sm80_utils
|
| 20 |
+
from flash_attn.cute import hopper_helpers as sm90_utils
|
| 21 |
+
from flash_attn.cute.seqlen_info import SeqlenInfoQK
|
| 22 |
+
import cutlass.cute.nvgpu.tcgen05 as tcgen05
|
| 23 |
+
from flash_attn.cute.tile_scheduler import (
|
| 24 |
+
ParamsBase,
|
| 25 |
+
SingleTileScheduler,
|
| 26 |
+
SingleTileVarlenScheduler,
|
| 27 |
+
TileSchedulerArguments,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class FlashAttentionBackwardPostprocess:
|
| 32 |
+
def __init__(
|
| 33 |
+
self,
|
| 34 |
+
dtype: Type[cutlass.Numeric],
|
| 35 |
+
head_dim: int,
|
| 36 |
+
arch: Literal[80, 90, 100],
|
| 37 |
+
tile_m: int = 128,
|
| 38 |
+
num_threads: int = 256,
|
| 39 |
+
AtomLayoutMdQ: int = 1,
|
| 40 |
+
dQ_swapAB: bool = False,
|
| 41 |
+
):
|
| 42 |
+
"""
|
| 43 |
+
:param head_dim: head dimension
|
| 44 |
+
:type head_dim: int
|
| 45 |
+
:param tile_m: m block size
|
| 46 |
+
:type tile_m: int
|
| 47 |
+
"""
|
| 48 |
+
self.dtype = dtype
|
| 49 |
+
self.tile_m = tile_m
|
| 50 |
+
assert arch in [80, 90, 100], (
|
| 51 |
+
"Only Ampere (80), Hopper (90), and Blackwell (100) are supported"
|
| 52 |
+
)
|
| 53 |
+
self.arch = arch
|
| 54 |
+
# padding head_dim to a multiple of 32 as k_block_size
|
| 55 |
+
hdim_multiple_of = 32
|
| 56 |
+
self.tile_hdim = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 57 |
+
self.check_hdim_oob = head_dim != self.tile_hdim
|
| 58 |
+
self.num_threads = num_threads
|
| 59 |
+
self.AtomLayoutMdQ = AtomLayoutMdQ
|
| 60 |
+
self.dQ_swapAB = dQ_swapAB
|
| 61 |
+
|
| 62 |
+
@staticmethod
|
| 63 |
+
def can_implement(dtype, head_dim, tile_m, num_threads) -> bool:
|
| 64 |
+
"""Check if the kernel can be implemented with the given parameters.
|
| 65 |
+
|
| 66 |
+
:param dtype: data type
|
| 67 |
+
:type dtype: cutlass.Numeric
|
| 68 |
+
:param head_dim: head dimension
|
| 69 |
+
:type head_dim: int
|
| 70 |
+
:param tile_m: m block size
|
| 71 |
+
:type tile_m: int
|
| 72 |
+
|
| 73 |
+
:return: True if the kernel can be implemented, False otherwise
|
| 74 |
+
:rtype: bool
|
| 75 |
+
"""
|
| 76 |
+
if dtype not in [cutlass.Float16, cutlass.BFloat16]:
|
| 77 |
+
return False
|
| 78 |
+
if head_dim % 8 != 0:
|
| 79 |
+
return False
|
| 80 |
+
if num_threads % 32 != 0:
|
| 81 |
+
return False
|
| 82 |
+
return True
|
| 83 |
+
|
| 84 |
+
def _get_tiled_mma(self):
|
| 85 |
+
if const_expr(self.arch == 80):
|
| 86 |
+
num_mma_warps = self.num_threads // 32
|
| 87 |
+
atom_layout_dQ = (
|
| 88 |
+
(self.AtomLayoutMdQ, num_mma_warps // self.AtomLayoutMdQ, 1)
|
| 89 |
+
if const_expr(not self.dQ_swapAB)
|
| 90 |
+
else (num_mma_warps // self.AtomLayoutMdQ, self.AtomLayoutMdQ, 1)
|
| 91 |
+
)
|
| 92 |
+
tiled_mma = cute.make_tiled_mma(
|
| 93 |
+
warp.MmaF16BF16Op(self.dtype, Float32, (16, 8, 16)),
|
| 94 |
+
atom_layout_dQ,
|
| 95 |
+
permutation_mnk=(atom_layout_dQ[0] * 16, atom_layout_dQ[1] * 16, 16),
|
| 96 |
+
)
|
| 97 |
+
elif const_expr(self.arch == 90):
|
| 98 |
+
num_mma_warp_groups = self.num_threads // 128
|
| 99 |
+
atom_layout_dQ = (self.AtomLayoutMdQ, num_mma_warp_groups // self.AtomLayoutMdQ)
|
| 100 |
+
tiler_mn_dQ = (self.tile_m // atom_layout_dQ[0], self.tile_hdim // atom_layout_dQ[1])
|
| 101 |
+
tiled_mma = sm90_utils_basic.make_trivial_tiled_mma(
|
| 102 |
+
self.dtype,
|
| 103 |
+
self.dtype,
|
| 104 |
+
warpgroup.OperandMajorMode.K, # These don't matter, we only care about the accum
|
| 105 |
+
warpgroup.OperandMajorMode.K,
|
| 106 |
+
Float32,
|
| 107 |
+
atom_layout_mnk=(atom_layout_dQ if not self.dQ_swapAB else atom_layout_dQ[::-1])
|
| 108 |
+
+ (1,),
|
| 109 |
+
tiler_mn=tiler_mn_dQ if not self.dQ_swapAB else tiler_mn_dQ[::-1],
|
| 110 |
+
)
|
| 111 |
+
else:
|
| 112 |
+
cta_group = tcgen05.CtaGroup.ONE
|
| 113 |
+
tiled_mma = sm100_utils_basic.make_trivial_tiled_mma(
|
| 114 |
+
self.dtype,
|
| 115 |
+
tcgen05.OperandMajorMode.MN, # dS_major_mode
|
| 116 |
+
tcgen05.OperandMajorMode.MN, # Kt_major_mode
|
| 117 |
+
Float32,
|
| 118 |
+
cta_group,
|
| 119 |
+
(self.tile_m, self.tile_hdim),
|
| 120 |
+
)
|
| 121 |
+
if const_expr(self.arch in [80, 90]):
|
| 122 |
+
assert self.num_threads == tiled_mma.size
|
| 123 |
+
return tiled_mma
|
| 124 |
+
|
| 125 |
+
def _setup_attributes(self):
|
| 126 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 127 |
+
# GMEM Tiled copy:
|
| 128 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 129 |
+
# Thread layouts for copies
|
| 130 |
+
universal_copy_bits = 128
|
| 131 |
+
async_copy_elems_accum = universal_copy_bits // Float32.width
|
| 132 |
+
atom_async_copy_accum = cute.make_copy_atom(
|
| 133 |
+
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.GLOBAL),
|
| 134 |
+
Float32,
|
| 135 |
+
num_bits_per_copy=universal_copy_bits,
|
| 136 |
+
)
|
| 137 |
+
# We don't do bound checking for the gmem -> smem load so we just assert here.
|
| 138 |
+
assert (self.tile_m * self.tile_hdim // async_copy_elems_accum) % self.num_threads == 0
|
| 139 |
+
self.g2s_tiled_copy_dQaccum = cute.make_tiled_copy_tv(
|
| 140 |
+
atom_async_copy_accum,
|
| 141 |
+
cute.make_layout(self.num_threads),
|
| 142 |
+
cute.make_layout(async_copy_elems_accum),
|
| 143 |
+
)
|
| 144 |
+
num_s2r_copy_elems = 1 if const_expr(self.arch == 80) else 4
|
| 145 |
+
if const_expr(self.arch == 80):
|
| 146 |
+
self.s2r_tiled_copy_dQaccum = copy_utils.tiled_copy_1d(
|
| 147 |
+
Float32, self.num_threads, num_s2r_copy_elems
|
| 148 |
+
)
|
| 149 |
+
self.sdQaccum_layout = cute.make_layout(self.tile_m * self.tile_hdim)
|
| 150 |
+
elif const_expr(self.arch == 90):
|
| 151 |
+
num_threads_per_warp_group = 128
|
| 152 |
+
num_mma_warp_groups = self.num_threads // 128
|
| 153 |
+
self.s2r_tiled_copy_dQaccum = cute.make_tiled_copy_tv(
|
| 154 |
+
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128),
|
| 155 |
+
cute.make_layout((num_threads_per_warp_group, num_mma_warp_groups)), # thr_layout
|
| 156 |
+
cute.make_layout(128 // Float32.width), # val_layout
|
| 157 |
+
)
|
| 158 |
+
self.sdQaccum_layout = cute.make_layout(
|
| 159 |
+
(self.tile_m * self.tile_hdim // num_mma_warp_groups, num_mma_warp_groups)
|
| 160 |
+
)
|
| 161 |
+
else:
|
| 162 |
+
self.dQ_reduce_ncol = 32
|
| 163 |
+
dQaccum_reduce_stage = self.tile_hdim // self.dQ_reduce_ncol
|
| 164 |
+
assert self.num_threads == 128 # TODO: currently hard-coded
|
| 165 |
+
self.s2r_tiled_copy_dQaccum = copy_utils.tiled_copy_1d(
|
| 166 |
+
Float32, self.num_threads, num_s2r_copy_elems
|
| 167 |
+
)
|
| 168 |
+
self.sdQaccum_layout = cute.make_layout(
|
| 169 |
+
(self.tile_m * self.tile_hdim // dQaccum_reduce_stage, dQaccum_reduce_stage)
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
self.gmem_tiled_copy_dQ = copy_utils.tiled_copy_2d(
|
| 173 |
+
self.dtype, self.tile_hdim, self.num_threads
|
| 174 |
+
)
|
| 175 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 176 |
+
# Shared memory layout: dQ
|
| 177 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 178 |
+
# We can't just use kHeadDim here. E.g. if MMA shape is 64 x 96 but split across 2 WGs,
|
| 179 |
+
# then setting kBlockKSmem to 32 will cause "Static shape_div failure".
|
| 180 |
+
# We want to treat it as 64 x 48, so kBlockKSmem should be 16.
|
| 181 |
+
mma_shape_n = self.tiled_mma.get_tile_size(1)
|
| 182 |
+
if const_expr(self.arch == 80):
|
| 183 |
+
sdQ_layout_atom = sm80_utils.get_smem_layout_atom(self.dtype, mma_shape_n)
|
| 184 |
+
self.sdQ_layout = cute.tile_to_shape(
|
| 185 |
+
sdQ_layout_atom, (self.tile_m, self.tile_hdim), (0, 1)
|
| 186 |
+
)
|
| 187 |
+
elif const_expr(self.arch == 90):
|
| 188 |
+
self.sdQ_layout = sm90_utils.make_smem_layout(
|
| 189 |
+
self.dtype, LayoutEnum.ROW_MAJOR, (self.tile_m, self.tile_hdim)
|
| 190 |
+
)
|
| 191 |
+
else:
|
| 192 |
+
# TODO: this is hard-coded for hdim 128
|
| 193 |
+
self.sdQ_layout = sm100_utils_basic.make_smem_layout_epi(
|
| 194 |
+
self.dtype, LayoutEnum.ROW_MAJOR, (self.tile_m, self.tile_hdim), 1
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
@cute.jit
|
| 198 |
+
def __call__(
|
| 199 |
+
self,
|
| 200 |
+
mdQaccum: cute.Tensor,
|
| 201 |
+
mdQ: cute.Tensor,
|
| 202 |
+
scale: cutlass.Float32,
|
| 203 |
+
stream: cuda.CUstream,
|
| 204 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 205 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 206 |
+
):
|
| 207 |
+
# Get the data type and check if it is fp16 or bf16
|
| 208 |
+
if const_expr(mdQ.element_type not in [cutlass.Float16, cutlass.BFloat16]):
|
| 209 |
+
raise TypeError("Only Float16 or BFloat16 is supported")
|
| 210 |
+
if const_expr(mdQaccum is not None):
|
| 211 |
+
if const_expr(mdQaccum.element_type not in [cutlass.Float32]):
|
| 212 |
+
raise TypeError("dQaccum tensor must be Float32")
|
| 213 |
+
|
| 214 |
+
# Assume all strides are divisible by 128 bits except the last stride
|
| 215 |
+
new_stride = lambda t: (
|
| 216 |
+
*(cute.assume(s, divby=128 // t.element_type.width) for s in t.stride[:-1]),
|
| 217 |
+
t.stride[-1],
|
| 218 |
+
)
|
| 219 |
+
mdQaccum, mdQ = [
|
| 220 |
+
cute.make_tensor(t.iterator, cute.make_layout(t.shape, stride=new_stride(t)))
|
| 221 |
+
for t in (mdQaccum, mdQ)
|
| 222 |
+
]
|
| 223 |
+
|
| 224 |
+
self.tiled_mma = self._get_tiled_mma()
|
| 225 |
+
self._setup_attributes()
|
| 226 |
+
|
| 227 |
+
smem_size = max(
|
| 228 |
+
cute.size_in_bytes(cutlass.Float32, self.sdQaccum_layout),
|
| 229 |
+
cute.size_in_bytes(self.dtype, self.sdQ_layout),
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
if const_expr(mCuSeqlensQ is not None):
|
| 233 |
+
TileScheduler = SingleTileVarlenScheduler
|
| 234 |
+
num_head = mdQ.shape[1]
|
| 235 |
+
num_batch = mCuSeqlensQ.shape[0] - 1
|
| 236 |
+
num_block = cute.ceil_div(mdQ.shape[0], self.tile_m)
|
| 237 |
+
else:
|
| 238 |
+
TileScheduler = SingleTileScheduler
|
| 239 |
+
num_head = mdQ.shape[2]
|
| 240 |
+
num_batch = mdQ.shape[0]
|
| 241 |
+
num_block = cute.ceil_div(mdQ.shape[1], self.tile_m)
|
| 242 |
+
|
| 243 |
+
tile_sched_args = TileSchedulerArguments(
|
| 244 |
+
num_block=num_block,
|
| 245 |
+
num_head=num_head,
|
| 246 |
+
num_batch=num_batch,
|
| 247 |
+
num_splits=1,
|
| 248 |
+
seqlen_k=0,
|
| 249 |
+
headdim=mdQ.shape[2],
|
| 250 |
+
headdim_v=0,
|
| 251 |
+
total_q=mdQ.shape[0],
|
| 252 |
+
tile_shape_mn=(self.tile_m, 1),
|
| 253 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 254 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 258 |
+
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 259 |
+
|
| 260 |
+
# grid_dim: (m_block, num_head, batch_size)
|
| 261 |
+
self.kernel(
|
| 262 |
+
mdQaccum,
|
| 263 |
+
mdQ,
|
| 264 |
+
mCuSeqlensQ,
|
| 265 |
+
mSeqUsedQ,
|
| 266 |
+
scale,
|
| 267 |
+
self.tiled_mma,
|
| 268 |
+
self.dQ_swapAB,
|
| 269 |
+
self.sdQaccum_layout,
|
| 270 |
+
self.sdQ_layout,
|
| 271 |
+
self.g2s_tiled_copy_dQaccum,
|
| 272 |
+
self.s2r_tiled_copy_dQaccum,
|
| 273 |
+
self.gmem_tiled_copy_dQ,
|
| 274 |
+
tile_sched_params,
|
| 275 |
+
TileScheduler,
|
| 276 |
+
).launch(
|
| 277 |
+
grid=grid_dim,
|
| 278 |
+
block=[self.num_threads, 1, 1],
|
| 279 |
+
smem=smem_size,
|
| 280 |
+
stream=stream,
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
@cute.kernel
|
| 284 |
+
def kernel(
|
| 285 |
+
self,
|
| 286 |
+
mdQaccum: cute.Tensor,
|
| 287 |
+
mdQ: cute.Tensor,
|
| 288 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 289 |
+
mSeqUsedQ: Optional[cute.Tensor],
|
| 290 |
+
scale: cutlass.Float32,
|
| 291 |
+
tiled_mma: cute.TiledMma,
|
| 292 |
+
dQ_swapAB: cutlass.Constexpr,
|
| 293 |
+
sdQaccum_layout: cute.Layout,
|
| 294 |
+
sdQ_layout: cute.ComposedLayout,
|
| 295 |
+
g2s_tiled_copy_dQaccum: cute.TiledCopy,
|
| 296 |
+
s2r_tiled_copy_dQaccum: cute.TiledCopy,
|
| 297 |
+
gmem_tiled_copy_dQ: cute.TiledCopy,
|
| 298 |
+
tile_sched_params: ParamsBase,
|
| 299 |
+
TileScheduler: cutlass.Constexpr[Callable],
|
| 300 |
+
):
|
| 301 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 302 |
+
# Get shared memory buffer
|
| 303 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 304 |
+
smem = cutlass.utils.SmemAllocator()
|
| 305 |
+
sdQaccum = smem.allocate_tensor(cutlass.Float32, sdQaccum_layout, byte_alignment=1024)
|
| 306 |
+
sdQaccum_flat = cute.make_tensor(sdQaccum.iterator, cute.make_layout(cute.size(sdQaccum)))
|
| 307 |
+
if const_expr(self.arch in [80, 90]):
|
| 308 |
+
sdQ = cute.make_tensor(cute.recast_ptr(sdQaccum.iterator, dtype=self.dtype), sdQ_layout)
|
| 309 |
+
else:
|
| 310 |
+
# extra stage dimension
|
| 311 |
+
sdQ = cute.make_tensor(
|
| 312 |
+
cute.recast_ptr(sdQaccum.iterator, sdQ_layout.inner, dtype=self.dtype),
|
| 313 |
+
sdQ_layout.outer,
|
| 314 |
+
)[None, None, 0]
|
| 315 |
+
sdQt = utils.transpose_view(sdQ)
|
| 316 |
+
|
| 317 |
+
# Thread index, block index
|
| 318 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 319 |
+
|
| 320 |
+
tile_scheduler = TileScheduler.create(tile_sched_params)
|
| 321 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 322 |
+
|
| 323 |
+
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 324 |
+
|
| 325 |
+
if work_tile.is_valid_tile:
|
| 326 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 327 |
+
# Get the appropriate tiles for this thread block.
|
| 328 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 329 |
+
|
| 330 |
+
seqlen = SeqlenInfoQK.create(
|
| 331 |
+
batch_idx,
|
| 332 |
+
mdQ.shape[1],
|
| 333 |
+
0,
|
| 334 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 335 |
+
mCuSeqlensK=None,
|
| 336 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 337 |
+
mSeqUsedK=None,
|
| 338 |
+
)
|
| 339 |
+
if const_expr(not seqlen.has_cu_seqlens_q):
|
| 340 |
+
mdQ_cur = mdQ[batch_idx, None, head_idx, None]
|
| 341 |
+
mdQaccum_cur = mdQaccum[batch_idx, head_idx, None]
|
| 342 |
+
head_dim = mdQ.shape[3]
|
| 343 |
+
else:
|
| 344 |
+
padded_offset_q = seqlen.offset_q + batch_idx * self.tile_m
|
| 345 |
+
if cutlass.const_expr(self.arch >= 90):
|
| 346 |
+
padded_offset_q = padded_offset_q // self.tile_m * self.tile_m
|
| 347 |
+
mdQ_cur = cute.domain_offset((seqlen.offset_q, 0), mdQ[None, head_idx, None])
|
| 348 |
+
mdQaccum_cur = cute.domain_offset(
|
| 349 |
+
(padded_offset_q * self.tile_hdim,), mdQaccum[head_idx, None]
|
| 350 |
+
)
|
| 351 |
+
head_dim = mdQ.shape[2]
|
| 352 |
+
|
| 353 |
+
# HACK: Compiler doesn't seem to recognize that padding
|
| 354 |
+
# by padded_offset_q * self.tile_hdim keeps alignment
|
| 355 |
+
# since statically divisible by 4
|
| 356 |
+
|
| 357 |
+
mdQaccum_cur_ptr = cute.make_ptr(
|
| 358 |
+
dtype=mdQaccum_cur.element_type,
|
| 359 |
+
value=mdQaccum_cur.iterator.toint(),
|
| 360 |
+
mem_space=mdQaccum_cur.iterator.memspace,
|
| 361 |
+
assumed_align=mdQaccum.iterator.alignment,
|
| 362 |
+
)
|
| 363 |
+
mdQaccum_cur = cute.make_tensor(mdQaccum_cur_ptr, mdQaccum_cur.layout)
|
| 364 |
+
|
| 365 |
+
gdQaccum = cute.local_tile(mdQaccum_cur, (self.tile_m * self.tile_hdim,), (m_block,))
|
| 366 |
+
gdQ = cute.local_tile(mdQ_cur, (self.tile_m, self.tile_hdim), (m_block, 0))
|
| 367 |
+
|
| 368 |
+
seqlen_q = seqlen.seqlen_q
|
| 369 |
+
seqlen_q_rounded = cute.round_up(seqlen_q, self.tile_m)
|
| 370 |
+
|
| 371 |
+
# Step 1: load dQaccum from gmem to smem
|
| 372 |
+
g2s_thr_copy_dQaccum = g2s_tiled_copy_dQaccum.get_slice(tidx)
|
| 373 |
+
tdQgdQaccum = g2s_thr_copy_dQaccum.partition_S(gdQaccum)
|
| 374 |
+
tdQsdQaccumg2s = g2s_thr_copy_dQaccum.partition_D(sdQaccum_flat)
|
| 375 |
+
cute.copy(g2s_tiled_copy_dQaccum, tdQgdQaccum, tdQsdQaccumg2s)
|
| 376 |
+
cute.arch.cp_async_commit_group()
|
| 377 |
+
cute.arch.cp_async_wait_group(0)
|
| 378 |
+
cute.arch.barrier()
|
| 379 |
+
|
| 380 |
+
# Step 2: load dQ from smem to rmem
|
| 381 |
+
s2r_thr_copy_dQaccum = s2r_tiled_copy_dQaccum.get_slice(tidx)
|
| 382 |
+
tdQsdQaccum = s2r_thr_copy_dQaccum.partition_S(sdQaccum)
|
| 383 |
+
tile_shape = (self.tile_m, self.tile_hdim)
|
| 384 |
+
acc = None
|
| 385 |
+
tiled_copy_t2r = None
|
| 386 |
+
if const_expr(self.arch in [80, 90]):
|
| 387 |
+
acc_shape = tiled_mma.partition_shape_C(
|
| 388 |
+
tile_shape if const_expr(not dQ_swapAB) else tile_shape[::-1]
|
| 389 |
+
)
|
| 390 |
+
acc = cute.make_fragment(acc_shape, cutlass.Float32)
|
| 391 |
+
assert cute.size(acc) == cute.size(tdQsdQaccum)
|
| 392 |
+
else:
|
| 393 |
+
thr_mma = tiled_mma.get_slice(0) # 1-CTA
|
| 394 |
+
dQacc_shape = tiled_mma.partition_shape_C((self.tile_m, self.tile_hdim))
|
| 395 |
+
tdQtdQ = tiled_mma.make_fragment_C(dQacc_shape)
|
| 396 |
+
tdQcdQ = thr_mma.partition_C(
|
| 397 |
+
cute.make_identity_tensor((self.tile_m, self.tile_hdim))
|
| 398 |
+
)
|
| 399 |
+
tmem_load_atom = cute.make_copy_atom(
|
| 400 |
+
tcgen05.copy.Ld32x32bOp(tcgen05.copy.Repetition(self.dQ_reduce_ncol)), Float32
|
| 401 |
+
)
|
| 402 |
+
tiled_copy_t2r = tcgen05.make_tmem_copy(tmem_load_atom, tdQtdQ)
|
| 403 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 404 |
+
tdQrdQ_t2r_shape = thr_copy_t2r.partition_D(tdQcdQ).shape
|
| 405 |
+
acc = cute.make_fragment(tdQrdQ_t2r_shape, Float32)
|
| 406 |
+
tdQrdQaccum = cute.make_tensor(acc.iterator, cute.make_layout(tdQsdQaccum.shape))
|
| 407 |
+
cute.autovec_copy(tdQsdQaccum, tdQrdQaccum)
|
| 408 |
+
# Convert tdQrdQaccum from fp32 to fp16/bf16
|
| 409 |
+
rdQ = cute.make_fragment_like(acc, self.dtype)
|
| 410 |
+
rdQ.store((acc.load() * scale).to(self.dtype))
|
| 411 |
+
|
| 412 |
+
# Step 3: Copy dQ from register to smem
|
| 413 |
+
cute.arch.barrier() # make sure all threads have finished loading dQaccum
|
| 414 |
+
if const_expr(self.arch in [80, 90]):
|
| 415 |
+
copy_atom_r2s_dQ = utils.get_smem_store_atom(
|
| 416 |
+
self.arch, self.dtype, transpose=self.dQ_swapAB
|
| 417 |
+
)
|
| 418 |
+
tiled_copy_r2s_dQ = cute.make_tiled_copy_C(copy_atom_r2s_dQ, tiled_mma)
|
| 419 |
+
else:
|
| 420 |
+
# copy_atom_r2s_dQ = sm100_utils_basic.get_smem_store_op(
|
| 421 |
+
# LayoutEnum.ROW_MAJOR, self.dtype, Float32, tiled_copy_t2r,
|
| 422 |
+
# )
|
| 423 |
+
# tiled_copy_r2s_dQ = cute.make_tiled_copy_D(copy_atom_r2s_dQ, tiled_copy_t2r)
|
| 424 |
+
thr_layout_r2s_dQ = cute.make_layout((self.num_threads, 1)) # 128 threads
|
| 425 |
+
val_layout_r2s_dQ = cute.make_layout((1, 128 // self.dtype.width))
|
| 426 |
+
copy_atom_r2s_dQ = cute.make_copy_atom(
|
| 427 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 428 |
+
self.dtype,
|
| 429 |
+
num_bits_per_copy=128,
|
| 430 |
+
)
|
| 431 |
+
tiled_copy_r2s_dQ = cute.make_tiled_copy_tv(
|
| 432 |
+
copy_atom_r2s_dQ, thr_layout_r2s_dQ, val_layout_r2s_dQ
|
| 433 |
+
)
|
| 434 |
+
thr_copy_r2s_dQ = tiled_copy_r2s_dQ.get_slice(tidx)
|
| 435 |
+
cdQ = cute.make_identity_tensor((self.tile_m, self.tile_hdim))
|
| 436 |
+
if const_expr(self.arch in [80, 90]):
|
| 437 |
+
taccdQrdQ = thr_copy_r2s_dQ.retile(rdQ)
|
| 438 |
+
else:
|
| 439 |
+
taccdQcdQ_shape = thr_copy_r2s_dQ.partition_S(cdQ).shape
|
| 440 |
+
taccdQrdQ = cute.make_tensor(rdQ.iterator, taccdQcdQ_shape)
|
| 441 |
+
taccdQsdQ = thr_copy_r2s_dQ.partition_D(sdQ if const_expr(not self.dQ_swapAB) else sdQt)
|
| 442 |
+
cute.copy(thr_copy_r2s_dQ, taccdQrdQ, taccdQsdQ)
|
| 443 |
+
|
| 444 |
+
# Step 4: Copy dQ from smem to register to prepare for coalesced write to gmem
|
| 445 |
+
cute.arch.barrier() # make sure all smem stores are done
|
| 446 |
+
gmem_thr_copy_dQ = gmem_tiled_copy_dQ.get_slice(tidx)
|
| 447 |
+
tdQgdQ = gmem_thr_copy_dQ.partition_S(gdQ)
|
| 448 |
+
tdQsdQ = gmem_thr_copy_dQ.partition_D(sdQ)
|
| 449 |
+
tdQrdQ = cute.make_fragment_like(tdQsdQ, self.dtype)
|
| 450 |
+
# TODO: check OOB when reading from smem if kBlockM isn't evenly tiled
|
| 451 |
+
cute.autovec_copy(tdQsdQ, tdQrdQ)
|
| 452 |
+
|
| 453 |
+
# Step 5: Copy dQ from register to gmem
|
| 454 |
+
tdQcdQ = gmem_thr_copy_dQ.partition_S(cdQ)
|
| 455 |
+
tdQpdQ = utils.predicate_k(tdQcdQ, limit=head_dim)
|
| 456 |
+
for rest_m in cutlass.range(cute.size(tdQrdQ.shape[1]), unroll_full=True):
|
| 457 |
+
if tdQcdQ[0, rest_m, 0][0] < seqlen_q - m_block * self.tile_m:
|
| 458 |
+
cute.copy(
|
| 459 |
+
gmem_tiled_copy_dQ,
|
| 460 |
+
tdQrdQ[None, rest_m, None],
|
| 461 |
+
tdQgdQ[None, rest_m, None],
|
| 462 |
+
pred=tdQpdQ[None, rest_m, None],
|
| 463 |
+
)
|
kernel/deps/flash_attn/cute/flash_bwd_preprocess.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
# A reimplementation of https://github.com/Dao-AILab/flash-attention/blob/main/hopper/flash_bwd_preprocess_kernel.h
|
| 3 |
+
# from Cutlass C++ to Cute-DSL.
|
| 4 |
+
import math
|
| 5 |
+
import operator
|
| 6 |
+
from typing import Callable, Type, Optional, Literal
|
| 7 |
+
|
| 8 |
+
import cuda.bindings.driver as cuda
|
| 9 |
+
|
| 10 |
+
import cutlass
|
| 11 |
+
import cutlass.cute as cute
|
| 12 |
+
from cutlass import Float32
|
| 13 |
+
|
| 14 |
+
from flash_attn.cute import utils
|
| 15 |
+
from flash_attn.cute import copy_utils
|
| 16 |
+
from flash_attn.cute.seqlen_info import SeqlenInfoQK
|
| 17 |
+
from flash_attn.cute.tile_scheduler import (
|
| 18 |
+
ParamsBase,
|
| 19 |
+
SingleTileScheduler,
|
| 20 |
+
SingleTileVarlenScheduler,
|
| 21 |
+
TileSchedulerArguments,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class FlashAttentionBackwardPreprocess:
|
| 26 |
+
def __init__(
|
| 27 |
+
self,
|
| 28 |
+
dtype: Type[cutlass.Numeric],
|
| 29 |
+
head_dim: int,
|
| 30 |
+
arch: Literal[80, 90, 100],
|
| 31 |
+
m_block_size: int = 128,
|
| 32 |
+
num_threads: int = 128,
|
| 33 |
+
):
|
| 34 |
+
"""
|
| 35 |
+
All contiguous dimensions must be at least 16 bytes aligned which indicates the head dimension
|
| 36 |
+
should be a multiple of 8.
|
| 37 |
+
|
| 38 |
+
:param head_dim: head dimension
|
| 39 |
+
:type head_dim: int
|
| 40 |
+
:param m_block_size: m block size
|
| 41 |
+
:type m_block_size: int
|
| 42 |
+
:param num_threads: number of threads
|
| 43 |
+
:type num_threads: int
|
| 44 |
+
"""
|
| 45 |
+
self.dtype = dtype
|
| 46 |
+
self.m_block_size = m_block_size
|
| 47 |
+
self.arch = arch
|
| 48 |
+
# padding head_dim to a multiple of 32 as k_block_size
|
| 49 |
+
hdim_multiple_of = 32
|
| 50 |
+
self.head_dim_padded = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 51 |
+
self.check_hdim_oob = head_dim != self.head_dim_padded
|
| 52 |
+
self.num_threads = num_threads
|
| 53 |
+
|
| 54 |
+
@staticmethod
|
| 55 |
+
def can_implement(dtype, head_dim, m_block_size, num_threads) -> bool:
|
| 56 |
+
"""Check if the kernel can be implemented with the given parameters.
|
| 57 |
+
|
| 58 |
+
:param dtype: data type
|
| 59 |
+
:type dtype: cutlass.Numeric
|
| 60 |
+
:param head_dim: head dimension
|
| 61 |
+
:type head_dim: int
|
| 62 |
+
:param m_block_size: m block size
|
| 63 |
+
:type m_block_size: int
|
| 64 |
+
:param num_threads: number of threads
|
| 65 |
+
:type num_threads: int
|
| 66 |
+
|
| 67 |
+
:return: True if the kernel can be implemented, False otherwise
|
| 68 |
+
:rtype: bool
|
| 69 |
+
"""
|
| 70 |
+
if dtype not in [cutlass.Float16, cutlass.BFloat16]:
|
| 71 |
+
return False
|
| 72 |
+
if head_dim % 8 != 0:
|
| 73 |
+
return False
|
| 74 |
+
if num_threads % 32 != 0:
|
| 75 |
+
return False
|
| 76 |
+
if num_threads < m_block_size: # For multiplying lse with log2
|
| 77 |
+
return False
|
| 78 |
+
return True
|
| 79 |
+
|
| 80 |
+
def _setup_attributes(self):
|
| 81 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 82 |
+
# GMEM Tiled copy:
|
| 83 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 84 |
+
# Thread layouts for copies
|
| 85 |
+
# We want kBlockKGmem to be a power of 2 so that when we do the summing,
|
| 86 |
+
# it's just between threads in the same warp
|
| 87 |
+
gmem_k_block_size = (
|
| 88 |
+
128
|
| 89 |
+
if self.head_dim_padded % 128 == 0
|
| 90 |
+
else (
|
| 91 |
+
64
|
| 92 |
+
if self.head_dim_padded % 64 == 0
|
| 93 |
+
else (32 if self.head_dim_padded % 32 == 0 else 16)
|
| 94 |
+
)
|
| 95 |
+
)
|
| 96 |
+
self.gmem_tiled_copy_O = copy_utils.tiled_copy_2d(
|
| 97 |
+
self.dtype, gmem_k_block_size, self.num_threads
|
| 98 |
+
)
|
| 99 |
+
universal_copy_bits = 128
|
| 100 |
+
num_copy_elems_dQaccum = universal_copy_bits // Float32.width
|
| 101 |
+
assert (
|
| 102 |
+
self.m_block_size * self.head_dim_padded // num_copy_elems_dQaccum
|
| 103 |
+
) % self.num_threads == 0
|
| 104 |
+
self.gmem_tiled_copy_dQaccum = copy_utils.tiled_copy_1d(
|
| 105 |
+
Float32, self.num_threads, num_copy_elems_dQaccum
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
@cute.jit
|
| 109 |
+
def __call__(
|
| 110 |
+
self,
|
| 111 |
+
mO: cute.Tensor,
|
| 112 |
+
mdO: cute.Tensor,
|
| 113 |
+
mdPsum: cute.Tensor,
|
| 114 |
+
stream: cuda.CUstream,
|
| 115 |
+
mLSE: Optional[cute.Tensor] = None,
|
| 116 |
+
mLSElog2: Optional[cute.Tensor] = None,
|
| 117 |
+
mdQaccum: Optional[cute.Tensor] = None,
|
| 118 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 119 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 120 |
+
):
|
| 121 |
+
# Get the data type and check if it is fp16 or bf16
|
| 122 |
+
if cutlass.const_expr(not (mO.element_type == mdO.element_type)):
|
| 123 |
+
raise TypeError("All tensors must have the same data type")
|
| 124 |
+
if cutlass.const_expr(mO.element_type not in [cutlass.Float16, cutlass.BFloat16]):
|
| 125 |
+
raise TypeError("Only Float16 or BFloat16 is supported")
|
| 126 |
+
if cutlass.const_expr(mdPsum.element_type not in [Float32]):
|
| 127 |
+
raise TypeError("dPsum tensor must be Float32")
|
| 128 |
+
if cutlass.const_expr(mdQaccum is not None):
|
| 129 |
+
if cutlass.const_expr(mdQaccum.element_type not in [Float32]):
|
| 130 |
+
raise TypeError("dQaccum tensor must be Float32")
|
| 131 |
+
if cutlass.const_expr(mLSE is not None):
|
| 132 |
+
assert mLSElog2 is not None, "If mLSE is provided, mLSElog2 must also be provided"
|
| 133 |
+
if cutlass.const_expr(mLSE.element_type not in [Float32]):
|
| 134 |
+
raise TypeError("LSE tensor must be Float32")
|
| 135 |
+
if cutlass.const_expr(mLSElog2.element_type not in [Float32]):
|
| 136 |
+
raise TypeError("LSElog2 tensor must be Float32")
|
| 137 |
+
|
| 138 |
+
# Assume all strides are divisible by 128 bits except the last stride
|
| 139 |
+
new_stride = lambda t: (
|
| 140 |
+
*(cute.assume(s, divby=128 // t.element_type.width) for s in t.stride[:-1]),
|
| 141 |
+
t.stride[-1],
|
| 142 |
+
)
|
| 143 |
+
mO, mdO, mdQaccum = [
|
| 144 |
+
cute.make_tensor(t.iterator, cute.make_layout(t.shape, stride=new_stride(t)))
|
| 145 |
+
if t is not None
|
| 146 |
+
else None
|
| 147 |
+
for t in (mO, mdO, mdQaccum)
|
| 148 |
+
]
|
| 149 |
+
|
| 150 |
+
self._setup_attributes()
|
| 151 |
+
|
| 152 |
+
if cutlass.const_expr(mCuSeqlensQ is not None):
|
| 153 |
+
TileScheduler = SingleTileVarlenScheduler
|
| 154 |
+
num_head = mO.shape[1]
|
| 155 |
+
num_batch = mCuSeqlensQ.shape[0] - 1
|
| 156 |
+
else:
|
| 157 |
+
TileScheduler = SingleTileScheduler
|
| 158 |
+
num_head = mO.shape[2]
|
| 159 |
+
num_batch = mO.shape[0]
|
| 160 |
+
|
| 161 |
+
tile_sched_args = TileSchedulerArguments(
|
| 162 |
+
num_block=cute.ceil_div(mO.shape[1], self.m_block_size),
|
| 163 |
+
num_head=num_head,
|
| 164 |
+
num_batch=num_batch,
|
| 165 |
+
num_splits=1,
|
| 166 |
+
seqlen_k=0,
|
| 167 |
+
headdim=0,
|
| 168 |
+
headdim_v=mO.shape[2],
|
| 169 |
+
total_q=mO.shape[0],
|
| 170 |
+
tile_shape_mn=(self.m_block_size, 1),
|
| 171 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 172 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 176 |
+
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 177 |
+
|
| 178 |
+
self.kernel(
|
| 179 |
+
mO,
|
| 180 |
+
mdO,
|
| 181 |
+
mdPsum,
|
| 182 |
+
mLSE,
|
| 183 |
+
mLSElog2,
|
| 184 |
+
mdQaccum,
|
| 185 |
+
mCuSeqlensQ,
|
| 186 |
+
mSeqUsedQ,
|
| 187 |
+
self.gmem_tiled_copy_O,
|
| 188 |
+
self.gmem_tiled_copy_dQaccum,
|
| 189 |
+
tile_sched_params,
|
| 190 |
+
TileScheduler,
|
| 191 |
+
).launch(
|
| 192 |
+
grid=grid_dim,
|
| 193 |
+
block=[self.num_threads, 1, 1],
|
| 194 |
+
stream=stream,
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
@cute.kernel
|
| 198 |
+
def kernel(
|
| 199 |
+
self,
|
| 200 |
+
mO: cute.Tensor,
|
| 201 |
+
mdO: cute.Tensor,
|
| 202 |
+
mdPsum: cute.Tensor,
|
| 203 |
+
mLSE: Optional[cute.Tensor],
|
| 204 |
+
mLSElog2: Optional[cute.Tensor],
|
| 205 |
+
mdQaccum: Optional[cute.Tensor],
|
| 206 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 207 |
+
mSeqUsedQ: Optional[cute.Tensor],
|
| 208 |
+
gmem_tiled_copy_O: cute.TiledCopy,
|
| 209 |
+
gmem_tiled_copy_dQaccum: cute.TiledCopy,
|
| 210 |
+
tile_sched_params: ParamsBase,
|
| 211 |
+
TileScheduler: cutlass.Constexpr[Callable],
|
| 212 |
+
):
|
| 213 |
+
# Thread index, block index
|
| 214 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 215 |
+
|
| 216 |
+
tile_scheduler = TileScheduler.create(tile_sched_params)
|
| 217 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 218 |
+
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 219 |
+
|
| 220 |
+
if work_tile.is_valid_tile:
|
| 221 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 222 |
+
# Get the appropriate tiles for this thread block.
|
| 223 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 224 |
+
seqlen = SeqlenInfoQK.create(
|
| 225 |
+
batch_idx,
|
| 226 |
+
mO.shape[1],
|
| 227 |
+
0,
|
| 228 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 229 |
+
mCuSeqlensK=None,
|
| 230 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 231 |
+
mSeqUsedK=None,
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
if cutlass.const_expr(not seqlen.has_cu_seqlens_q):
|
| 235 |
+
mO_cur = mO[batch_idx, None, head_idx, None]
|
| 236 |
+
mdO_cur = mdO[batch_idx, None, head_idx, None]
|
| 237 |
+
mdPsum_cur = mdPsum[batch_idx, head_idx, None]
|
| 238 |
+
headdim_v = mO.shape[3]
|
| 239 |
+
else:
|
| 240 |
+
mO_cur = cute.domain_offset((seqlen.offset_q, 0), mO[None, head_idx, None])
|
| 241 |
+
mdO_cur = cute.domain_offset((seqlen.offset_q, 0), mdO[None, head_idx, None])
|
| 242 |
+
|
| 243 |
+
padded_offset_q = seqlen.offset_q + batch_idx * self.m_block_size
|
| 244 |
+
if cutlass.const_expr(self.arch >= 90):
|
| 245 |
+
padded_offset_q = padded_offset_q // self.m_block_size * self.m_block_size
|
| 246 |
+
mdPsum_cur = cute.domain_offset((padded_offset_q,), mdPsum[head_idx, None])
|
| 247 |
+
headdim_v = mO.shape[2]
|
| 248 |
+
|
| 249 |
+
blkOdO_shape = (self.m_block_size, self.head_dim_padded)
|
| 250 |
+
# (m_block_size, head_dim)
|
| 251 |
+
gO = cute.local_tile(mO_cur, blkOdO_shape, (m_block, 0))
|
| 252 |
+
gdO = cute.local_tile(mdO_cur, blkOdO_shape, (m_block, 0))
|
| 253 |
+
|
| 254 |
+
gmem_thr_copy_O = gmem_tiled_copy_O.get_slice(tidx)
|
| 255 |
+
# (CPY_Atom, CPY_M, CPY_K)
|
| 256 |
+
tOgO = gmem_thr_copy_O.partition_S(gO)
|
| 257 |
+
tOgdO = gmem_thr_copy_O.partition_S(gdO)
|
| 258 |
+
|
| 259 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 260 |
+
# Predicate: Mark indices that need to copy when problem_shape isn't a multiple
|
| 261 |
+
# of tile_shape
|
| 262 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 263 |
+
# Construct identity layout for KV
|
| 264 |
+
cO = cute.make_identity_tensor((self.m_block_size, self.head_dim_padded))
|
| 265 |
+
tOcO = gmem_thr_copy_O.partition_S(cO)
|
| 266 |
+
t0OcO = gmem_thr_copy_O.get_slice(0).partition_S(cO)
|
| 267 |
+
tOpO = utils.predicate_k(tOcO, limit=headdim_v)
|
| 268 |
+
tOpdO = utils.predicate_k(tOcO, limit=headdim_v)
|
| 269 |
+
|
| 270 |
+
seqlen_q = seqlen.seqlen_q
|
| 271 |
+
seqlen_q_rounded = cute.round_up(seqlen_q, self.m_block_size)
|
| 272 |
+
|
| 273 |
+
if cutlass.const_expr(mLSE is not None):
|
| 274 |
+
if cutlass.const_expr(not seqlen.has_cu_seqlens_q):
|
| 275 |
+
mLSE_cur = mLSE[batch_idx, head_idx, None]
|
| 276 |
+
else:
|
| 277 |
+
mLSE_cur = cute.domain_offset((seqlen.offset_q,), mLSE[head_idx, None])
|
| 278 |
+
|
| 279 |
+
gLSE = cute.local_tile(mLSE_cur, (self.m_block_size,), (m_block,))
|
| 280 |
+
lse = Float32.inf
|
| 281 |
+
if tidx < seqlen_q - m_block * self.m_block_size:
|
| 282 |
+
lse = gLSE[tidx]
|
| 283 |
+
|
| 284 |
+
tOrO = cute.make_fragment_like(tOgO)
|
| 285 |
+
tOrdO = cute.make_fragment_like(tOgdO)
|
| 286 |
+
assert cute.size(tOgO, mode=[0]) == cute.size(tOgdO, mode=[0])
|
| 287 |
+
assert cute.size(tOgO, mode=[1]) == cute.size(tOgdO, mode=[1])
|
| 288 |
+
assert cute.size(tOgO, mode=[2]) == cute.size(tOgdO, mode=[2])
|
| 289 |
+
for m in cutlass.range(cute.size(tOrO.shape[1]), unroll_full=True):
|
| 290 |
+
# Instead of using tOcO, we using t0OcO and subtract the offset from the limit
|
| 291 |
+
# (seqlen_q - m_block * kBlockM). This is because the entries of t0OcO are known at compile time.
|
| 292 |
+
if t0OcO[0, m, 0][0] < seqlen_q - m_block * self.m_block_size - tOcO[0][0]:
|
| 293 |
+
cute.copy(
|
| 294 |
+
gmem_thr_copy_O,
|
| 295 |
+
tOgO[None, m, None],
|
| 296 |
+
tOrO[None, m, None],
|
| 297 |
+
pred=tOpO[None, m, None]
|
| 298 |
+
if cutlass.const_expr(self.check_hdim_oob)
|
| 299 |
+
else None,
|
| 300 |
+
)
|
| 301 |
+
cute.copy(
|
| 302 |
+
gmem_thr_copy_O,
|
| 303 |
+
tOgdO[None, m, None],
|
| 304 |
+
tOrdO[None, m, None],
|
| 305 |
+
pred=tOpdO[None, m, None]
|
| 306 |
+
if cutlass.const_expr(self.check_hdim_oob)
|
| 307 |
+
else None,
|
| 308 |
+
)
|
| 309 |
+
# Sum across the "k" dimension
|
| 310 |
+
dpsum = (tOrO.load().to(Float32) * tOrdO.load().to(Float32)).reduce(
|
| 311 |
+
cute.ReductionOp.ADD, init_val=0.0, reduction_profile=(0, None, 1)
|
| 312 |
+
)
|
| 313 |
+
threads_per_row = gmem_tiled_copy_O.layout_src_tv_tiled[0].shape[0]
|
| 314 |
+
assert cute.arch.WARP_SIZE % threads_per_row == 0
|
| 315 |
+
dpsum = utils.warp_reduce(dpsum, operator.add, width=threads_per_row)
|
| 316 |
+
dP_sum = cute.make_fragment(cute.size(tOrO, mode=[1]), Float32)
|
| 317 |
+
dP_sum.store(dpsum)
|
| 318 |
+
|
| 319 |
+
# Write dPsum from rmem -> gmem
|
| 320 |
+
gdPsum = cute.local_tile(mdPsum_cur, (self.m_block_size,), (m_block,))
|
| 321 |
+
# Only the thread corresponding to column 0 writes out the dPsum to gmem
|
| 322 |
+
if tOcO[0, 0, 0][1] == 0:
|
| 323 |
+
for m in cutlass.range(cute.size(dP_sum), unroll_full=True):
|
| 324 |
+
row = tOcO[0, m, 0][0]
|
| 325 |
+
gdPsum[row] = dP_sum[m] if row < seqlen_q - m_block * self.m_block_size else 0.0
|
| 326 |
+
|
| 327 |
+
# Clear dQaccum
|
| 328 |
+
if cutlass.const_expr(mdQaccum is not None):
|
| 329 |
+
if cutlass.const_expr(not seqlen.has_cu_seqlens_q):
|
| 330 |
+
mdQaccum_cur = mdQaccum[batch_idx, head_idx, None]
|
| 331 |
+
else:
|
| 332 |
+
mdQaccum_cur = cute.domain_offset(
|
| 333 |
+
(padded_offset_q * self.head_dim_padded,), mdQaccum[head_idx, None]
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
# HACK: Compiler doesn't seem to recognize that padding
|
| 337 |
+
# by padded_offset_q * self.head_dim_padded keeps alignment
|
| 338 |
+
# since statically divisible by 4
|
| 339 |
+
|
| 340 |
+
mdQaccum_cur_ptr = cute.make_ptr(
|
| 341 |
+
dtype=mdQaccum_cur.element_type,
|
| 342 |
+
value=mdQaccum_cur.iterator.toint(),
|
| 343 |
+
mem_space=mdQaccum_cur.iterator.memspace,
|
| 344 |
+
assumed_align=mdQaccum.iterator.alignment,
|
| 345 |
+
)
|
| 346 |
+
mdQaccum_cur = cute.make_tensor(mdQaccum_cur_ptr, mdQaccum_cur.layout)
|
| 347 |
+
|
| 348 |
+
blkdQaccum_shape = (self.m_block_size * self.head_dim_padded,)
|
| 349 |
+
gdQaccum = cute.local_tile(mdQaccum_cur, blkdQaccum_shape, (m_block,))
|
| 350 |
+
gmem_thr_copy_dQaccum = gmem_tiled_copy_dQaccum.get_slice(tidx)
|
| 351 |
+
tdQgdQaccum = gmem_thr_copy_dQaccum.partition_S(gdQaccum)
|
| 352 |
+
zero = cute.make_fragment_like(tdQgdQaccum)
|
| 353 |
+
zero.fill(0.0)
|
| 354 |
+
cute.copy(gmem_tiled_copy_dQaccum, zero, tdQgdQaccum)
|
| 355 |
+
|
| 356 |
+
if cutlass.const_expr(mLSE is not None):
|
| 357 |
+
if cutlass.const_expr(not seqlen.has_cu_seqlens_q):
|
| 358 |
+
mLSElog2_cur = mLSElog2[batch_idx, head_idx, None]
|
| 359 |
+
else:
|
| 360 |
+
mLSElog2_cur = cute.domain_offset((padded_offset_q,), mLSElog2[head_idx, None])
|
| 361 |
+
|
| 362 |
+
gLSElog2 = cute.local_tile(mLSElog2_cur, (self.m_block_size,), (m_block,))
|
| 363 |
+
LOG2_E = math.log2(math.e)
|
| 364 |
+
if tidx < seqlen_q_rounded - m_block * self.m_block_size:
|
| 365 |
+
gLSElog2[tidx] = lse * LOG2_E if lse != -Float32.inf else 0.0
|
kernel/deps/flash_attn/cute/flash_bwd_sm100.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
kernel/deps/flash_attn/cute/flash_bwd_sm90.py
ADDED
|
@@ -0,0 +1,1708 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from typing import Callable, Optional, Type
|
| 3 |
+
from functools import partial
|
| 4 |
+
|
| 5 |
+
import cuda.bindings.driver as cuda
|
| 6 |
+
|
| 7 |
+
import cutlass
|
| 8 |
+
import cutlass.cute as cute
|
| 9 |
+
import cutlass.utils.hopper_helpers as sm90_utils_basic
|
| 10 |
+
from cutlass.cute.nvgpu import cpasync, warpgroup
|
| 11 |
+
from cutlass.cute.arch import ProxyKind, SharedSpace
|
| 12 |
+
from cutlass.cute import FastDivmodDivisor
|
| 13 |
+
from cutlass import Float32, Int32, Boolean, const_expr
|
| 14 |
+
from cutlass.utils import LayoutEnum
|
| 15 |
+
|
| 16 |
+
from flash_attn.cute import hopper_helpers as sm90_utils
|
| 17 |
+
from flash_attn.cute import utils
|
| 18 |
+
from flash_attn.cute import copy_utils
|
| 19 |
+
from flash_attn.cute.hopper_helpers import gemm_zero_init, gemm_w_idx
|
| 20 |
+
from flash_attn.cute.mask import AttentionMask
|
| 21 |
+
from flash_attn.cute.seqlen_info import SeqlenInfoQK
|
| 22 |
+
from flash_attn.cute.block_info import BlockInfo
|
| 23 |
+
from flash_attn.cute import pipeline
|
| 24 |
+
from flash_attn.cute.tile_scheduler import TileSchedulerArguments, SingleTileScheduler, ParamsBase
|
| 25 |
+
from flash_attn.cute.named_barrier import NamedBarrierFwd, NamedBarrierBwd
|
| 26 |
+
from flash_attn.cute.softmax import apply_score_mod_inner, apply_score_mod_bwd_inner
|
| 27 |
+
from flash_attn.cute.block_sparsity import BlockSparseTensors
|
| 28 |
+
from flash_attn.cute.block_sparse_utils import (
|
| 29 |
+
get_total_q_block_count_bwd,
|
| 30 |
+
produce_block_sparse_q_loads_bwd_sm90,
|
| 31 |
+
consume_block_sparse_mma_bwd_sm90,
|
| 32 |
+
dQaccum_store_block_sparse_bwd_sm90,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def mma_partition_fragment_AB(
|
| 37 |
+
thr_mma: cute.core.ThrMma, sA: Optional[cute.Tensor], sB: Optional[cute.Tensor], swap_AB: bool
|
| 38 |
+
):
|
| 39 |
+
if const_expr(not swap_AB):
|
| 40 |
+
return (
|
| 41 |
+
thr_mma.make_fragment_A(thr_mma.partition_A(sA)) if sA is not None else None,
|
| 42 |
+
thr_mma.make_fragment_B(thr_mma.partition_B(sB)) if sB is not None else None,
|
| 43 |
+
)
|
| 44 |
+
else:
|
| 45 |
+
return (
|
| 46 |
+
thr_mma.make_fragment_B(thr_mma.partition_B(sA)) if sA is not None else None,
|
| 47 |
+
thr_mma.make_fragment_A(thr_mma.partition_A(sB)) if sB is not None else None,
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class FlashAttentionBackwardSm90:
|
| 52 |
+
arch = 90
|
| 53 |
+
|
| 54 |
+
def __init__(
|
| 55 |
+
self,
|
| 56 |
+
dtype: Type[cutlass.Numeric],
|
| 57 |
+
head_dim: int,
|
| 58 |
+
head_dim_v: Optional[int] = None,
|
| 59 |
+
qhead_per_kvhead: int = 1,
|
| 60 |
+
is_causal: bool = False,
|
| 61 |
+
tile_m: int = 64,
|
| 62 |
+
tile_n: int = 128,
|
| 63 |
+
Q_stage: int = 2,
|
| 64 |
+
dO_stage: int = 2,
|
| 65 |
+
PdS_stage: int = 2,
|
| 66 |
+
SdP_swapAB: bool = False,
|
| 67 |
+
dKV_swapAB: bool = False,
|
| 68 |
+
dQ_swapAB: bool = False,
|
| 69 |
+
AtomLayoutMSdP: int = 1,
|
| 70 |
+
AtomLayoutNdKV: int = 2,
|
| 71 |
+
AtomLayoutMdQ: int = 1,
|
| 72 |
+
num_threads: int = 384,
|
| 73 |
+
V_in_regs: bool = False,
|
| 74 |
+
score_mod: cutlass.Constexpr | None = None,
|
| 75 |
+
score_mod_bwd: cutlass.Constexpr | None = None,
|
| 76 |
+
mask_mod: cutlass.Constexpr | None = None,
|
| 77 |
+
has_aux_tensors: cutlass.Constexpr = False,
|
| 78 |
+
subtile_factor: cutlass.Constexpr[int] = 1,
|
| 79 |
+
):
|
| 80 |
+
self.dtype = dtype
|
| 81 |
+
# padding head_dim to a multiple of 16 as k_block_size
|
| 82 |
+
hdim_multiple_of = 16
|
| 83 |
+
self.tile_hdim = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 84 |
+
head_dim_v = head_dim_v if head_dim_v is not None else head_dim
|
| 85 |
+
self.same_hdim_kv = head_dim == head_dim_v
|
| 86 |
+
self.tile_hdimv = int(math.ceil(head_dim_v / hdim_multiple_of) * hdim_multiple_of)
|
| 87 |
+
# Can save registers (and hence be faster) if we don't have to check hdim predication
|
| 88 |
+
self.check_hdim_oob = head_dim != self.tile_hdim
|
| 89 |
+
self.check_hdim_v_oob = head_dim_v != self.tile_hdimv
|
| 90 |
+
self.qhead_per_kvhead = qhead_per_kvhead
|
| 91 |
+
self.is_causal = is_causal
|
| 92 |
+
self.is_local = False
|
| 93 |
+
self.tile_m = tile_m
|
| 94 |
+
self.tile_n = tile_n
|
| 95 |
+
self.num_threads = num_threads
|
| 96 |
+
self.Q_stage = Q_stage
|
| 97 |
+
self.dO_stage = dO_stage
|
| 98 |
+
self.PdS_stage = PdS_stage
|
| 99 |
+
assert self.dO_stage in [1, self.Q_stage]
|
| 100 |
+
assert self.PdS_stage in [1, self.Q_stage]
|
| 101 |
+
self.SdP_swapAB = SdP_swapAB
|
| 102 |
+
self.dKV_swapAB = dKV_swapAB
|
| 103 |
+
self.dQ_swapAB = dQ_swapAB
|
| 104 |
+
self.AtomLayoutMSdP = AtomLayoutMSdP
|
| 105 |
+
self.AtomLayoutNdKV = AtomLayoutNdKV
|
| 106 |
+
self.AtomLayoutMdQ = AtomLayoutMdQ
|
| 107 |
+
self.num_mma_warp_groups = (self.num_threads // 128) - 1
|
| 108 |
+
self.mma_dkv_is_rs = (
|
| 109 |
+
AtomLayoutMSdP == 1
|
| 110 |
+
and AtomLayoutNdKV == self.num_mma_warp_groups
|
| 111 |
+
and SdP_swapAB
|
| 112 |
+
and not dKV_swapAB
|
| 113 |
+
)
|
| 114 |
+
self.V_in_regs = V_in_regs
|
| 115 |
+
if qhead_per_kvhead > 1:
|
| 116 |
+
assert self.same_hdim_kv, "GQA backward requires head_dim == head_dim_v"
|
| 117 |
+
assert self.num_mma_warp_groups == 2, "GQA backward assumes 2 warp groups"
|
| 118 |
+
# These are tuned for speed
|
| 119 |
+
# Do we keep the LSE and dPsum in each thread, or split them across 8 threads that share
|
| 120 |
+
# them and then shuffle to get the value whenever we need? This can reduce register
|
| 121 |
+
# pressure when SdP_swapAB, where each thread needs to keep statistics for (kBlockM / 4)
|
| 122 |
+
# rows. If !SdP_swapAB, each thread only needs to keep statistics for 2 rows.
|
| 123 |
+
# TODO: impl these for hdim 64
|
| 124 |
+
self.shuffle_LSE = self.SdP_swapAB and self.tile_hdim <= 64
|
| 125 |
+
self.shuffle_dPsum = self.SdP_swapAB and self.tile_hdim <= 64
|
| 126 |
+
|
| 127 |
+
self.score_mod = score_mod
|
| 128 |
+
self.score_mod_bwd = score_mod_bwd
|
| 129 |
+
self.mask_mod = mask_mod
|
| 130 |
+
self.has_aux_tensors = has_aux_tensors
|
| 131 |
+
self.subtile_factor = subtile_factor
|
| 132 |
+
if cutlass.const_expr(has_aux_tensors):
|
| 133 |
+
self.vec_size: cutlass.Constexpr = 1
|
| 134 |
+
else:
|
| 135 |
+
self.vec_size: cutlass.Constexpr = 4
|
| 136 |
+
self.qk_acc_dtype = Float32
|
| 137 |
+
|
| 138 |
+
@staticmethod
|
| 139 |
+
def can_implement(
|
| 140 |
+
dtype,
|
| 141 |
+
head_dim,
|
| 142 |
+
head_dim_v,
|
| 143 |
+
tile_m,
|
| 144 |
+
tile_n,
|
| 145 |
+
Q_stage,
|
| 146 |
+
num_threads,
|
| 147 |
+
V_in_regs=False,
|
| 148 |
+
) -> bool:
|
| 149 |
+
if dtype not in [cutlass.Float16, cutlass.BFloat16]:
|
| 150 |
+
return False
|
| 151 |
+
if head_dim % 8 != 0:
|
| 152 |
+
return False
|
| 153 |
+
if head_dim_v % 8 != 0:
|
| 154 |
+
return False
|
| 155 |
+
if tile_n % 16 != 0:
|
| 156 |
+
return False
|
| 157 |
+
if num_threads % 32 != 0:
|
| 158 |
+
return False
|
| 159 |
+
if (tile_m * 2) % num_threads != 0:
|
| 160 |
+
return False
|
| 161 |
+
return True
|
| 162 |
+
|
| 163 |
+
def _check_type(
|
| 164 |
+
self,
|
| 165 |
+
mQ_type: Type[cutlass.Numeric],
|
| 166 |
+
mK_type: Type[cutlass.Numeric],
|
| 167 |
+
mV_type: Type[cutlass.Numeric],
|
| 168 |
+
mdO_type: Type[cutlass.Numeric],
|
| 169 |
+
mLSE_type: Type[cutlass.Numeric],
|
| 170 |
+
mdPsum_type: Type[cutlass.Numeric],
|
| 171 |
+
mdQaccum_type: Type[cutlass.Numeric],
|
| 172 |
+
mdK_type: Type[cutlass.Numeric],
|
| 173 |
+
mdV_type: Type[cutlass.Numeric],
|
| 174 |
+
):
|
| 175 |
+
# Get the data type and check if it is fp16 or bf16
|
| 176 |
+
if const_expr(not (mQ_type == mK_type == mV_type == mdO_type)):
|
| 177 |
+
raise TypeError("All tensors must have the same data type")
|
| 178 |
+
if const_expr(mQ_type not in [cutlass.Float16, cutlass.BFloat16]):
|
| 179 |
+
raise TypeError("Only Float16 or BFloat16 is supported")
|
| 180 |
+
if const_expr(mLSE_type not in [Float32]):
|
| 181 |
+
raise TypeError("LSE tensor must be Float32")
|
| 182 |
+
if const_expr(mdPsum_type not in [Float32]):
|
| 183 |
+
raise TypeError("dPsum tensor must be Float32")
|
| 184 |
+
if const_expr(mdQaccum_type not in [Float32]):
|
| 185 |
+
raise TypeError("dQaccum tensor must be Float32")
|
| 186 |
+
if const_expr(self.qhead_per_kvhead == 1):
|
| 187 |
+
if const_expr(not (mdK_type == mdV_type == mQ_type)):
|
| 188 |
+
raise TypeError("mdK and mdV tensors must have the same data type as mQ")
|
| 189 |
+
else:
|
| 190 |
+
if const_expr(not (mdK_type == mdV_type == Float32)):
|
| 191 |
+
raise TypeError("mdKaccum and mdVaccum tensors must have the data type Float32")
|
| 192 |
+
assert mQ_type == self.dtype
|
| 193 |
+
|
| 194 |
+
def _setup_attributes(self):
|
| 195 |
+
self.sQ_layout, self.sK_layout, self.sV_layout, self.sdO_layout, self.sPdS_layout = [
|
| 196 |
+
sm90_utils.make_smem_layout(self.dtype, LayoutEnum.ROW_MAJOR, shape, stage)
|
| 197 |
+
for shape, stage in [
|
| 198 |
+
((self.tile_m, self.tile_hdim), self.Q_stage),
|
| 199 |
+
((self.tile_n, self.tile_hdim), None),
|
| 200 |
+
((self.tile_n, self.tile_hdimv), None),
|
| 201 |
+
((self.tile_m, self.tile_hdimv), self.dO_stage),
|
| 202 |
+
((self.tile_m, self.tile_n), self.PdS_stage),
|
| 203 |
+
]
|
| 204 |
+
]
|
| 205 |
+
self.sdQaccum_layout = cute.make_layout(
|
| 206 |
+
(self.tile_m * self.tile_hdim // self.num_mma_warp_groups, self.num_mma_warp_groups)
|
| 207 |
+
)
|
| 208 |
+
# dQaccum R->S
|
| 209 |
+
self.r2s_tiled_copy_dQaccum = cute.make_tiled_copy_tv(
|
| 210 |
+
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128),
|
| 211 |
+
# thr_layout
|
| 212 |
+
cute.make_layout((self.num_threads_per_warp_group, self.num_mma_warp_groups)),
|
| 213 |
+
cute.make_layout(128 // Float32.width), # val_layout
|
| 214 |
+
)
|
| 215 |
+
# dKVaccum for GQA epilogue - reuses sV+sK memory recast as f32
|
| 216 |
+
self.sdKVaccum_layout = cute.make_layout(
|
| 217 |
+
(self.tile_n * self.tile_hdim // self.num_mma_warp_groups, self.num_mma_warp_groups)
|
| 218 |
+
)
|
| 219 |
+
# dKVaccum R->S (same pattern as dQaccum but sized for tile_n)
|
| 220 |
+
self.r2s_tiled_copy_dKVaccum = cute.make_tiled_copy_tv(
|
| 221 |
+
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128),
|
| 222 |
+
cute.make_layout((self.num_threads_per_warp_group, self.num_mma_warp_groups)),
|
| 223 |
+
cute.make_layout(128 // Float32.width),
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
def _get_tiled_mma(self):
|
| 227 |
+
# S = Q @ K.T, dP = dO @ V.T
|
| 228 |
+
atom_layout_SdP = (self.AtomLayoutMSdP, self.num_mma_warp_groups // self.AtomLayoutMSdP)
|
| 229 |
+
tiler_mn_SdP = (self.tile_m // atom_layout_SdP[0], self.tile_n // atom_layout_SdP[1])
|
| 230 |
+
tiled_mma_SdP = sm90_utils_basic.make_trivial_tiled_mma(
|
| 231 |
+
self.dtype,
|
| 232 |
+
self.dtype,
|
| 233 |
+
warpgroup.OperandMajorMode.K,
|
| 234 |
+
warpgroup.OperandMajorMode.K,
|
| 235 |
+
Float32,
|
| 236 |
+
atom_layout_mnk=(atom_layout_SdP if not self.SdP_swapAB else atom_layout_SdP[::-1])
|
| 237 |
+
+ (1,),
|
| 238 |
+
tiler_mn=tiler_mn_SdP if not self.SdP_swapAB else tiler_mn_SdP[::-1],
|
| 239 |
+
)
|
| 240 |
+
# dV = P.T @ dO, dK = dS.T @ Q
|
| 241 |
+
atom_layout_dKV = (self.AtomLayoutNdKV, self.num_mma_warp_groups // self.AtomLayoutNdKV)
|
| 242 |
+
tiler_mn_dK = (self.tile_n // atom_layout_dKV[0], self.tile_hdim // atom_layout_dKV[1])
|
| 243 |
+
tiler_mn_dV = (self.tile_n // atom_layout_dKV[0], self.tile_hdimv // atom_layout_dKV[1])
|
| 244 |
+
tiled_mma_dK, tiled_mma_dV = [
|
| 245 |
+
sm90_utils_basic.make_trivial_tiled_mma(
|
| 246 |
+
self.dtype,
|
| 247 |
+
self.dtype,
|
| 248 |
+
warpgroup.OperandMajorMode.MN
|
| 249 |
+
if not self.mma_dkv_is_rs
|
| 250 |
+
else warpgroup.OperandMajorMode.K,
|
| 251 |
+
warpgroup.OperandMajorMode.MN,
|
| 252 |
+
Float32,
|
| 253 |
+
atom_layout_mnk=(atom_layout_dKV if not self.dKV_swapAB else atom_layout_dKV[::-1])
|
| 254 |
+
+ (1,),
|
| 255 |
+
tiler_mn=tiler_mn_d if not self.dKV_swapAB else tiler_mn_d[::-1],
|
| 256 |
+
a_source=warpgroup.OperandSource.RMEM
|
| 257 |
+
if self.mma_dkv_is_rs
|
| 258 |
+
else warpgroup.OperandSource.SMEM,
|
| 259 |
+
)
|
| 260 |
+
for tiler_mn_d in (tiler_mn_dK, tiler_mn_dV)
|
| 261 |
+
]
|
| 262 |
+
# dQ = dS @ K
|
| 263 |
+
atom_layout_dQ = (self.AtomLayoutMdQ, self.num_mma_warp_groups // self.AtomLayoutMdQ)
|
| 264 |
+
tiler_mn_dQ = (self.tile_m // atom_layout_dQ[0], self.tile_hdim // atom_layout_dQ[1])
|
| 265 |
+
tiled_mma_dQ = sm90_utils_basic.make_trivial_tiled_mma(
|
| 266 |
+
self.dtype,
|
| 267 |
+
self.dtype,
|
| 268 |
+
warpgroup.OperandMajorMode.K if not self.dQ_swapAB else warpgroup.OperandMajorMode.MN,
|
| 269 |
+
warpgroup.OperandMajorMode.MN if not self.dQ_swapAB else warpgroup.OperandMajorMode.K,
|
| 270 |
+
Float32,
|
| 271 |
+
atom_layout_mnk=(atom_layout_dQ if not self.dQ_swapAB else atom_layout_dQ[::-1]) + (1,),
|
| 272 |
+
tiler_mn=tiler_mn_dQ if not self.dQ_swapAB else tiler_mn_dQ[::-1],
|
| 273 |
+
)
|
| 274 |
+
return tiled_mma_SdP, tiled_mma_dK, tiled_mma_dV, tiled_mma_dQ
|
| 275 |
+
|
| 276 |
+
def _get_shared_storage_cls(self):
|
| 277 |
+
sQ_alignment = sK_alignment = sV_alighment = sdQaccum_alignment = sdO_alignment = 1024
|
| 278 |
+
|
| 279 |
+
sQ_struct, sK_struct, sV_struct, sdO_struct, sdQaccum_struct = [
|
| 280 |
+
cute.struct.Align[cute.struct.MemRange[type, cute.cosize(layout)], alignment]
|
| 281 |
+
for (layout, type, alignment) in [
|
| 282 |
+
(self.sQ_layout, self.dtype, sQ_alignment),
|
| 283 |
+
(self.sK_layout, self.dtype, sK_alignment),
|
| 284 |
+
(self.sV_layout, self.dtype, sV_alighment),
|
| 285 |
+
(self.sdO_layout, self.dtype, sdO_alignment),
|
| 286 |
+
(self.sdQaccum_layout, Float32, sdQaccum_alignment),
|
| 287 |
+
]
|
| 288 |
+
]
|
| 289 |
+
|
| 290 |
+
cosize_sdS = cute.cosize(self.sPdS_layout)
|
| 291 |
+
cosize_sP = cute.cosize(self.sPdS_layout) if const_expr(not self.mma_dkv_is_rs) else 0
|
| 292 |
+
sLSE_struct = cute.struct.Align[
|
| 293 |
+
cute.struct.MemRange[Float32, cute.round_up(self.tile_m, 64) * self.Q_stage], 128
|
| 294 |
+
]
|
| 295 |
+
sdPsum_struct = cute.struct.Align[
|
| 296 |
+
cute.struct.MemRange[Float32, cute.round_up(self.tile_m, 64) * self.dO_stage], 128
|
| 297 |
+
]
|
| 298 |
+
|
| 299 |
+
@cute.struct
|
| 300 |
+
class SharedStorageQKV:
|
| 301 |
+
mbar_ptr_Q: cute.struct.MemRange[cutlass.Int64, self.Q_stage * 2]
|
| 302 |
+
mbar_ptr_dO: cute.struct.MemRange[cutlass.Int64, self.dO_stage * 2]
|
| 303 |
+
sLSE: sLSE_struct
|
| 304 |
+
sdPsum: sdPsum_struct
|
| 305 |
+
sQ: sQ_struct
|
| 306 |
+
sV: sV_struct
|
| 307 |
+
sK: sK_struct
|
| 308 |
+
sdO: sdO_struct
|
| 309 |
+
sP: cute.struct.Align[cute.struct.MemRange[self.dtype, cosize_sP], 1024]
|
| 310 |
+
sdS: cute.struct.Align[cute.struct.MemRange[self.dtype, cosize_sdS], 1024]
|
| 311 |
+
sdQaccum: sdQaccum_struct
|
| 312 |
+
|
| 313 |
+
return SharedStorageQKV
|
| 314 |
+
|
| 315 |
+
@cute.jit
|
| 316 |
+
def __call__(
|
| 317 |
+
self,
|
| 318 |
+
mQ: cute.Tensor,
|
| 319 |
+
mK: cute.Tensor,
|
| 320 |
+
mV: cute.Tensor,
|
| 321 |
+
mdO: cute.Tensor,
|
| 322 |
+
mLSE: cute.Tensor,
|
| 323 |
+
mdPsum: cute.Tensor,
|
| 324 |
+
mdQaccum: cute.Tensor,
|
| 325 |
+
mdK: cute.Tensor,
|
| 326 |
+
mdV: cute.Tensor,
|
| 327 |
+
softmax_scale: Float32,
|
| 328 |
+
stream: cuda.CUstream,
|
| 329 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 330 |
+
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 331 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 332 |
+
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 333 |
+
softcap: Float32 | float | None = None,
|
| 334 |
+
window_size_left: Int32 | int | None = None,
|
| 335 |
+
window_size_right: Int32 | int | None = None,
|
| 336 |
+
mdQ_semaphore: Optional[cute.Tensor] = None,
|
| 337 |
+
mdK_semaphore: Optional[cute.Tensor] = None,
|
| 338 |
+
mdV_semaphore: Optional[cute.Tensor] = None,
|
| 339 |
+
aux_tensors: Optional[list] = None,
|
| 340 |
+
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 341 |
+
):
|
| 342 |
+
assert mdQ_semaphore is None and mdK_semaphore is None and mdV_semaphore is None, (
|
| 343 |
+
"determinism not supported yet for Sm90"
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
self._check_type(
|
| 347 |
+
*(
|
| 348 |
+
t.element_type if t is not None else None
|
| 349 |
+
for t in (mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV)
|
| 350 |
+
)
|
| 351 |
+
)
|
| 352 |
+
|
| 353 |
+
# Assume all strides are divisible by 128 bits except the last stride
|
| 354 |
+
# Skip cute.assume() for stride=0 (broadcast dims from expand() are Python ints)
|
| 355 |
+
new_stride = lambda t: (
|
| 356 |
+
*(
|
| 357 |
+
cute.assume(s, divby=128 // t.element_type.width)
|
| 358 |
+
if not isinstance(s, int) or s != 0
|
| 359 |
+
else s
|
| 360 |
+
for s in t.stride[:-1]
|
| 361 |
+
),
|
| 362 |
+
t.stride[-1],
|
| 363 |
+
)
|
| 364 |
+
mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV = [
|
| 365 |
+
cute.make_tensor(t.iterator, cute.make_layout(t.shape, stride=new_stride(t)))
|
| 366 |
+
if t is not None
|
| 367 |
+
else None
|
| 368 |
+
for t in (mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV)
|
| 369 |
+
]
|
| 370 |
+
|
| 371 |
+
layout_transpose = [1, 3, 2, 0] # (b, s, n, h) --> (s, h, n, b)
|
| 372 |
+
mQ, mK, mV, mdO = [utils.select(t, layout_transpose) for t in (mQ, mK, mV, mdO)]
|
| 373 |
+
if const_expr(self.qhead_per_kvhead == 1):
|
| 374 |
+
mdK, mdV = [utils.select(t, layout_transpose) for t in (mdK, mdV)]
|
| 375 |
+
else:
|
| 376 |
+
accum_transpose = [2, 1, 0] # (b, n, s*h) -> (s*h, n, b)
|
| 377 |
+
mdK, mdV = [utils.select(t, accum_transpose) for t in (mdK, mdV)]
|
| 378 |
+
LSE_dPsum_dQaccum_transpose = [2, 1, 0] # (b, n, s) -> (s, n, b)
|
| 379 |
+
mLSE, mdPsum, mdQaccum = [
|
| 380 |
+
utils.select(t, LSE_dPsum_dQaccum_transpose) for t in (mLSE, mdPsum, mdQaccum)
|
| 381 |
+
]
|
| 382 |
+
|
| 383 |
+
tiled_mma_SdP, tiled_mma_dK, tiled_mma_dV, tiled_mma_dQ = self._get_tiled_mma()
|
| 384 |
+
|
| 385 |
+
self.num_mma_threads = tiled_mma_SdP.size
|
| 386 |
+
assert self.num_mma_threads + 128 == self.num_threads
|
| 387 |
+
|
| 388 |
+
self.num_threads_per_warp_group = 128
|
| 389 |
+
self.num_producer_threads = 32
|
| 390 |
+
|
| 391 |
+
self.num_mma_regs = 240
|
| 392 |
+
self.num_producer_regs = 24
|
| 393 |
+
# self.num_mma_regs = 232
|
| 394 |
+
# self.num_producer_regs = 40
|
| 395 |
+
|
| 396 |
+
self._setup_attributes()
|
| 397 |
+
SharedStorage = self._get_shared_storage_cls()
|
| 398 |
+
|
| 399 |
+
self.tma_copy_bytes = {
|
| 400 |
+
name: cute.size_in_bytes(mX.element_type, cute.select(layout, mode=[0, 1]))
|
| 401 |
+
for name, mX, layout in [
|
| 402 |
+
("Q", mQ, self.sQ_layout),
|
| 403 |
+
("K", mK, self.sK_layout),
|
| 404 |
+
("V", mV, self.sV_layout),
|
| 405 |
+
("dO", mdO, self.sdO_layout),
|
| 406 |
+
]
|
| 407 |
+
}
|
| 408 |
+
self.tma_copy_bytes["LSE"] = self.tile_m * Float32.width // 8
|
| 409 |
+
self.tma_copy_bytes["dPsum"] = self.tile_m * Float32.width // 8
|
| 410 |
+
self.tma_copy_bytes["dQ"] = (
|
| 411 |
+
self.tile_m * self.tile_hdim * Float32.width // 8 // self.num_mma_warp_groups
|
| 412 |
+
)
|
| 413 |
+
self.tma_copy_bytes["dKacc"] = self.tile_n * self.tile_hdim * Float32.width // 8
|
| 414 |
+
self.tma_copy_bytes["dVacc"] = self.tile_n * self.tile_hdimv * Float32.width // 8
|
| 415 |
+
|
| 416 |
+
tma_atom_Q, tma_tensor_Q = cpasync.make_tiled_tma_atom(
|
| 417 |
+
cpasync.CopyBulkTensorTileG2SOp(),
|
| 418 |
+
mQ,
|
| 419 |
+
cute.select(self.sQ_layout, mode=[0, 1]),
|
| 420 |
+
(self.tile_m, self.tile_hdim),
|
| 421 |
+
)
|
| 422 |
+
tma_atom_K, tma_tensor_K = cpasync.make_tiled_tma_atom(
|
| 423 |
+
cpasync.CopyBulkTensorTileG2SOp(),
|
| 424 |
+
mK,
|
| 425 |
+
cute.select(self.sK_layout, mode=[0, 1]),
|
| 426 |
+
(self.tile_n, self.tile_hdim),
|
| 427 |
+
)
|
| 428 |
+
tma_atom_V, tma_tensor_V = cpasync.make_tiled_tma_atom(
|
| 429 |
+
cpasync.CopyBulkTensorTileG2SOp(),
|
| 430 |
+
mV,
|
| 431 |
+
cute.select(self.sV_layout, mode=[0, 1]),
|
| 432 |
+
(self.tile_n, self.tile_hdimv),
|
| 433 |
+
)
|
| 434 |
+
tma_atom_dO, tma_tensor_dO = cpasync.make_tiled_tma_atom(
|
| 435 |
+
cpasync.CopyBulkTensorTileG2SOp(),
|
| 436 |
+
mdO,
|
| 437 |
+
cute.select(self.sdO_layout, mode=[0, 1]),
|
| 438 |
+
(self.tile_m, self.tile_hdimv),
|
| 439 |
+
)
|
| 440 |
+
if const_expr(self.qhead_per_kvhead == 1):
|
| 441 |
+
tma_atom_dK, tma_tensor_dK = cpasync.make_tiled_tma_atom(
|
| 442 |
+
cpasync.CopyBulkTensorTileS2GOp(),
|
| 443 |
+
mdK,
|
| 444 |
+
cute.select(self.sK_layout, mode=[0, 1]),
|
| 445 |
+
(self.tile_n, self.tile_hdim),
|
| 446 |
+
)
|
| 447 |
+
tma_atom_dV, tma_tensor_dV = cpasync.make_tiled_tma_atom(
|
| 448 |
+
cpasync.CopyBulkTensorTileS2GOp(),
|
| 449 |
+
mdV,
|
| 450 |
+
cute.select(self.sV_layout, mode=[0, 1]),
|
| 451 |
+
(self.tile_n, self.tile_hdimv),
|
| 452 |
+
)
|
| 453 |
+
else:
|
| 454 |
+
tma_atom_dK = tma_atom_dV = tma_tensor_dK = tma_tensor_dV = None
|
| 455 |
+
|
| 456 |
+
TileScheduler = SingleTileScheduler
|
| 457 |
+
tile_sched_args = TileSchedulerArguments(
|
| 458 |
+
cute.ceil_div(cute.size(mK.shape[0]), self.tile_n),
|
| 459 |
+
cute.size(mQ.shape[2]),
|
| 460 |
+
cute.size(mQ.shape[3]),
|
| 461 |
+
1, # num_splits
|
| 462 |
+
cute.size(mK.shape[0]),
|
| 463 |
+
mQ.shape[1],
|
| 464 |
+
mV.shape[1],
|
| 465 |
+
total_q=cute.size(mQ.shape[0]) * cute.size(mQ.shape[3]),
|
| 466 |
+
tile_shape_mn=(self.tile_m, self.tile_n),
|
| 467 |
+
mCuSeqlensQ=None,
|
| 468 |
+
mSeqUsedQ=None,
|
| 469 |
+
qhead_per_kvhead_packgqa=1,
|
| 470 |
+
element_size=self.dtype.width // 8,
|
| 471 |
+
is_persistent=False,
|
| 472 |
+
lpt=False,
|
| 473 |
+
)
|
| 474 |
+
|
| 475 |
+
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 476 |
+
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 477 |
+
|
| 478 |
+
LOG2_E = math.log2(math.e)
|
| 479 |
+
if const_expr(self.score_mod is None):
|
| 480 |
+
softmax_scale_log2 = softmax_scale * LOG2_E
|
| 481 |
+
else:
|
| 482 |
+
softmax_scale_log2 = LOG2_E
|
| 483 |
+
|
| 484 |
+
fastdiv_mods = None
|
| 485 |
+
if const_expr(aux_tensors is not None):
|
| 486 |
+
seqlen_q = cute.size(mQ.shape[0])
|
| 487 |
+
seqlen_k = cute.size(mK.shape[0])
|
| 488 |
+
seqlen_q_divmod = FastDivmodDivisor(seqlen_q)
|
| 489 |
+
seqlen_k_divmod = FastDivmodDivisor(seqlen_k)
|
| 490 |
+
fastdiv_mods = (seqlen_q_divmod, seqlen_k_divmod)
|
| 491 |
+
|
| 492 |
+
qhead_per_kvhead_divmod = None
|
| 493 |
+
if const_expr(self.qhead_per_kvhead > 1):
|
| 494 |
+
qhead_per_kvhead_divmod = FastDivmodDivisor(self.qhead_per_kvhead)
|
| 495 |
+
|
| 496 |
+
self.use_block_sparsity = cutlass.const_expr(blocksparse_tensors is not None)
|
| 497 |
+
|
| 498 |
+
self.kernel(
|
| 499 |
+
tma_tensor_Q,
|
| 500 |
+
tma_tensor_K,
|
| 501 |
+
tma_tensor_V,
|
| 502 |
+
tma_tensor_dO,
|
| 503 |
+
tma_tensor_dK if const_expr(self.qhead_per_kvhead == 1) else mdK,
|
| 504 |
+
tma_tensor_dV if const_expr(self.qhead_per_kvhead == 1) else mdV,
|
| 505 |
+
tma_atom_Q,
|
| 506 |
+
tma_atom_K,
|
| 507 |
+
tma_atom_V,
|
| 508 |
+
tma_atom_dO,
|
| 509 |
+
tma_atom_dK,
|
| 510 |
+
tma_atom_dV,
|
| 511 |
+
mLSE,
|
| 512 |
+
mdPsum,
|
| 513 |
+
mdQaccum,
|
| 514 |
+
self.sQ_layout,
|
| 515 |
+
self.sK_layout,
|
| 516 |
+
self.sV_layout,
|
| 517 |
+
self.sPdS_layout,
|
| 518 |
+
self.sdO_layout,
|
| 519 |
+
self.sdQaccum_layout,
|
| 520 |
+
self.sdKVaccum_layout,
|
| 521 |
+
self.r2s_tiled_copy_dQaccum,
|
| 522 |
+
self.r2s_tiled_copy_dKVaccum,
|
| 523 |
+
tiled_mma_SdP,
|
| 524 |
+
tiled_mma_dK,
|
| 525 |
+
tiled_mma_dV,
|
| 526 |
+
tiled_mma_dQ,
|
| 527 |
+
softmax_scale_log2,
|
| 528 |
+
softmax_scale,
|
| 529 |
+
tile_sched_params,
|
| 530 |
+
TileScheduler,
|
| 531 |
+
SharedStorage,
|
| 532 |
+
aux_tensors,
|
| 533 |
+
fastdiv_mods,
|
| 534 |
+
blocksparse_tensors,
|
| 535 |
+
qhead_per_kvhead_divmod,
|
| 536 |
+
).launch(
|
| 537 |
+
grid=grid_dim,
|
| 538 |
+
block=[self.num_threads, 1, 1],
|
| 539 |
+
smem=SharedStorage.size_in_bytes(),
|
| 540 |
+
stream=stream,
|
| 541 |
+
min_blocks_per_mp=1,
|
| 542 |
+
)
|
| 543 |
+
|
| 544 |
+
@cute.kernel
|
| 545 |
+
def kernel(
|
| 546 |
+
self,
|
| 547 |
+
mQ: cute.Tensor,
|
| 548 |
+
mK: cute.Tensor,
|
| 549 |
+
mV: cute.Tensor,
|
| 550 |
+
mdO: cute.Tensor,
|
| 551 |
+
mdK: cute.Tensor,
|
| 552 |
+
mdV: cute.Tensor,
|
| 553 |
+
tma_atom_Q: cute.CopyAtom,
|
| 554 |
+
tma_atom_K: cute.CopyAtom,
|
| 555 |
+
tma_atom_V: cute.CopyAtom,
|
| 556 |
+
tma_atom_dO: cute.CopyAtom,
|
| 557 |
+
tma_atom_dK: cute.CopyAtom,
|
| 558 |
+
tma_atom_dV: cute.CopyAtom,
|
| 559 |
+
mLSE: cute.Tensor,
|
| 560 |
+
mdPsum: cute.Tensor,
|
| 561 |
+
mdQaccum: cute.Tensor,
|
| 562 |
+
sQ_layout: cute.ComposedLayout,
|
| 563 |
+
sK_layout: cute.ComposedLayout,
|
| 564 |
+
sV_layout: cute.ComposedLayout,
|
| 565 |
+
sPdS_layout: cute.ComposedLayout,
|
| 566 |
+
sdO_layout: cute.ComposedLayout,
|
| 567 |
+
sdQaccum_layout: cute.Layout,
|
| 568 |
+
sdKVaccum_layout: cute.Layout,
|
| 569 |
+
r2s_tiled_copy_dQaccum: cute.TiledCopy,
|
| 570 |
+
r2s_tiled_copy_dKVaccum: cute.TiledCopy,
|
| 571 |
+
tiled_mma_SdP: cute.TiledMma,
|
| 572 |
+
tiled_mma_dK: cute.TiledMma,
|
| 573 |
+
tiled_mma_dV: cute.TiledMma,
|
| 574 |
+
tiled_mma_dQ: cute.TiledMma,
|
| 575 |
+
softmax_scale_log2,
|
| 576 |
+
softmax_scale,
|
| 577 |
+
tile_sched_params: ParamsBase,
|
| 578 |
+
TileScheduler: cutlass.Constexpr[Callable],
|
| 579 |
+
SharedStorage: cutlass.Constexpr[Callable],
|
| 580 |
+
aux_tensors: Optional[list] = None,
|
| 581 |
+
fastdiv_mods=(None, None),
|
| 582 |
+
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 583 |
+
qhead_per_kvhead_divmod: Optional[FastDivmodDivisor] = None,
|
| 584 |
+
):
|
| 585 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 586 |
+
|
| 587 |
+
# prefetch TMA descriptors
|
| 588 |
+
if warp_idx == 0:
|
| 589 |
+
cpasync.prefetch_descriptor(tma_atom_Q)
|
| 590 |
+
cpasync.prefetch_descriptor(tma_atom_K)
|
| 591 |
+
cpasync.prefetch_descriptor(tma_atom_V)
|
| 592 |
+
cpasync.prefetch_descriptor(tma_atom_dO)
|
| 593 |
+
|
| 594 |
+
smem = cutlass.utils.SmemAllocator()
|
| 595 |
+
storage = smem.allocate(SharedStorage)
|
| 596 |
+
|
| 597 |
+
pipeline_producer_group = cutlass.pipeline.CooperativeGroup(cutlass.pipeline.Agent.Thread)
|
| 598 |
+
pipeline_consumer_group = cutlass.pipeline.CooperativeGroup(
|
| 599 |
+
cutlass.pipeline.Agent.Thread, self.num_mma_threads // cute.arch.WARP_SIZE
|
| 600 |
+
)
|
| 601 |
+
pipeline_Q = pipeline.PipelineTmaAsync.create(
|
| 602 |
+
barrier_storage=storage.mbar_ptr_Q.data_ptr(),
|
| 603 |
+
num_stages=self.Q_stage,
|
| 604 |
+
producer_group=pipeline_producer_group,
|
| 605 |
+
consumer_group=pipeline_consumer_group,
|
| 606 |
+
tx_count=self.tma_copy_bytes["Q"] + self.tma_copy_bytes["LSE"],
|
| 607 |
+
defer_sync=True,
|
| 608 |
+
)
|
| 609 |
+
pipeline_dO = pipeline.PipelineTmaAsync.create(
|
| 610 |
+
barrier_storage=storage.mbar_ptr_dO.data_ptr(),
|
| 611 |
+
num_stages=self.dO_stage,
|
| 612 |
+
producer_group=pipeline_producer_group,
|
| 613 |
+
consumer_group=pipeline_consumer_group,
|
| 614 |
+
tx_count=self.tma_copy_bytes["dO"] + self.tma_copy_bytes["dPsum"],
|
| 615 |
+
defer_sync=False,
|
| 616 |
+
)
|
| 617 |
+
|
| 618 |
+
sQ = storage.sQ.get_tensor(sQ_layout.outer, swizzle=sQ_layout.inner)
|
| 619 |
+
sdO = storage.sdO.get_tensor(sdO_layout.outer, swizzle=sdO_layout.inner)
|
| 620 |
+
sK = storage.sK.get_tensor(sK_layout.outer, swizzle=sK_layout.inner)
|
| 621 |
+
sV = storage.sV.get_tensor(sV_layout.outer, swizzle=sV_layout.inner)
|
| 622 |
+
sP = None
|
| 623 |
+
if const_expr(not self.mma_dkv_is_rs):
|
| 624 |
+
sP = storage.sP.get_tensor(sPdS_layout.outer, swizzle=sPdS_layout.inner)
|
| 625 |
+
sdS = storage.sdS.get_tensor(sPdS_layout.outer, swizzle=sPdS_layout.inner)
|
| 626 |
+
sLSE = storage.sLSE.get_tensor(
|
| 627 |
+
cute.make_layout(
|
| 628 |
+
(self.tile_m, self.Q_stage),
|
| 629 |
+
stride=(1, cute.round_up(self.tile_m, 64)),
|
| 630 |
+
)
|
| 631 |
+
)
|
| 632 |
+
sdPsum = storage.sdPsum.get_tensor(
|
| 633 |
+
cute.make_layout(
|
| 634 |
+
(self.tile_m, self.dO_stage),
|
| 635 |
+
stride=(1, cute.round_up(self.tile_m, 64)),
|
| 636 |
+
)
|
| 637 |
+
)
|
| 638 |
+
sdQaccum = storage.sdQaccum.get_tensor(sdQaccum_layout)
|
| 639 |
+
|
| 640 |
+
block_info = BlockInfo(
|
| 641 |
+
self.tile_m,
|
| 642 |
+
self.tile_n,
|
| 643 |
+
self.is_causal,
|
| 644 |
+
self.is_local,
|
| 645 |
+
False, # is_split_kv
|
| 646 |
+
None,
|
| 647 |
+
None,
|
| 648 |
+
qhead_per_kvhead_packgqa=1,
|
| 649 |
+
)
|
| 650 |
+
SeqlenInfoCls = partial(
|
| 651 |
+
SeqlenInfoQK.create,
|
| 652 |
+
seqlen_q_static=mQ.shape[0],
|
| 653 |
+
seqlen_k_static=mK.shape[0],
|
| 654 |
+
mCuSeqlensQ=None,
|
| 655 |
+
mCuSeqlensK=None,
|
| 656 |
+
mSeqUsedQ=None,
|
| 657 |
+
mSeqUsedK=None,
|
| 658 |
+
)
|
| 659 |
+
AttentionMaskCls = partial(
|
| 660 |
+
AttentionMask,
|
| 661 |
+
self.tile_m,
|
| 662 |
+
self.tile_n,
|
| 663 |
+
window_size_left=None,
|
| 664 |
+
window_size_right=None,
|
| 665 |
+
swap_AB=self.SdP_swapAB,
|
| 666 |
+
)
|
| 667 |
+
TileSchedulerCls = partial(TileScheduler.create, tile_sched_params)
|
| 668 |
+
|
| 669 |
+
if warp_idx < 4:
|
| 670 |
+
cute.arch.warpgroup_reg_dealloc(self.num_producer_regs)
|
| 671 |
+
if warp_idx == 0:
|
| 672 |
+
self.load(
|
| 673 |
+
mQ,
|
| 674 |
+
mK,
|
| 675 |
+
mV,
|
| 676 |
+
mdO,
|
| 677 |
+
mLSE,
|
| 678 |
+
mdPsum,
|
| 679 |
+
sQ,
|
| 680 |
+
sK,
|
| 681 |
+
sV,
|
| 682 |
+
sdO,
|
| 683 |
+
sLSE,
|
| 684 |
+
sdPsum,
|
| 685 |
+
tma_atom_Q,
|
| 686 |
+
tma_atom_K,
|
| 687 |
+
tma_atom_V,
|
| 688 |
+
tma_atom_dO,
|
| 689 |
+
pipeline_Q,
|
| 690 |
+
pipeline_dO,
|
| 691 |
+
block_info,
|
| 692 |
+
SeqlenInfoCls,
|
| 693 |
+
TileSchedulerCls,
|
| 694 |
+
blocksparse_tensors,
|
| 695 |
+
qhead_per_kvhead_divmod,
|
| 696 |
+
)
|
| 697 |
+
if warp_idx == 1:
|
| 698 |
+
for warp_group_idx in cutlass.range(self.num_mma_warp_groups):
|
| 699 |
+
cute.arch.barrier_arrive(
|
| 700 |
+
barrier_id=int(NamedBarrierBwd.dQEmptyWG0) + warp_group_idx,
|
| 701 |
+
number_of_threads=self.num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 702 |
+
)
|
| 703 |
+
self.dQaccum_store(
|
| 704 |
+
mdQaccum,
|
| 705 |
+
sdQaccum,
|
| 706 |
+
block_info,
|
| 707 |
+
TileSchedulerCls,
|
| 708 |
+
SeqlenInfoCls,
|
| 709 |
+
blocksparse_tensors,
|
| 710 |
+
)
|
| 711 |
+
else:
|
| 712 |
+
cute.arch.warpgroup_reg_alloc(self.num_mma_regs)
|
| 713 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 714 |
+
tidx = tidx - 128
|
| 715 |
+
self.mma(
|
| 716 |
+
tiled_mma_SdP,
|
| 717 |
+
tiled_mma_dK,
|
| 718 |
+
tiled_mma_dV,
|
| 719 |
+
tiled_mma_dQ,
|
| 720 |
+
mdK,
|
| 721 |
+
mdV,
|
| 722 |
+
mdQaccum,
|
| 723 |
+
sQ,
|
| 724 |
+
sK,
|
| 725 |
+
sV,
|
| 726 |
+
sdO,
|
| 727 |
+
sP,
|
| 728 |
+
sdS,
|
| 729 |
+
sLSE,
|
| 730 |
+
sdPsum,
|
| 731 |
+
sdQaccum,
|
| 732 |
+
pipeline_Q,
|
| 733 |
+
pipeline_dO,
|
| 734 |
+
tidx,
|
| 735 |
+
tma_atom_dK,
|
| 736 |
+
tma_atom_dV,
|
| 737 |
+
r2s_tiled_copy_dQaccum,
|
| 738 |
+
r2s_tiled_copy_dKVaccum,
|
| 739 |
+
sdKVaccum_layout,
|
| 740 |
+
softmax_scale_log2,
|
| 741 |
+
softmax_scale,
|
| 742 |
+
block_info,
|
| 743 |
+
SeqlenInfoCls,
|
| 744 |
+
AttentionMaskCls,
|
| 745 |
+
TileSchedulerCls,
|
| 746 |
+
aux_tensors,
|
| 747 |
+
fastdiv_mods,
|
| 748 |
+
blocksparse_tensors,
|
| 749 |
+
qhead_per_kvhead_divmod,
|
| 750 |
+
)
|
| 751 |
+
|
| 752 |
+
@cute.jit
|
| 753 |
+
def load(
|
| 754 |
+
self,
|
| 755 |
+
mQ: cute.Tensor,
|
| 756 |
+
mK: cute.Tensor,
|
| 757 |
+
mV: cute.Tensor,
|
| 758 |
+
mdO: cute.Tensor,
|
| 759 |
+
mLSE: cute.Tensor,
|
| 760 |
+
mdPsum: cute.Tensor,
|
| 761 |
+
sQ: cute.Tensor,
|
| 762 |
+
sK: cute.Tensor,
|
| 763 |
+
sV: cute.Tensor,
|
| 764 |
+
sdO: cute.Tensor,
|
| 765 |
+
sLSE: cute.Tensor,
|
| 766 |
+
sdPsum: cute.Tensor,
|
| 767 |
+
tma_atom_Q: cute.CopyAtom,
|
| 768 |
+
tma_atom_K: cute.CopyAtom,
|
| 769 |
+
tma_atom_V: cute.CopyAtom,
|
| 770 |
+
tma_atom_dO: cute.CopyAtom,
|
| 771 |
+
pipeline_Q: cutlass.pipeline.PipelineAsync,
|
| 772 |
+
pipeline_dO: cutlass.pipeline.PipelineAsync,
|
| 773 |
+
block_info: BlockInfo,
|
| 774 |
+
SeqlenInfoCls: Callable,
|
| 775 |
+
TileSchedulerCls: Callable,
|
| 776 |
+
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 777 |
+
qhead_per_kvhead_divmod: Optional[FastDivmodDivisor] = None,
|
| 778 |
+
):
|
| 779 |
+
warp_idx_in_wg = cute.arch.make_warp_uniform(cute.arch.warp_idx()) % 4
|
| 780 |
+
|
| 781 |
+
if warp_idx_in_wg == 0:
|
| 782 |
+
producer_state_Q = cutlass.pipeline.make_pipeline_state(
|
| 783 |
+
cutlass.pipeline.PipelineUserType.Producer, self.Q_stage
|
| 784 |
+
)
|
| 785 |
+
producer_state_dO = cutlass.pipeline.make_pipeline_state(
|
| 786 |
+
cutlass.pipeline.PipelineUserType.Producer, self.dO_stage
|
| 787 |
+
)
|
| 788 |
+
tile_scheduler = TileSchedulerCls()
|
| 789 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 790 |
+
while work_tile.is_valid_tile:
|
| 791 |
+
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 792 |
+
seqlen = SeqlenInfoCls(batch_idx)
|
| 793 |
+
head_idx_kv = (
|
| 794 |
+
head_idx
|
| 795 |
+
if const_expr(self.qhead_per_kvhead == 1)
|
| 796 |
+
else head_idx // qhead_per_kvhead_divmod
|
| 797 |
+
)
|
| 798 |
+
mK_cur = mK[None, None, head_idx_kv, batch_idx]
|
| 799 |
+
gK = cute.local_tile(mK_cur, (self.tile_n, self.tile_hdim), (n_block, 0))
|
| 800 |
+
mV_cur = mV[None, None, head_idx_kv, batch_idx]
|
| 801 |
+
gV = cute.local_tile(mV_cur, (self.tile_n, self.tile_hdimv), (n_block, 0))
|
| 802 |
+
|
| 803 |
+
mQ_cur = mQ[None, None, head_idx, batch_idx]
|
| 804 |
+
gQ = cute.local_tile(mQ_cur, (self.tile_m, self.tile_hdim), (None, 0))
|
| 805 |
+
mdO_cur = mdO[None, None, head_idx, batch_idx]
|
| 806 |
+
gdO = cute.local_tile(mdO_cur, (self.tile_m, self.tile_hdimv), (None, 0))
|
| 807 |
+
mLSE_cur = mLSE[None, head_idx, batch_idx]
|
| 808 |
+
gLSE = cute.local_tile(mLSE_cur, (self.tile_m,), (None,))
|
| 809 |
+
mdPsum_cur = mdPsum[None, head_idx, batch_idx]
|
| 810 |
+
gdPsum = cute.local_tile(mdPsum_cur, (self.tile_m,), (None,))
|
| 811 |
+
|
| 812 |
+
load_K, _, _ = copy_utils.tma_get_copy_fn(
|
| 813 |
+
tma_atom_K, 0, cute.make_layout(1), gK, sK, single_stage=True
|
| 814 |
+
)
|
| 815 |
+
load_V, _, _ = copy_utils.tma_get_copy_fn(
|
| 816 |
+
tma_atom_V, 0, cute.make_layout(1), gV, sV, single_stage=True
|
| 817 |
+
)
|
| 818 |
+
load_Q, _, _ = copy_utils.tma_get_copy_fn(
|
| 819 |
+
tma_atom_Q, 0, cute.make_layout(1), gQ, sQ
|
| 820 |
+
)
|
| 821 |
+
load_Q = copy_utils.tma_producer_copy_fn(load_Q, pipeline_Q)
|
| 822 |
+
load_dO, _, _ = copy_utils.tma_get_copy_fn(
|
| 823 |
+
tma_atom_dO, 0, cute.make_layout(1), gdO, sdO
|
| 824 |
+
)
|
| 825 |
+
load_dO = copy_utils.tma_producer_copy_fn(load_dO, pipeline_dO)
|
| 826 |
+
load_LSE = copy_utils.cpasync_bulk_get_copy_fn(gLSE, sLSE)
|
| 827 |
+
load_LSE = copy_utils.tma_producer_copy_fn(load_LSE, pipeline_Q)
|
| 828 |
+
load_dPsum = copy_utils.cpasync_bulk_get_copy_fn(gdPsum, sdPsum)
|
| 829 |
+
load_dPsum = copy_utils.tma_producer_copy_fn(load_dPsum, pipeline_dO)
|
| 830 |
+
|
| 831 |
+
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block)
|
| 832 |
+
|
| 833 |
+
if const_expr(not self.use_block_sparsity):
|
| 834 |
+
total_m_block_cnt = m_block_max - m_block_min
|
| 835 |
+
process_tile = const_expr(not self.is_local) or m_block_min < m_block_max
|
| 836 |
+
else:
|
| 837 |
+
total_m_block_cnt = get_total_q_block_count_bwd(
|
| 838 |
+
blocksparse_tensors,
|
| 839 |
+
batch_idx,
|
| 840 |
+
head_idx,
|
| 841 |
+
n_block,
|
| 842 |
+
subtile_factor=self.subtile_factor,
|
| 843 |
+
m_block_max=m_block_max,
|
| 844 |
+
)
|
| 845 |
+
process_tile = total_m_block_cnt > Int32(0)
|
| 846 |
+
|
| 847 |
+
if process_tile:
|
| 848 |
+
if const_expr(not self.use_block_sparsity):
|
| 849 |
+
first_m_block = m_block_min
|
| 850 |
+
pipeline_Q.producer_acquire(
|
| 851 |
+
producer_state_Q, extra_tx_count=self.tma_copy_bytes["K"]
|
| 852 |
+
)
|
| 853 |
+
load_K(tma_bar_ptr=pipeline_Q.producer_get_barrier(producer_state_Q))
|
| 854 |
+
load_Q(first_m_block, producer_state=producer_state_Q)
|
| 855 |
+
with cute.arch.elect_one():
|
| 856 |
+
load_LSE(first_m_block, producer_state=producer_state_Q)
|
| 857 |
+
producer_state_dO_cur = (
|
| 858 |
+
producer_state_dO
|
| 859 |
+
if const_expr(self.Q_stage != self.dO_stage)
|
| 860 |
+
else producer_state_Q
|
| 861 |
+
)
|
| 862 |
+
pipeline_dO.producer_acquire(
|
| 863 |
+
producer_state_dO_cur, extra_tx_count=self.tma_copy_bytes["V"]
|
| 864 |
+
)
|
| 865 |
+
load_V(tma_bar_ptr=pipeline_dO.producer_get_barrier(producer_state_dO_cur))
|
| 866 |
+
load_dO(first_m_block, producer_state=producer_state_dO_cur)
|
| 867 |
+
with cute.arch.elect_one():
|
| 868 |
+
load_dPsum(first_m_block, producer_state=producer_state_dO_cur)
|
| 869 |
+
producer_state_Q.advance()
|
| 870 |
+
producer_state_dO.advance()
|
| 871 |
+
|
| 872 |
+
for m_block in cutlass.range(m_block_min + 1, m_block_max, unroll=1):
|
| 873 |
+
pipeline_Q.producer_acquire(producer_state_Q)
|
| 874 |
+
load_Q(m_block, producer_state=producer_state_Q)
|
| 875 |
+
with cute.arch.elect_one():
|
| 876 |
+
load_LSE(m_block, producer_state=producer_state_Q)
|
| 877 |
+
producer_state_dO_cur = (
|
| 878 |
+
producer_state_dO
|
| 879 |
+
if const_expr(self.Q_stage != self.dO_stage)
|
| 880 |
+
else producer_state_Q
|
| 881 |
+
)
|
| 882 |
+
pipeline_dO.producer_acquire(producer_state_dO_cur)
|
| 883 |
+
load_dO(m_block, producer_state=producer_state_dO_cur)
|
| 884 |
+
with cute.arch.elect_one():
|
| 885 |
+
load_dPsum(m_block, producer_state=producer_state_dO_cur)
|
| 886 |
+
producer_state_Q.advance()
|
| 887 |
+
producer_state_dO.advance()
|
| 888 |
+
else:
|
| 889 |
+
producer_state_Q, producer_state_dO = produce_block_sparse_q_loads_bwd_sm90(
|
| 890 |
+
blocksparse_tensors,
|
| 891 |
+
batch_idx,
|
| 892 |
+
head_idx,
|
| 893 |
+
n_block,
|
| 894 |
+
producer_state_Q,
|
| 895 |
+
producer_state_dO,
|
| 896 |
+
pipeline_Q,
|
| 897 |
+
pipeline_dO,
|
| 898 |
+
load_K,
|
| 899 |
+
load_V,
|
| 900 |
+
load_Q,
|
| 901 |
+
load_dO,
|
| 902 |
+
load_LSE,
|
| 903 |
+
load_dPsum,
|
| 904 |
+
self.tma_copy_bytes["K"],
|
| 905 |
+
self.tma_copy_bytes["V"],
|
| 906 |
+
Q_stage_eq_dO_stage=(self.Q_stage == self.dO_stage),
|
| 907 |
+
subtile_factor=self.subtile_factor,
|
| 908 |
+
m_block_max=m_block_max,
|
| 909 |
+
)
|
| 910 |
+
|
| 911 |
+
tile_scheduler.prefetch_next_work()
|
| 912 |
+
tile_scheduler.advance_to_next_work()
|
| 913 |
+
work_tile = tile_scheduler.get_current_work()
|
| 914 |
+
|
| 915 |
+
@cute.jit
|
| 916 |
+
def apply_score_mod(
|
| 917 |
+
self,
|
| 918 |
+
acc_S: cute.Tensor,
|
| 919 |
+
thr_mma_SdP: cute.core.ThrMma,
|
| 920 |
+
batch_idx,
|
| 921 |
+
head_idx,
|
| 922 |
+
m_block,
|
| 923 |
+
n_block,
|
| 924 |
+
softmax_scale,
|
| 925 |
+
seqlen_info: SeqlenInfoQK,
|
| 926 |
+
aux_tensors=None,
|
| 927 |
+
fastdiv_mods=(None, None),
|
| 928 |
+
):
|
| 929 |
+
# [NOTE] SdP_swapAB: swapAB transposes the tile, so use (n, m) indexing
|
| 930 |
+
cS = cute.make_identity_tensor(
|
| 931 |
+
(self.tile_n, self.tile_m) if self.SdP_swapAB else (self.tile_m, self.tile_n)
|
| 932 |
+
)
|
| 933 |
+
cS = cute.domain_offset(
|
| 934 |
+
(n_block * self.tile_n, m_block * self.tile_m)
|
| 935 |
+
if self.SdP_swapAB
|
| 936 |
+
else (m_block * self.tile_m, n_block * self.tile_n),
|
| 937 |
+
cS,
|
| 938 |
+
)
|
| 939 |
+
tScS = thr_mma_SdP.partition_C(cS)
|
| 940 |
+
|
| 941 |
+
apply_score_mod_inner(
|
| 942 |
+
acc_S,
|
| 943 |
+
tScS,
|
| 944 |
+
self.score_mod,
|
| 945 |
+
batch_idx,
|
| 946 |
+
head_idx,
|
| 947 |
+
softmax_scale,
|
| 948 |
+
self.vec_size,
|
| 949 |
+
self.qk_acc_dtype,
|
| 950 |
+
aux_tensors,
|
| 951 |
+
fastdiv_mods,
|
| 952 |
+
seqlen_info,
|
| 953 |
+
constant_q_idx=None,
|
| 954 |
+
qhead_per_kvhead=self.qhead_per_kvhead,
|
| 955 |
+
transpose_indices=self.SdP_swapAB,
|
| 956 |
+
)
|
| 957 |
+
|
| 958 |
+
@cute.jit
|
| 959 |
+
def apply_score_mod_bwd(
|
| 960 |
+
self,
|
| 961 |
+
grad_tensor: cute.Tensor,
|
| 962 |
+
score_tensor: cute.Tensor,
|
| 963 |
+
thr_mma_SdP: cute.core.ThrMma,
|
| 964 |
+
batch_idx,
|
| 965 |
+
head_idx,
|
| 966 |
+
m_block,
|
| 967 |
+
n_block,
|
| 968 |
+
softmax_scale,
|
| 969 |
+
seqlen_info: SeqlenInfoQK,
|
| 970 |
+
aux_tensors=None,
|
| 971 |
+
fastdiv_mods=(None, None),
|
| 972 |
+
):
|
| 973 |
+
cS = cute.make_identity_tensor(
|
| 974 |
+
(self.tile_n, self.tile_m) if self.SdP_swapAB else (self.tile_m, self.tile_n)
|
| 975 |
+
)
|
| 976 |
+
cS = cute.domain_offset(
|
| 977 |
+
(n_block * self.tile_n, m_block * self.tile_m)
|
| 978 |
+
if self.SdP_swapAB
|
| 979 |
+
else (m_block * self.tile_m, n_block * self.tile_n),
|
| 980 |
+
cS,
|
| 981 |
+
)
|
| 982 |
+
tScS = thr_mma_SdP.partition_C(cS)
|
| 983 |
+
|
| 984 |
+
apply_score_mod_bwd_inner(
|
| 985 |
+
grad_tensor,
|
| 986 |
+
score_tensor,
|
| 987 |
+
tScS,
|
| 988 |
+
self.score_mod_bwd,
|
| 989 |
+
batch_idx,
|
| 990 |
+
head_idx,
|
| 991 |
+
softmax_scale,
|
| 992 |
+
self.vec_size,
|
| 993 |
+
self.qk_acc_dtype,
|
| 994 |
+
aux_tensors,
|
| 995 |
+
fastdiv_mods,
|
| 996 |
+
seqlen_info,
|
| 997 |
+
constant_q_idx=None,
|
| 998 |
+
qhead_per_kvhead=self.qhead_per_kvhead,
|
| 999 |
+
transpose_indices=self.SdP_swapAB,
|
| 1000 |
+
)
|
| 1001 |
+
|
| 1002 |
+
@cute.jit
|
| 1003 |
+
def mma(
|
| 1004 |
+
self,
|
| 1005 |
+
tiled_mma_SdP: cute.TiledMma,
|
| 1006 |
+
tiled_mma_dK: cute.TiledMma,
|
| 1007 |
+
tiled_mma_dV: cute.TiledMma,
|
| 1008 |
+
tiled_mma_dQ: cute.TiledMma,
|
| 1009 |
+
mdK: cute.Tensor,
|
| 1010 |
+
mdV: cute.Tensor,
|
| 1011 |
+
mdQaccum: cute.Tensor,
|
| 1012 |
+
sQ: cute.Tensor,
|
| 1013 |
+
sK: cute.Tensor,
|
| 1014 |
+
sV: cute.Tensor,
|
| 1015 |
+
sdO: cute.Tensor,
|
| 1016 |
+
sP: Optional[cute.Tensor],
|
| 1017 |
+
sdS: cute.Tensor,
|
| 1018 |
+
sLSE: cute.Tensor,
|
| 1019 |
+
sdPsum: cute.Tensor,
|
| 1020 |
+
sdQaccum: cute.Tensor,
|
| 1021 |
+
pipeline_Q: cutlass.pipeline.PipelineAsync,
|
| 1022 |
+
pipeline_dO: cutlass.pipeline.PipelineAsync,
|
| 1023 |
+
tidx: Int32,
|
| 1024 |
+
tma_atom_dK: cute.CopyAtom,
|
| 1025 |
+
tma_atom_dV: cute.CopyAtom,
|
| 1026 |
+
r2s_tiled_copy_dQaccum: cute.TiledCopy,
|
| 1027 |
+
r2s_tiled_copy_dKVaccum: cute.TiledCopy,
|
| 1028 |
+
sdKVaccum_layout: cute.Layout,
|
| 1029 |
+
softmax_scale_log2: Float32,
|
| 1030 |
+
softmax_scale: Float32,
|
| 1031 |
+
block_info: BlockInfo,
|
| 1032 |
+
SeqlenInfoCls: Callable,
|
| 1033 |
+
AttentionMaskCls: Callable,
|
| 1034 |
+
TileSchedulerCls: Callable,
|
| 1035 |
+
aux_tensors: Optional[list] = None,
|
| 1036 |
+
fastdiv_mods=(None, None),
|
| 1037 |
+
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 1038 |
+
qhead_per_kvhead_divmod: Optional[FastDivmodDivisor] = None,
|
| 1039 |
+
):
|
| 1040 |
+
warp_group_idx = cute.arch.make_warp_uniform(tidx // self.num_threads_per_warp_group)
|
| 1041 |
+
warp_group_thread_layout = cute.make_layout(
|
| 1042 |
+
self.num_mma_warp_groups, stride=self.num_threads_per_warp_group
|
| 1043 |
+
)
|
| 1044 |
+
thr_mma_SdP = tiled_mma_SdP.get_slice(tidx)
|
| 1045 |
+
wg_mma_SdP = tiled_mma_SdP.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 1046 |
+
wg_mma_dK = tiled_mma_dK.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 1047 |
+
wg_mma_dV = tiled_mma_dV.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 1048 |
+
wg_mma_dQ = tiled_mma_dQ.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 1049 |
+
# S = Q @ K.T
|
| 1050 |
+
tSrQ, tSrK = mma_partition_fragment_AB(wg_mma_SdP, sQ, sK, self.SdP_swapAB)
|
| 1051 |
+
# dP = dO @ V.T
|
| 1052 |
+
tdPrdO, tdPrV = mma_partition_fragment_AB(wg_mma_SdP, sdO, sV, self.SdP_swapAB)
|
| 1053 |
+
# dV += P.T @ dO
|
| 1054 |
+
sPt = utils.transpose_view(sP) if sP is not None else None
|
| 1055 |
+
sdOt = utils.transpose_view(sdO)
|
| 1056 |
+
tdVrPt, tdVrdOt = mma_partition_fragment_AB(wg_mma_dV, sPt, sdOt, self.dKV_swapAB)
|
| 1057 |
+
# dK += dS.T @ Q
|
| 1058 |
+
sdSt = utils.transpose_view(sdS)
|
| 1059 |
+
sQt = utils.transpose_view(sQ)
|
| 1060 |
+
tdKrdSt, tdKrQt = mma_partition_fragment_AB(wg_mma_dK, sdSt, sQt, self.dKV_swapAB)
|
| 1061 |
+
# dQ = dS @ K
|
| 1062 |
+
sKt = utils.transpose_view(sK)
|
| 1063 |
+
tdQrdS, tdQrKt = mma_partition_fragment_AB(wg_mma_dQ, sdS, sKt, self.dQ_swapAB)
|
| 1064 |
+
|
| 1065 |
+
# Smem copy atom tiling
|
| 1066 |
+
smem_copy_atom_PdS = utils.get_smem_store_atom(
|
| 1067 |
+
self.arch, self.dtype, transpose=self.SdP_swapAB
|
| 1068 |
+
)
|
| 1069 |
+
smem_thr_copy_PdS = cute.make_tiled_copy_C(smem_copy_atom_PdS, tiled_mma_SdP).get_slice(
|
| 1070 |
+
tidx
|
| 1071 |
+
)
|
| 1072 |
+
tPsP = None
|
| 1073 |
+
if const_expr(sP is not None):
|
| 1074 |
+
tPsP = smem_thr_copy_PdS.partition_D(sP if const_expr(not self.SdP_swapAB) else sPt)
|
| 1075 |
+
tdSsdS = smem_thr_copy_PdS.partition_D(sdS if const_expr(not self.SdP_swapAB) else sdSt)
|
| 1076 |
+
|
| 1077 |
+
sLSE_mma = cute.make_tensor(
|
| 1078 |
+
sLSE.iterator,
|
| 1079 |
+
cute.make_layout(
|
| 1080 |
+
(self.tile_m, self.tile_n, self.Q_stage),
|
| 1081 |
+
stride=(1, 0, cute.round_up(self.tile_m, 64)),
|
| 1082 |
+
),
|
| 1083 |
+
)
|
| 1084 |
+
sdPsum_mma = cute.make_tensor(
|
| 1085 |
+
sdPsum.iterator,
|
| 1086 |
+
cute.make_layout(
|
| 1087 |
+
(self.tile_m, self.tile_n, self.dO_stage),
|
| 1088 |
+
stride=(1, 0, cute.round_up(self.tile_m, 64)),
|
| 1089 |
+
),
|
| 1090 |
+
)
|
| 1091 |
+
if const_expr(self.SdP_swapAB):
|
| 1092 |
+
sLSE_mma = utils.transpose_view(sLSE_mma)
|
| 1093 |
+
sdPsum_mma = utils.transpose_view(sdPsum_mma)
|
| 1094 |
+
LSEslice = (None, 0, None) if const_expr(not self.SdP_swapAB) else (0, None, None)
|
| 1095 |
+
tLSEsLSE = utils.make_acc_tensor_mn_view(thr_mma_SdP.partition_C(sLSE_mma))[LSEslice]
|
| 1096 |
+
tLSEsdPsum = utils.make_acc_tensor_mn_view(thr_mma_SdP.partition_C(sdPsum_mma))[LSEslice]
|
| 1097 |
+
|
| 1098 |
+
smem_thr_copy_dQaccum = r2s_tiled_copy_dQaccum.get_slice(tidx)
|
| 1099 |
+
tdQsdQaccum = smem_thr_copy_dQaccum.partition_D(sdQaccum)
|
| 1100 |
+
|
| 1101 |
+
dV_shape = (self.tile_n, self.tile_hdimv)
|
| 1102 |
+
acc_dV = cute.make_fragment(
|
| 1103 |
+
tiled_mma_dV.partition_shape_C(dV_shape if not self.dKV_swapAB else dV_shape[::-1]),
|
| 1104 |
+
Float32,
|
| 1105 |
+
)
|
| 1106 |
+
dK_shape = (self.tile_n, self.tile_hdim)
|
| 1107 |
+
acc_dK = cute.make_fragment(
|
| 1108 |
+
tiled_mma_dK.partition_shape_C(dK_shape if not self.dKV_swapAB else dK_shape[::-1]),
|
| 1109 |
+
Float32,
|
| 1110 |
+
)
|
| 1111 |
+
|
| 1112 |
+
mma_qk_fn = partial(
|
| 1113 |
+
gemm_zero_init,
|
| 1114 |
+
tiled_mma_SdP,
|
| 1115 |
+
(self.tile_m, self.tile_n),
|
| 1116 |
+
tSrQ,
|
| 1117 |
+
tSrK,
|
| 1118 |
+
swap_AB=self.SdP_swapAB,
|
| 1119 |
+
)
|
| 1120 |
+
mma_dov_fn = partial(
|
| 1121 |
+
gemm_zero_init,
|
| 1122 |
+
tiled_mma_SdP,
|
| 1123 |
+
(self.tile_m, self.tile_n),
|
| 1124 |
+
tdPrdO,
|
| 1125 |
+
tdPrV,
|
| 1126 |
+
swap_AB=self.SdP_swapAB,
|
| 1127 |
+
)
|
| 1128 |
+
if const_expr(not self.mma_dkv_is_rs):
|
| 1129 |
+
mma_pdo_fn = partial(
|
| 1130 |
+
gemm_w_idx, tiled_mma_dV, acc_dV, tdVrPt, tdVrdOt, swap_AB=self.dKV_swapAB
|
| 1131 |
+
)
|
| 1132 |
+
mma_dsq_fn = partial(
|
| 1133 |
+
gemm_w_idx, tiled_mma_dK, acc_dK, tdKrdSt, tdKrQt, swap_AB=self.dKV_swapAB
|
| 1134 |
+
)
|
| 1135 |
+
else:
|
| 1136 |
+
assert not self.dKV_swapAB
|
| 1137 |
+
mma_pdo_fn = partial(gemm_w_idx, tiled_mma_dV, acc_dV, tCrB=tdVrdOt)
|
| 1138 |
+
mma_dsq_fn = partial(gemm_w_idx, tiled_mma_dK, acc_dK, tCrB=tdKrQt)
|
| 1139 |
+
mma_dsk_fn = partial(
|
| 1140 |
+
gemm_zero_init,
|
| 1141 |
+
tiled_mma_dQ,
|
| 1142 |
+
(self.tile_m, self.tile_hdim),
|
| 1143 |
+
tdQrdS,
|
| 1144 |
+
tdQrKt,
|
| 1145 |
+
swap_AB=self.dQ_swapAB,
|
| 1146 |
+
)
|
| 1147 |
+
|
| 1148 |
+
mma_one_m_block_all = partial(
|
| 1149 |
+
self.mma_one_m_block,
|
| 1150 |
+
warp_group_idx=warp_group_idx,
|
| 1151 |
+
mma_qk_fn=mma_qk_fn,
|
| 1152 |
+
mma_dov_fn=mma_dov_fn,
|
| 1153 |
+
mma_pdo_fn=mma_pdo_fn,
|
| 1154 |
+
mma_dsq_fn=mma_dsq_fn,
|
| 1155 |
+
mma_dsk_fn=mma_dsk_fn,
|
| 1156 |
+
pipeline_Q=pipeline_Q,
|
| 1157 |
+
pipeline_dO=pipeline_dO,
|
| 1158 |
+
tLSEsLSE=tLSEsLSE,
|
| 1159 |
+
tLSEsdPsum=tLSEsdPsum,
|
| 1160 |
+
tPsP=tPsP,
|
| 1161 |
+
tdSsdS=tdSsdS,
|
| 1162 |
+
tdQsdQaccum=tdQsdQaccum,
|
| 1163 |
+
smem_thr_copy_PdS=smem_thr_copy_PdS,
|
| 1164 |
+
smem_thr_copy_dQaccum=smem_thr_copy_dQaccum,
|
| 1165 |
+
softmax_scale_log2=softmax_scale_log2,
|
| 1166 |
+
# acc_dV=acc_dV,
|
| 1167 |
+
# acc_dK=acc_dK,
|
| 1168 |
+
)
|
| 1169 |
+
|
| 1170 |
+
consumer_state_Q = cutlass.pipeline.make_pipeline_state(
|
| 1171 |
+
cutlass.pipeline.PipelineUserType.Consumer, self.Q_stage
|
| 1172 |
+
)
|
| 1173 |
+
consumer_state_dO = cutlass.pipeline.make_pipeline_state(
|
| 1174 |
+
cutlass.pipeline.PipelineUserType.Consumer, self.dO_stage
|
| 1175 |
+
)
|
| 1176 |
+
tile_scheduler = TileSchedulerCls()
|
| 1177 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1178 |
+
while work_tile.is_valid_tile:
|
| 1179 |
+
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1180 |
+
seqlen = SeqlenInfoCls(batch_idx)
|
| 1181 |
+
mask = AttentionMaskCls(seqlen)
|
| 1182 |
+
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block)
|
| 1183 |
+
|
| 1184 |
+
if const_expr(not self.use_block_sparsity):
|
| 1185 |
+
process_tile = const_expr(not self.is_local) or m_block_min < m_block_max
|
| 1186 |
+
else:
|
| 1187 |
+
total_m_block_cnt = get_total_q_block_count_bwd(
|
| 1188 |
+
blocksparse_tensors,
|
| 1189 |
+
batch_idx,
|
| 1190 |
+
head_idx,
|
| 1191 |
+
n_block,
|
| 1192 |
+
subtile_factor=self.subtile_factor,
|
| 1193 |
+
m_block_max=m_block_max,
|
| 1194 |
+
)
|
| 1195 |
+
process_tile = total_m_block_cnt > Int32(0)
|
| 1196 |
+
|
| 1197 |
+
if process_tile:
|
| 1198 |
+
if const_expr(not self.use_block_sparsity):
|
| 1199 |
+
mask_fn = partial(
|
| 1200 |
+
mask.apply_mask,
|
| 1201 |
+
batch_idx=batch_idx,
|
| 1202 |
+
head_idx=head_idx,
|
| 1203 |
+
n_block=n_block,
|
| 1204 |
+
thr_mma=thr_mma_SdP,
|
| 1205 |
+
mask_seqlen=True,
|
| 1206 |
+
mask_causal=self.is_causal,
|
| 1207 |
+
mask_local=self.is_local,
|
| 1208 |
+
mask_mod=self.mask_mod,
|
| 1209 |
+
aux_tensors=aux_tensors,
|
| 1210 |
+
fastdiv_mods=fastdiv_mods,
|
| 1211 |
+
)
|
| 1212 |
+
dKV_accumulate = False
|
| 1213 |
+
for m_block in cutlass.range(m_block_min, m_block_max, unroll=1):
|
| 1214 |
+
consumer_state_Q, consumer_state_dO = mma_one_m_block_all(
|
| 1215 |
+
m_block,
|
| 1216 |
+
consumer_state_Q,
|
| 1217 |
+
consumer_state_dO,
|
| 1218 |
+
mask_fn=mask_fn,
|
| 1219 |
+
dKV_accumulate=dKV_accumulate,
|
| 1220 |
+
thr_mma_SdP=thr_mma_SdP,
|
| 1221 |
+
batch_idx=batch_idx,
|
| 1222 |
+
head_idx=head_idx,
|
| 1223 |
+
n_block=n_block,
|
| 1224 |
+
softmax_scale=softmax_scale,
|
| 1225 |
+
seqlen=seqlen,
|
| 1226 |
+
aux_tensors=aux_tensors,
|
| 1227 |
+
fastdiv_mods=fastdiv_mods,
|
| 1228 |
+
)
|
| 1229 |
+
dKV_accumulate = True
|
| 1230 |
+
else:
|
| 1231 |
+
consumer_state_Q, consumer_state_dO = consume_block_sparse_mma_bwd_sm90(
|
| 1232 |
+
blocksparse_tensors,
|
| 1233 |
+
batch_idx,
|
| 1234 |
+
head_idx,
|
| 1235 |
+
n_block,
|
| 1236 |
+
consumer_state_Q,
|
| 1237 |
+
consumer_state_dO,
|
| 1238 |
+
mma_one_m_block_all,
|
| 1239 |
+
mask,
|
| 1240 |
+
self.mask_mod,
|
| 1241 |
+
is_causal=self.is_causal,
|
| 1242 |
+
is_local=self.is_local,
|
| 1243 |
+
thr_mma_SdP=thr_mma_SdP,
|
| 1244 |
+
softmax_scale=softmax_scale,
|
| 1245 |
+
seqlen=seqlen,
|
| 1246 |
+
subtile_factor=self.subtile_factor,
|
| 1247 |
+
m_block_max=m_block_max,
|
| 1248 |
+
aux_tensors=aux_tensors,
|
| 1249 |
+
fastdiv_mods=fastdiv_mods,
|
| 1250 |
+
)
|
| 1251 |
+
|
| 1252 |
+
if const_expr(self.qhead_per_kvhead == 1):
|
| 1253 |
+
acc_dK.store(acc_dK.load() * softmax_scale)
|
| 1254 |
+
self.epilogue_dKV(
|
| 1255 |
+
acc_dV,
|
| 1256 |
+
mdV,
|
| 1257 |
+
sV,
|
| 1258 |
+
acc_dK,
|
| 1259 |
+
mdK,
|
| 1260 |
+
sK,
|
| 1261 |
+
seqlen,
|
| 1262 |
+
tma_atom_dK,
|
| 1263 |
+
tma_atom_dV,
|
| 1264 |
+
tiled_mma_dK,
|
| 1265 |
+
tiled_mma_dV,
|
| 1266 |
+
r2s_tiled_copy_dKVaccum,
|
| 1267 |
+
sdKVaccum_layout,
|
| 1268 |
+
tidx,
|
| 1269 |
+
n_block,
|
| 1270 |
+
head_idx,
|
| 1271 |
+
batch_idx,
|
| 1272 |
+
qhead_per_kvhead_divmod,
|
| 1273 |
+
)
|
| 1274 |
+
else:
|
| 1275 |
+
# Block sparsity: KV tile with zero Q blocks produces no dK/dV; write zeros.
|
| 1276 |
+
if const_expr(self.use_block_sparsity):
|
| 1277 |
+
acc_dK.fill(0.0)
|
| 1278 |
+
acc_dV.fill(0.0)
|
| 1279 |
+
self.epilogue_dKV(
|
| 1280 |
+
acc_dV,
|
| 1281 |
+
mdV,
|
| 1282 |
+
sV,
|
| 1283 |
+
acc_dK,
|
| 1284 |
+
mdK,
|
| 1285 |
+
sK,
|
| 1286 |
+
seqlen,
|
| 1287 |
+
tma_atom_dK,
|
| 1288 |
+
tma_atom_dV,
|
| 1289 |
+
tiled_mma_dK,
|
| 1290 |
+
tiled_mma_dV,
|
| 1291 |
+
r2s_tiled_copy_dKVaccum,
|
| 1292 |
+
sdKVaccum_layout,
|
| 1293 |
+
tidx,
|
| 1294 |
+
n_block,
|
| 1295 |
+
head_idx,
|
| 1296 |
+
batch_idx,
|
| 1297 |
+
qhead_per_kvhead_divmod,
|
| 1298 |
+
)
|
| 1299 |
+
|
| 1300 |
+
tile_scheduler.advance_to_next_work()
|
| 1301 |
+
work_tile = tile_scheduler.get_current_work()
|
| 1302 |
+
|
| 1303 |
+
@cute.jit
|
| 1304 |
+
def mma_one_m_block(
|
| 1305 |
+
self,
|
| 1306 |
+
m_block: Int32,
|
| 1307 |
+
consumer_state_Q: cutlass.pipeline.PipelineState | pipeline.PipelineStateSimple,
|
| 1308 |
+
consumer_state_dO: cutlass.pipeline.PipelineState | pipeline.PipelineStateSimple,
|
| 1309 |
+
warp_group_idx: Int32,
|
| 1310 |
+
mma_qk_fn: Callable,
|
| 1311 |
+
mma_dov_fn: Callable,
|
| 1312 |
+
mma_pdo_fn: Callable,
|
| 1313 |
+
mma_dsq_fn: Callable,
|
| 1314 |
+
mma_dsk_fn: Callable,
|
| 1315 |
+
pipeline_Q: cutlass.pipeline.PipelineAsync,
|
| 1316 |
+
pipeline_dO: cutlass.pipeline.PipelineAsync,
|
| 1317 |
+
tLSEsLSE: cute.Tensor,
|
| 1318 |
+
tLSEsdPsum: cute.Tensor,
|
| 1319 |
+
tPsP: Optional[cute.Tensor],
|
| 1320 |
+
tdSsdS: Optional[cute.Tensor],
|
| 1321 |
+
tdQsdQaccum: cute.Tensor,
|
| 1322 |
+
smem_thr_copy_PdS: cute.TiledCopy,
|
| 1323 |
+
smem_thr_copy_dQaccum: cute.TiledCopy,
|
| 1324 |
+
softmax_scale_log2: Float32,
|
| 1325 |
+
mask_fn: Optional[Callable] = None,
|
| 1326 |
+
dKV_accumulate: Boolean = True,
|
| 1327 |
+
thr_mma_SdP: Optional[cute.core.ThrMma] = None,
|
| 1328 |
+
batch_idx: Int32 = 0,
|
| 1329 |
+
head_idx: Int32 = 0,
|
| 1330 |
+
n_block: Int32 = 0,
|
| 1331 |
+
softmax_scale: Float32 = 1.0,
|
| 1332 |
+
seqlen: Optional[SeqlenInfoQK] = None,
|
| 1333 |
+
aux_tensors: Optional[list] = None,
|
| 1334 |
+
fastdiv_mods=(None, None),
|
| 1335 |
+
):
|
| 1336 |
+
consumer_state_dO_cur = (
|
| 1337 |
+
consumer_state_dO if const_expr(self.Q_stage == self.dO_stage) else consumer_state_Q
|
| 1338 |
+
)
|
| 1339 |
+
smem_idx_Q = consumer_state_Q.index
|
| 1340 |
+
smem_idx_dO = consumer_state_dO_cur.index if const_expr(self.dO_stage > 1) else 0
|
| 1341 |
+
smem_idx_PdS = smem_idx_Q if const_expr(self.PdS_stage > 1) else 0
|
| 1342 |
+
# (1) [GEMM 1] S = Q @ K^T
|
| 1343 |
+
pipeline_Q.consumer_wait(consumer_state_Q, pipeline_Q.consumer_try_wait(consumer_state_Q))
|
| 1344 |
+
acc_S = mma_qk_fn(A_idx=smem_idx_Q, wg_wait=-1)
|
| 1345 |
+
tLSErLSE = copy_utils.load_s2r(tLSEsLSE[None, smem_idx_Q])
|
| 1346 |
+
# (2) [GEMM 2] dP = dO @ V.T
|
| 1347 |
+
pipeline_dO.consumer_wait(
|
| 1348 |
+
consumer_state_dO_cur, pipeline_dO.consumer_try_wait(consumer_state_dO_cur)
|
| 1349 |
+
)
|
| 1350 |
+
acc_dP = mma_dov_fn(A_idx=smem_idx_Q, wg_wait=1)
|
| 1351 |
+
|
| 1352 |
+
if const_expr(self.score_mod_bwd is not None):
|
| 1353 |
+
acc_S_pre = cute.make_fragment_like(acc_S)
|
| 1354 |
+
cute.autovec_copy(acc_S, acc_S_pre)
|
| 1355 |
+
|
| 1356 |
+
if const_expr(self.score_mod is not None):
|
| 1357 |
+
self.apply_score_mod(
|
| 1358 |
+
acc_S,
|
| 1359 |
+
thr_mma_SdP,
|
| 1360 |
+
batch_idx,
|
| 1361 |
+
head_idx,
|
| 1362 |
+
m_block,
|
| 1363 |
+
n_block,
|
| 1364 |
+
softmax_scale,
|
| 1365 |
+
seqlen,
|
| 1366 |
+
aux_tensors,
|
| 1367 |
+
fastdiv_mods,
|
| 1368 |
+
)
|
| 1369 |
+
|
| 1370 |
+
# (3) [Pointwise 1] P = exp(S - LSE)
|
| 1371 |
+
if cutlass.const_expr(mask_fn is not None):
|
| 1372 |
+
mask_fn(acc_S, m_block=m_block)
|
| 1373 |
+
acc_S_mn = utils.make_acc_tensor_mn_view(acc_S, transpose=self.SdP_swapAB)
|
| 1374 |
+
for r in cutlass.range_constexpr(cute.size(acc_S_mn, mode=[0])):
|
| 1375 |
+
for c in cutlass.range(cute.size(acc_S_mn, mode=[1]), unroll_full=True):
|
| 1376 |
+
acc_S_mn[r, c] = cute.math.exp2(
|
| 1377 |
+
acc_S_mn[r, c] * softmax_scale_log2 - tLSErLSE[r], fastmath=True
|
| 1378 |
+
)
|
| 1379 |
+
tLSErdPsum = copy_utils.load_s2r(tLSEsdPsum[None, smem_idx_dO])
|
| 1380 |
+
|
| 1381 |
+
# Convert P from f32 -> f16
|
| 1382 |
+
tdVrP = utils.cvt_f16(utils.make_acc_tensor_frgA_view(acc_S), self.dtype)
|
| 1383 |
+
# R2S for P
|
| 1384 |
+
if const_expr(not self.mma_dkv_is_rs):
|
| 1385 |
+
# sync to ensure P has already been used in the previous iteration before overwriting
|
| 1386 |
+
if const_expr(self.PdS_stage == 1):
|
| 1387 |
+
cute.arch.barrier(
|
| 1388 |
+
barrier_id=int(NamedBarrierBwd.PdS), number_of_threads=self.num_mma_threads
|
| 1389 |
+
)
|
| 1390 |
+
tPrP = smem_thr_copy_PdS.retile(tdVrP)
|
| 1391 |
+
cute.copy(smem_thr_copy_PdS, tPrP, tPsP[None, None, None, smem_idx_PdS])
|
| 1392 |
+
|
| 1393 |
+
# (4) [Pointwise 2] dS = P*(dP-dPsum)
|
| 1394 |
+
warpgroup.wait_group(0)
|
| 1395 |
+
acc_dP_mn = utils.make_acc_tensor_mn_view(acc_dP, transpose=self.SdP_swapAB)
|
| 1396 |
+
for r in cutlass.range_constexpr(cute.size(acc_dP_mn, mode=[0])):
|
| 1397 |
+
for c in cutlass.range(cute.size(acc_dP_mn, mode=[1]), unroll_full=True):
|
| 1398 |
+
acc_dP_mn[r, c] = acc_S_mn[r, c] * (acc_dP_mn[r, c] - tLSErdPsum[r])
|
| 1399 |
+
|
| 1400 |
+
if const_expr(self.score_mod_bwd is not None):
|
| 1401 |
+
self.apply_score_mod_bwd(
|
| 1402 |
+
acc_dP,
|
| 1403 |
+
acc_S_pre,
|
| 1404 |
+
thr_mma_SdP,
|
| 1405 |
+
batch_idx,
|
| 1406 |
+
head_idx,
|
| 1407 |
+
m_block,
|
| 1408 |
+
n_block,
|
| 1409 |
+
softmax_scale,
|
| 1410 |
+
seqlen,
|
| 1411 |
+
aux_tensors,
|
| 1412 |
+
fastdiv_mods,
|
| 1413 |
+
)
|
| 1414 |
+
|
| 1415 |
+
# Convert dS from f32 -> f16
|
| 1416 |
+
tdKrdS = utils.cvt_f16(utils.make_acc_tensor_frgA_view(acc_dP), self.dtype)
|
| 1417 |
+
|
| 1418 |
+
# If there's double buffering on dS, we don't need to sync here.
|
| 1419 |
+
# Otherwise we might have WG1 writing to dS before WG2 is done reading from it during MmadQ.
|
| 1420 |
+
# But because both WGs have to sync at the end of the loop and double buffering,
|
| 1421 |
+
# this race condition is not possible.
|
| 1422 |
+
# This sync is to ensure (1) P is written in case of !mma_dkv_is_rs and
|
| 1423 |
+
# (2) dS is already read by the Mma in the previous iteration in case of mma_dkv_is_rs.
|
| 1424 |
+
if const_expr(not self.mma_dkv_is_rs or (self.PdS_stage == 1 and self.mma_dkv_is_rs)):
|
| 1425 |
+
cute.arch.fence_proxy(ProxyKind.async_shared, space=SharedSpace.shared_cta)
|
| 1426 |
+
cute.arch.barrier(
|
| 1427 |
+
barrier_id=int(NamedBarrierBwd.PdS), number_of_threads=self.num_mma_threads
|
| 1428 |
+
)
|
| 1429 |
+
|
| 1430 |
+
# R2S for dS
|
| 1431 |
+
tdSrdS = smem_thr_copy_PdS.retile(tdKrdS)
|
| 1432 |
+
cute.copy(smem_thr_copy_PdS, tdSrdS, tdSsdS[None, None, None, smem_idx_PdS])
|
| 1433 |
+
|
| 1434 |
+
# (5) [GEMM 3] dV += P.T @ dO
|
| 1435 |
+
if const_expr(not self.mma_dkv_is_rs):
|
| 1436 |
+
mma_pdo_fn(
|
| 1437 |
+
A_idx=smem_idx_PdS, B_idx=smem_idx_dO, zero_init=not dKV_accumulate, wg_wait=-1
|
| 1438 |
+
)
|
| 1439 |
+
else:
|
| 1440 |
+
mma_pdo_fn(tCrA=tdVrP, B_idx=smem_idx_dO, zero_init=not dKV_accumulate, wg_wait=-1)
|
| 1441 |
+
|
| 1442 |
+
# smem fence to make sure sdS is written before it's read by WGMMA
|
| 1443 |
+
cute.arch.fence_proxy(ProxyKind.async_shared, space=SharedSpace.shared_cta)
|
| 1444 |
+
cute.arch.barrier(
|
| 1445 |
+
barrier_id=int(NamedBarrierBwd.PdS), number_of_threads=self.num_mma_threads
|
| 1446 |
+
)
|
| 1447 |
+
# (6) [GEMM 4] dQ = dS @ K
|
| 1448 |
+
acc_dQ = mma_dsk_fn(A_idx=smem_idx_PdS, wg_wait=1)
|
| 1449 |
+
# if cute.arch.thread_idx()[0] == 128: cute.print_tensor(acc_dV)
|
| 1450 |
+
pipeline_dO.consumer_release(consumer_state_dO_cur) # release dO as dV mma is done
|
| 1451 |
+
|
| 1452 |
+
# (7) [GEMM 5] dK += dS.T @ Q
|
| 1453 |
+
if const_expr(not self.mma_dkv_is_rs):
|
| 1454 |
+
mma_dsq_fn(
|
| 1455 |
+
A_idx=smem_idx_PdS, B_idx=smem_idx_Q, zero_init=not dKV_accumulate, wg_wait=1
|
| 1456 |
+
)
|
| 1457 |
+
else:
|
| 1458 |
+
mma_dsq_fn(tCrA=tdKrdS, B_idx=smem_idx_Q, zero_init=not dKV_accumulate, wg_wait=1)
|
| 1459 |
+
# if cute.arch.thread_idx()[0] == 128: cute.print_tensor(acc_dQ)
|
| 1460 |
+
|
| 1461 |
+
cute.arch.barrier(
|
| 1462 |
+
barrier_id=int(NamedBarrierBwd.dQEmptyWG0) + warp_group_idx,
|
| 1463 |
+
number_of_threads=self.num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1464 |
+
)
|
| 1465 |
+
tdQrdQaccum_flat = cute.make_tensor(acc_dQ.iterator, cute.make_layout(tdQsdQaccum.shape))
|
| 1466 |
+
cute.autovec_copy(tdQrdQaccum_flat, tdQsdQaccum)
|
| 1467 |
+
cute.arch.fence_proxy(ProxyKind.async_shared, space=SharedSpace.shared_cta)
|
| 1468 |
+
cute.arch.barrier_arrive(
|
| 1469 |
+
barrier_id=int(NamedBarrierBwd.dQFullWG0) + warp_group_idx,
|
| 1470 |
+
number_of_threads=self.num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1471 |
+
)
|
| 1472 |
+
|
| 1473 |
+
warpgroup.wait_group(0)
|
| 1474 |
+
# if cute.arch.thread_idx()[0] == 128: cute.print_tensor(acc_dK)
|
| 1475 |
+
pipeline_Q.consumer_release(consumer_state_Q)
|
| 1476 |
+
# if cute.arch.thread_idx()[0] % 32 == 0: cute.printf("tidx = {}, m_block = {}, after pipeline_Q consumer release", cute.arch.thread_idx()[0], m_block)
|
| 1477 |
+
|
| 1478 |
+
consumer_state_Q.advance()
|
| 1479 |
+
consumer_state_dO.advance()
|
| 1480 |
+
return consumer_state_Q, consumer_state_dO
|
| 1481 |
+
|
| 1482 |
+
@cute.jit
|
| 1483 |
+
def epilogue_dKV(
|
| 1484 |
+
self,
|
| 1485 |
+
acc_dV: cute.Tensor,
|
| 1486 |
+
mdV: cute.Tensor,
|
| 1487 |
+
sV: cute.Tensor,
|
| 1488 |
+
acc_dK: cute.Tensor,
|
| 1489 |
+
mdK: cute.Tensor,
|
| 1490 |
+
sK: cute.Tensor,
|
| 1491 |
+
seqlen: SeqlenInfoQK,
|
| 1492 |
+
tma_atom_dK: cute.CopyAtom,
|
| 1493 |
+
tma_atom_dV: cute.CopyAtom,
|
| 1494 |
+
tiled_mma_dK: cute.TiledMma,
|
| 1495 |
+
tiled_mma_dV: cute.TiledMma,
|
| 1496 |
+
r2s_tiled_copy_dKVaccum: cute.TiledCopy,
|
| 1497 |
+
sdKVaccum_layout: cute.Layout,
|
| 1498 |
+
tidx: Int32,
|
| 1499 |
+
n_block: Int32,
|
| 1500 |
+
head_idx: Int32,
|
| 1501 |
+
batch_idx: Int32,
|
| 1502 |
+
qhead_per_kvhead_divmod: Optional[FastDivmodDivisor] = None,
|
| 1503 |
+
):
|
| 1504 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 1505 |
+
|
| 1506 |
+
if const_expr(self.qhead_per_kvhead == 1):
|
| 1507 |
+
rdV = cute.make_fragment_like(acc_dV, self.dtype)
|
| 1508 |
+
rdV.store(acc_dV.load().to(self.dtype))
|
| 1509 |
+
rdK = utils.cvt_f16(acc_dK, self.dtype)
|
| 1510 |
+
|
| 1511 |
+
cute.arch.barrier(
|
| 1512 |
+
barrier_id=int(NamedBarrierFwd.Epilogue), number_of_threads=self.num_mma_threads
|
| 1513 |
+
)
|
| 1514 |
+
|
| 1515 |
+
smem_copy_atom_dKV = cute.make_copy_atom(
|
| 1516 |
+
cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=self.dKV_swapAB, num_matrices=4),
|
| 1517 |
+
self.dtype,
|
| 1518 |
+
)
|
| 1519 |
+
smem_thr_copy_dK = cute.make_tiled_copy_C(smem_copy_atom_dKV, tiled_mma_dK).get_slice(
|
| 1520 |
+
tidx
|
| 1521 |
+
)
|
| 1522 |
+
smem_thr_copy_dV = cute.make_tiled_copy_C(smem_copy_atom_dKV, tiled_mma_dV).get_slice(
|
| 1523 |
+
tidx
|
| 1524 |
+
)
|
| 1525 |
+
mdV_cur = mdV[None, None, head_idx, batch_idx]
|
| 1526 |
+
mdK_cur = mdK[None, None, head_idx, batch_idx]
|
| 1527 |
+
gdK = cute.local_tile(mdK_cur, (self.tile_n, self.tile_hdim), (n_block, 0))
|
| 1528 |
+
gdV = cute.local_tile(mdV_cur, (self.tile_n, self.tile_hdimv), (n_block, 0))
|
| 1529 |
+
store_dK, _, _ = copy_utils.tma_get_copy_fn(
|
| 1530 |
+
tma_atom_dK, 0, cute.make_layout(1), sK, gdK, single_stage=True
|
| 1531 |
+
)
|
| 1532 |
+
store_dV, _, _ = copy_utils.tma_get_copy_fn(
|
| 1533 |
+
tma_atom_dV, 0, cute.make_layout(1), sV, gdV, single_stage=True
|
| 1534 |
+
)
|
| 1535 |
+
|
| 1536 |
+
taccdVrdV = smem_thr_copy_dV.retile(rdV)
|
| 1537 |
+
sdV = sV if const_expr(not self.dKV_swapAB) else utils.transpose_view(sV)
|
| 1538 |
+
taccdVsdV = smem_thr_copy_dV.partition_D(sdV)
|
| 1539 |
+
cute.copy(smem_copy_atom_dKV, taccdVrdV, taccdVsdV)
|
| 1540 |
+
cute.arch.fence_proxy(ProxyKind.async_shared, space=SharedSpace.shared_cta)
|
| 1541 |
+
cute.arch.barrier(
|
| 1542 |
+
barrier_id=int(NamedBarrierFwd.Epilogue), number_of_threads=self.num_mma_threads
|
| 1543 |
+
)
|
| 1544 |
+
if warp_idx == 4:
|
| 1545 |
+
store_dV()
|
| 1546 |
+
taccdKrdK = smem_thr_copy_dK.retile(rdK)
|
| 1547 |
+
sdK = sK if const_expr(not self.dKV_swapAB) else utils.transpose_view(sK)
|
| 1548 |
+
taccdKsdK = smem_thr_copy_dK.partition_D(sdK)
|
| 1549 |
+
cute.copy(smem_copy_atom_dKV, taccdKrdK, taccdKsdK)
|
| 1550 |
+
cute.arch.fence_proxy(ProxyKind.async_shared, space=SharedSpace.shared_cta)
|
| 1551 |
+
cute.arch.barrier(
|
| 1552 |
+
barrier_id=int(NamedBarrierFwd.Epilogue), number_of_threads=self.num_mma_threads
|
| 1553 |
+
)
|
| 1554 |
+
if warp_idx == 4:
|
| 1555 |
+
store_dK()
|
| 1556 |
+
cute.arch.cp_async_bulk_commit_group()
|
| 1557 |
+
cute.arch.cp_async_bulk_wait_group(0, read=True)
|
| 1558 |
+
else:
|
| 1559 |
+
head_idx_kv = head_idx // qhead_per_kvhead_divmod
|
| 1560 |
+
|
| 1561 |
+
mdKaccum_cur = mdK[None, head_idx_kv, batch_idx]
|
| 1562 |
+
gdKaccum_ = cute.local_tile(mdKaccum_cur, (self.tile_n * self.tile_hdim,), (n_block,))
|
| 1563 |
+
gdKaccum = cute.flat_divide(
|
| 1564 |
+
gdKaccum_, (self.tile_n * self.tile_hdim // self.num_mma_warp_groups,)
|
| 1565 |
+
)
|
| 1566 |
+
|
| 1567 |
+
mdVaccum_cur = mdV[None, head_idx_kv, batch_idx]
|
| 1568 |
+
gdVaccum_ = cute.local_tile(mdVaccum_cur, (self.tile_n * self.tile_hdimv,), (n_block,))
|
| 1569 |
+
gdVaccum = cute.flat_divide(
|
| 1570 |
+
gdVaccum_, (self.tile_n * self.tile_hdimv // self.num_mma_warp_groups,)
|
| 1571 |
+
)
|
| 1572 |
+
|
| 1573 |
+
sdKVaccum = cute.make_tensor(
|
| 1574 |
+
cute.recast_ptr(sV.iterator, dtype=Float32),
|
| 1575 |
+
sdKVaccum_layout,
|
| 1576 |
+
)
|
| 1577 |
+
|
| 1578 |
+
smem_thr_copy_dKVaccum = r2s_tiled_copy_dKVaccum.get_slice(tidx)
|
| 1579 |
+
tdKsdKVaccum = smem_thr_copy_dKVaccum.partition_D(sdKVaccum)
|
| 1580 |
+
|
| 1581 |
+
cute.arch.barrier(
|
| 1582 |
+
barrier_id=int(NamedBarrierFwd.Epilogue), number_of_threads=self.num_mma_threads
|
| 1583 |
+
)
|
| 1584 |
+
|
| 1585 |
+
tdKrdKaccum_flat = cute.make_tensor(
|
| 1586 |
+
acc_dK.iterator, cute.make_layout(tdKsdKVaccum.shape)
|
| 1587 |
+
)
|
| 1588 |
+
cute.autovec_copy(tdKrdKaccum_flat, tdKsdKVaccum)
|
| 1589 |
+
cute.arch.fence_proxy(ProxyKind.async_shared, space=SharedSpace.shared_cta)
|
| 1590 |
+
cute.arch.barrier(
|
| 1591 |
+
barrier_id=int(NamedBarrierFwd.Epilogue), number_of_threads=self.num_mma_threads
|
| 1592 |
+
)
|
| 1593 |
+
|
| 1594 |
+
if warp_idx == 4:
|
| 1595 |
+
with cute.arch.elect_one():
|
| 1596 |
+
for wg_idx in cutlass.range_constexpr(self.num_mma_warp_groups):
|
| 1597 |
+
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1598 |
+
sdKVaccum[None, wg_idx].iterator,
|
| 1599 |
+
gdKaccum[None, wg_idx].iterator,
|
| 1600 |
+
self.tma_copy_bytes["dKacc"] // self.num_mma_warp_groups,
|
| 1601 |
+
)
|
| 1602 |
+
cute.arch.cp_async_bulk_commit_group()
|
| 1603 |
+
cute.arch.cp_async_bulk_wait_group(0, read=True)
|
| 1604 |
+
|
| 1605 |
+
cute.arch.barrier(
|
| 1606 |
+
barrier_id=int(NamedBarrierFwd.Epilogue), number_of_threads=self.num_mma_threads
|
| 1607 |
+
)
|
| 1608 |
+
|
| 1609 |
+
tdVrdVaccum_flat = cute.make_tensor(
|
| 1610 |
+
acc_dV.iterator, cute.make_layout(tdKsdKVaccum.shape)
|
| 1611 |
+
)
|
| 1612 |
+
cute.autovec_copy(tdVrdVaccum_flat, tdKsdKVaccum)
|
| 1613 |
+
cute.arch.fence_proxy(ProxyKind.async_shared, space=SharedSpace.shared_cta)
|
| 1614 |
+
cute.arch.barrier(
|
| 1615 |
+
barrier_id=int(NamedBarrierFwd.Epilogue), number_of_threads=self.num_mma_threads
|
| 1616 |
+
)
|
| 1617 |
+
|
| 1618 |
+
if warp_idx == 4:
|
| 1619 |
+
with cute.arch.elect_one():
|
| 1620 |
+
for wg_idx in cutlass.range_constexpr(self.num_mma_warp_groups):
|
| 1621 |
+
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1622 |
+
sdKVaccum[None, wg_idx].iterator,
|
| 1623 |
+
gdVaccum[None, wg_idx].iterator,
|
| 1624 |
+
self.tma_copy_bytes["dVacc"] // self.num_mma_warp_groups,
|
| 1625 |
+
)
|
| 1626 |
+
cute.arch.cp_async_bulk_commit_group()
|
| 1627 |
+
cute.arch.cp_async_bulk_wait_group(0, read=True)
|
| 1628 |
+
|
| 1629 |
+
@cute.jit
|
| 1630 |
+
def dQaccum_store(
|
| 1631 |
+
self,
|
| 1632 |
+
mdQaccum: cute.Tensor,
|
| 1633 |
+
sdQaccum: cute.Tensor,
|
| 1634 |
+
block_info: BlockInfo,
|
| 1635 |
+
TileSchedulerCls: cutlass.Constexpr[Callable],
|
| 1636 |
+
SeqlenInfoCls: cutlass.Constexpr[Callable],
|
| 1637 |
+
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 1638 |
+
):
|
| 1639 |
+
tile_scheduler = TileSchedulerCls()
|
| 1640 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1641 |
+
while work_tile.is_valid_tile:
|
| 1642 |
+
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1643 |
+
seqlen = SeqlenInfoCls(batch_idx)
|
| 1644 |
+
mdQaccum_cur = mdQaccum[None, head_idx, batch_idx]
|
| 1645 |
+
gdQaccum_ = cute.local_tile(mdQaccum_cur, (self.tile_m * self.tile_hdim,), (None,))
|
| 1646 |
+
# (M * K / WG, WG, _)
|
| 1647 |
+
gdQaccum = cute.flat_divide(
|
| 1648 |
+
gdQaccum_, (self.tile_m * self.tile_hdim // self.num_mma_warp_groups,)
|
| 1649 |
+
)
|
| 1650 |
+
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block)
|
| 1651 |
+
if const_expr(not self.use_block_sparsity):
|
| 1652 |
+
process_tile = const_expr(not self.is_local) or m_block_min < m_block_max
|
| 1653 |
+
loop_count = m_block_max - m_block_min
|
| 1654 |
+
else:
|
| 1655 |
+
total_block_cnt = get_total_q_block_count_bwd(
|
| 1656 |
+
blocksparse_tensors,
|
| 1657 |
+
batch_idx,
|
| 1658 |
+
head_idx,
|
| 1659 |
+
n_block,
|
| 1660 |
+
subtile_factor=self.subtile_factor,
|
| 1661 |
+
m_block_max=m_block_max,
|
| 1662 |
+
)
|
| 1663 |
+
process_tile = total_block_cnt > Int32(0)
|
| 1664 |
+
|
| 1665 |
+
if process_tile:
|
| 1666 |
+
if const_expr(not self.use_block_sparsity):
|
| 1667 |
+
for iter_idx in cutlass.range(loop_count, unroll=1):
|
| 1668 |
+
m_block = m_block_min + iter_idx
|
| 1669 |
+
m_block_safe = m_block
|
| 1670 |
+
|
| 1671 |
+
for warp_group_idx in cutlass.range_constexpr(self.num_mma_warp_groups):
|
| 1672 |
+
cute.arch.barrier(
|
| 1673 |
+
barrier_id=int(NamedBarrierBwd.dQFullWG0) + warp_group_idx,
|
| 1674 |
+
number_of_threads=self.num_threads_per_warp_group
|
| 1675 |
+
+ cute.arch.WARP_SIZE,
|
| 1676 |
+
)
|
| 1677 |
+
with cute.arch.elect_one():
|
| 1678 |
+
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1679 |
+
sdQaccum[None, warp_group_idx].iterator,
|
| 1680 |
+
gdQaccum[None, warp_group_idx, m_block_safe].iterator,
|
| 1681 |
+
self.tma_copy_bytes["dQ"],
|
| 1682 |
+
)
|
| 1683 |
+
cute.arch.cp_async_bulk_commit_group()
|
| 1684 |
+
for warp_group_idx in cutlass.range_constexpr(self.num_mma_warp_groups):
|
| 1685 |
+
cute.arch.cp_async_bulk_wait_group(
|
| 1686 |
+
self.num_mma_warp_groups - 1 - warp_group_idx, read=True
|
| 1687 |
+
)
|
| 1688 |
+
cute.arch.barrier_arrive(
|
| 1689 |
+
barrier_id=int(NamedBarrierBwd.dQEmptyWG0) + warp_group_idx,
|
| 1690 |
+
number_of_threads=self.num_threads_per_warp_group
|
| 1691 |
+
+ cute.arch.WARP_SIZE,
|
| 1692 |
+
)
|
| 1693 |
+
else:
|
| 1694 |
+
dQaccum_store_block_sparse_bwd_sm90(
|
| 1695 |
+
blocksparse_tensors,
|
| 1696 |
+
batch_idx,
|
| 1697 |
+
head_idx,
|
| 1698 |
+
n_block,
|
| 1699 |
+
sdQaccum,
|
| 1700 |
+
gdQaccum,
|
| 1701 |
+
subtile_factor=self.subtile_factor,
|
| 1702 |
+
m_block_max=m_block_max,
|
| 1703 |
+
num_mma_warp_groups=self.num_mma_warp_groups,
|
| 1704 |
+
num_threads_per_warp_group=self.num_threads_per_warp_group,
|
| 1705 |
+
tma_copy_bytes_dQ=self.tma_copy_bytes["dQ"],
|
| 1706 |
+
)
|
| 1707 |
+
tile_scheduler.advance_to_next_work()
|
| 1708 |
+
work_tile = tile_scheduler.get_current_work()
|
kernel/deps/flash_attn/cute/flash_fwd.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
kernel/deps/flash_attn/cute/flash_fwd_combine.py
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
# A reimplementation of https://github.com/Dao-AILab/flash-attention/blob/main/hopper/flash_fwd_combine_kernel.h
|
| 3 |
+
# from Cutlass C++ to Cute-DSL.
|
| 4 |
+
import math
|
| 5 |
+
import operator
|
| 6 |
+
from typing import Type, Optional
|
| 7 |
+
from functools import partial
|
| 8 |
+
|
| 9 |
+
import cuda.bindings.driver as cuda
|
| 10 |
+
|
| 11 |
+
import cutlass
|
| 12 |
+
import cutlass.cute as cute
|
| 13 |
+
from cutlass.cute.nvgpu import cpasync
|
| 14 |
+
from cutlass import Float32, Int32, const_expr
|
| 15 |
+
|
| 16 |
+
from flash_attn.cute import utils
|
| 17 |
+
from flash_attn.cute.seqlen_info import SeqlenInfo
|
| 18 |
+
from cutlass.cute import FastDivmodDivisor
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class FlashAttentionForwardCombine:
|
| 22 |
+
def __init__(
|
| 23 |
+
self,
|
| 24 |
+
dtype: Type[cutlass.Numeric],
|
| 25 |
+
dtype_partial: Type[cutlass.Numeric],
|
| 26 |
+
head_dim: int,
|
| 27 |
+
m_block_size: int = 8,
|
| 28 |
+
k_block_size: int = 64,
|
| 29 |
+
log_max_splits: int = 4,
|
| 30 |
+
num_threads: int = 256,
|
| 31 |
+
stages: int = 4,
|
| 32 |
+
):
|
| 33 |
+
"""
|
| 34 |
+
Forward combine kernel for split attention computation.
|
| 35 |
+
|
| 36 |
+
:param dtype: output data type
|
| 37 |
+
:param dtype_partial: partial accumulation data type
|
| 38 |
+
:param head_dim: head dimension
|
| 39 |
+
:param m_block_size: m block size
|
| 40 |
+
:param k_block_size: k block size
|
| 41 |
+
:param log_max_splits: log2 of maximum splits
|
| 42 |
+
:param num_threads: number of threads
|
| 43 |
+
:param varlen: whether using variable length sequences
|
| 44 |
+
:param stages: number of pipeline stages
|
| 45 |
+
"""
|
| 46 |
+
self.dtype = dtype
|
| 47 |
+
self.dtype_partial = dtype_partial
|
| 48 |
+
self.head_dim = head_dim
|
| 49 |
+
self.m_block_size = m_block_size
|
| 50 |
+
self.k_block_size = k_block_size
|
| 51 |
+
self.max_splits = 1 << log_max_splits
|
| 52 |
+
self.num_threads = num_threads
|
| 53 |
+
self.is_even_k = head_dim % k_block_size == 0
|
| 54 |
+
self.stages = stages
|
| 55 |
+
|
| 56 |
+
@staticmethod
|
| 57 |
+
def can_implement(
|
| 58 |
+
dtype,
|
| 59 |
+
dtype_partial,
|
| 60 |
+
head_dim,
|
| 61 |
+
m_block_size,
|
| 62 |
+
k_block_size,
|
| 63 |
+
log_max_splits,
|
| 64 |
+
num_threads,
|
| 65 |
+
) -> bool:
|
| 66 |
+
"""Check if the kernel can be implemented with the given parameters."""
|
| 67 |
+
if dtype not in [cutlass.Float16, cutlass.BFloat16, cutlass.Float32]:
|
| 68 |
+
return False
|
| 69 |
+
if dtype_partial not in [cutlass.Float16, cutlass.BFloat16, Float32]:
|
| 70 |
+
return False
|
| 71 |
+
if head_dim % 8 != 0:
|
| 72 |
+
return False
|
| 73 |
+
if num_threads % 32 != 0:
|
| 74 |
+
return False
|
| 75 |
+
if m_block_size % 8 != 0:
|
| 76 |
+
return False
|
| 77 |
+
max_splits = 1 << log_max_splits
|
| 78 |
+
if max_splits > 256:
|
| 79 |
+
return False
|
| 80 |
+
if (m_block_size * max_splits) % num_threads != 0:
|
| 81 |
+
return False
|
| 82 |
+
return True
|
| 83 |
+
|
| 84 |
+
def _setup_attributes(self):
|
| 85 |
+
# GMEM copy setup for O partial
|
| 86 |
+
universal_copy_bits = 128
|
| 87 |
+
async_copy_elems = universal_copy_bits // self.dtype_partial.width
|
| 88 |
+
assert self.k_block_size % async_copy_elems == 0
|
| 89 |
+
|
| 90 |
+
k_block_gmem = (
|
| 91 |
+
128 if self.k_block_size % 128 == 0 else (64 if self.k_block_size % 64 == 0 else 32)
|
| 92 |
+
)
|
| 93 |
+
gmem_threads_per_row = k_block_gmem // async_copy_elems
|
| 94 |
+
assert self.num_threads % gmem_threads_per_row == 0
|
| 95 |
+
|
| 96 |
+
# Async copy atom for O partial load
|
| 97 |
+
atom_async_copy_partial = cute.make_copy_atom(
|
| 98 |
+
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.GLOBAL),
|
| 99 |
+
self.dtype_partial,
|
| 100 |
+
num_bits_per_copy=universal_copy_bits,
|
| 101 |
+
)
|
| 102 |
+
tOpartial_layout = cute.make_ordered_layout(
|
| 103 |
+
(self.num_threads // gmem_threads_per_row, gmem_threads_per_row),
|
| 104 |
+
order=(1, 0),
|
| 105 |
+
)
|
| 106 |
+
vOpartial_layout = cute.make_layout((1, async_copy_elems)) # 4 vals per load
|
| 107 |
+
self.gmem_tiled_copy_O_partial = cute.make_tiled_copy_tv(
|
| 108 |
+
atom_async_copy_partial, tOpartial_layout, vOpartial_layout
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
# GMEM copy setup for final O (use universal copy for store)
|
| 112 |
+
atom_universal_copy = cute.make_copy_atom(
|
| 113 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 114 |
+
self.dtype,
|
| 115 |
+
num_bits_per_copy=async_copy_elems * self.dtype.width,
|
| 116 |
+
)
|
| 117 |
+
self.gmem_tiled_copy_O = cute.make_tiled_copy_tv(
|
| 118 |
+
atom_universal_copy,
|
| 119 |
+
tOpartial_layout,
|
| 120 |
+
vOpartial_layout, # 4 vals per store
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# LSE copy setup with async copy (alignment = 1)
|
| 124 |
+
lse_copy_bits = Float32.width # 1 element per copy, width is in bits
|
| 125 |
+
m_block_smem = (
|
| 126 |
+
128
|
| 127 |
+
if self.m_block_size % 128 == 0
|
| 128 |
+
else (
|
| 129 |
+
64
|
| 130 |
+
if self.m_block_size % 64 == 0
|
| 131 |
+
else (
|
| 132 |
+
32
|
| 133 |
+
if self.m_block_size % 32 == 0
|
| 134 |
+
else (16 if self.m_block_size % 16 == 0 else 8)
|
| 135 |
+
)
|
| 136 |
+
)
|
| 137 |
+
)
|
| 138 |
+
gmem_threads_per_row_lse = m_block_smem
|
| 139 |
+
assert self.num_threads % gmem_threads_per_row_lse == 0
|
| 140 |
+
|
| 141 |
+
# Async copy atom for LSE load
|
| 142 |
+
atom_async_copy_lse = cute.make_copy_atom(
|
| 143 |
+
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.ALWAYS),
|
| 144 |
+
Float32,
|
| 145 |
+
num_bits_per_copy=lse_copy_bits,
|
| 146 |
+
)
|
| 147 |
+
tLSE_layout = cute.make_ordered_layout(
|
| 148 |
+
(self.num_threads // gmem_threads_per_row_lse, gmem_threads_per_row_lse),
|
| 149 |
+
order=(1, 0),
|
| 150 |
+
)
|
| 151 |
+
vLSE_layout = cute.make_layout(1)
|
| 152 |
+
self.gmem_tiled_copy_LSE = cute.make_tiled_copy_tv(
|
| 153 |
+
atom_async_copy_lse, tLSE_layout, vLSE_layout
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 157 |
+
# Shared memory
|
| 158 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 159 |
+
|
| 160 |
+
# Shared memory to register copy for LSE
|
| 161 |
+
self.smem_threads_per_col_lse = self.num_threads // m_block_smem
|
| 162 |
+
assert 32 % self.smem_threads_per_col_lse == 0 # Must divide warp size
|
| 163 |
+
|
| 164 |
+
s2r_layout_atom_lse = cute.make_ordered_layout(
|
| 165 |
+
(self.smem_threads_per_col_lse, self.num_threads // self.smem_threads_per_col_lse),
|
| 166 |
+
order=(0, 1),
|
| 167 |
+
)
|
| 168 |
+
self.s2r_tiled_copy_LSE = cute.make_tiled_copy_tv(
|
| 169 |
+
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32),
|
| 170 |
+
s2r_layout_atom_lse,
|
| 171 |
+
cute.make_layout(1),
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
# LSE shared memory layout with swizzling to avoid bank conflicts
|
| 175 |
+
# This works for kBlockMSmem = 8, 16, 32, 64, 128, no bank conflicts
|
| 176 |
+
if const_expr(m_block_smem == 8):
|
| 177 |
+
smem_lse_swizzle = cute.make_swizzle(5, 0, 5)
|
| 178 |
+
elif const_expr(m_block_smem == 16):
|
| 179 |
+
smem_lse_swizzle = cute.make_swizzle(4, 0, 4)
|
| 180 |
+
else:
|
| 181 |
+
smem_lse_swizzle = cute.make_swizzle(3, 2, 3)
|
| 182 |
+
smem_layout_atom_lse = cute.make_composed_layout(
|
| 183 |
+
smem_lse_swizzle, 0, cute.make_ordered_layout((8, m_block_smem), order=(1, 0))
|
| 184 |
+
)
|
| 185 |
+
self.smem_layout_lse = cute.tile_to_shape(
|
| 186 |
+
smem_layout_atom_lse, (self.max_splits, self.m_block_size), (0, 1)
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
# O partial shared memory layout (simple layout for pipeline stages)
|
| 190 |
+
self.smem_layout_o = cute.make_ordered_layout(
|
| 191 |
+
(self.m_block_size, self.k_block_size, self.stages), order=(1, 0, 2)
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
@cute.jit
|
| 195 |
+
def __call__(
|
| 196 |
+
self,
|
| 197 |
+
mO_partial: cute.Tensor,
|
| 198 |
+
mLSE_partial: cute.Tensor,
|
| 199 |
+
mO: cute.Tensor,
|
| 200 |
+
mLSE: Optional[cute.Tensor] = None,
|
| 201 |
+
cu_seqlens: Optional[cute.Tensor] = None,
|
| 202 |
+
seqused: Optional[cute.Tensor] = None,
|
| 203 |
+
num_splits_dynamic_ptr: Optional[cute.Tensor] = None,
|
| 204 |
+
semaphore_to_reset: Optional[cute.Tensor] = None,
|
| 205 |
+
stream: cuda.CUstream = None,
|
| 206 |
+
):
|
| 207 |
+
# Type checking
|
| 208 |
+
if const_expr(not (mO_partial.element_type == self.dtype_partial)):
|
| 209 |
+
raise TypeError("O partial tensor must match dtype_partial")
|
| 210 |
+
if const_expr(not (mO.element_type == self.dtype)):
|
| 211 |
+
raise TypeError("O tensor must match dtype")
|
| 212 |
+
if const_expr(mLSE_partial.element_type not in [Float32]):
|
| 213 |
+
raise TypeError("LSE partial tensor must be Float32")
|
| 214 |
+
if const_expr(mLSE is not None and mLSE.element_type not in [Float32]):
|
| 215 |
+
raise TypeError("LSE tensor must be Float32")
|
| 216 |
+
|
| 217 |
+
# Shape validation - input tensors are in user format, need to be converted to kernel format
|
| 218 |
+
if const_expr(len(mO_partial.shape) not in [4, 5]):
|
| 219 |
+
raise ValueError(
|
| 220 |
+
"O partial tensor must have 4 or 5 dimensions: (num_splits, batch, seqlen, nheads, headdim) or (num_splits, total_q, nheads, headdim)"
|
| 221 |
+
)
|
| 222 |
+
if const_expr(len(mLSE_partial.shape) not in [3, 4]):
|
| 223 |
+
raise ValueError(
|
| 224 |
+
"LSE partial tensor must have 3 or 4 dimensions: (num_splits, batch, seqlen, nheads) or (num_splits, total_q, nheads)"
|
| 225 |
+
)
|
| 226 |
+
if const_expr(len(mO.shape) not in [3, 4]):
|
| 227 |
+
raise ValueError(
|
| 228 |
+
"O tensor must have 3 or 4 dimensions: (batch, seqlen, nheads, headdim) or (total_q, nheads, headdim)"
|
| 229 |
+
)
|
| 230 |
+
if const_expr(mLSE is not None and len(mLSE.shape) not in [2, 3]):
|
| 231 |
+
raise ValueError(
|
| 232 |
+
"LSE tensor must have 2 or 3 dimensions: (batch, seqlen, nheads) or (total_q, nheads)"
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
# Assume all strides are divisible by 128 bits except the last stride
|
| 236 |
+
new_stride = lambda t: (
|
| 237 |
+
*(cute.assume(s, divby=128 // t.element_type.width) for s in t.stride[:-1]),
|
| 238 |
+
t.stride[-1],
|
| 239 |
+
)
|
| 240 |
+
mO_partial, mO = [
|
| 241 |
+
cute.make_tensor(t.iterator, cute.make_layout(t.shape, stride=new_stride(t)))
|
| 242 |
+
for t in (mO_partial, mO)
|
| 243 |
+
]
|
| 244 |
+
# (num_splits, b, seqlen, h, d) -> (seqlen, d, num_splits, h, b)
|
| 245 |
+
# or (num_splits, total_q, h, d) -> (total_q, d, num_splits, h)
|
| 246 |
+
O_partial_layout_transpose = (
|
| 247 |
+
[2, 4, 0, 3, 1] if const_expr(cu_seqlens is None) else [1, 3, 0, 2]
|
| 248 |
+
)
|
| 249 |
+
# (b, seqlen, h, d) -> (seqlen, d, h, b) or (total_q, h, d) -> (total_q, d, h)
|
| 250 |
+
mO_partial = cute.make_tensor(
|
| 251 |
+
mO_partial.iterator, cute.select(mO_partial.layout, mode=O_partial_layout_transpose)
|
| 252 |
+
)
|
| 253 |
+
O_layout_transpose = [1, 3, 2, 0] if const_expr(cu_seqlens is None) else [0, 2, 1]
|
| 254 |
+
mO = cute.make_tensor(mO.iterator, cute.select(mO.layout, mode=O_layout_transpose))
|
| 255 |
+
# (num_splits, b, seqlen, h) -> (seqlen, num_splits, h, b)
|
| 256 |
+
# or (num_splits, total_q, h) -> (total_q, num_splits, h)
|
| 257 |
+
LSE_partial_layout_transpose = [2, 0, 3, 1] if const_expr(cu_seqlens is None) else [1, 0, 2]
|
| 258 |
+
mLSE_partial = cute.make_tensor(
|
| 259 |
+
mLSE_partial.iterator,
|
| 260 |
+
cute.select(mLSE_partial.layout, mode=LSE_partial_layout_transpose),
|
| 261 |
+
)
|
| 262 |
+
# (b, seqlen, h) -> (seqlen, h, b) or (total_q, h) -> (total_q, h)
|
| 263 |
+
LSE_layout_transpose = [1, 2, 0] if const_expr(cu_seqlens is None) else [0, 1]
|
| 264 |
+
mLSE = (
|
| 265 |
+
cute.make_tensor(mLSE.iterator, cute.select(mLSE.layout, mode=LSE_layout_transpose))
|
| 266 |
+
if mLSE is not None
|
| 267 |
+
else None
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
# Determine if we have variable length sequences
|
| 271 |
+
varlen = const_expr(cu_seqlens is not None or seqused is not None)
|
| 272 |
+
|
| 273 |
+
self._setup_attributes()
|
| 274 |
+
|
| 275 |
+
@cute.struct
|
| 276 |
+
class SharedStorage:
|
| 277 |
+
sLSE: cute.struct.Align[
|
| 278 |
+
cute.struct.MemRange[Float32, cute.cosize(self.smem_layout_lse)], 128
|
| 279 |
+
]
|
| 280 |
+
sMaxValidSplit: cute.struct.Align[cute.struct.MemRange[Int32, self.m_block_size], 128]
|
| 281 |
+
sO: cute.struct.Align[
|
| 282 |
+
cute.struct.MemRange[self.dtype_partial, cute.cosize(self.smem_layout_o)], 128
|
| 283 |
+
]
|
| 284 |
+
|
| 285 |
+
smem_size = SharedStorage.size_in_bytes()
|
| 286 |
+
|
| 287 |
+
# Grid dimensions: (ceil_div(seqlen, m_block), ceil_div(head_dim, k_block), num_head * batch)
|
| 288 |
+
seqlen = mO_partial.shape[0]
|
| 289 |
+
num_head = mO_partial.shape[3]
|
| 290 |
+
batch_size = (
|
| 291 |
+
mO_partial.shape[4]
|
| 292 |
+
if const_expr(cu_seqlens is None)
|
| 293 |
+
else Int32(cu_seqlens.shape[0] - 1)
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
# Create FastDivmodDivisor objects for efficient division
|
| 297 |
+
seqlen_divmod = FastDivmodDivisor(seqlen)
|
| 298 |
+
head_divmod = FastDivmodDivisor(num_head)
|
| 299 |
+
|
| 300 |
+
grid_dim = (
|
| 301 |
+
cute.ceil_div(seqlen * num_head, self.m_block_size),
|
| 302 |
+
cute.ceil_div(self.head_dim, self.k_block_size),
|
| 303 |
+
batch_size,
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
self.kernel(
|
| 307 |
+
mO_partial,
|
| 308 |
+
mLSE_partial,
|
| 309 |
+
mO,
|
| 310 |
+
mLSE,
|
| 311 |
+
cu_seqlens,
|
| 312 |
+
seqused,
|
| 313 |
+
num_splits_dynamic_ptr,
|
| 314 |
+
semaphore_to_reset,
|
| 315 |
+
SharedStorage,
|
| 316 |
+
self.smem_layout_lse,
|
| 317 |
+
self.smem_layout_o,
|
| 318 |
+
self.gmem_tiled_copy_O_partial,
|
| 319 |
+
self.gmem_tiled_copy_O,
|
| 320 |
+
self.gmem_tiled_copy_LSE,
|
| 321 |
+
self.s2r_tiled_copy_LSE,
|
| 322 |
+
seqlen_divmod,
|
| 323 |
+
head_divmod,
|
| 324 |
+
varlen,
|
| 325 |
+
).launch(
|
| 326 |
+
grid=grid_dim,
|
| 327 |
+
block=[self.num_threads, 1, 1],
|
| 328 |
+
smem=smem_size,
|
| 329 |
+
stream=stream,
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
@cute.kernel
|
| 333 |
+
def kernel(
|
| 334 |
+
self,
|
| 335 |
+
mO_partial: cute.Tensor,
|
| 336 |
+
mLSE_partial: cute.Tensor,
|
| 337 |
+
mO: cute.Tensor,
|
| 338 |
+
mLSE: Optional[cute.Tensor],
|
| 339 |
+
cu_seqlens: Optional[cute.Tensor],
|
| 340 |
+
seqused: Optional[cute.Tensor],
|
| 341 |
+
num_splits_dynamic_ptr: Optional[cute.Tensor],
|
| 342 |
+
semaphore_to_reset: Optional[cute.Tensor],
|
| 343 |
+
SharedStorage: cutlass.Constexpr,
|
| 344 |
+
smem_layout_lse: cute.Layout | cute.ComposedLayout,
|
| 345 |
+
smem_layout_o: cute.Layout,
|
| 346 |
+
gmem_tiled_copy_O_partial: cute.TiledCopy,
|
| 347 |
+
gmem_tiled_copy_O: cute.TiledCopy,
|
| 348 |
+
gmem_tiled_copy_LSE: cute.TiledCopy,
|
| 349 |
+
s2r_tiled_copy_LSE: cute.TiledCopy,
|
| 350 |
+
seqlen_divmod: FastDivmodDivisor,
|
| 351 |
+
head_divmod: FastDivmodDivisor,
|
| 352 |
+
varlen: cutlass.Constexpr[bool],
|
| 353 |
+
):
|
| 354 |
+
# Thread and block indices
|
| 355 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 356 |
+
m_block, k_block, batch_idx = cute.arch.block_idx()
|
| 357 |
+
|
| 358 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 359 |
+
# Get shared memory buffer
|
| 360 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 361 |
+
smem = cutlass.utils.SmemAllocator()
|
| 362 |
+
storage = smem.allocate(SharedStorage)
|
| 363 |
+
sLSE = storage.sLSE.get_tensor(smem_layout_lse)
|
| 364 |
+
sMaxValidSplit = storage.sMaxValidSplit.get_tensor((self.m_block_size,))
|
| 365 |
+
sO = storage.sO.get_tensor(smem_layout_o)
|
| 366 |
+
|
| 367 |
+
# Handle semaphore reset
|
| 368 |
+
if const_expr(semaphore_to_reset is not None):
|
| 369 |
+
if (
|
| 370 |
+
tidx == 0
|
| 371 |
+
and m_block == cute.arch.grid_dim()[0] - 1
|
| 372 |
+
and k_block == cute.arch.grid_dim()[1] - 1
|
| 373 |
+
and batch_idx == cute.arch.grid_dim()[2] - 1
|
| 374 |
+
):
|
| 375 |
+
semaphore_to_reset[0] = 0
|
| 376 |
+
|
| 377 |
+
# Get number of splits
|
| 378 |
+
num_splits = (
|
| 379 |
+
num_splits_dynamic_ptr[batch_idx]
|
| 380 |
+
if const_expr(num_splits_dynamic_ptr is not None)
|
| 381 |
+
else mLSE_partial.shape[1]
|
| 382 |
+
)
|
| 383 |
+
# Handle variable length sequences using SeqlenInfo
|
| 384 |
+
seqlen_info = SeqlenInfo.create(
|
| 385 |
+
batch_idx=batch_idx,
|
| 386 |
+
seqlen_static=mO_partial.shape[0],
|
| 387 |
+
cu_seqlens=cu_seqlens,
|
| 388 |
+
seqused=seqused,
|
| 389 |
+
)
|
| 390 |
+
seqlen, offset = seqlen_info.seqlen, seqlen_info.offset
|
| 391 |
+
|
| 392 |
+
# Extract number of heads (head index will be determined dynamically)
|
| 393 |
+
num_head = mO_partial.shape[3]
|
| 394 |
+
max_idx = seqlen * num_head
|
| 395 |
+
|
| 396 |
+
# Early exit for single split if dynamic
|
| 397 |
+
if (const_expr(num_splits_dynamic_ptr is None) or num_splits > 1) and (
|
| 398 |
+
const_expr(not varlen) or m_block * self.m_block_size < max_idx
|
| 399 |
+
):
|
| 400 |
+
# ===============================
|
| 401 |
+
# Step 1: Load LSE_partial from gmem to shared memory
|
| 402 |
+
# ===============================
|
| 403 |
+
|
| 404 |
+
if const_expr(cu_seqlens is None):
|
| 405 |
+
# mLSE_partial_cur = mLSE_partial[None, None, None, batch_idx]
|
| 406 |
+
mLSE_partial_cur = utils.coord_offset_i64(mLSE_partial, batch_idx, dim=3)
|
| 407 |
+
else:
|
| 408 |
+
# mLSE_partial_cur = cute.domain_offset((offset, 0, 0), mLSE_partial)
|
| 409 |
+
mLSE_partial_cur = utils.domain_offset_i64((offset, 0, 0), mLSE_partial)
|
| 410 |
+
mLSE_partial_copy = cute.tiled_divide(mLSE_partial_cur, (1,))
|
| 411 |
+
|
| 412 |
+
gmem_thr_copy_LSE = gmem_tiled_copy_LSE.get_slice(tidx)
|
| 413 |
+
tLSEsLSE = gmem_thr_copy_LSE.partition_D(sLSE)
|
| 414 |
+
|
| 415 |
+
# Create identity tensor for coordinate tracking
|
| 416 |
+
cLSE = cute.make_identity_tensor((self.max_splits, self.m_block_size))
|
| 417 |
+
tLSEcLSE = gmem_thr_copy_LSE.partition_S(cLSE)
|
| 418 |
+
|
| 419 |
+
# Load LSE partial values
|
| 420 |
+
for m in cutlass.range(cute.size(tLSEcLSE, mode=[2]), unroll_full=True):
|
| 421 |
+
mi = tLSEcLSE[0, 0, m][1] # Get m coordinate
|
| 422 |
+
idx = m_block * self.m_block_size + mi
|
| 423 |
+
if idx < max_idx:
|
| 424 |
+
# Calculate actual sequence position and head using FastDivmodDivisor
|
| 425 |
+
if const_expr(not varlen):
|
| 426 |
+
head_idx, m_idx = divmod(idx, seqlen_divmod)
|
| 427 |
+
else:
|
| 428 |
+
head_idx = idx // seqlen
|
| 429 |
+
m_idx = idx - head_idx * seqlen
|
| 430 |
+
mLSE_partial_cur_copy = mLSE_partial_copy[None, m_idx, None, head_idx]
|
| 431 |
+
for s in cutlass.range(cute.size(tLSEcLSE, mode=[1]), unroll_full=True):
|
| 432 |
+
si = tLSEcLSE[0, s, 0][0] # Get split coordinate
|
| 433 |
+
if si < num_splits:
|
| 434 |
+
cute.copy(
|
| 435 |
+
gmem_thr_copy_LSE,
|
| 436 |
+
mLSE_partial_cur_copy[None, si],
|
| 437 |
+
tLSEsLSE[None, s, m],
|
| 438 |
+
)
|
| 439 |
+
else:
|
| 440 |
+
tLSEsLSE[None, s, m].fill(-Float32.inf)
|
| 441 |
+
# Don't need to zero out the rest of the LSEs, as we will not write the output to gmem
|
| 442 |
+
cute.arch.cp_async_commit_group()
|
| 443 |
+
|
| 444 |
+
# ===============================
|
| 445 |
+
# Step 2: Load O_partial for pipeline stages
|
| 446 |
+
# ===============================
|
| 447 |
+
|
| 448 |
+
gmem_thr_copy_O_partial = gmem_tiled_copy_O_partial.get_slice(tidx)
|
| 449 |
+
cO = cute.make_identity_tensor((self.m_block_size, self.k_block_size))
|
| 450 |
+
tOcO = gmem_thr_copy_O_partial.partition_D(cO)
|
| 451 |
+
tOsO_partial = gmem_thr_copy_O_partial.partition_D(sO)
|
| 452 |
+
if const_expr(cu_seqlens is None):
|
| 453 |
+
# mO_partial_cur = mO_partial[None, None, None, None, batch_idx]
|
| 454 |
+
mO_partial_cur = utils.coord_offset_i64(mO_partial, batch_idx, dim=4)
|
| 455 |
+
else:
|
| 456 |
+
# mO_partial_cur = cute.domain_offset((offset, 0, 0, 0), mO_partial)
|
| 457 |
+
mO_partial_cur = utils.domain_offset_i64((offset, 0, 0, 0), mO_partial)
|
| 458 |
+
|
| 459 |
+
# Precompute these values to avoid recomputing them in the loop
|
| 460 |
+
num_rows = const_expr(cute.size(tOcO, mode=[1]))
|
| 461 |
+
tOmidx = cute.make_fragment(num_rows, cutlass.Int32)
|
| 462 |
+
tOhidx = cute.make_fragment(num_rows, cutlass.Int32)
|
| 463 |
+
tOrOptr = cute.make_fragment(num_rows, cutlass.Int64)
|
| 464 |
+
for m in cutlass.range(num_rows, unroll_full=True):
|
| 465 |
+
mi = tOcO[0, m, 0][0] # m coordinate
|
| 466 |
+
idx = m_block * self.m_block_size + mi
|
| 467 |
+
if const_expr(not varlen):
|
| 468 |
+
tOhidx[m], tOmidx[m] = divmod(idx, seqlen_divmod)
|
| 469 |
+
else:
|
| 470 |
+
tOhidx[m] = idx // seqlen
|
| 471 |
+
tOmidx[m] = idx - tOhidx[m] * seqlen
|
| 472 |
+
tOrOptr[m] = utils.elem_pointer_i64(
|
| 473 |
+
mO_partial_cur, (tOmidx[m], k_block * self.k_block_size, 0, tOhidx[m])
|
| 474 |
+
).toint()
|
| 475 |
+
if idx >= max_idx:
|
| 476 |
+
tOhidx[m] = -1
|
| 477 |
+
|
| 478 |
+
tOpO = cute.make_fragment(cute.size(tOcO, [2]), cutlass.Boolean)
|
| 479 |
+
if const_expr(not self.is_even_k):
|
| 480 |
+
for k in cutlass.range(cute.size(tOpO), unroll_full=True):
|
| 481 |
+
tOpO[k] = tOcO[0, 0, k][1] < mO_partial.shape[1] - k_block * self.k_block_size
|
| 482 |
+
# if cute.arch.thread_idx()[0] == 0 and k_block == 1: cute.print_tensor(tOpO)
|
| 483 |
+
|
| 484 |
+
load_O_partial = partial(
|
| 485 |
+
self.load_O_partial,
|
| 486 |
+
gmem_tiled_copy_O_partial,
|
| 487 |
+
tOrOptr,
|
| 488 |
+
tOsO_partial,
|
| 489 |
+
tOhidx,
|
| 490 |
+
tOpO,
|
| 491 |
+
tOcO,
|
| 492 |
+
mO_partial_cur.layout,
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
# Load first few stages of O_partial
|
| 496 |
+
for stage in cutlass.range(self.stages - 1, unroll_full=True):
|
| 497 |
+
if stage < num_splits:
|
| 498 |
+
load_O_partial(stage, stage)
|
| 499 |
+
cute.arch.cp_async_commit_group()
|
| 500 |
+
|
| 501 |
+
# ===============================
|
| 502 |
+
# Step 3: Load and transpose LSE from smem to registers
|
| 503 |
+
# ===============================
|
| 504 |
+
|
| 505 |
+
# Wait for LSE and initial O partial stages to complete
|
| 506 |
+
cute.arch.cp_async_wait_group(self.stages - 1)
|
| 507 |
+
cute.arch.sync_threads()
|
| 508 |
+
# if cute.arch.thread_idx()[0] == 0:
|
| 509 |
+
# # cute.print_tensor(sLSE)
|
| 510 |
+
# for i in range(64):
|
| 511 |
+
# cute.printf("sLSE[%d, 0] = %f", i, sLSE[i, 0])
|
| 512 |
+
# cute.arch.sync_threads()
|
| 513 |
+
|
| 514 |
+
s2r_thr_copy_LSE = s2r_tiled_copy_LSE.get_slice(tidx)
|
| 515 |
+
ts2rsLSE = s2r_thr_copy_LSE.partition_S(sLSE)
|
| 516 |
+
ts2rrLSE = cute.make_fragment_like(ts2rsLSE)
|
| 517 |
+
cute.copy(s2r_tiled_copy_LSE, ts2rsLSE, ts2rrLSE)
|
| 518 |
+
|
| 519 |
+
# ===============================
|
| 520 |
+
# Step 4: Compute final LSE along split dimension
|
| 521 |
+
# ===============================
|
| 522 |
+
|
| 523 |
+
lse_sum = cute.make_fragment(cute.size(ts2rrLSE, mode=[2]), Float32)
|
| 524 |
+
ts2rcLSE = s2r_thr_copy_LSE.partition_D(cLSE)
|
| 525 |
+
# We compute the max valid split for each row to short-circuit the computation later
|
| 526 |
+
max_valid_split = cute.make_fragment(cute.size(ts2rrLSE, mode=[2]), Int32)
|
| 527 |
+
assert cute.size(ts2rrLSE, mode=[0]) == 1
|
| 528 |
+
# Compute max, scales, and final LSE for each row
|
| 529 |
+
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 530 |
+
# Find max LSE value across splits
|
| 531 |
+
threads_per_col = const_expr(self.smem_threads_per_col_lse)
|
| 532 |
+
lse_max = utils.warp_reduce(
|
| 533 |
+
ts2rrLSE[None, None, m]
|
| 534 |
+
.load()
|
| 535 |
+
.reduce(cute.ReductionOp.MAX, init_val=-Float32.inf, reduction_profile=0),
|
| 536 |
+
op=cute.arch.fmax,
|
| 537 |
+
width=threads_per_col,
|
| 538 |
+
)
|
| 539 |
+
# if cute.arch.thread_idx()[0] == 0: cute.printf(lse_max)
|
| 540 |
+
# Find max valid split index
|
| 541 |
+
max_valid_idx = -1
|
| 542 |
+
for s in cutlass.range(cute.size(ts2rrLSE, mode=[1]), unroll_full=True):
|
| 543 |
+
if ts2rrLSE[0, s, m] != -Float32.inf:
|
| 544 |
+
max_valid_idx = ts2rcLSE[0, s, 0][0] # Get split coordinate
|
| 545 |
+
# if cute.arch.thread_idx()[0] < 32: cute.printf(max_valid_idx)
|
| 546 |
+
max_valid_split[m] = utils.warp_reduce(max_valid_idx, max, width=threads_per_col)
|
| 547 |
+
# Compute exp scales and sum
|
| 548 |
+
lse_max_cur = (
|
| 549 |
+
0.0 if lse_max == -Float32.inf else lse_max
|
| 550 |
+
) # In case all local LSEs are -inf
|
| 551 |
+
LOG2_E = math.log2(math.e)
|
| 552 |
+
lse_sum_cur = 0.0
|
| 553 |
+
for s in cutlass.range(cute.size(ts2rrLSE, mode=[1]), unroll_full=True):
|
| 554 |
+
scale = utils.exp2f(ts2rrLSE[0, s, m] * LOG2_E - (lse_max_cur * LOG2_E))
|
| 555 |
+
lse_sum_cur += scale
|
| 556 |
+
ts2rrLSE[0, s, m] = scale # Store scale for later use
|
| 557 |
+
lse_sum_cur = utils.warp_reduce(lse_sum_cur, operator.add, width=threads_per_col)
|
| 558 |
+
lse_sum[m] = utils.logf(lse_sum_cur) + lse_max
|
| 559 |
+
# Normalize scales
|
| 560 |
+
inv_sum = (
|
| 561 |
+
0.0 if (lse_sum_cur == 0.0 or lse_sum_cur != lse_sum_cur) else 1.0 / lse_sum_cur
|
| 562 |
+
)
|
| 563 |
+
ts2rrLSE[None, None, m].store(ts2rrLSE[None, None, m].load() * inv_sum)
|
| 564 |
+
# Store the scales exp(lse - lse_logsum) back to smem
|
| 565 |
+
cute.copy(s2r_tiled_copy_LSE, ts2rrLSE, ts2rsLSE)
|
| 566 |
+
|
| 567 |
+
# Store max valid split to smem
|
| 568 |
+
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 569 |
+
if ts2rcLSE[0, 0, m][0] == 0: # Only thread responsible for s=0 writes
|
| 570 |
+
mi = ts2rcLSE[0, 0, m][1]
|
| 571 |
+
if mi < self.m_block_size:
|
| 572 |
+
sMaxValidSplit[mi] = max_valid_split[m]
|
| 573 |
+
|
| 574 |
+
# ===============================
|
| 575 |
+
# Step 5: Store final LSE to gmem
|
| 576 |
+
# ===============================
|
| 577 |
+
|
| 578 |
+
if const_expr(mLSE is not None):
|
| 579 |
+
if const_expr(cu_seqlens is None):
|
| 580 |
+
# mLSE_cur = mLSE[None, None, batch_idx]
|
| 581 |
+
mLSE_cur = utils.coord_offset_i64(mLSE, batch_idx, dim=2)
|
| 582 |
+
else:
|
| 583 |
+
# mLSE_cur = cute.domain_offset((offset, 0), mLSE)
|
| 584 |
+
mLSE_cur = utils.domain_offset_i64((offset, 0), mLSE)
|
| 585 |
+
if k_block == 0: # Only first k_block writes LSE when mLSE is provided
|
| 586 |
+
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 587 |
+
if ts2rcLSE[0, 0, m][0] == 0: # Only thread responsible for s=0 writes
|
| 588 |
+
mi = ts2rcLSE[0, 0, m][1]
|
| 589 |
+
idx = m_block * self.m_block_size + mi
|
| 590 |
+
if idx < max_idx:
|
| 591 |
+
if const_expr(not varlen):
|
| 592 |
+
head_idx, m_idx = divmod(idx, seqlen_divmod)
|
| 593 |
+
else:
|
| 594 |
+
head_idx = idx // seqlen
|
| 595 |
+
m_idx = idx - head_idx * seqlen
|
| 596 |
+
mLSE_cur[m_idx, head_idx] = lse_sum[m]
|
| 597 |
+
|
| 598 |
+
# ===============================
|
| 599 |
+
# Step 6: Read O_partial and accumulate final O
|
| 600 |
+
# ===============================
|
| 601 |
+
|
| 602 |
+
cute.arch.sync_threads()
|
| 603 |
+
|
| 604 |
+
# Get max valid split for this thread
|
| 605 |
+
thr_max_valid_split = sMaxValidSplit[tOcO[0, 0, 0][0]]
|
| 606 |
+
for m in cutlass.range(1, cute.size(tOcO, mode=[1])):
|
| 607 |
+
thr_max_valid_split = max(thr_max_valid_split, sMaxValidSplit[tOcO[0, m, 0][0]])
|
| 608 |
+
|
| 609 |
+
tOrO_partial = cute.make_fragment_like(tOsO_partial[None, None, None, 0])
|
| 610 |
+
tOrO = cute.make_fragment_like(tOrO_partial, Float32)
|
| 611 |
+
tOrO.fill(0.0)
|
| 612 |
+
|
| 613 |
+
stage_load = self.stages - 1
|
| 614 |
+
stage_compute = 0
|
| 615 |
+
|
| 616 |
+
# Main accumulation loop
|
| 617 |
+
for s in cutlass.range(thr_max_valid_split + 1, unroll=4):
|
| 618 |
+
# Get scales for this split
|
| 619 |
+
scale = cute.make_fragment(num_rows, Float32)
|
| 620 |
+
for m in cutlass.range(num_rows, unroll_full=True):
|
| 621 |
+
scale[m] = sLSE[s, tOcO[0, m, 0][0]] # Get scale from smem
|
| 622 |
+
|
| 623 |
+
# Load next stage if needed
|
| 624 |
+
split_to_load = s + self.stages - 1
|
| 625 |
+
if split_to_load <= thr_max_valid_split:
|
| 626 |
+
load_O_partial(split_to_load, stage_load)
|
| 627 |
+
cute.arch.cp_async_commit_group()
|
| 628 |
+
stage_load = 0 if stage_load == self.stages - 1 else stage_load + 1
|
| 629 |
+
|
| 630 |
+
# Wait for the current stage to be ready
|
| 631 |
+
cute.arch.cp_async_wait_group(self.stages - 1)
|
| 632 |
+
# We don't need __syncthreads() because each thread is just reading its own data from smem
|
| 633 |
+
# Copy from smem to registers
|
| 634 |
+
cute.autovec_copy(tOsO_partial[None, None, None, stage_compute], tOrO_partial)
|
| 635 |
+
stage_compute = 0 if stage_compute == self.stages - 1 else stage_compute + 1
|
| 636 |
+
|
| 637 |
+
# Accumulate scaled partial results
|
| 638 |
+
for m in cutlass.range(num_rows, unroll_full=True):
|
| 639 |
+
if tOhidx[m] >= 0 and scale[m] > 0.0:
|
| 640 |
+
tOrO[None, m, None].store(
|
| 641 |
+
tOrO[None, m, None].load()
|
| 642 |
+
+ scale[m] * tOrO_partial[None, m, None].load().to(Float32)
|
| 643 |
+
)
|
| 644 |
+
|
| 645 |
+
# ===============================
|
| 646 |
+
# Step 7: Write final O to gmem
|
| 647 |
+
# ===============================
|
| 648 |
+
|
| 649 |
+
rO = cute.make_fragment_like(tOrO, self.dtype)
|
| 650 |
+
rO.store(tOrO.load().to(self.dtype))
|
| 651 |
+
if const_expr(cu_seqlens is None):
|
| 652 |
+
# mO_cur = mO[None, None, None, batch_idx]
|
| 653 |
+
mO_cur = utils.coord_offset_i64(mO, batch_idx, dim=3)
|
| 654 |
+
else:
|
| 655 |
+
# mO_cur = cute.domain_offset((offset, 0, 0), mO)
|
| 656 |
+
mO_cur = utils.domain_offset_i64((offset, 0, 0), mO)
|
| 657 |
+
mO_cur = utils.domain_offset_aligned((0, k_block * self.k_block_size, 0), mO_cur)
|
| 658 |
+
elems_per_store = const_expr(cute.size(gmem_tiled_copy_O.layout_tv_tiled[1]))
|
| 659 |
+
# mO_cur_copy = cute.tiled_divide(mO_cur, (1, elems_per_store,))
|
| 660 |
+
gmem_thr_copy_O = gmem_tiled_copy_O.get_slice(tidx)
|
| 661 |
+
# Write final results
|
| 662 |
+
for m in cutlass.range(num_rows, unroll_full=True):
|
| 663 |
+
if tOhidx[m] >= 0:
|
| 664 |
+
mO_cur_copy = cute.tiled_divide(
|
| 665 |
+
mO_cur[tOmidx[m], None, tOhidx[m]], (elems_per_store,)
|
| 666 |
+
)
|
| 667 |
+
for k in cutlass.range(cute.size(tOcO, mode=[2]), unroll_full=True):
|
| 668 |
+
k_idx = tOcO[0, 0, k][1] // elems_per_store
|
| 669 |
+
if const_expr(self.is_even_k) or tOpO[k]:
|
| 670 |
+
cute.copy(gmem_thr_copy_O, rO[None, m, k], mO_cur_copy[None, k_idx])
|
| 671 |
+
|
| 672 |
+
@cute.jit
|
| 673 |
+
def load_O_partial(
|
| 674 |
+
self,
|
| 675 |
+
gmem_tiled_copy_O_partial: cute.TiledCopy,
|
| 676 |
+
tOrOptr: cute.Tensor,
|
| 677 |
+
tOsO_partial: cute.Tensor,
|
| 678 |
+
tOhidx: cute.Tensor,
|
| 679 |
+
tOpO: cute.Tensor,
|
| 680 |
+
tOcO: cute.Tensor,
|
| 681 |
+
mO_cur_partial_layout: cute.Layout,
|
| 682 |
+
split: Int32,
|
| 683 |
+
stage: Int32,
|
| 684 |
+
) -> None:
|
| 685 |
+
elems_per_load = const_expr(cute.size(gmem_tiled_copy_O_partial.layout_tv_tiled[1]))
|
| 686 |
+
tOsO_partial_cur = tOsO_partial[None, None, None, stage]
|
| 687 |
+
for m in cutlass.range(cute.size(tOcO, [1]), unroll_full=True):
|
| 688 |
+
if tOhidx[m] >= 0:
|
| 689 |
+
o_gmem_ptr = cute.make_ptr(
|
| 690 |
+
tOsO_partial.element_type, tOrOptr[m], cute.AddressSpace.gmem, assumed_align=16
|
| 691 |
+
)
|
| 692 |
+
mO_partial_cur = cute.make_tensor(
|
| 693 |
+
o_gmem_ptr, cute.slice_(mO_cur_partial_layout, (0, None, None, 0))
|
| 694 |
+
)
|
| 695 |
+
mO_partial_cur_copy = cute.tiled_divide(mO_partial_cur, (elems_per_load,))
|
| 696 |
+
for k in cutlass.range(cute.size(tOcO, mode=[2]), unroll_full=True):
|
| 697 |
+
k_idx = tOcO[0, 0, k][1] // elems_per_load
|
| 698 |
+
if const_expr(self.is_even_k) or tOpO[k]:
|
| 699 |
+
cute.copy(
|
| 700 |
+
gmem_tiled_copy_O_partial,
|
| 701 |
+
# mO_partial_cur_copy[None, k_idx, split],
|
| 702 |
+
utils.coord_offset_i64(mO_partial_cur_copy, split, dim=2)[None, k_idx],
|
| 703 |
+
tOsO_partial_cur[None, m, k],
|
| 704 |
+
)
|
kernel/deps/flash_attn/cute/flash_fwd_sm100.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
kernel/deps/flash_attn/cute/hopper_helpers.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
from typing import Type, Union, Optional
|
| 3 |
+
import cutlass
|
| 4 |
+
import cutlass.cute as cute
|
| 5 |
+
from cutlass import Int32, Float32, Boolean, const_expr
|
| 6 |
+
from cutlass.cute.nvgpu import warpgroup
|
| 7 |
+
from cutlass.cutlass_dsl import Numeric, dsl_user_op
|
| 8 |
+
from cutlass.utils import LayoutEnum
|
| 9 |
+
import cutlass.utils.hopper_helpers as sm90_utils_og
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@cute.jit
|
| 13 |
+
def gemm(
|
| 14 |
+
tiled_mma: cute.TiledMma,
|
| 15 |
+
acc: cute.Tensor,
|
| 16 |
+
tCrA: cute.Tensor,
|
| 17 |
+
tCrB: cute.Tensor,
|
| 18 |
+
zero_init: cutlass.Constexpr[bool] = False,
|
| 19 |
+
wg_wait: cutlass.Constexpr[int] = 0,
|
| 20 |
+
# A_in_regs: cutlass.Constexpr[bool] = False,
|
| 21 |
+
swap_AB: cutlass.Constexpr[bool] = False,
|
| 22 |
+
) -> None:
|
| 23 |
+
if const_expr(swap_AB):
|
| 24 |
+
gemm(tiled_mma, acc, tCrB, tCrA, zero_init=zero_init, wg_wait=wg_wait, swap_AB=False)
|
| 25 |
+
else:
|
| 26 |
+
warpgroup.fence()
|
| 27 |
+
# We make a new mma_atom since we'll be modifying its attribute (accumulate).
|
| 28 |
+
# Otherwise the compiler complains "operand #0 does not dominate this use"
|
| 29 |
+
mma_atom = cute.make_mma_atom(tiled_mma.op)
|
| 30 |
+
mma_atom.set(warpgroup.Field.ACCUMULATE, not zero_init)
|
| 31 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2])):
|
| 32 |
+
cute.gemm(mma_atom, acc, tCrA[None, None, k], tCrB[None, None, k], acc)
|
| 33 |
+
mma_atom.set(warpgroup.Field.ACCUMULATE, True)
|
| 34 |
+
warpgroup.commit_group()
|
| 35 |
+
if const_expr(wg_wait >= 0):
|
| 36 |
+
warpgroup.wait_group(wg_wait)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def gemm_zero_init(
|
| 40 |
+
tiled_mma: cute.TiledMma,
|
| 41 |
+
shape: cute.Shape,
|
| 42 |
+
tCrA: cute.Tensor,
|
| 43 |
+
tCrB: cute.Tensor,
|
| 44 |
+
A_idx: Optional[Int32] = None,
|
| 45 |
+
B_idx: Optional[Int32] = None,
|
| 46 |
+
wg_wait: int = -1,
|
| 47 |
+
swap_AB: bool = False,
|
| 48 |
+
) -> cute.Tensor:
|
| 49 |
+
if const_expr(swap_AB):
|
| 50 |
+
return gemm_zero_init(
|
| 51 |
+
tiled_mma, shape[::-1], tCrB, tCrA, B_idx, A_idx, wg_wait, swap_AB=False
|
| 52 |
+
)
|
| 53 |
+
else:
|
| 54 |
+
acc = cute.make_fragment(tiled_mma.partition_shape_C(shape), Float32)
|
| 55 |
+
rA = tCrA if const_expr(A_idx is None) else tCrA[None, None, None, A_idx]
|
| 56 |
+
rB = tCrB if const_expr(B_idx is None) else tCrB[None, None, None, B_idx]
|
| 57 |
+
gemm(tiled_mma, acc, rA, rB, zero_init=True, wg_wait=wg_wait)
|
| 58 |
+
return acc
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def gemm_w_idx(
|
| 62 |
+
tiled_mma: cute.TiledMma,
|
| 63 |
+
acc: cute.Tensor,
|
| 64 |
+
tCrA: cute.Tensor,
|
| 65 |
+
tCrB: cute.Tensor,
|
| 66 |
+
zero_init: Boolean,
|
| 67 |
+
A_idx: Optional[Int32] = None,
|
| 68 |
+
B_idx: Optional[Int32] = None,
|
| 69 |
+
wg_wait: int = -1,
|
| 70 |
+
swap_AB: bool = False,
|
| 71 |
+
) -> None:
|
| 72 |
+
if const_expr(swap_AB):
|
| 73 |
+
gemm_w_idx(tiled_mma, acc, tCrB, tCrA, zero_init, B_idx, A_idx, wg_wait, swap_AB=False)
|
| 74 |
+
else:
|
| 75 |
+
rA = tCrA if const_expr(A_idx is None) else tCrA[None, None, None, A_idx]
|
| 76 |
+
rB = tCrB if const_expr(B_idx is None) else tCrB[None, None, None, B_idx]
|
| 77 |
+
gemm(tiled_mma, acc, rA, rB, zero_init=zero_init, wg_wait=wg_wait)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
@dsl_user_op
|
| 81 |
+
def make_smem_layout(
|
| 82 |
+
dtype: Type[Numeric],
|
| 83 |
+
layout: LayoutEnum,
|
| 84 |
+
shape: cute.Shape,
|
| 85 |
+
stage: Optional[int] = None,
|
| 86 |
+
*,
|
| 87 |
+
loc=None,
|
| 88 |
+
ip=None,
|
| 89 |
+
) -> Union[cute.Layout, cute.ComposedLayout]:
|
| 90 |
+
major_mode_size = shape[1] if layout.is_n_major_c() else shape[0]
|
| 91 |
+
smem_layout_atom = warpgroup.make_smem_layout_atom(
|
| 92 |
+
sm90_utils_og.get_smem_layout_atom(layout, dtype, major_mode_size),
|
| 93 |
+
dtype,
|
| 94 |
+
)
|
| 95 |
+
order = (1, 0, 2) if const_expr(layout.is_m_major_c()) else (0, 1, 2)
|
| 96 |
+
smem_layout_staged = cute.tile_to_shape(
|
| 97 |
+
smem_layout_atom,
|
| 98 |
+
cute.append(shape, stage) if const_expr(stage is not None) else shape,
|
| 99 |
+
order=order if const_expr(stage is not None) else order[:2],
|
| 100 |
+
)
|
| 101 |
+
return smem_layout_staged
|
kernel/deps/flash_attn/cute/interface.py
ADDED
|
@@ -0,0 +1,1762 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
# [2025-07-04] Version in Cute-DSL, for Hopper and Blackwell. You'll need install nvidia-cutlass-dsl==4.2.0.
|
| 3 |
+
|
| 4 |
+
# Supported features:
|
| 5 |
+
# - BF16 & FP16 dtype
|
| 6 |
+
# - noncausal & causal attention
|
| 7 |
+
# - MHA, GQA, MQA
|
| 8 |
+
# - hdim 64, 96, 128.
|
| 9 |
+
# - (hdim_qk, hdim_v) = (192, 128) for Blackwell (i.e. DeepSeek shape)
|
| 10 |
+
# - varlen
|
| 11 |
+
# - sliding window
|
| 12 |
+
# - bwd pass for Ampere (will also run on Hopper/Blackwell, but will be slow)
|
| 13 |
+
|
| 14 |
+
# Features not supported yet:
|
| 15 |
+
# - split (i.e. FlashDecoding)
|
| 16 |
+
# - tuned block sizes
|
| 17 |
+
# - paged KV
|
| 18 |
+
# - append KV to existing KV cache
|
| 19 |
+
# - FP8
|
| 20 |
+
# - bwd pass optimized for Hopper/Blackwell
|
| 21 |
+
|
| 22 |
+
import math
|
| 23 |
+
from functools import lru_cache
|
| 24 |
+
from typing import Optional, Tuple, Callable
|
| 25 |
+
|
| 26 |
+
import torch
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
import cuda.bindings.driver as cuda
|
| 30 |
+
|
| 31 |
+
import cutlass
|
| 32 |
+
import cutlass.cute as cute
|
| 33 |
+
|
| 34 |
+
from flash_attn.cute import utils
|
| 35 |
+
from flash_attn.cute.cute_dsl_utils import to_cute_tensor
|
| 36 |
+
from flash_attn.cute.flash_fwd import FlashAttentionForwardSm90
|
| 37 |
+
from flash_attn.cute.flash_fwd_sm100 import FlashAttentionForwardSm100
|
| 38 |
+
from flash_attn.cute.flash_bwd_preprocess import FlashAttentionBackwardPreprocess
|
| 39 |
+
from flash_attn.cute.flash_bwd import FlashAttentionBackwardSm80
|
| 40 |
+
from flash_attn.cute.flash_bwd_sm90 import FlashAttentionBackwardSm90
|
| 41 |
+
from flash_attn.cute.flash_bwd_sm100 import FlashAttentionBackwardSm100
|
| 42 |
+
from flash_attn.cute.flash_bwd_postprocess import FlashAttentionBackwardPostprocess
|
| 43 |
+
from flash_attn.cute.flash_fwd_combine import FlashAttentionForwardCombine
|
| 44 |
+
|
| 45 |
+
from flash_attn.cute.block_sparsity import (
|
| 46 |
+
BlockSparseTensorsTorch,
|
| 47 |
+
to_cute_block_sparse_tensors,
|
| 48 |
+
normalize_block_sparse_config,
|
| 49 |
+
normalize_block_sparse_config_bwd,
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
@lru_cache(maxsize=None)
|
| 53 |
+
def _get_device_capability():
|
| 54 |
+
"""Cached device capability check."""
|
| 55 |
+
return torch.cuda.get_device_capability()[0]
|
| 56 |
+
|
| 57 |
+
def maybe_contiguous(x):
|
| 58 |
+
return x.contiguous() if x is not None and x.stride(-1) != 1 else x
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _validate_tensor(t, name, expected_shape, expected_dtype, expected_device):
|
| 62 |
+
assert t.shape == expected_shape, f"{name} shape {t.shape} != expected {expected_shape}"
|
| 63 |
+
assert t.dtype == expected_dtype, f"{name} dtype {t.dtype} != expected {expected_dtype}"
|
| 64 |
+
assert t.device == expected_device, f"{name} device {t.device} != expected {expected_device}"
|
| 65 |
+
assert t.is_cuda, f"{name} must be on CUDA"
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
torch2cute_dtype_map = {
|
| 69 |
+
torch.float16: cutlass.Float16,
|
| 70 |
+
torch.bfloat16: cutlass.BFloat16,
|
| 71 |
+
torch.float32: cutlass.Float32,
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def num_splits_heuristic(total_mblocks, num_SMs, num_n_blocks, max_splits):
|
| 76 |
+
# If num_n_blocks is too small, use 1 split. For example, we never split for hdim = 128 and seqlen_k = 512.
|
| 77 |
+
if num_n_blocks <= 4:
|
| 78 |
+
return 1
|
| 79 |
+
|
| 80 |
+
# NOTE: We should revisit this heuristic after persistence is supported for split KV.
|
| 81 |
+
# Sometimes, it's ideal to over-schedule splits for better efficiency.
|
| 82 |
+
return min(num_SMs // total_mblocks, max_splits, num_n_blocks)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _flash_attn_fwd(
|
| 86 |
+
q: torch.Tensor,
|
| 87 |
+
k: torch.Tensor,
|
| 88 |
+
v: torch.Tensor,
|
| 89 |
+
cu_seqlens_q: Optional[torch.Tensor] = None,
|
| 90 |
+
cu_seqlens_k: Optional[torch.Tensor] = None,
|
| 91 |
+
seqused_q: Optional[torch.Tensor] = None,
|
| 92 |
+
seqused_k: Optional[torch.Tensor] = None,
|
| 93 |
+
max_seqlen_q: Optional[int] = None,
|
| 94 |
+
max_seqlen_k: Optional[int] = None,
|
| 95 |
+
page_table: Optional[torch.Tensor] = None,
|
| 96 |
+
softmax_scale: Optional[float] = None,
|
| 97 |
+
causal: bool = False,
|
| 98 |
+
softcap: Optional[float] = None,
|
| 99 |
+
window_size_left: Optional[int] = None,
|
| 100 |
+
window_size_right: Optional[int] = None,
|
| 101 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 102 |
+
# m_block_size: int = 128,
|
| 103 |
+
# n_block_size: int = 64,
|
| 104 |
+
# num_threads: int = 128,
|
| 105 |
+
m_block_size: int = 128,
|
| 106 |
+
n_block_size: int = 128,
|
| 107 |
+
num_threads: int = 384,
|
| 108 |
+
num_splits: int = 1,
|
| 109 |
+
pack_gqa: Optional[bool] = None,
|
| 110 |
+
_compute_capability: Optional[int] = None,
|
| 111 |
+
score_mod: Optional[Callable] = None,
|
| 112 |
+
mask_mod: Optional[Callable] = None,
|
| 113 |
+
block_sparse_tensors: Optional[BlockSparseTensorsTorch] = None,
|
| 114 |
+
return_lse: bool = False,
|
| 115 |
+
out: Optional[torch.Tensor] = None,
|
| 116 |
+
lse: Optional[torch.Tensor] = None,
|
| 117 |
+
aux_tensors: Optional[list[torch.Tensor]] = None,
|
| 118 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 119 |
+
"""Forward pass for FlashAttention.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
...
|
| 123 |
+
score_mod: A callable that takes the attention scores and applies a modification.
|
| 124 |
+
mask_mod: A callable that takes token position information and selectively masks
|
| 125 |
+
block_sparse_tensors: A tuple of tensors used for block sparsity.
|
| 126 |
+
return_lse: Whether to return the log softmax of the attention scores. If set to True will always calculate
|
| 127 |
+
out: Optional pre-allocated output tensor. If None, will be allocated internally.
|
| 128 |
+
lse: Optional pre-allocated log-sum-exp tensor. If None, will be allocated when needed.
|
| 129 |
+
aux_tensors: Some score_mods will want to read from global aux_tensors. This is how we thread them through to the inner kernel.
|
| 130 |
+
"""
|
| 131 |
+
q, k, v = [maybe_contiguous(t) for t in (q, k, v)]
|
| 132 |
+
num_head, head_dim = q.shape[-2:]
|
| 133 |
+
if cu_seqlens_q is None:
|
| 134 |
+
batch_size, seqlen_q = q.shape[:2]
|
| 135 |
+
total_q = batch_size * seqlen_q
|
| 136 |
+
else:
|
| 137 |
+
batch_size = cu_seqlens_q.shape[0] - 1
|
| 138 |
+
seqlen_q = None
|
| 139 |
+
total_q = q.shape[0]
|
| 140 |
+
if page_table is not None:
|
| 141 |
+
assert cu_seqlens_k is None, "page_table is not supported with cu_seqlens_k"
|
| 142 |
+
assert page_table.dtype == torch.int32, "page_table must be int32"
|
| 143 |
+
assert page_table.stride(-1) == 1, "page_table must be contiguous in the last dimension"
|
| 144 |
+
max_num_pages_per_seq = page_table.shape[1]
|
| 145 |
+
assert page_table.shape == (batch_size, max_num_pages_per_seq)
|
| 146 |
+
num_pages, page_size = k.shape[:2]
|
| 147 |
+
seqlen_k = num_pages * page_size
|
| 148 |
+
else:
|
| 149 |
+
num_pages, page_size = None, None
|
| 150 |
+
seqlen_k = k.shape[-3]
|
| 151 |
+
num_head_kv = k.shape[-2]
|
| 152 |
+
head_dim_v = v.shape[-1]
|
| 153 |
+
if cu_seqlens_k is None:
|
| 154 |
+
if page_table is None:
|
| 155 |
+
assert k.shape == (batch_size, seqlen_k, num_head_kv, head_dim)
|
| 156 |
+
assert v.shape == (batch_size, seqlen_k, num_head_kv, head_dim_v)
|
| 157 |
+
else:
|
| 158 |
+
assert k.shape == (num_pages, page_size, num_head_kv, head_dim)
|
| 159 |
+
assert v.shape == (num_pages, page_size, num_head_kv, head_dim_v)
|
| 160 |
+
else:
|
| 161 |
+
assert k.shape == (seqlen_k, num_head_kv, head_dim)
|
| 162 |
+
assert v.shape == (seqlen_k, num_head_kv, head_dim_v)
|
| 163 |
+
assert cu_seqlens_k.shape == (batch_size + 1,), (
|
| 164 |
+
"cu_seqlens_k must have shape (batch_size + 1,)"
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
if cu_seqlens_q is not None:
|
| 168 |
+
assert cu_seqlens_q.shape == (batch_size + 1,), (
|
| 169 |
+
"cu_seqlens_q must have shape (batch_size + 1,)"
|
| 170 |
+
)
|
| 171 |
+
assert seqused_q is None or seqused_q.shape == (batch_size,), (
|
| 172 |
+
"seqused_q must have shape (batch_size,)"
|
| 173 |
+
)
|
| 174 |
+
assert seqused_k is None or seqused_k.shape == (batch_size,), (
|
| 175 |
+
"seqused_k must have shape (batch_size,)"
|
| 176 |
+
)
|
| 177 |
+
assert q.dtype in [torch.float16, torch.bfloat16], "inputs must be float16 or bfloat16"
|
| 178 |
+
assert q.dtype == k.dtype == v.dtype, "inputs must have the same dtype"
|
| 179 |
+
for t in [cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k]:
|
| 180 |
+
if t is not None:
|
| 181 |
+
assert t.dtype == torch.int32, (
|
| 182 |
+
"cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k must be int32"
|
| 183 |
+
)
|
| 184 |
+
assert t.stride(0) == 1, (
|
| 185 |
+
"cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k must be contiguous"
|
| 186 |
+
)
|
| 187 |
+
if learnable_sink is not None:
|
| 188 |
+
assert learnable_sink.shape == (num_head,)
|
| 189 |
+
assert learnable_sink.dtype == torch.bfloat16, "learnable_sink must be bfloat16"
|
| 190 |
+
|
| 191 |
+
assert all(
|
| 192 |
+
t is None or t.is_cuda
|
| 193 |
+
for t in (
|
| 194 |
+
q,
|
| 195 |
+
k,
|
| 196 |
+
v,
|
| 197 |
+
cu_seqlens_q,
|
| 198 |
+
cu_seqlens_k,
|
| 199 |
+
seqused_q,
|
| 200 |
+
seqused_k,
|
| 201 |
+
page_table,
|
| 202 |
+
learnable_sink,
|
| 203 |
+
)
|
| 204 |
+
), "inputs must be on CUDA device"
|
| 205 |
+
assert num_head % num_head_kv == 0, "num_head must be divisible by num_head_kv"
|
| 206 |
+
assert head_dim <= 256, "head_dim must be less than or equal to 256"
|
| 207 |
+
alignment = 16 // q.element_size()
|
| 208 |
+
assert head_dim % alignment == 0, f"head_dim must be divisible by {alignment}"
|
| 209 |
+
assert head_dim_v % alignment == 0, f"head_dim_v must be divisible by {alignment}"
|
| 210 |
+
if softmax_scale is None:
|
| 211 |
+
softmax_scale = 1.0 / math.sqrt(head_dim)
|
| 212 |
+
if softcap == 0.0:
|
| 213 |
+
softcap = None
|
| 214 |
+
qhead_per_kvhead = num_head // num_head_kv
|
| 215 |
+
if pack_gqa is None:
|
| 216 |
+
pack_gqa = qhead_per_kvhead > 1
|
| 217 |
+
|
| 218 |
+
out_torch_dtype = q.dtype
|
| 219 |
+
device = q.device
|
| 220 |
+
q_batch_seqlen_shape = (batch_size, seqlen_q) if cu_seqlens_q is None else (total_q,)
|
| 221 |
+
lse_shape = (batch_size, num_head, seqlen_q) if cu_seqlens_q is None else (num_head, total_q)
|
| 222 |
+
requires_grad = q.requires_grad or k.requires_grad or v.requires_grad
|
| 223 |
+
|
| 224 |
+
if out is None:
|
| 225 |
+
out = torch.empty(
|
| 226 |
+
*q_batch_seqlen_shape, num_head, head_dim_v, dtype=out_torch_dtype, device=device
|
| 227 |
+
)
|
| 228 |
+
else:
|
| 229 |
+
_validate_tensor(out, "out", (*q_batch_seqlen_shape, num_head, head_dim_v), out_torch_dtype, device)
|
| 230 |
+
|
| 231 |
+
if lse is None:
|
| 232 |
+
lse = (
|
| 233 |
+
torch.empty(lse_shape, dtype=torch.float32, device=device)
|
| 234 |
+
if requires_grad or return_lse
|
| 235 |
+
else None
|
| 236 |
+
)
|
| 237 |
+
elif lse is not None:
|
| 238 |
+
_validate_tensor(lse, "lse", lse_shape, torch.float32, device)
|
| 239 |
+
|
| 240 |
+
dtype = torch2cute_dtype_map[q.dtype]
|
| 241 |
+
compute_capability = (
|
| 242 |
+
_get_device_capability()
|
| 243 |
+
if _compute_capability is None
|
| 244 |
+
else _compute_capability
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
assert compute_capability in [9, 10, 11], "Unsupported compute capability. Supported: 9.x, 10.x, 11.x"
|
| 248 |
+
|
| 249 |
+
use_block_sparsity = block_sparse_tensors is not None
|
| 250 |
+
|
| 251 |
+
if mask_mod is None:
|
| 252 |
+
if causal:
|
| 253 |
+
window_size_right = 0
|
| 254 |
+
local = window_size_left is not None or window_size_right is not None
|
| 255 |
+
if window_size_left is not None or window_size_right is not None:
|
| 256 |
+
if window_size_left is None and window_size_right == 0:
|
| 257 |
+
causal, local = True, False
|
| 258 |
+
window_size_right = None
|
| 259 |
+
else:
|
| 260 |
+
causal, local = False, True
|
| 261 |
+
else:
|
| 262 |
+
causal, local = False, False
|
| 263 |
+
|
| 264 |
+
current_stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
|
| 265 |
+
|
| 266 |
+
if compute_capability == 9: # TODO: tune block size according to hdim.
|
| 267 |
+
if head_dim == head_dim_v == 128 and not causal and not local and not use_block_sparsity:
|
| 268 |
+
n_block_size = 192
|
| 269 |
+
|
| 270 |
+
if compute_capability in [10, 11]:
|
| 271 |
+
if (
|
| 272 |
+
pack_gqa
|
| 273 |
+
and (128 % qhead_per_kvhead != 0)
|
| 274 |
+
):
|
| 275 |
+
pack_gqa = False
|
| 276 |
+
# TODO: fix GQA + SplitKV + non-varlen
|
| 277 |
+
if pack_gqa and num_splits != 1 and cu_seqlens_q is None:
|
| 278 |
+
pack_gqa = False
|
| 279 |
+
|
| 280 |
+
if max_seqlen_q is None:
|
| 281 |
+
max_seqlen_q = seqlen_q if cu_seqlens_q is None else total_q
|
| 282 |
+
if max_seqlen_k is None:
|
| 283 |
+
max_seqlen_k = seqlen_k
|
| 284 |
+
seqlen_q_packgqa = max_seqlen_q * qhead_per_kvhead
|
| 285 |
+
if compute_capability == 10:
|
| 286 |
+
q_stage = 2 if seqlen_q_packgqa > m_block_size else 1
|
| 287 |
+
else:
|
| 288 |
+
q_stage = 1
|
| 289 |
+
|
| 290 |
+
if num_splits < 1:
|
| 291 |
+
m_block_size_effective = q_stage * m_block_size
|
| 292 |
+
seqlen_k_loaded = max_seqlen_k if not local else max(0, min(max_seqlen_k, window_size_right + window_size_left + 1 + m_block_size))
|
| 293 |
+
num_n_blocks = (seqlen_k_loaded + n_block_size - 1) // n_block_size
|
| 294 |
+
num_m_blocks = (seqlen_q_packgqa + m_block_size_effective - 1) // m_block_size_effective
|
| 295 |
+
total_mblocks = batch_size * num_head_kv * num_m_blocks
|
| 296 |
+
num_splits = num_splits_heuristic(
|
| 297 |
+
total_mblocks,
|
| 298 |
+
torch.cuda.get_device_properties(device).multi_processor_count,
|
| 299 |
+
num_n_blocks,
|
| 300 |
+
128,
|
| 301 |
+
)
|
| 302 |
+
|
| 303 |
+
is_split_kv = num_splits > 1
|
| 304 |
+
if is_split_kv:
|
| 305 |
+
out_partial = torch.empty(num_splits, *q_batch_seqlen_shape, num_head, head_dim_v, dtype=torch.float32, device=device)
|
| 306 |
+
lse_partial = torch.empty(num_splits, *lse_shape, dtype=torch.float32, device=device)
|
| 307 |
+
|
| 308 |
+
# hash score and mask mods for compile cache
|
| 309 |
+
score_mod_hash = utils.hash_callable(score_mod) if score_mod is not None else False
|
| 310 |
+
mask_mod_hash = utils.hash_callable(mask_mod) if mask_mod is not None else False
|
| 311 |
+
|
| 312 |
+
if softcap is not None:
|
| 313 |
+
assert score_mod is None, "softcap and score_mod cannot be used together"
|
| 314 |
+
score_mod = utils.create_softcap_scoremod(softcap)
|
| 315 |
+
|
| 316 |
+
is_varlen = (
|
| 317 |
+
cu_seqlens_q is not None
|
| 318 |
+
or cu_seqlens_k is not None
|
| 319 |
+
or seqused_q is not None
|
| 320 |
+
or seqused_k is not None
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
if mask_mod is not None:
|
| 324 |
+
if is_varlen:
|
| 325 |
+
raise NotImplementedError(
|
| 326 |
+
"mask_mod with aux_tensors is not yet supported for varlen sequences. This will be fixed in a future PR."
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
if use_block_sparsity:
|
| 330 |
+
if is_varlen:
|
| 331 |
+
raise NotImplementedError(
|
| 332 |
+
"Block sparsity is not yet supported for varlen sequences. This will be fixed in a future PR."
|
| 333 |
+
)
|
| 334 |
+
# NB: pack_gqa requires block sparse head dim == 1 (broadcasted)
|
| 335 |
+
if pack_gqa and block_sparse_tensors.mask_block_cnt.shape[1] != 1:
|
| 336 |
+
pack_gqa = False
|
| 337 |
+
if is_split_kv:
|
| 338 |
+
raise NotImplementedError(
|
| 339 |
+
"Block sparsity is not yet supported with SplitKV. TODO: partition sparse block lists per split."
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
# See get_broadcast_dims for why this is needed in compile key
|
| 343 |
+
block_sparse_broadcast_pattern = None
|
| 344 |
+
normalized_block_sparse_tensors = None
|
| 345 |
+
q_subtile_factor = None
|
| 346 |
+
if block_sparse_tensors is not None:
|
| 347 |
+
if seqlen_q is None:
|
| 348 |
+
raise ValueError("Block sparsity requires fixed-length sequences (seqlen_q must be known).")
|
| 349 |
+
(
|
| 350 |
+
normalized_block_sparse_tensors,
|
| 351 |
+
block_sparse_broadcast_pattern,
|
| 352 |
+
q_subtile_factor,
|
| 353 |
+
) = normalize_block_sparse_config(
|
| 354 |
+
block_sparse_tensors,
|
| 355 |
+
batch_size=batch_size,
|
| 356 |
+
num_head=num_head,
|
| 357 |
+
seqlen_q=seqlen_q,
|
| 358 |
+
seqlen_k=seqlen_k,
|
| 359 |
+
block_size=(m_block_size, n_block_size),
|
| 360 |
+
q_stage=q_stage,
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
compile_key = (
|
| 364 |
+
dtype,
|
| 365 |
+
head_dim,
|
| 366 |
+
head_dim_v,
|
| 367 |
+
qhead_per_kvhead,
|
| 368 |
+
causal,
|
| 369 |
+
score_mod_hash,
|
| 370 |
+
mask_mod_hash,
|
| 371 |
+
use_block_sparsity,
|
| 372 |
+
block_sparse_broadcast_pattern,
|
| 373 |
+
len(aux_tensors) if aux_tensors is not None else 0,
|
| 374 |
+
lse is None,
|
| 375 |
+
cu_seqlens_q is None,
|
| 376 |
+
cu_seqlens_k is None,
|
| 377 |
+
seqused_q is None,
|
| 378 |
+
seqused_k is None,
|
| 379 |
+
page_table is not None,
|
| 380 |
+
window_size_left is not None,
|
| 381 |
+
window_size_right is not None,
|
| 382 |
+
learnable_sink is not None,
|
| 383 |
+
m_block_size,
|
| 384 |
+
n_block_size,
|
| 385 |
+
q_stage,
|
| 386 |
+
num_threads,
|
| 387 |
+
is_split_kv,
|
| 388 |
+
pack_gqa,
|
| 389 |
+
compute_capability,
|
| 390 |
+
page_size not in [None, 128], # paged KV non-TMA
|
| 391 |
+
q_subtile_factor,
|
| 392 |
+
)
|
| 393 |
+
if compile_key not in _flash_attn_fwd.compile_cache:
|
| 394 |
+
(
|
| 395 |
+
cu_seqlens_q_tensor,
|
| 396 |
+
cu_seqlens_k_tensor,
|
| 397 |
+
seqused_q_tensor,
|
| 398 |
+
seqused_k_tensor,
|
| 399 |
+
learnable_sink_tensor,
|
| 400 |
+
) = [
|
| 401 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=0)
|
| 402 |
+
if t is not None
|
| 403 |
+
else None
|
| 404 |
+
for t in (cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k, learnable_sink)
|
| 405 |
+
]
|
| 406 |
+
page_table_tensor = (
|
| 407 |
+
to_cute_tensor(page_table, assumed_align=4, leading_dim=1)
|
| 408 |
+
if page_table is not None
|
| 409 |
+
else None
|
| 410 |
+
)
|
| 411 |
+
q_tensor, k_tensor, v_tensor, o_tensor = [
|
| 412 |
+
to_cute_tensor(t) for t in (q, k, v, out if not is_split_kv else out_partial)
|
| 413 |
+
]
|
| 414 |
+
if is_split_kv:
|
| 415 |
+
lse_tensor = to_cute_tensor(lse_partial, assumed_align=4)
|
| 416 |
+
elif lse is not None:
|
| 417 |
+
lse_tensor = to_cute_tensor(lse, assumed_align=4)
|
| 418 |
+
else:
|
| 419 |
+
lse_tensor = None
|
| 420 |
+
|
| 421 |
+
sparse_tensors = None
|
| 422 |
+
if normalized_block_sparse_tensors is not None:
|
| 423 |
+
sparse_tensors = to_cute_block_sparse_tensors(normalized_block_sparse_tensors)
|
| 424 |
+
|
| 425 |
+
cute_aux_tensors = None
|
| 426 |
+
if aux_tensors is not None:
|
| 427 |
+
cute_aux_tensors = [to_cute_tensor(buf, assumed_align=None, fully_dynamic=True) for buf in aux_tensors]
|
| 428 |
+
|
| 429 |
+
if compute_capability == 9:
|
| 430 |
+
assert page_table is None, "paged KV not supported on SM 9.0"
|
| 431 |
+
assert not is_split_kv, "SplitKV not supported on SM 9.0"
|
| 432 |
+
# fa_fwd = FlashAttentionForwardSm80(
|
| 433 |
+
fa_fwd = FlashAttentionForwardSm90(
|
| 434 |
+
dtype,
|
| 435 |
+
head_dim,
|
| 436 |
+
head_dim_v,
|
| 437 |
+
qhead_per_kvhead,
|
| 438 |
+
is_causal=causal,
|
| 439 |
+
is_local=local,
|
| 440 |
+
pack_gqa=pack_gqa,
|
| 441 |
+
tile_m=m_block_size,
|
| 442 |
+
tile_n=n_block_size,
|
| 443 |
+
# num_stages=1,
|
| 444 |
+
num_stages=2,
|
| 445 |
+
num_threads=num_threads,
|
| 446 |
+
Q_in_regs=False,
|
| 447 |
+
intra_wg_overlap=True,
|
| 448 |
+
mma_pv_is_rs=True,
|
| 449 |
+
mask_mod=mask_mod,
|
| 450 |
+
score_mod=score_mod,
|
| 451 |
+
has_aux_tensors=aux_tensors is not None,
|
| 452 |
+
q_subtile_factor=q_subtile_factor,
|
| 453 |
+
)
|
| 454 |
+
elif compute_capability in [10, 11]:
|
| 455 |
+
fa_fwd = FlashAttentionForwardSm100(
|
| 456 |
+
head_dim,
|
| 457 |
+
head_dim_v,
|
| 458 |
+
qhead_per_kvhead=qhead_per_kvhead,
|
| 459 |
+
is_causal=causal,
|
| 460 |
+
is_local=local,
|
| 461 |
+
is_split_kv=is_split_kv,
|
| 462 |
+
pack_gqa=pack_gqa,
|
| 463 |
+
m_block_size=m_block_size,
|
| 464 |
+
n_block_size=n_block_size,
|
| 465 |
+
q_stage=q_stage,
|
| 466 |
+
is_persistent=not causal
|
| 467 |
+
and not local
|
| 468 |
+
and cu_seqlens_q is None
|
| 469 |
+
and seqused_q is None
|
| 470 |
+
and not is_split_kv,
|
| 471 |
+
score_mod=score_mod,
|
| 472 |
+
mask_mod=mask_mod,
|
| 473 |
+
has_aux_tensors=aux_tensors is not None,
|
| 474 |
+
paged_kv_non_tma=page_size not in [None, 128],
|
| 475 |
+
is_varlen_q=cu_seqlens_q is not None
|
| 476 |
+
or seqused_q is not None,
|
| 477 |
+
q_subtile_factor=q_subtile_factor,
|
| 478 |
+
)
|
| 479 |
+
else:
|
| 480 |
+
raise ValueError(
|
| 481 |
+
f"Unsupported compute capability: {compute_capability}. Supported: 9.x, 10.x, 11.x"
|
| 482 |
+
)
|
| 483 |
+
# TODO: check @can_implement
|
| 484 |
+
_flash_attn_fwd.compile_cache[compile_key] = cute.compile(
|
| 485 |
+
fa_fwd,
|
| 486 |
+
q_tensor,
|
| 487 |
+
k_tensor,
|
| 488 |
+
v_tensor,
|
| 489 |
+
o_tensor,
|
| 490 |
+
lse_tensor,
|
| 491 |
+
softmax_scale,
|
| 492 |
+
current_stream,
|
| 493 |
+
cu_seqlens_q_tensor,
|
| 494 |
+
cu_seqlens_k_tensor,
|
| 495 |
+
seqused_q_tensor,
|
| 496 |
+
seqused_k_tensor,
|
| 497 |
+
page_table_tensor,
|
| 498 |
+
window_size_left,
|
| 499 |
+
window_size_right,
|
| 500 |
+
learnable_sink_tensor,
|
| 501 |
+
sparse_tensors,
|
| 502 |
+
cute_aux_tensors,
|
| 503 |
+
options="--enable-tvm-ffi",
|
| 504 |
+
)
|
| 505 |
+
|
| 506 |
+
_flash_attn_fwd.compile_cache[compile_key](
|
| 507 |
+
q,
|
| 508 |
+
k,
|
| 509 |
+
v,
|
| 510 |
+
out if not is_split_kv else out_partial,
|
| 511 |
+
lse_partial if is_split_kv else lse,
|
| 512 |
+
softmax_scale,
|
| 513 |
+
current_stream,
|
| 514 |
+
cu_seqlens_q,
|
| 515 |
+
cu_seqlens_k,
|
| 516 |
+
seqused_q,
|
| 517 |
+
seqused_k,
|
| 518 |
+
page_table,
|
| 519 |
+
window_size_left,
|
| 520 |
+
window_size_right,
|
| 521 |
+
learnable_sink,
|
| 522 |
+
normalized_block_sparse_tensors[:4] if normalized_block_sparse_tensors is not None else None,
|
| 523 |
+
aux_tensors,
|
| 524 |
+
)
|
| 525 |
+
if is_split_kv:
|
| 526 |
+
_flash_attn_fwd_combine(
|
| 527 |
+
out_partial,
|
| 528 |
+
lse_partial.transpose(-1, -2),
|
| 529 |
+
out,
|
| 530 |
+
lse.transpose(-1, -2) if lse is not None else None,
|
| 531 |
+
cu_seqlens_q,
|
| 532 |
+
seqused_q,
|
| 533 |
+
)
|
| 534 |
+
return out, lse
|
| 535 |
+
|
| 536 |
+
|
| 537 |
+
_flash_attn_fwd.compile_cache = {}
|
| 538 |
+
|
| 539 |
+
|
| 540 |
+
def _flash_attn_bwd(
|
| 541 |
+
q: torch.Tensor,
|
| 542 |
+
k: torch.Tensor,
|
| 543 |
+
v: torch.Tensor,
|
| 544 |
+
out: torch.Tensor,
|
| 545 |
+
dout: torch.Tensor,
|
| 546 |
+
lse: torch.Tensor,
|
| 547 |
+
softmax_scale: Optional[float] = None,
|
| 548 |
+
causal: bool = False,
|
| 549 |
+
softcap: float = 0.0,
|
| 550 |
+
window_size_left: Optional[int] = None,
|
| 551 |
+
window_size_right: Optional[int] = None,
|
| 552 |
+
m_block_size: int = 64,
|
| 553 |
+
n_block_size: int = 128,
|
| 554 |
+
num_threads: int = 256,
|
| 555 |
+
pack_gqa: bool = False,
|
| 556 |
+
num_stages_Q: int = 2,
|
| 557 |
+
num_stages_dO: int = 2,
|
| 558 |
+
SdP_swapAB: bool = False,
|
| 559 |
+
dKV_swapAB: bool = False,
|
| 560 |
+
dQ_swapAB: bool = False,
|
| 561 |
+
AtomLayoutMSdP: int = 2,
|
| 562 |
+
AtomLayoutNdKV: int = 2,
|
| 563 |
+
AtomLayoutMdQ: int = 2,
|
| 564 |
+
V_in_regs: bool = False,
|
| 565 |
+
cu_seqlens_q: Optional[torch.Tensor] = None,
|
| 566 |
+
cu_seqlens_k: Optional[torch.Tensor] = None,
|
| 567 |
+
seqused_q: Optional[torch.Tensor] = None,
|
| 568 |
+
seqused_k: Optional[torch.Tensor] = None,
|
| 569 |
+
max_seqlen_q: Optional[int] = None,
|
| 570 |
+
max_seqlen_k: Optional[int] = None,
|
| 571 |
+
deterministic: bool = False,
|
| 572 |
+
dq: Optional[torch.Tensor] = None,
|
| 573 |
+
dk: Optional[torch.Tensor] = None,
|
| 574 |
+
dv: Optional[torch.Tensor] = None,
|
| 575 |
+
score_mod: Optional[Callable] = None,
|
| 576 |
+
score_mod_bwd: Optional[Callable] = None,
|
| 577 |
+
mask_mod: Optional[Callable] = None,
|
| 578 |
+
aux_tensors: Optional[list[torch.Tensor]] = None,
|
| 579 |
+
block_sparse_tensors: Optional[BlockSparseTensorsTorch] = None,
|
| 580 |
+
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 581 |
+
compute_capability = _get_device_capability()
|
| 582 |
+
assert compute_capability in [9, 10, 11], "Unsupported compute capability. Supported: 9.x, 10.x, 11.x"
|
| 583 |
+
|
| 584 |
+
if compute_capability == 9:
|
| 585 |
+
m_block_size = 80 if not causal else 64
|
| 586 |
+
# Keep the caller-selected KV tile. Upstream hard-codes 128 here,
|
| 587 |
+
# but SKSA's raw-8 routes already use a 64-token forward schedule and
|
| 588 |
+
# benefit from transposing that same schedule directly for dK/dV.
|
| 589 |
+
assert n_block_size in [64, 128], "SM90 backward supports 64 or 128 KV tiles"
|
| 590 |
+
num_stages_Q = 2
|
| 591 |
+
num_stages_dO = 2
|
| 592 |
+
num_stages_PdS = 2
|
| 593 |
+
SdP_swapAB = True
|
| 594 |
+
dKV_swapAB = False
|
| 595 |
+
dQ_swapAB = not causal
|
| 596 |
+
# Preserve the 128-thread logical coverage expected by the kernels:
|
| 597 |
+
# 64-wide KV tiles use two M atoms and one N atom, whereas the
|
| 598 |
+
# upstream 128-wide layout uses one M atom and two N atoms.
|
| 599 |
+
AtomLayoutMSdP = 1 if n_block_size == 128 else 2
|
| 600 |
+
AtomLayoutNdKV = 2 if n_block_size == 128 else 1
|
| 601 |
+
AtomLayoutMdQ = 1
|
| 602 |
+
cluster_size = 1
|
| 603 |
+
assert window_size_left is None and window_size_right is None, "local not supported yet on 9.x"
|
| 604 |
+
else:
|
| 605 |
+
m_block_size = 128
|
| 606 |
+
n_block_size = 128
|
| 607 |
+
dQ_swapAB = False
|
| 608 |
+
dKV_swapAB = False
|
| 609 |
+
AtomLayoutMdQ = 1
|
| 610 |
+
AtomLayoutNdKV = 1
|
| 611 |
+
# TODO: support cluster size 2
|
| 612 |
+
cluster_size = 1
|
| 613 |
+
q, k, v, out, dout, lse, cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k = [
|
| 614 |
+
maybe_contiguous(t)
|
| 615 |
+
for t in (q, k, v, out, dout, lse, cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k)
|
| 616 |
+
]
|
| 617 |
+
num_head, head_dim = q.shape[-2:]
|
| 618 |
+
if cu_seqlens_q is None:
|
| 619 |
+
batch_size, seqlen_q = q.shape[:2]
|
| 620 |
+
total_q = batch_size * seqlen_q
|
| 621 |
+
else:
|
| 622 |
+
batch_size = cu_seqlens_q.shape[0] - 1
|
| 623 |
+
total_q = q.shape[0]
|
| 624 |
+
seqlen_q = max_seqlen_q if max_seqlen_q is not None else total_q
|
| 625 |
+
|
| 626 |
+
if cu_seqlens_k is None:
|
| 627 |
+
batch_size, seqlen_k = k.shape[:2]
|
| 628 |
+
total_k = batch_size * seqlen_k
|
| 629 |
+
else:
|
| 630 |
+
batch_size = cu_seqlens_k.shape[0] - 1
|
| 631 |
+
total_k = k.shape[0]
|
| 632 |
+
seqlen_k = max_seqlen_k if max_seqlen_k is not None else total_k
|
| 633 |
+
|
| 634 |
+
num_head_kv = k.shape[-2]
|
| 635 |
+
head_dim_v = v.shape[-1]
|
| 636 |
+
|
| 637 |
+
if causal:
|
| 638 |
+
window_size_right = 0
|
| 639 |
+
local = window_size_left is not None or window_size_right is not None
|
| 640 |
+
if local:
|
| 641 |
+
if window_size_left is None and window_size_right == 0:
|
| 642 |
+
causal, local = True, False
|
| 643 |
+
window_size_right = None
|
| 644 |
+
else:
|
| 645 |
+
causal, local = False, True
|
| 646 |
+
|
| 647 |
+
use_block_sparsity = block_sparse_tensors is not None
|
| 648 |
+
|
| 649 |
+
# SM90 block-sparse backward: tile_m=64 is the GCD between a m_block_size that fits,
|
| 650 |
+
# the base block_m of 128 from forward, and block-sparse size for subtiling.
|
| 651 |
+
if compute_capability == 9 and use_block_sparsity:
|
| 652 |
+
m_block_size = 64
|
| 653 |
+
# dQ_swapAB tuning: use False when m_block_size=64 (same as causal case)
|
| 654 |
+
dQ_swapAB = False
|
| 655 |
+
|
| 656 |
+
# NB: this could be derived from the block_sparse_tensors but for now we hardcode it to 2
|
| 657 |
+
subtile_factor = 2
|
| 658 |
+
seqlen_q_rounded = (seqlen_q + m_block_size - 1) // m_block_size * m_block_size
|
| 659 |
+
seqlen_k_rounded = (seqlen_k + n_block_size - 1) // n_block_size * n_block_size
|
| 660 |
+
|
| 661 |
+
if cu_seqlens_k is None:
|
| 662 |
+
assert k.shape == (batch_size, seqlen_k, num_head_kv, head_dim)
|
| 663 |
+
assert v.shape == (batch_size, seqlen_k, num_head_kv, head_dim_v)
|
| 664 |
+
else:
|
| 665 |
+
assert k.shape == (total_k, num_head_kv, head_dim)
|
| 666 |
+
assert v.shape == (total_k, num_head_kv, head_dim_v)
|
| 667 |
+
assert cu_seqlens_k.shape == (batch_size + 1,), (
|
| 668 |
+
"cu_seqlens_k must have shape (batch_size + 1,)"
|
| 669 |
+
)
|
| 670 |
+
|
| 671 |
+
if cu_seqlens_q is not None:
|
| 672 |
+
assert cu_seqlens_q.shape == (batch_size + 1,), (
|
| 673 |
+
"cu_seqlens_q must have shape (batch_size + 1,)"
|
| 674 |
+
)
|
| 675 |
+
|
| 676 |
+
assert out.shape == (total_q, num_head, head_dim_v)
|
| 677 |
+
assert dout.shape == (total_q, num_head, head_dim_v)
|
| 678 |
+
assert lse.shape == (num_head, total_q), "lse must have shape (num_head, total_q)"
|
| 679 |
+
else:
|
| 680 |
+
assert out.shape == (batch_size, seqlen_q, num_head, head_dim_v)
|
| 681 |
+
assert dout.shape == (batch_size, seqlen_q, num_head, head_dim_v)
|
| 682 |
+
assert lse.shape == (batch_size, num_head, seqlen_q), (
|
| 683 |
+
"lse must have shape (batch_size, num_head, seqlen_q)"
|
| 684 |
+
)
|
| 685 |
+
|
| 686 |
+
assert q.dtype in [torch.float16, torch.bfloat16], "inputs must be float16 or bfloat16"
|
| 687 |
+
assert q.dtype == k.dtype == v.dtype == out.dtype == dout.dtype, (
|
| 688 |
+
"inputs must have the same dtype"
|
| 689 |
+
)
|
| 690 |
+
for t in [cu_seqlens_q, cu_seqlens_k]:
|
| 691 |
+
if t is not None:
|
| 692 |
+
assert t.dtype == torch.int32, "cu_seqlens_q, cu_seqlens_k must be int32"
|
| 693 |
+
assert lse.dtype == torch.float32, "lse must be float32"
|
| 694 |
+
assert all(
|
| 695 |
+
t is None or t.is_cuda for t in (q, k, v, out, dout, lse, cu_seqlens_q, cu_seqlens_k)
|
| 696 |
+
), "inputs must be on CUDA device"
|
| 697 |
+
assert num_head % num_head_kv == 0, "num_head must be divisible by num_head_kv"
|
| 698 |
+
assert head_dim <= 256, "head_dim must be less than or equal to 256"
|
| 699 |
+
alignment = 16 // q.element_size()
|
| 700 |
+
assert head_dim % alignment == 0, f"head_dim must be divisible by {alignment}"
|
| 701 |
+
assert head_dim_v % alignment == 0, f"head_dim_v must be divisible by {alignment}"
|
| 702 |
+
if softmax_scale is None:
|
| 703 |
+
softmax_scale = 1.0 / math.sqrt(head_dim)
|
| 704 |
+
qhead_per_kvhead = num_head // num_head_kv
|
| 705 |
+
if pack_gqa is None:
|
| 706 |
+
pack_gqa = qhead_per_kvhead > 1
|
| 707 |
+
# pack_gqa backward not yet supported in bwd
|
| 708 |
+
pack_gqa = False
|
| 709 |
+
if compute_capability not in [10, 11]:
|
| 710 |
+
assert deterministic is False, "bwd deterministic only supported for sm100/sm110 for now"
|
| 711 |
+
|
| 712 |
+
if score_mod is not None:
|
| 713 |
+
assert score_mod_bwd is not None, "score_mod_bwd is required when score_mod is provided"
|
| 714 |
+
assert softcap == 0.0, "softcap and score_mod are mutually exclusive (different log2 scaling)"
|
| 715 |
+
assert cu_seqlens_q is None and cu_seqlens_k is None, (
|
| 716 |
+
"varlen + score_mod not supported in bwd yet"
|
| 717 |
+
)
|
| 718 |
+
|
| 719 |
+
device = q.device
|
| 720 |
+
out_torch_dtype = q.dtype
|
| 721 |
+
|
| 722 |
+
if dq is None:
|
| 723 |
+
dq = torch.empty_like(q)
|
| 724 |
+
else:
|
| 725 |
+
_validate_tensor(dq, "dq", q.shape, out_torch_dtype, device)
|
| 726 |
+
|
| 727 |
+
if dk is None:
|
| 728 |
+
dk = torch.empty_like(k)
|
| 729 |
+
else:
|
| 730 |
+
_validate_tensor(dk, "dk", k.shape, out_torch_dtype, device)
|
| 731 |
+
|
| 732 |
+
if dv is None:
|
| 733 |
+
dv = torch.empty_like(v)
|
| 734 |
+
else:
|
| 735 |
+
_validate_tensor(dv, "dv", v.shape, out_torch_dtype, device)
|
| 736 |
+
|
| 737 |
+
head_dim_rounded = (head_dim + 32 - 1) // 32 * 32
|
| 738 |
+
|
| 739 |
+
if cu_seqlens_q is None:
|
| 740 |
+
dq_accum = torch.empty(
|
| 741 |
+
batch_size,
|
| 742 |
+
num_head,
|
| 743 |
+
seqlen_q_rounded * head_dim_rounded,
|
| 744 |
+
dtype=torch.float32,
|
| 745 |
+
device=device,
|
| 746 |
+
)
|
| 747 |
+
dpsum = torch.empty(
|
| 748 |
+
batch_size, num_head, seqlen_q_rounded, dtype=torch.float32, device=device
|
| 749 |
+
)
|
| 750 |
+
lse_log2 = torch.empty(
|
| 751 |
+
batch_size, num_head, seqlen_q_rounded, dtype=torch.float32, device=device
|
| 752 |
+
)
|
| 753 |
+
else:
|
| 754 |
+
total_q_rounded_padded = (
|
| 755 |
+
(total_q + cu_seqlens_q.shape[0] * m_block_size - 1) // m_block_size * m_block_size
|
| 756 |
+
)
|
| 757 |
+
dq_accum = torch.empty(
|
| 758 |
+
num_head, total_q_rounded_padded * head_dim_rounded, dtype=torch.float32, device=device
|
| 759 |
+
)
|
| 760 |
+
dpsum = torch.empty(num_head, total_q_rounded_padded, dtype=torch.float32, device=device)
|
| 761 |
+
lse_log2 = torch.empty(num_head, total_q_rounded_padded, dtype=torch.float32, device=device)
|
| 762 |
+
|
| 763 |
+
dKV_postprocess = qhead_per_kvhead > 1
|
| 764 |
+
if dKV_postprocess:
|
| 765 |
+
head_dim_v_rounded = (head_dim_v + 32 - 1) // 32 * 32
|
| 766 |
+
if cu_seqlens_k is None:
|
| 767 |
+
num_n_blocks = seqlen_k_rounded // n_block_size
|
| 768 |
+
if cluster_size == 2 and num_n_blocks % cluster_size != 0:
|
| 769 |
+
seqlen_k_rounded = seqlen_k_rounded + n_block_size
|
| 770 |
+
dk_accum = torch.zeros(
|
| 771 |
+
batch_size,
|
| 772 |
+
num_head_kv,
|
| 773 |
+
seqlen_k_rounded * head_dim_rounded,
|
| 774 |
+
dtype=torch.float32,
|
| 775 |
+
device=device,
|
| 776 |
+
)
|
| 777 |
+
dv_accum = torch.zeros(
|
| 778 |
+
batch_size,
|
| 779 |
+
num_head_kv,
|
| 780 |
+
seqlen_k_rounded * head_dim_v_rounded,
|
| 781 |
+
dtype=torch.float32,
|
| 782 |
+
device=device,
|
| 783 |
+
)
|
| 784 |
+
else:
|
| 785 |
+
total_k_rounded_padded = (
|
| 786 |
+
(total_k + cu_seqlens_k.shape[0] * n_block_size - 1) // n_block_size * n_block_size
|
| 787 |
+
)
|
| 788 |
+
num_n_blocks = total_k_rounded_padded // n_block_size
|
| 789 |
+
if cluster_size == 2 and num_n_blocks % cluster_size != 0:
|
| 790 |
+
total_k_rounded_padded = total_k_rounded_padded + n_block_size
|
| 791 |
+
dk_accum = torch.zeros(
|
| 792 |
+
num_head_kv,
|
| 793 |
+
total_k_rounded_padded * head_dim_rounded,
|
| 794 |
+
dtype=torch.float32,
|
| 795 |
+
device=device,
|
| 796 |
+
)
|
| 797 |
+
dv_accum = torch.zeros(
|
| 798 |
+
num_head_kv,
|
| 799 |
+
total_k_rounded_padded * head_dim_v_rounded,
|
| 800 |
+
dtype=torch.float32,
|
| 801 |
+
device=device,
|
| 802 |
+
)
|
| 803 |
+
|
| 804 |
+
dtype = torch2cute_dtype_map[q.dtype]
|
| 805 |
+
current_stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
|
| 806 |
+
|
| 807 |
+
if deterministic:
|
| 808 |
+
dQ_semaphore = torch.zeros(batch_size, num_head, seqlen_q_rounded // m_block_size, 1, dtype=torch.int32, device="cuda")
|
| 809 |
+
else:
|
| 810 |
+
dQ_semaphore = None
|
| 811 |
+
|
| 812 |
+
if deterministic and qhead_per_kvhead > 1:
|
| 813 |
+
dK_semaphore = torch.zeros(batch_size, num_head_kv, seqlen_k_rounded // n_block_size, 2, dtype=torch.int32, device="cuda")
|
| 814 |
+
dV_semaphore = torch.zeros(batch_size, num_head_kv, seqlen_k_rounded // n_block_size, 2, dtype=torch.int32, device="cuda")
|
| 815 |
+
else:
|
| 816 |
+
dK_semaphore = None
|
| 817 |
+
dV_semaphore = None
|
| 818 |
+
|
| 819 |
+
# Preprocess kernel: compute (o * dout).sum(dim=-1), lse * log2_e, and zero out dq_accum.
|
| 820 |
+
compile_key_pre = (
|
| 821 |
+
compute_capability,
|
| 822 |
+
dtype,
|
| 823 |
+
head_dim_v,
|
| 824 |
+
m_block_size,
|
| 825 |
+
num_threads,
|
| 826 |
+
cu_seqlens_q is None,
|
| 827 |
+
seqused_q is None,
|
| 828 |
+
)
|
| 829 |
+
if compile_key_pre not in _flash_attn_bwd.compile_cache_pre:
|
| 830 |
+
o_tensor, do_tensor = [to_cute_tensor(t) for t in (out, dout)]
|
| 831 |
+
dq_accum_tensor, dpsum_tensor, lse_log2_tensor = [
|
| 832 |
+
to_cute_tensor(t) for t in (dq_accum, dpsum, lse_log2)
|
| 833 |
+
]
|
| 834 |
+
lse_tensor = to_cute_tensor(lse, assumed_align=4)
|
| 835 |
+
cu_seqlens_q_tensor, seqused_q_tensor = [
|
| 836 |
+
to_cute_tensor(t, assumed_align=4) if t is not None else None
|
| 837 |
+
for t in (cu_seqlens_q, seqused_q)
|
| 838 |
+
]
|
| 839 |
+
arch = compute_capability * 10
|
| 840 |
+
fa_bwd_pre = FlashAttentionBackwardPreprocess(
|
| 841 |
+
dtype,
|
| 842 |
+
head_dim_v,
|
| 843 |
+
arch,
|
| 844 |
+
m_block_size,
|
| 845 |
+
num_threads=num_threads,
|
| 846 |
+
)
|
| 847 |
+
# TODO: check @can_implement
|
| 848 |
+
_flash_attn_bwd.compile_cache_pre[compile_key_pre] = cute.compile(
|
| 849 |
+
fa_bwd_pre,
|
| 850 |
+
o_tensor,
|
| 851 |
+
do_tensor,
|
| 852 |
+
dpsum_tensor,
|
| 853 |
+
current_stream,
|
| 854 |
+
lse_tensor,
|
| 855 |
+
lse_log2_tensor,
|
| 856 |
+
dq_accum_tensor,
|
| 857 |
+
cu_seqlens_q_tensor,
|
| 858 |
+
seqused_q_tensor,
|
| 859 |
+
options="--enable-tvm-ffi",
|
| 860 |
+
)
|
| 861 |
+
_flash_attn_bwd.compile_cache_pre[compile_key_pre](
|
| 862 |
+
out,
|
| 863 |
+
dout,
|
| 864 |
+
dpsum,
|
| 865 |
+
current_stream,
|
| 866 |
+
lse,
|
| 867 |
+
lse_log2,
|
| 868 |
+
dq_accum,
|
| 869 |
+
cu_seqlens_q,
|
| 870 |
+
seqused_q,
|
| 871 |
+
)
|
| 872 |
+
|
| 873 |
+
# NB num_threads application for 3 kernels
|
| 874 |
+
# There are pre, main, post processing kernels, currenlty num_threads is only actually
|
| 875 |
+
# used for the pre proc, and then we hard code to 384 for the main and post proc, and we do
|
| 876 |
+
# before cache key gen
|
| 877 |
+
num_threads = 384
|
| 878 |
+
|
| 879 |
+
# Backward kernel: compute dk, dv, dq_accum.
|
| 880 |
+
score_mod_hash = utils.hash_callable(score_mod) if score_mod else False
|
| 881 |
+
score_mod_bwd_hash = utils.hash_callable(score_mod_bwd) if score_mod_bwd else False
|
| 882 |
+
mask_mod_hash = utils.hash_callable(mask_mod) if mask_mod else False
|
| 883 |
+
num_aux_tensors = len(aux_tensors) if aux_tensors else 0
|
| 884 |
+
cute_aux_tensors = None
|
| 885 |
+
if aux_tensors is not None:
|
| 886 |
+
cute_aux_tensors = [to_cute_tensor(buf, assumed_align=None, fully_dynamic=True) for buf in aux_tensors]
|
| 887 |
+
|
| 888 |
+
block_sparse_broadcast_pattern = None
|
| 889 |
+
normalized_block_sparse_tensors = None
|
| 890 |
+
if block_sparse_tensors is not None:
|
| 891 |
+
(
|
| 892 |
+
normalized_block_sparse_tensors,
|
| 893 |
+
block_sparse_broadcast_pattern,
|
| 894 |
+
) = normalize_block_sparse_config_bwd(
|
| 895 |
+
block_sparse_tensors,
|
| 896 |
+
batch_size=batch_size,
|
| 897 |
+
num_head=num_head,
|
| 898 |
+
seqlen_q=seqlen_q,
|
| 899 |
+
seqlen_k=seqlen_k,
|
| 900 |
+
block_size=(m_block_size, n_block_size),
|
| 901 |
+
subtile_factor=subtile_factor,
|
| 902 |
+
)
|
| 903 |
+
|
| 904 |
+
if compute_capability == 9:
|
| 905 |
+
compile_key = (
|
| 906 |
+
compute_capability,
|
| 907 |
+
dtype,
|
| 908 |
+
head_dim,
|
| 909 |
+
head_dim_v,
|
| 910 |
+
qhead_per_kvhead,
|
| 911 |
+
causal,
|
| 912 |
+
softcap != 0.0,
|
| 913 |
+
m_block_size,
|
| 914 |
+
n_block_size,
|
| 915 |
+
num_threads,
|
| 916 |
+
pack_gqa,
|
| 917 |
+
num_stages_Q,
|
| 918 |
+
num_stages_dO,
|
| 919 |
+
SdP_swapAB,
|
| 920 |
+
dKV_swapAB,
|
| 921 |
+
dQ_swapAB,
|
| 922 |
+
AtomLayoutMSdP,
|
| 923 |
+
AtomLayoutNdKV,
|
| 924 |
+
AtomLayoutMdQ,
|
| 925 |
+
V_in_regs,
|
| 926 |
+
cu_seqlens_q is None,
|
| 927 |
+
cu_seqlens_k is None,
|
| 928 |
+
seqused_q is None,
|
| 929 |
+
seqused_k is None,
|
| 930 |
+
score_mod_hash,
|
| 931 |
+
score_mod_bwd_hash,
|
| 932 |
+
mask_mod_hash,
|
| 933 |
+
num_aux_tensors,
|
| 934 |
+
use_block_sparsity,
|
| 935 |
+
block_sparse_broadcast_pattern,
|
| 936 |
+
)
|
| 937 |
+
else:
|
| 938 |
+
compile_key = (
|
| 939 |
+
compute_capability,
|
| 940 |
+
dtype,
|
| 941 |
+
head_dim,
|
| 942 |
+
head_dim_v,
|
| 943 |
+
qhead_per_kvhead,
|
| 944 |
+
causal,
|
| 945 |
+
window_size_left is not None,
|
| 946 |
+
window_size_right is not None,
|
| 947 |
+
softcap != 0.0,
|
| 948 |
+
m_block_size,
|
| 949 |
+
n_block_size,
|
| 950 |
+
num_threads,
|
| 951 |
+
pack_gqa,
|
| 952 |
+
cluster_size,
|
| 953 |
+
deterministic,
|
| 954 |
+
score_mod_hash,
|
| 955 |
+
score_mod_bwd_hash,
|
| 956 |
+
mask_mod_hash,
|
| 957 |
+
num_aux_tensors,
|
| 958 |
+
use_block_sparsity,
|
| 959 |
+
block_sparse_broadcast_pattern,
|
| 960 |
+
cu_seqlens_q is None,
|
| 961 |
+
cu_seqlens_k is None,
|
| 962 |
+
seqused_q is None,
|
| 963 |
+
seqused_k is None,
|
| 964 |
+
)
|
| 965 |
+
if compile_key not in _flash_attn_bwd.compile_cache:
|
| 966 |
+
q_tensor, k_tensor, v_tensor, do_tensor, dq_tensor, dk_tensor, dv_tensor = [
|
| 967 |
+
to_cute_tensor(t) for t in (q, k, v, dout, dq, dk, dv)
|
| 968 |
+
]
|
| 969 |
+
dq_accum_tensor, dpsum_tensor, lse_log2_tensor = [
|
| 970 |
+
to_cute_tensor(t) for t in (dq_accum, dpsum, lse_log2)
|
| 971 |
+
]
|
| 972 |
+
if dKV_postprocess:
|
| 973 |
+
dk_accum_tensor, dv_accum_tensor = [
|
| 974 |
+
to_cute_tensor(t) for t in (dk_accum, dv_accum)
|
| 975 |
+
]
|
| 976 |
+
cu_seqlens_q_tensor, cu_seqlens_k_tensor, seqused_q_tensor, seqused_k_tensor = [
|
| 977 |
+
to_cute_tensor(t, assumed_align=4) if t is not None else None
|
| 978 |
+
for t in (cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k)
|
| 979 |
+
]
|
| 980 |
+
dQ_semaphore_tensor, dK_semaphore_tensor, dV_semaphore_tensor = [
|
| 981 |
+
utils.convert_from_dlpack_leading_static(t.detach(), leading_dim=3, alignment=4, stride_order=t.dim_order())
|
| 982 |
+
if t is not None else None
|
| 983 |
+
for t in (dQ_semaphore, dK_semaphore, dV_semaphore)
|
| 984 |
+
]
|
| 985 |
+
fa_bwd_sm80 = FlashAttentionBackwardSm80(
|
| 986 |
+
dtype,
|
| 987 |
+
head_dim,
|
| 988 |
+
head_dim_v,
|
| 989 |
+
qhead_per_kvhead,
|
| 990 |
+
m_block_size,
|
| 991 |
+
n_block_size,
|
| 992 |
+
num_stages_Q,
|
| 993 |
+
num_stages_dO,
|
| 994 |
+
num_threads,
|
| 995 |
+
pack_gqa,
|
| 996 |
+
causal,
|
| 997 |
+
SdP_swapAB,
|
| 998 |
+
dKV_swapAB,
|
| 999 |
+
dQ_swapAB,
|
| 1000 |
+
AtomLayoutMSdP,
|
| 1001 |
+
AtomLayoutNdKV,
|
| 1002 |
+
AtomLayoutMdQ,
|
| 1003 |
+
V_in_regs=V_in_regs,
|
| 1004 |
+
)
|
| 1005 |
+
if compute_capability == 9:
|
| 1006 |
+
fa_bwd_obj = FlashAttentionBackwardSm90(
|
| 1007 |
+
dtype,
|
| 1008 |
+
head_dim,
|
| 1009 |
+
head_dim_v,
|
| 1010 |
+
qhead_per_kvhead,
|
| 1011 |
+
causal,
|
| 1012 |
+
m_block_size,
|
| 1013 |
+
n_block_size,
|
| 1014 |
+
num_stages_Q,
|
| 1015 |
+
num_stages_dO,
|
| 1016 |
+
num_stages_PdS,
|
| 1017 |
+
SdP_swapAB,
|
| 1018 |
+
dKV_swapAB,
|
| 1019 |
+
dQ_swapAB,
|
| 1020 |
+
AtomLayoutMSdP,
|
| 1021 |
+
AtomLayoutNdKV,
|
| 1022 |
+
AtomLayoutMdQ,
|
| 1023 |
+
num_threads,
|
| 1024 |
+
V_in_regs=V_in_regs,
|
| 1025 |
+
score_mod=score_mod,
|
| 1026 |
+
score_mod_bwd=score_mod_bwd,
|
| 1027 |
+
mask_mod=mask_mod,
|
| 1028 |
+
has_aux_tensors=aux_tensors is not None,
|
| 1029 |
+
subtile_factor=subtile_factor,
|
| 1030 |
+
)
|
| 1031 |
+
else:
|
| 1032 |
+
fa_bwd_obj = FlashAttentionBackwardSm100(
|
| 1033 |
+
head_dim,
|
| 1034 |
+
head_dim_v,
|
| 1035 |
+
is_causal=causal,
|
| 1036 |
+
is_local=local,
|
| 1037 |
+
qhead_per_kvhead=qhead_per_kvhead,
|
| 1038 |
+
# tile_m=m_block_size,
|
| 1039 |
+
# tile_n=n_block_size,
|
| 1040 |
+
cluster_size=cluster_size,
|
| 1041 |
+
# cluster_size=1,
|
| 1042 |
+
deterministic=deterministic,
|
| 1043 |
+
score_mod=score_mod,
|
| 1044 |
+
score_mod_bwd=score_mod_bwd,
|
| 1045 |
+
mask_mod=mask_mod,
|
| 1046 |
+
has_aux_tensors=aux_tensors is not None,
|
| 1047 |
+
subtile_factor=subtile_factor,
|
| 1048 |
+
)
|
| 1049 |
+
|
| 1050 |
+
# Block sparse tensors for backward use Q-direction indexing (transposed from forward).
|
| 1051 |
+
sparse_tensors_compile = None
|
| 1052 |
+
if normalized_block_sparse_tensors is not None:
|
| 1053 |
+
sparse_tensors_compile = to_cute_block_sparse_tensors(normalized_block_sparse_tensors)
|
| 1054 |
+
|
| 1055 |
+
# TODO: check @can_implement
|
| 1056 |
+
_flash_attn_bwd.compile_cache[compile_key] = cute.compile(
|
| 1057 |
+
fa_bwd_obj,
|
| 1058 |
+
q_tensor,
|
| 1059 |
+
k_tensor,
|
| 1060 |
+
v_tensor,
|
| 1061 |
+
do_tensor,
|
| 1062 |
+
lse_log2_tensor,
|
| 1063 |
+
dpsum_tensor,
|
| 1064 |
+
dq_accum_tensor,
|
| 1065 |
+
dk_tensor if not dKV_postprocess else dk_accum_tensor,
|
| 1066 |
+
dv_tensor if not dKV_postprocess else dv_accum_tensor,
|
| 1067 |
+
softmax_scale,
|
| 1068 |
+
current_stream,
|
| 1069 |
+
cu_seqlens_q_tensor,
|
| 1070 |
+
cu_seqlens_k_tensor,
|
| 1071 |
+
seqused_q_tensor,
|
| 1072 |
+
seqused_k_tensor,
|
| 1073 |
+
None, # softcap - not yet supported in backward
|
| 1074 |
+
window_size_left,
|
| 1075 |
+
window_size_right,
|
| 1076 |
+
dQ_semaphore_tensor,
|
| 1077 |
+
dK_semaphore_tensor,
|
| 1078 |
+
dV_semaphore_tensor,
|
| 1079 |
+
cute_aux_tensors,
|
| 1080 |
+
sparse_tensors_compile,
|
| 1081 |
+
options="--enable-tvm-ffi",
|
| 1082 |
+
)
|
| 1083 |
+
_flash_attn_bwd.compile_cache[compile_key](
|
| 1084 |
+
q,
|
| 1085 |
+
k,
|
| 1086 |
+
v,
|
| 1087 |
+
dout,
|
| 1088 |
+
lse_log2,
|
| 1089 |
+
dpsum,
|
| 1090 |
+
dq_accum,
|
| 1091 |
+
dk if not dKV_postprocess else dk_accum,
|
| 1092 |
+
dv if not dKV_postprocess else dv_accum,
|
| 1093 |
+
softmax_scale,
|
| 1094 |
+
current_stream,
|
| 1095 |
+
cu_seqlens_q,
|
| 1096 |
+
cu_seqlens_k,
|
| 1097 |
+
seqused_q,
|
| 1098 |
+
seqused_k,
|
| 1099 |
+
None, # softcap - not yet supported in backward
|
| 1100 |
+
window_size_left,
|
| 1101 |
+
window_size_right,
|
| 1102 |
+
dQ_semaphore,
|
| 1103 |
+
dK_semaphore,
|
| 1104 |
+
dV_semaphore,
|
| 1105 |
+
aux_tensors,
|
| 1106 |
+
normalized_block_sparse_tensors[:4] if normalized_block_sparse_tensors is not None else None,
|
| 1107 |
+
)
|
| 1108 |
+
|
| 1109 |
+
num_threads = 256 if compute_capability == 9 else 128
|
| 1110 |
+
arch = compute_capability * 10
|
| 1111 |
+
# Postprocess kernel: convert dq_accum from float32 to dq in bf16/fp16
|
| 1112 |
+
compile_key_post = (
|
| 1113 |
+
compute_capability,
|
| 1114 |
+
dtype,
|
| 1115 |
+
head_dim,
|
| 1116 |
+
m_block_size,
|
| 1117 |
+
num_threads,
|
| 1118 |
+
AtomLayoutMdQ,
|
| 1119 |
+
dQ_swapAB,
|
| 1120 |
+
cu_seqlens_q is None,
|
| 1121 |
+
seqused_q is None,
|
| 1122 |
+
)
|
| 1123 |
+
if compile_key_post not in _flash_attn_bwd.compile_cache_post:
|
| 1124 |
+
dq_accum_tensor = to_cute_tensor(dq_accum)
|
| 1125 |
+
dq_tensor = to_cute_tensor(dq)
|
| 1126 |
+
cu_seqlens_q_tensor, seqused_q_tensor = [
|
| 1127 |
+
to_cute_tensor(t, assumed_align=4) if t is not None else None
|
| 1128 |
+
for t in (cu_seqlens_q, seqused_q)
|
| 1129 |
+
]
|
| 1130 |
+
fa_bwd_post = FlashAttentionBackwardPostprocess(
|
| 1131 |
+
dtype, head_dim, arch, m_block_size, num_threads, AtomLayoutMdQ, dQ_swapAB
|
| 1132 |
+
)
|
| 1133 |
+
# TODO: check @can_implement
|
| 1134 |
+
_flash_attn_bwd.compile_cache_post[compile_key_post] = cute.compile(
|
| 1135 |
+
fa_bwd_post,
|
| 1136 |
+
dq_accum_tensor,
|
| 1137 |
+
dq_tensor,
|
| 1138 |
+
softmax_scale,
|
| 1139 |
+
current_stream,
|
| 1140 |
+
cu_seqlens_q_tensor,
|
| 1141 |
+
seqused_q_tensor,
|
| 1142 |
+
options="--enable-tvm-ffi",
|
| 1143 |
+
)
|
| 1144 |
+
_flash_attn_bwd.compile_cache_post[compile_key_post](
|
| 1145 |
+
dq_accum,
|
| 1146 |
+
dq,
|
| 1147 |
+
softmax_scale,
|
| 1148 |
+
current_stream,
|
| 1149 |
+
cu_seqlens_q,
|
| 1150 |
+
seqused_q,
|
| 1151 |
+
)
|
| 1152 |
+
|
| 1153 |
+
if dKV_postprocess:
|
| 1154 |
+
# Postprocess kernel: convert dk_accum & dv_accum from float32 to bf16/fp16
|
| 1155 |
+
compile_key_post = (
|
| 1156 |
+
compute_capability,
|
| 1157 |
+
dtype,
|
| 1158 |
+
head_dim,
|
| 1159 |
+
n_block_size,
|
| 1160 |
+
num_threads,
|
| 1161 |
+
AtomLayoutNdKV,
|
| 1162 |
+
dKV_swapAB,
|
| 1163 |
+
cu_seqlens_k is None,
|
| 1164 |
+
seqused_k is None,
|
| 1165 |
+
)
|
| 1166 |
+
if compile_key_post not in _flash_attn_bwd.compile_cache_post:
|
| 1167 |
+
dk_accum_tensor = to_cute_tensor(dk_accum)
|
| 1168 |
+
dk_tensor = to_cute_tensor(dk)
|
| 1169 |
+
cu_seqlens_k_tensor, seqused_k_tensor = [
|
| 1170 |
+
to_cute_tensor(t, assumed_align=4) if t is not None else None
|
| 1171 |
+
for t in (cu_seqlens_k, seqused_k)
|
| 1172 |
+
]
|
| 1173 |
+
arch = compute_capability * 10
|
| 1174 |
+
fa_bwd_post = FlashAttentionBackwardPostprocess(
|
| 1175 |
+
dtype, head_dim, arch, n_block_size, num_threads, AtomLayoutNdKV, dKV_swapAB
|
| 1176 |
+
)
|
| 1177 |
+
# TODO: check @can_implement
|
| 1178 |
+
_flash_attn_bwd.compile_cache_post[compile_key_post] = cute.compile(
|
| 1179 |
+
fa_bwd_post,
|
| 1180 |
+
dk_accum_tensor,
|
| 1181 |
+
dk_tensor,
|
| 1182 |
+
softmax_scale,
|
| 1183 |
+
current_stream,
|
| 1184 |
+
cu_seqlens_k_tensor,
|
| 1185 |
+
seqused_k_tensor,
|
| 1186 |
+
options="--enable-tvm-ffi",
|
| 1187 |
+
)
|
| 1188 |
+
_flash_attn_bwd.compile_cache_post[compile_key_post](
|
| 1189 |
+
dk_accum,
|
| 1190 |
+
dk,
|
| 1191 |
+
softmax_scale,
|
| 1192 |
+
current_stream,
|
| 1193 |
+
cu_seqlens_k,
|
| 1194 |
+
seqused_k,
|
| 1195 |
+
)
|
| 1196 |
+
compile_key_post = (
|
| 1197 |
+
compute_capability,
|
| 1198 |
+
dtype,
|
| 1199 |
+
head_dim_v,
|
| 1200 |
+
n_block_size,
|
| 1201 |
+
num_threads,
|
| 1202 |
+
AtomLayoutNdKV,
|
| 1203 |
+
dKV_swapAB,
|
| 1204 |
+
cu_seqlens_k is None,
|
| 1205 |
+
seqused_k is None,
|
| 1206 |
+
)
|
| 1207 |
+
if compile_key_post not in _flash_attn_bwd.compile_cache_post:
|
| 1208 |
+
dv_accum_tensor = to_cute_tensor(dv_accum)
|
| 1209 |
+
dv_tensor = to_cute_tensor(dv)
|
| 1210 |
+
cu_seqlens_k_tensor, seqused_k_tensor = [
|
| 1211 |
+
to_cute_tensor(t, assumed_align=4) if t is not None else None
|
| 1212 |
+
for t in (cu_seqlens_k, seqused_k)
|
| 1213 |
+
]
|
| 1214 |
+
arch = compute_capability * 10
|
| 1215 |
+
fa_bwd_post = FlashAttentionBackwardPostprocess(
|
| 1216 |
+
dtype, head_dim_v, arch, n_block_size, num_threads, AtomLayoutNdKV, dKV_swapAB
|
| 1217 |
+
)
|
| 1218 |
+
# TODO: check @can_implement
|
| 1219 |
+
_flash_attn_bwd.compile_cache_post[compile_key_post] = cute.compile(
|
| 1220 |
+
fa_bwd_post,
|
| 1221 |
+
dv_accum_tensor,
|
| 1222 |
+
dv_tensor,
|
| 1223 |
+
cutlass.Float32(1.0),
|
| 1224 |
+
current_stream,
|
| 1225 |
+
cu_seqlens_k_tensor,
|
| 1226 |
+
seqused_k_tensor,
|
| 1227 |
+
options="--enable-tvm-ffi",
|
| 1228 |
+
)
|
| 1229 |
+
_flash_attn_bwd.compile_cache_post[compile_key_post](
|
| 1230 |
+
dv_accum,
|
| 1231 |
+
dv,
|
| 1232 |
+
1.0,
|
| 1233 |
+
current_stream,
|
| 1234 |
+
cu_seqlens_k,
|
| 1235 |
+
seqused_k,
|
| 1236 |
+
)
|
| 1237 |
+
|
| 1238 |
+
return dq, dk, dv
|
| 1239 |
+
|
| 1240 |
+
|
| 1241 |
+
_flash_attn_bwd.compile_cache_pre = {}
|
| 1242 |
+
_flash_attn_bwd.compile_cache = {}
|
| 1243 |
+
_flash_attn_bwd.compile_cache_post = {}
|
| 1244 |
+
|
| 1245 |
+
|
| 1246 |
+
class FlashAttnFunc(torch.autograd.Function):
|
| 1247 |
+
@staticmethod
|
| 1248 |
+
def forward(
|
| 1249 |
+
ctx,
|
| 1250 |
+
q: torch.Tensor,
|
| 1251 |
+
k: torch.Tensor,
|
| 1252 |
+
v: torch.Tensor,
|
| 1253 |
+
softmax_scale: Optional[float] = None,
|
| 1254 |
+
causal: bool = False,
|
| 1255 |
+
window_size: Tuple[Optional[int], Optional[int]] = (None, None),
|
| 1256 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 1257 |
+
softcap: float = 0.0,
|
| 1258 |
+
num_splits: int = 1,
|
| 1259 |
+
pack_gqa: Optional[bool] = None,
|
| 1260 |
+
deterministic: bool = False,
|
| 1261 |
+
mask_mod: Optional[Callable] = None,
|
| 1262 |
+
full_block_cnt: Optional[torch.Tensor] = None,
|
| 1263 |
+
full_block_idx: Optional[torch.Tensor] = None,
|
| 1264 |
+
mask_block_cnt: Optional[torch.Tensor] = None,
|
| 1265 |
+
mask_block_idx: Optional[torch.Tensor] = None,
|
| 1266 |
+
block_size: Optional[Tuple[int, int]] = None,
|
| 1267 |
+
):
|
| 1268 |
+
# Only create block sparse tensors if at least one block sparse parameter is provided
|
| 1269 |
+
block_sparse_tensors = None
|
| 1270 |
+
if any(t is not None for t in [full_block_cnt, full_block_idx, mask_block_cnt, mask_block_idx]):
|
| 1271 |
+
block_sparse_tensors = BlockSparseTensorsTorch(
|
| 1272 |
+
full_block_cnt=full_block_cnt,
|
| 1273 |
+
full_block_idx=full_block_idx,
|
| 1274 |
+
mask_block_cnt=mask_block_cnt,
|
| 1275 |
+
mask_block_idx=mask_block_idx,
|
| 1276 |
+
block_size=block_size,
|
| 1277 |
+
)
|
| 1278 |
+
out, lse = _flash_attn_fwd(
|
| 1279 |
+
q,
|
| 1280 |
+
k,
|
| 1281 |
+
v,
|
| 1282 |
+
softmax_scale=softmax_scale,
|
| 1283 |
+
causal=causal,
|
| 1284 |
+
window_size_left=window_size[0],
|
| 1285 |
+
window_size_right=window_size[1],
|
| 1286 |
+
learnable_sink=learnable_sink,
|
| 1287 |
+
softcap=softcap,
|
| 1288 |
+
num_splits=num_splits,
|
| 1289 |
+
pack_gqa=pack_gqa,
|
| 1290 |
+
mask_mod=mask_mod,
|
| 1291 |
+
block_sparse_tensors=block_sparse_tensors
|
| 1292 |
+
)
|
| 1293 |
+
ctx.save_for_backward(q, k, v, out, lse)
|
| 1294 |
+
ctx.softmax_scale = softmax_scale
|
| 1295 |
+
ctx.causal = causal
|
| 1296 |
+
ctx.window_size = window_size
|
| 1297 |
+
ctx.softcap = softcap
|
| 1298 |
+
ctx.deterministic = deterministic
|
| 1299 |
+
return out, lse
|
| 1300 |
+
|
| 1301 |
+
@staticmethod
|
| 1302 |
+
def backward(ctx, dout, *args):
|
| 1303 |
+
q, k, v, out, lse = ctx.saved_tensors
|
| 1304 |
+
dq, dk, dv = _flash_attn_bwd(
|
| 1305 |
+
q,
|
| 1306 |
+
k,
|
| 1307 |
+
v,
|
| 1308 |
+
out,
|
| 1309 |
+
dout,
|
| 1310 |
+
lse,
|
| 1311 |
+
ctx.softmax_scale,
|
| 1312 |
+
ctx.causal,
|
| 1313 |
+
ctx.softcap,
|
| 1314 |
+
window_size_left=ctx.window_size[0],
|
| 1315 |
+
window_size_right=ctx.window_size[1],
|
| 1316 |
+
deterministic=ctx.deterministic,
|
| 1317 |
+
)
|
| 1318 |
+
return dq, dk, dv, *((None,) * 20) # Extra Nones is fine
|
| 1319 |
+
|
| 1320 |
+
|
| 1321 |
+
class FlashAttnVarlenFunc(torch.autograd.Function):
|
| 1322 |
+
@staticmethod
|
| 1323 |
+
def forward(
|
| 1324 |
+
ctx,
|
| 1325 |
+
q: torch.Tensor,
|
| 1326 |
+
k: torch.Tensor,
|
| 1327 |
+
v: torch.Tensor,
|
| 1328 |
+
cu_seqlens_q: Optional[torch.Tensor],
|
| 1329 |
+
cu_seqlens_k: Optional[torch.Tensor],
|
| 1330 |
+
seqused_q: Optional[torch.Tensor] = None,
|
| 1331 |
+
seqused_k: Optional[torch.Tensor] = None,
|
| 1332 |
+
max_seqlen_q: Optional[int] = None,
|
| 1333 |
+
max_seqlen_k: Optional[int] = None,
|
| 1334 |
+
page_table: Optional[torch.Tensor] = None,
|
| 1335 |
+
softmax_scale: Optional[float] = None,
|
| 1336 |
+
causal: bool = False,
|
| 1337 |
+
window_size: Tuple[Optional[int], Optional[int]] = (None, None),
|
| 1338 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 1339 |
+
softcap: float = 0.0,
|
| 1340 |
+
num_splits: int = 1,
|
| 1341 |
+
pack_gqa: Optional[bool] = None,
|
| 1342 |
+
deterministic: bool = False,
|
| 1343 |
+
score_mod: Optional[Callable] = None,
|
| 1344 |
+
aux_tensors: Optional[list] = None,
|
| 1345 |
+
):
|
| 1346 |
+
out, lse = _flash_attn_fwd(
|
| 1347 |
+
q,
|
| 1348 |
+
k,
|
| 1349 |
+
v,
|
| 1350 |
+
cu_seqlens_q,
|
| 1351 |
+
cu_seqlens_k,
|
| 1352 |
+
seqused_q,
|
| 1353 |
+
seqused_k,
|
| 1354 |
+
max_seqlen_q=max_seqlen_q,
|
| 1355 |
+
max_seqlen_k=max_seqlen_k,
|
| 1356 |
+
page_table=page_table,
|
| 1357 |
+
softmax_scale=softmax_scale,
|
| 1358 |
+
causal=causal,
|
| 1359 |
+
window_size_left=window_size[0],
|
| 1360 |
+
window_size_right=window_size[1],
|
| 1361 |
+
learnable_sink=learnable_sink,
|
| 1362 |
+
softcap=softcap,
|
| 1363 |
+
num_splits=num_splits,
|
| 1364 |
+
pack_gqa=pack_gqa,
|
| 1365 |
+
score_mod=score_mod,
|
| 1366 |
+
aux_tensors=aux_tensors,
|
| 1367 |
+
)
|
| 1368 |
+
ctx.save_for_backward(q, k, v, out, lse, cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k)
|
| 1369 |
+
ctx.softmax_scale = softmax_scale
|
| 1370 |
+
ctx.causal = causal
|
| 1371 |
+
ctx.window_size = window_size
|
| 1372 |
+
ctx.softcap = softcap
|
| 1373 |
+
ctx.deterministic = deterministic
|
| 1374 |
+
ctx.max_seqlen_q = max_seqlen_q
|
| 1375 |
+
ctx.max_seqlen_k = max_seqlen_k
|
| 1376 |
+
return out, lse
|
| 1377 |
+
|
| 1378 |
+
@staticmethod
|
| 1379 |
+
def backward(ctx, dout, *args):
|
| 1380 |
+
q, k, v, out, lse, cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k = ctx.saved_tensors
|
| 1381 |
+
assert ctx.softcap == 0.0
|
| 1382 |
+
dq, dk, dv = _flash_attn_bwd(
|
| 1383 |
+
q,
|
| 1384 |
+
k,
|
| 1385 |
+
v,
|
| 1386 |
+
out,
|
| 1387 |
+
dout,
|
| 1388 |
+
lse,
|
| 1389 |
+
ctx.softmax_scale,
|
| 1390 |
+
ctx.causal,
|
| 1391 |
+
ctx.softcap,
|
| 1392 |
+
window_size_left=ctx.window_size[0],
|
| 1393 |
+
window_size_right=ctx.window_size[1],
|
| 1394 |
+
cu_seqlens_q=cu_seqlens_q,
|
| 1395 |
+
cu_seqlens_k=cu_seqlens_k,
|
| 1396 |
+
seqused_q=seqused_q,
|
| 1397 |
+
seqused_k=seqused_k,
|
| 1398 |
+
max_seqlen_q=ctx.max_seqlen_q,
|
| 1399 |
+
max_seqlen_k=ctx.max_seqlen_k,
|
| 1400 |
+
deterministic=ctx.deterministic,
|
| 1401 |
+
)
|
| 1402 |
+
|
| 1403 |
+
return dq, dk, dv, *((None,) * 20)
|
| 1404 |
+
|
| 1405 |
+
|
| 1406 |
+
def flash_attn_func(
|
| 1407 |
+
q: torch.Tensor,
|
| 1408 |
+
k: torch.Tensor,
|
| 1409 |
+
v: torch.Tensor,
|
| 1410 |
+
softmax_scale: Optional[float] = None,
|
| 1411 |
+
causal: bool = False,
|
| 1412 |
+
window_size: Tuple[Optional[int], Optional[int]] = (None, None),
|
| 1413 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 1414 |
+
softcap: float = 0.0,
|
| 1415 |
+
num_splits: int = 1,
|
| 1416 |
+
pack_gqa: Optional[bool] = None,
|
| 1417 |
+
deterministic: bool = False,
|
| 1418 |
+
mask_mod: Optional[Callable] = None,
|
| 1419 |
+
full_block_cnt: Optional[torch.Tensor] = None,
|
| 1420 |
+
full_block_idx: Optional[torch.Tensor] = None,
|
| 1421 |
+
mask_block_cnt: Optional[torch.Tensor] = None,
|
| 1422 |
+
mask_block_idx: Optional[torch.Tensor] = None,
|
| 1423 |
+
block_size: Optional[Tuple[int, int]] = None,
|
| 1424 |
+
):
|
| 1425 |
+
return FlashAttnFunc.apply(
|
| 1426 |
+
q,
|
| 1427 |
+
k,
|
| 1428 |
+
v,
|
| 1429 |
+
softmax_scale,
|
| 1430 |
+
causal,
|
| 1431 |
+
window_size,
|
| 1432 |
+
learnable_sink,
|
| 1433 |
+
softcap,
|
| 1434 |
+
num_splits,
|
| 1435 |
+
pack_gqa,
|
| 1436 |
+
deterministic,
|
| 1437 |
+
mask_mod,
|
| 1438 |
+
full_block_cnt,
|
| 1439 |
+
full_block_idx,
|
| 1440 |
+
mask_block_cnt,
|
| 1441 |
+
mask_block_idx,
|
| 1442 |
+
block_size,
|
| 1443 |
+
)
|
| 1444 |
+
|
| 1445 |
+
|
| 1446 |
+
def flash_attn_varlen_func(
|
| 1447 |
+
q: torch.Tensor,
|
| 1448 |
+
k: torch.Tensor,
|
| 1449 |
+
v: torch.Tensor,
|
| 1450 |
+
cu_seqlens_q: Optional[torch.Tensor] = None,
|
| 1451 |
+
cu_seqlens_k: Optional[torch.Tensor] = None,
|
| 1452 |
+
max_seqlen_q: Optional[int] = None,
|
| 1453 |
+
max_seqlen_k: Optional[int] = None,
|
| 1454 |
+
seqused_q: Optional[torch.Tensor] = None,
|
| 1455 |
+
seqused_k: Optional[torch.Tensor] = None,
|
| 1456 |
+
page_table: Optional[torch.Tensor] = None,
|
| 1457 |
+
softmax_scale: Optional[float] = None,
|
| 1458 |
+
causal: bool = False,
|
| 1459 |
+
window_size: Tuple[Optional[int], Optional[int]] = (None, None),
|
| 1460 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 1461 |
+
softcap: float = 0.0,
|
| 1462 |
+
num_splits: int = 1,
|
| 1463 |
+
pack_gqa: Optional[bool] = None,
|
| 1464 |
+
deterministic: bool = False,
|
| 1465 |
+
score_mod: Optional[Callable] = None,
|
| 1466 |
+
aux_tensors: Optional[list] = None,
|
| 1467 |
+
):
|
| 1468 |
+
return FlashAttnVarlenFunc.apply(
|
| 1469 |
+
q,
|
| 1470 |
+
k,
|
| 1471 |
+
v,
|
| 1472 |
+
cu_seqlens_q,
|
| 1473 |
+
cu_seqlens_k,
|
| 1474 |
+
seqused_q,
|
| 1475 |
+
seqused_k,
|
| 1476 |
+
max_seqlen_q,
|
| 1477 |
+
max_seqlen_k,
|
| 1478 |
+
page_table,
|
| 1479 |
+
softmax_scale,
|
| 1480 |
+
causal,
|
| 1481 |
+
window_size,
|
| 1482 |
+
learnable_sink,
|
| 1483 |
+
softcap,
|
| 1484 |
+
num_splits,
|
| 1485 |
+
pack_gqa,
|
| 1486 |
+
deterministic,
|
| 1487 |
+
score_mod,
|
| 1488 |
+
aux_tensors,
|
| 1489 |
+
)
|
| 1490 |
+
|
| 1491 |
+
|
| 1492 |
+
def _flash_attn_fwd_combine(
|
| 1493 |
+
out_partial: torch.Tensor,
|
| 1494 |
+
lse_partial: torch.Tensor,
|
| 1495 |
+
out: torch.Tensor,
|
| 1496 |
+
lse: Optional[torch.Tensor] = None,
|
| 1497 |
+
cu_seqlens: Optional[torch.Tensor] = None,
|
| 1498 |
+
seqused: Optional[torch.Tensor] = None,
|
| 1499 |
+
num_splits_dynamic_ptr: Optional[torch.Tensor] = None,
|
| 1500 |
+
semaphore_to_reset: Optional[torch.Tensor] = None,
|
| 1501 |
+
) -> None:
|
| 1502 |
+
"""Forward combine kernel for split attention computation.
|
| 1503 |
+
|
| 1504 |
+
Combines partial outputs and log-sum-exp values from multiple splits
|
| 1505 |
+
of attention computation into final outputs.
|
| 1506 |
+
|
| 1507 |
+
Args:
|
| 1508 |
+
out_partial: Partial outputs tensor (num_splits, batch, seqlen, nheads, headdim) or
|
| 1509 |
+
(num_splits, total_q, nheads, headdim) if there's cu_seqlens
|
| 1510 |
+
lse_partial: Partial LSE tensor (num_splits, batch, seqlen, nheads) or
|
| 1511 |
+
(num_splits, total_q, nheads) if there's cu_seqlens
|
| 1512 |
+
out: Output tensor (batch, seqlen, nheads, headdim) or (total_q, nheads, headdim) if there's cu_seqlens
|
| 1513 |
+
lse: Output LSE tensor (batch, seqlen, nheads) or (total_q, nheads) if there's cu_seqlens.
|
| 1514 |
+
cu_seqlens: Cumulative sequence lengths for variable length sequences
|
| 1515 |
+
seqused: Used sequence lengths for each batch
|
| 1516 |
+
num_splits_dynamic_ptr: Dynamic number of splits per batch
|
| 1517 |
+
semaphore_to_reset: Semaphore for synchronization
|
| 1518 |
+
k_block_size: Block size for head dimension
|
| 1519 |
+
|
| 1520 |
+
Returns:
|
| 1521 |
+
None
|
| 1522 |
+
"""
|
| 1523 |
+
# Input validation
|
| 1524 |
+
assert out_partial.dim() in [4, 5], "out_partial must have 4 or 5 dimensions"
|
| 1525 |
+
assert lse_partial.dim() in [3, 4], "lse_partial must have 3 or 4 dimensions"
|
| 1526 |
+
assert out_partial.dtype in [torch.float16, torch.bfloat16, torch.float32], (
|
| 1527 |
+
"out_partial must be fp16, bf16, or fp32"
|
| 1528 |
+
)
|
| 1529 |
+
assert lse_partial.dtype == torch.float32, "lse_partial must be fp32"
|
| 1530 |
+
assert out_partial.is_cuda and lse_partial.is_cuda, "tensors must be on CUDA device"
|
| 1531 |
+
assert out_partial.stride(-1) == 1, "out_partial must be contiguous in the last dimension"
|
| 1532 |
+
assert lse_partial.stride(-2) == 1, "lse_partial must be contiguous in the seqlen dimension"
|
| 1533 |
+
assert lse_partial.shape == out_partial.shape[:-1]
|
| 1534 |
+
|
| 1535 |
+
# Determine if this is variable length based on dimensions
|
| 1536 |
+
is_varlen = out_partial.dim() == 4
|
| 1537 |
+
|
| 1538 |
+
# Validate output tensor shapes and types
|
| 1539 |
+
assert out.shape == out_partial.shape[1:], "out shape mismatch"
|
| 1540 |
+
if lse is not None:
|
| 1541 |
+
assert lse.shape == lse_partial.shape[1:], "lse shape mismatch"
|
| 1542 |
+
assert lse.dtype == torch.float32, "lse must be fp32"
|
| 1543 |
+
|
| 1544 |
+
# Validate optional tensors
|
| 1545 |
+
for t, name in [
|
| 1546 |
+
(cu_seqlens, "cu_seqlens"),
|
| 1547 |
+
(seqused, "seqused"),
|
| 1548 |
+
(num_splits_dynamic_ptr, "num_splits_dynamic_ptr"),
|
| 1549 |
+
]:
|
| 1550 |
+
if t is not None:
|
| 1551 |
+
assert t.dtype == torch.int32, f"{name} must be int32"
|
| 1552 |
+
assert t.is_cuda, f"{name} must be on CUDA device"
|
| 1553 |
+
assert t.is_contiguous(), f"{name} must be contiguous"
|
| 1554 |
+
|
| 1555 |
+
head_dim = out_partial.shape[-1]
|
| 1556 |
+
num_splits = out_partial.shape[0]
|
| 1557 |
+
assert num_splits <= 256
|
| 1558 |
+
# If hdim is 96 or 192, it's faster to round them to 128 or 256 respectively
|
| 1559 |
+
# so that kBlockM is smaller and we have more parallelism.
|
| 1560 |
+
k_block_size = 64 if head_dim <= 64 else 128
|
| 1561 |
+
# We want kBlockM to be as small as possible to maximize parallelism.
|
| 1562 |
+
# E.g., if hdim is 64, we want kBlockM to be 16 so that we can use 256 threads, each reading 4 elements (floats).
|
| 1563 |
+
m_block_size = 8 if k_block_size % 128 == 0 else (16 if k_block_size % 64 == 0 else 32)
|
| 1564 |
+
log_max_splits = max(math.ceil(math.log2(num_splits)), 4)
|
| 1565 |
+
if m_block_size == 8:
|
| 1566 |
+
# If kBlockM == 8 then the minimum number of splits is 32.
|
| 1567 |
+
# TODO: we can deal w this by using 128 threads instead
|
| 1568 |
+
log_max_splits = max(log_max_splits, 5)
|
| 1569 |
+
|
| 1570 |
+
current_stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
|
| 1571 |
+
|
| 1572 |
+
# Create combine kernel configuration
|
| 1573 |
+
dtype = torch2cute_dtype_map[out.dtype]
|
| 1574 |
+
dtype_partial = torch2cute_dtype_map[out_partial.dtype]
|
| 1575 |
+
|
| 1576 |
+
compile_key = (
|
| 1577 |
+
dtype,
|
| 1578 |
+
dtype_partial,
|
| 1579 |
+
head_dim,
|
| 1580 |
+
m_block_size,
|
| 1581 |
+
k_block_size,
|
| 1582 |
+
log_max_splits,
|
| 1583 |
+
cu_seqlens is not None,
|
| 1584 |
+
seqused is not None,
|
| 1585 |
+
lse is not None,
|
| 1586 |
+
)
|
| 1587 |
+
|
| 1588 |
+
if compile_key not in _flash_attn_fwd_combine.compile_cache:
|
| 1589 |
+
out_partial_tensor = to_cute_tensor(
|
| 1590 |
+
out_partial, leading_dim=4 if not is_varlen else 3
|
| 1591 |
+
)
|
| 1592 |
+
lse_partial_tensor = to_cute_tensor(
|
| 1593 |
+
lse_partial, assumed_align=4, leading_dim=lse_partial.ndim - 2
|
| 1594 |
+
)
|
| 1595 |
+
out_tensor = to_cute_tensor(out, leading_dim=3 if not is_varlen else 2)
|
| 1596 |
+
lse_tensor = (
|
| 1597 |
+
to_cute_tensor(lse, assumed_align=4, leading_dim=lse.ndim - 2)
|
| 1598 |
+
if lse is not None
|
| 1599 |
+
else None
|
| 1600 |
+
)
|
| 1601 |
+
|
| 1602 |
+
optional_tensors = [
|
| 1603 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=0)
|
| 1604 |
+
if t is not None
|
| 1605 |
+
else None
|
| 1606 |
+
for t in (cu_seqlens, seqused, num_splits_dynamic_ptr, semaphore_to_reset)
|
| 1607 |
+
]
|
| 1608 |
+
cu_seqlens_tensor, seqused_tensor, num_splits_dynamic_tensor, semaphore_tensor = (
|
| 1609 |
+
optional_tensors
|
| 1610 |
+
)
|
| 1611 |
+
fa_combine = FlashAttentionForwardCombine(
|
| 1612 |
+
dtype=dtype,
|
| 1613 |
+
dtype_partial=dtype_partial,
|
| 1614 |
+
head_dim=head_dim,
|
| 1615 |
+
m_block_size=m_block_size,
|
| 1616 |
+
k_block_size=k_block_size,
|
| 1617 |
+
log_max_splits=log_max_splits,
|
| 1618 |
+
)
|
| 1619 |
+
|
| 1620 |
+
# Check if implementation is supported
|
| 1621 |
+
if not fa_combine.can_implement(
|
| 1622 |
+
dtype,
|
| 1623 |
+
dtype_partial,
|
| 1624 |
+
head_dim,
|
| 1625 |
+
m_block_size,
|
| 1626 |
+
k_block_size,
|
| 1627 |
+
log_max_splits,
|
| 1628 |
+
num_threads=256,
|
| 1629 |
+
):
|
| 1630 |
+
raise RuntimeError(
|
| 1631 |
+
"FlashAttention combine kernel cannot be implemented with given parameters"
|
| 1632 |
+
)
|
| 1633 |
+
|
| 1634 |
+
_flash_attn_fwd_combine.compile_cache[compile_key] = cute.compile(
|
| 1635 |
+
fa_combine,
|
| 1636 |
+
out_partial_tensor,
|
| 1637 |
+
lse_partial_tensor,
|
| 1638 |
+
out_tensor,
|
| 1639 |
+
lse_tensor,
|
| 1640 |
+
cu_seqlens_tensor,
|
| 1641 |
+
seqused_tensor,
|
| 1642 |
+
num_splits_dynamic_tensor,
|
| 1643 |
+
semaphore_tensor,
|
| 1644 |
+
current_stream,
|
| 1645 |
+
options="--enable-tvm-ffi",
|
| 1646 |
+
)
|
| 1647 |
+
_flash_attn_fwd_combine.compile_cache[compile_key](
|
| 1648 |
+
out_partial,
|
| 1649 |
+
lse_partial,
|
| 1650 |
+
out,
|
| 1651 |
+
lse,
|
| 1652 |
+
cu_seqlens,
|
| 1653 |
+
seqused,
|
| 1654 |
+
num_splits_dynamic_ptr,
|
| 1655 |
+
semaphore_to_reset,
|
| 1656 |
+
current_stream,
|
| 1657 |
+
)
|
| 1658 |
+
|
| 1659 |
+
|
| 1660 |
+
_flash_attn_fwd_combine.compile_cache = {}
|
| 1661 |
+
|
| 1662 |
+
|
| 1663 |
+
def flash_attn_combine(
|
| 1664 |
+
out_partial: torch.Tensor,
|
| 1665 |
+
lse_partial: torch.Tensor,
|
| 1666 |
+
out: Optional[torch.Tensor] = None,
|
| 1667 |
+
out_dtype: Optional[torch.dtype] = None,
|
| 1668 |
+
cu_seqlens: Optional[torch.Tensor] = None,
|
| 1669 |
+
seqused: Optional[torch.Tensor] = None,
|
| 1670 |
+
return_lse: bool = True,
|
| 1671 |
+
) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 1672 |
+
"""Flash Attention combine function for split attention computation.
|
| 1673 |
+
|
| 1674 |
+
Combines partial outputs and log-sum-exp values from multiple splits
|
| 1675 |
+
of attention computation into final outputs. This is the main user-facing
|
| 1676 |
+
interface for the combine kernel.
|
| 1677 |
+
|
| 1678 |
+
Args:
|
| 1679 |
+
out_partial: Partial outputs tensor with shape:
|
| 1680 |
+
- (num_splits, batch_size, seqlen, num_heads, head_size) for regular batched input
|
| 1681 |
+
- (num_splits, total_q, num_heads, head_size) for variable length input
|
| 1682 |
+
lse_partial: Partial LSE tensor with shape:
|
| 1683 |
+
- (num_splits, batch_size, seqlen, num_heads) for regular batched input
|
| 1684 |
+
- (num_splits, total_q, num_heads) for variable length input
|
| 1685 |
+
out: Optional output tensor. If None, will be created automatically.
|
| 1686 |
+
out_dtype: Optional output dtype. If None, will use fp16/bf16 based on input.
|
| 1687 |
+
cu_seqlens: Cumulative sequence lengths for variable length sequences
|
| 1688 |
+
seqused: Used sequence lengths for each batch
|
| 1689 |
+
return_lse: Whether to return the combined LSE tensor. Default is True.
|
| 1690 |
+
|
| 1691 |
+
Returns:
|
| 1692 |
+
Tuple of (out, lse) where:
|
| 1693 |
+
- out: Combined output tensor with shape (batch_size, seqlen, num_heads, head_size)
|
| 1694 |
+
or (total_q, num_heads, head_size) for varlen
|
| 1695 |
+
- lse: Combined log-sum-exp tensor with shape (batch_size, seqlen, num_heads)
|
| 1696 |
+
or (total_q, num_heads) for varlen. None if return_lse=False
|
| 1697 |
+
|
| 1698 |
+
Note:
|
| 1699 |
+
This function expects the input tensors to be in the format produced by
|
| 1700 |
+
split attention computation, where the first dimension is num_splits.
|
| 1701 |
+
The permuting from user format to kernel format is now done inside the kernel.
|
| 1702 |
+
"""
|
| 1703 |
+
# Input validation
|
| 1704 |
+
assert out_partial.dim() in [4, 5], "out_partial must have 4 or 5 dimensions"
|
| 1705 |
+
assert lse_partial.dim() in [3, 4], "lse_partial must have 3 or 4 dimensions"
|
| 1706 |
+
assert out_partial.dtype == torch.float32, "out_partial must be fp32 (from accumulation)"
|
| 1707 |
+
assert lse_partial.dtype == torch.float32, "lse_partial must be fp32"
|
| 1708 |
+
|
| 1709 |
+
# Determine if this is variable length based on dimensions
|
| 1710 |
+
is_varlen = out_partial.dim() == 4
|
| 1711 |
+
|
| 1712 |
+
if is_varlen:
|
| 1713 |
+
# Variable length: (num_splits, total_q, num_heads, head_size)
|
| 1714 |
+
num_splits, total_q, num_heads, head_size = out_partial.shape
|
| 1715 |
+
assert lse_partial.shape == (num_splits, total_q, num_heads), (
|
| 1716 |
+
"lse_partial shape mismatch for varlen"
|
| 1717 |
+
)
|
| 1718 |
+
batch_size = 1 # Treat as single batch for varlen
|
| 1719 |
+
seqlen = total_q
|
| 1720 |
+
else:
|
| 1721 |
+
# Regular batched: (num_splits, batch_size, seqlen, num_heads, head_size)
|
| 1722 |
+
num_splits, batch_size, seqlen, num_heads, head_size = out_partial.shape
|
| 1723 |
+
assert lse_partial.shape == (num_splits, batch_size, seqlen, num_heads), (
|
| 1724 |
+
"lse_partial shape mismatch"
|
| 1725 |
+
)
|
| 1726 |
+
|
| 1727 |
+
# Determine output dtype
|
| 1728 |
+
if out_dtype is None:
|
| 1729 |
+
out_dtype = out_partial.dtype
|
| 1730 |
+
|
| 1731 |
+
# Create output if not provided
|
| 1732 |
+
device = out_partial.device
|
| 1733 |
+
if out is None:
|
| 1734 |
+
if is_varlen:
|
| 1735 |
+
out = torch.empty(total_q, num_heads, head_size, dtype=out_dtype, device=device)
|
| 1736 |
+
else:
|
| 1737 |
+
out = torch.empty(
|
| 1738 |
+
batch_size, seqlen, num_heads, head_size, dtype=out_dtype, device=device
|
| 1739 |
+
)
|
| 1740 |
+
|
| 1741 |
+
# Create lse output only if requested
|
| 1742 |
+
if return_lse:
|
| 1743 |
+
if is_varlen:
|
| 1744 |
+
lse = torch.empty(num_heads, total_q, dtype=torch.float32, device=device).transpose(
|
| 1745 |
+
0, 1
|
| 1746 |
+
)
|
| 1747 |
+
else:
|
| 1748 |
+
lse = torch.empty(
|
| 1749 |
+
batch_size, num_heads, seqlen, dtype=torch.float32, device=device
|
| 1750 |
+
).transpose(1, 2)
|
| 1751 |
+
else:
|
| 1752 |
+
lse = None
|
| 1753 |
+
|
| 1754 |
+
_flash_attn_fwd_combine(
|
| 1755 |
+
out_partial,
|
| 1756 |
+
lse_partial,
|
| 1757 |
+
out,
|
| 1758 |
+
lse,
|
| 1759 |
+
cu_seqlens,
|
| 1760 |
+
seqused,
|
| 1761 |
+
)
|
| 1762 |
+
return out, lse
|
kernel/deps/flash_attn/cute/mask.py
ADDED
|
@@ -0,0 +1,651 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
from typing import Optional, Callable
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
|
| 6 |
+
import cutlass
|
| 7 |
+
import cutlass.cute as cute
|
| 8 |
+
from cutlass import Float32, Int32, const_expr
|
| 9 |
+
|
| 10 |
+
import flash_attn.cute.utils as utils
|
| 11 |
+
from flash_attn.cute.seqlen_info import SeqlenInfoQK
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@cute.jit
|
| 15 |
+
def mask_r2p(X: cute.Tensor, col_limit: Int32, arch: int = 90, rank1: bool = False) -> None:
|
| 16 |
+
# Bit manipulation, compiles down to the R2P instruction
|
| 17 |
+
# For sm100: we know that tScS_t2r[i][1] == i, for the particular tmem copy atom we're using.
|
| 18 |
+
# For sm90: instead of comparing limit to 0, 1, 8, 9, 16, 17, ...,
|
| 19 |
+
# we compare a transformed version of limit to 0, 1, 2, 3, 4, 5, ...
|
| 20 |
+
if const_expr(arch == 90):
|
| 21 |
+
col_limit_transformed = col_limit // 8 * 2 + min(col_limit % 8, 2)
|
| 22 |
+
else:
|
| 23 |
+
col_limit_transformed = col_limit
|
| 24 |
+
ncol = const_expr(cute.size(X.shape[cute.rank(X) - 1]) if not rank1 else cute.size(X.shape))
|
| 25 |
+
# Ideally we'd move by 32 instead of 24, but mask >> i isn't correct for i == 31
|
| 26 |
+
for s in cutlass.range_constexpr(cute.ceil_div(ncol, 24)):
|
| 27 |
+
# Don't need to clamp to 32 since the shr.u32 instruction does that already
|
| 28 |
+
col_limit_right_s = max(col_limit_transformed - s * 24, 0)
|
| 29 |
+
# 0 -> 0b00...00, 1 -> 0b00...01, ..., 31 -> 0b01...11, 32 -> 0b11...11
|
| 30 |
+
mask = (1 << col_limit_right_s) - 1
|
| 31 |
+
# This needs to be range_constexpr, o/w the compiler can't generate the R2P instruction
|
| 32 |
+
for i in cutlass.range_constexpr(min(24, ncol - s * 24)):
|
| 33 |
+
in_bound = cutlass.Boolean(mask & (1 << i))
|
| 34 |
+
c = s * 24 + i
|
| 35 |
+
if const_expr(rank1):
|
| 36 |
+
X[c] = X[c] if in_bound else -Float32.inf
|
| 37 |
+
# This is the equivalent of:
|
| 38 |
+
# X[s * 24 + i] = X[s * 24 + i] if col_limit_right_s <= i else -Float32.inf
|
| 39 |
+
else:
|
| 40 |
+
for r in cutlass.range_constexpr(cute.size(X.shape[0])):
|
| 41 |
+
X[r, c] = X[r, c] if in_bound else -Float32.inf
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@cute.jit
|
| 45 |
+
def mask_r2p_transposed(X: cute.Tensor, row_limit_top: Int32, num_rep: int) -> None:
|
| 46 |
+
# Bit manipulation, compiles down to the R2P instruction
|
| 47 |
+
# For sm100: we know that tScS_t2r[i][0] has the form 0, 1, ..., 31, 64, ..., 127
|
| 48 |
+
# or 0, 1, ..., 15, 32, ..., 47, 64, ...
|
| 49 |
+
# We compare a transformed version of limit to 0, 1, 2, 3, 4, 5, ...
|
| 50 |
+
# Here we hardcode for the case of 2 warp groups.
|
| 51 |
+
num_wg = 2
|
| 52 |
+
row_limit_top_transformed = row_limit_top // (num_rep * num_wg) * num_rep + min(
|
| 53 |
+
row_limit_top % (num_rep * num_wg), num_rep
|
| 54 |
+
)
|
| 55 |
+
ncol = cute.size(X.shape)
|
| 56 |
+
# Ideally we'd move by 32 instead of 24, but mask >> i isn't correct for i == 31
|
| 57 |
+
for s in cutlass.range_constexpr(cute.ceil_div(ncol, 24)):
|
| 58 |
+
row_limit_top_s = max(row_limit_top_transformed - s * 24, 0)
|
| 59 |
+
# 0 -> 0b00...00, 1 -> 0b00...01, ..., 31 -> 0b01...11, 32 -> 0b11...11
|
| 60 |
+
mask = (1 << row_limit_top_s) - 1
|
| 61 |
+
# This needs to be range_constexpr, o/w the compiler can't generate the R2P instruction
|
| 62 |
+
for i in cutlass.range_constexpr(min(24, ncol - s * 24)):
|
| 63 |
+
out_bound = cutlass.Boolean(mask & (1 << i))
|
| 64 |
+
c = s * 24 + i
|
| 65 |
+
X[c] = -Float32.inf if out_bound else X[c]
|
| 66 |
+
# tidx = cute.arch.thread_idx()[0] % 256
|
| 67 |
+
# if tidx == 128:
|
| 68 |
+
# cute.printf("tidx = {}, s = {}, i = {}, row_limit_top = {}, row_limit_top_s = {}, mask = {}, out_bound = {}", tidx, s, i, row_limit_top, row_limit_top_s, mask, out_bound)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@cute.jit
|
| 72 |
+
def mask_r2p_dual_bound(
|
| 73 |
+
X: cute.Tensor,
|
| 74 |
+
col_limit_left: Int32, # Inclusive lower bound
|
| 75 |
+
col_limit_right: Int32, # Exclusive upper bound
|
| 76 |
+
) -> None:
|
| 77 |
+
"""
|
| 78 |
+
Dual-bound masking using two bitmasks for SM100, following mask_r2p.
|
| 79 |
+
Masks elements where: NOT (col_limit_left <= col < col_limit_right)
|
| 80 |
+
|
| 81 |
+
Uses bit manipulation to create a range mask:
|
| 82 |
+
mask_right = (1 << right) - 1 -> bits (right-1)..0 are 1
|
| 83 |
+
mask_left = (1 << left) - 1 -> bits (left-1)..0 are 1
|
| 84 |
+
mask_range = mask_range = mask_right & ~ mask_left -> bits (right-1)..left are 1
|
| 85 |
+
"""
|
| 86 |
+
ncol = const_expr(cute.size(X.shape))
|
| 87 |
+
|
| 88 |
+
for s in cutlass.range_constexpr(cute.ceil_div(ncol, 24)):
|
| 89 |
+
right_s = max(col_limit_right - s * 24, 0)
|
| 90 |
+
left_s = max(col_limit_left - s * 24, 0)
|
| 91 |
+
|
| 92 |
+
# otherwise cute dsl complains about python int too large to convert into c long
|
| 93 |
+
right_s = min(right_s, 24)
|
| 94 |
+
left_s = min(left_s, 24)
|
| 95 |
+
|
| 96 |
+
# bits (right-1)..left are 1
|
| 97 |
+
mask_right = (1 << right_s) - 1
|
| 98 |
+
mask_left = (1 << left_s) - 1
|
| 99 |
+
mask_range = mask_right & ~mask_left
|
| 100 |
+
|
| 101 |
+
# This needs to be range_constexpr, o/w the compiler can't generate the R2P instruction
|
| 102 |
+
for i in cutlass.range_constexpr(min(24, ncol - s * 24)):
|
| 103 |
+
in_bound = cutlass.Boolean(mask_range & (1 << i))
|
| 104 |
+
c = s * 24 + i
|
| 105 |
+
X[c] = X[c] if in_bound else -Float32.inf
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
@dataclass(frozen=True)
|
| 109 |
+
class AttentionMask:
|
| 110 |
+
tile_m: cutlass.Constexpr[int]
|
| 111 |
+
tile_n: cutlass.Constexpr[int]
|
| 112 |
+
seqlen_info: SeqlenInfoQK
|
| 113 |
+
window_size_left: Optional[Int32] = None
|
| 114 |
+
window_size_right: Optional[Int32] = None
|
| 115 |
+
qhead_per_kvhead_packgqa: cutlass.Constexpr[int] = 1 # only pass in if we're doing PackGQA
|
| 116 |
+
swap_AB: cutlass.Constexpr[bool] = False
|
| 117 |
+
|
| 118 |
+
@property
|
| 119 |
+
def seqlen_q(self) -> Int32:
|
| 120 |
+
return self.seqlen_info.seqlen_q
|
| 121 |
+
|
| 122 |
+
@property
|
| 123 |
+
def seqlen_k(self) -> Int32:
|
| 124 |
+
return self.seqlen_info.seqlen_k
|
| 125 |
+
|
| 126 |
+
@cute.jit
|
| 127 |
+
def apply_mask(
|
| 128 |
+
self,
|
| 129 |
+
acc_S: cute.Tensor,
|
| 130 |
+
batch_idx: cutlass.Int32,
|
| 131 |
+
head_idx: cutlass.Int32,
|
| 132 |
+
m_block: cutlass.Int32,
|
| 133 |
+
n_block: cutlass.Int32,
|
| 134 |
+
thr_mma: cute.TiledMma,
|
| 135 |
+
mask_seqlen: cutlass.Constexpr[bool],
|
| 136 |
+
mask_causal: cutlass.Constexpr[bool],
|
| 137 |
+
mask_local: cutlass.Constexpr[bool] = False,
|
| 138 |
+
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 139 |
+
aux_tensors: Optional[list] = None,
|
| 140 |
+
fastdiv_mods=(None, None),
|
| 141 |
+
) -> None:
|
| 142 |
+
assert not (mask_causal and mask_local), "mask_causal and mask_local cannot be both True"
|
| 143 |
+
acc_S_mn = utils.make_acc_tensor_mn_view(acc_S, transpose=self.swap_AB)
|
| 144 |
+
acc_shape = (self.tile_m, self.tile_n)
|
| 145 |
+
cS = cute.make_identity_tensor(acc_shape if not self.swap_AB else acc_shape[::-1])
|
| 146 |
+
tScS_mn = utils.make_acc_tensor_mn_view(thr_mma.partition_C(cS), transpose=self.swap_AB)
|
| 147 |
+
# We use t0ScS as these indices are known at compile time. We then must subtract the
|
| 148 |
+
# column limit by the thread column offset.
|
| 149 |
+
t0ScS_mn = utils.make_acc_tensor_mn_view(
|
| 150 |
+
thr_mma.get_slice(0).partition_C(cS), transpose=self.swap_AB
|
| 151 |
+
)
|
| 152 |
+
ROW = 0 if const_expr(not self.swap_AB) else 1
|
| 153 |
+
COL = 1 if const_expr(not self.swap_AB) else 0
|
| 154 |
+
thr_col_offset = tScS_mn[0][COL]
|
| 155 |
+
# To handle edge cases of completely masked out rows where n_block_max = 0,
|
| 156 |
+
# we treat negative n_blocks as 0th n_block
|
| 157 |
+
# TODO: find more transparent solution
|
| 158 |
+
if n_block < 0:
|
| 159 |
+
n_block = 0
|
| 160 |
+
seqlenk_col_limit = self.seqlen_k - n_block * self.tile_n - thr_col_offset
|
| 161 |
+
if const_expr(not mask_causal and not mask_local and mask_mod is None):
|
| 162 |
+
if const_expr(mask_seqlen):
|
| 163 |
+
# The compiler now choses not to use R2P
|
| 164 |
+
r2p = const_expr(False and not self.swap_AB)
|
| 165 |
+
if const_expr(not r2p):
|
| 166 |
+
# traverse column index.
|
| 167 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 168 |
+
oob = t0ScS_mn[0, c][COL] >= seqlenk_col_limit
|
| 169 |
+
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 170 |
+
acc_S_mn[r, c] = -Float32.inf if oob else acc_S_mn[r, c]
|
| 171 |
+
else:
|
| 172 |
+
mask_r2p(acc_S_mn, seqlenk_col_limit, arch=90)
|
| 173 |
+
|
| 174 |
+
elif const_expr(
|
| 175 |
+
not mask_causal and not mask_local and mask_mod is not None
|
| 176 |
+
): # FlexAttention mask mod
|
| 177 |
+
nrow = const_expr(cute.size(tScS_mn.shape[0]))
|
| 178 |
+
ncol = const_expr(cute.size(tScS_mn.shape[1]))
|
| 179 |
+
has_fastdiv = const_expr(
|
| 180 |
+
fastdiv_mods is not None
|
| 181 |
+
and fastdiv_mods[0] is not None
|
| 182 |
+
and fastdiv_mods[1] is not None
|
| 183 |
+
)
|
| 184 |
+
wrap_aux_indices = const_expr(
|
| 185 |
+
has_fastdiv and mask_seqlen and const_expr(aux_tensors is not None)
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
for r in cutlass.range_constexpr(nrow):
|
| 189 |
+
# Respect swap_AB: ROW/COL determine which coordinate component corresponds to Q/KV.
|
| 190 |
+
local_row = tScS_mn[r, 0][ROW]
|
| 191 |
+
global_row_idx = local_row + m_block * self.tile_m
|
| 192 |
+
row_for_mod = global_row_idx
|
| 193 |
+
head_idx_for_mod = head_idx
|
| 194 |
+
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 195 |
+
head_offset = global_row_idx % self.qhead_per_kvhead_packgqa
|
| 196 |
+
head_idx_for_mod = head_idx * self.qhead_per_kvhead_packgqa + head_offset
|
| 197 |
+
row_for_mod = global_row_idx // self.qhead_per_kvhead_packgqa
|
| 198 |
+
row_for_seqlen = row_for_mod
|
| 199 |
+
if const_expr(wrap_aux_indices):
|
| 200 |
+
_, row_for_mod = divmod(row_for_mod, fastdiv_mods[0])
|
| 201 |
+
|
| 202 |
+
for col in cutlass.range_constexpr(ncol):
|
| 203 |
+
col_idx_local = t0ScS_mn[0, col][COL]
|
| 204 |
+
# Convert to absolute column index
|
| 205 |
+
global_col_idx = thr_col_offset + col_idx_local + n_block * self.tile_n
|
| 206 |
+
col_for_mod = global_col_idx
|
| 207 |
+
if const_expr(wrap_aux_indices):
|
| 208 |
+
_, col_for_mod = divmod(global_col_idx, fastdiv_mods[1])
|
| 209 |
+
|
| 210 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32)
|
| 211 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx_for_mod, cutlass.Int32)
|
| 212 |
+
q_idx_ssa = utils.scalar_to_ssa(row_for_mod, cutlass.Int32)
|
| 213 |
+
kv_idx_ssa = utils.scalar_to_ssa(col_for_mod, cutlass.Int32)
|
| 214 |
+
mask_value = mask_mod(
|
| 215 |
+
batch_idx_ssa,
|
| 216 |
+
head_idx_ssa,
|
| 217 |
+
q_idx_ssa,
|
| 218 |
+
kv_idx_ssa,
|
| 219 |
+
self.seqlen_info,
|
| 220 |
+
aux_tensors,
|
| 221 |
+
)
|
| 222 |
+
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 223 |
+
if const_expr(mask_seqlen):
|
| 224 |
+
out_of_bounds = (row_for_seqlen >= self.seqlen_q) or (
|
| 225 |
+
global_col_idx >= self.seqlen_k
|
| 226 |
+
)
|
| 227 |
+
if out_of_bounds:
|
| 228 |
+
acc_S_mn[r, col] = -cutlass.Float32.inf
|
| 229 |
+
else:
|
| 230 |
+
acc_S_mn[r, col] = acc_S_mn[r, col] if cond else -cutlass.Float32.inf
|
| 231 |
+
else:
|
| 232 |
+
acc_S_mn[r, col] = acc_S_mn[r, col] if cond else -cutlass.Float32.inf
|
| 233 |
+
|
| 234 |
+
else: # Causal or local
|
| 235 |
+
if const_expr(not self.swap_AB):
|
| 236 |
+
# If PackGQA, we split the work of compute divmod among threads in the same row
|
| 237 |
+
threads_per_row = thr_mma.tv_layout_C.shape[0][0]
|
| 238 |
+
mma_m_idx = None
|
| 239 |
+
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 240 |
+
assert not self.swap_AB, "swap_AB with PackGQA not supported yet"
|
| 241 |
+
assert cute.arch.WARP_SIZE % threads_per_row == 0, (
|
| 242 |
+
"threads_per_row must divide WARP_SIZE"
|
| 243 |
+
)
|
| 244 |
+
assert cute.size(acc_S_mn.shape[0]) <= threads_per_row
|
| 245 |
+
tidx = thr_mma.thr_idx
|
| 246 |
+
mma_m_idx = (
|
| 247 |
+
m_block * self.tile_m + tScS_mn[tidx % threads_per_row, 0][0]
|
| 248 |
+
) // self.qhead_per_kvhead_packgqa
|
| 249 |
+
causal_row_offset = (
|
| 250 |
+
1 + self.seqlen_k - n_block * self.tile_n - self.seqlen_q - thr_col_offset
|
| 251 |
+
)
|
| 252 |
+
if const_expr(mask_causal):
|
| 253 |
+
r2p = const_expr(not self.swap_AB) # R2P trick, see apply_mask_sm100
|
| 254 |
+
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 255 |
+
# get the column index limit based on current row. Only consider the row index, so the column index sets to 0.
|
| 256 |
+
if const_expr(self.qhead_per_kvhead_packgqa == 1):
|
| 257 |
+
row_idx = tScS_mn[r, 0][0] + m_block * self.tile_m
|
| 258 |
+
else:
|
| 259 |
+
row_idx = utils.shuffle_sync(
|
| 260 |
+
mma_m_idx, r % threads_per_row, width=threads_per_row
|
| 261 |
+
)
|
| 262 |
+
col_limit_right = row_idx + causal_row_offset
|
| 263 |
+
if const_expr(mask_seqlen):
|
| 264 |
+
col_limit_right = cutlass.min(col_limit_right, seqlenk_col_limit)
|
| 265 |
+
if const_expr(not r2p):
|
| 266 |
+
# traverse column index.
|
| 267 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 268 |
+
acc_S_mn[r, c] = (
|
| 269 |
+
-Float32.inf
|
| 270 |
+
if t0ScS_mn[0, c][1] >= col_limit_right
|
| 271 |
+
else acc_S_mn[r, c]
|
| 272 |
+
)
|
| 273 |
+
else:
|
| 274 |
+
mask_r2p(acc_S_mn[r, None], col_limit_right, arch=90, rank1=True)
|
| 275 |
+
else: # Local
|
| 276 |
+
local_row_offset_right = (
|
| 277 |
+
causal_row_offset + self.window_size_right
|
| 278 |
+
if const_expr(self.window_size_right is not None)
|
| 279 |
+
else None
|
| 280 |
+
)
|
| 281 |
+
local_row_offset_left = (
|
| 282 |
+
causal_row_offset - 1 - self.window_size_left
|
| 283 |
+
if const_expr(self.window_size_left is not None)
|
| 284 |
+
else None
|
| 285 |
+
)
|
| 286 |
+
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 287 |
+
if const_expr(self.qhead_per_kvhead_packgqa == 1):
|
| 288 |
+
row_idx = tScS_mn[r, 0][0] + m_block * self.tile_m
|
| 289 |
+
else:
|
| 290 |
+
row_idx = utils.shuffle_sync(
|
| 291 |
+
mma_m_idx, r % threads_per_row, width=threads_per_row
|
| 292 |
+
)
|
| 293 |
+
if const_expr(self.window_size_right is not None):
|
| 294 |
+
col_limit_right = row_idx + local_row_offset_right
|
| 295 |
+
else:
|
| 296 |
+
col_limit_right = self.tile_n
|
| 297 |
+
if const_expr(mask_seqlen):
|
| 298 |
+
col_limit_right = cutlass.min(col_limit_right, seqlenk_col_limit)
|
| 299 |
+
col_limit_left = (
|
| 300 |
+
row_idx + local_row_offset_left
|
| 301 |
+
if const_expr(self.window_size_left is not None)
|
| 302 |
+
else 0
|
| 303 |
+
)
|
| 304 |
+
# if cute.arch.thread_idx()[0] == 128: cute.printf("n_block = {}, r = {}, row_idx = {}, causal_row_offset = {}, col_limit_right = {}, col_limit_left = {}", n_block, r, row_idx, causal_row_offset, col_limit_right, col_limit_left)
|
| 305 |
+
# traverse column index.
|
| 306 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 307 |
+
col_idx = t0ScS_mn[0, c][1]
|
| 308 |
+
# only consider the column index, so the row index sets to 0.
|
| 309 |
+
if col_idx >= col_limit_right or col_idx < col_limit_left:
|
| 310 |
+
acc_S_mn[r, c] = -Float32.inf
|
| 311 |
+
else: # swap_AB
|
| 312 |
+
assert self.qhead_per_kvhead_packgqa == 1
|
| 313 |
+
thr_row_offset = tScS_mn[0][ROW]
|
| 314 |
+
causal_row_offset = (
|
| 315 |
+
seqlenk_col_limit - self.seqlen_q + m_block * self.tile_m + thr_row_offset
|
| 316 |
+
)
|
| 317 |
+
if const_expr(mask_causal):
|
| 318 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 319 |
+
col0 = t0ScS_mn[0, c][COL]
|
| 320 |
+
# If col0 is beyond the column limit, we want to mask out the entire
|
| 321 |
+
# column, by setting row limit to be self.tile_m.
|
| 322 |
+
row_limit_top = (
|
| 323 |
+
self.tile_m
|
| 324 |
+
if col0 >= seqlenk_col_limit and mask_seqlen
|
| 325 |
+
else col0 - causal_row_offset
|
| 326 |
+
)
|
| 327 |
+
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 328 |
+
acc_S_mn[r, c] = (
|
| 329 |
+
-Float32.inf
|
| 330 |
+
if t0ScS_mn[r, 0][ROW] < row_limit_top
|
| 331 |
+
else acc_S_mn[r, c]
|
| 332 |
+
)
|
| 333 |
+
else:
|
| 334 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 335 |
+
col0 = t0ScS_mn[0, c][COL]
|
| 336 |
+
# If col0 is beyond the column limit, we want to mask out the entire
|
| 337 |
+
# column, by setting row limit to be self.tile_m.
|
| 338 |
+
row_limit_top = (
|
| 339 |
+
self.tile_m
|
| 340 |
+
if col0 >= seqlenk_col_limit
|
| 341 |
+
else col0 - causal_row_offset - self.window_size_right
|
| 342 |
+
)
|
| 343 |
+
# TODO: do we need col_limit_sink?
|
| 344 |
+
row_limit_bot = col0 - causal_row_offset + self.window_size_left
|
| 345 |
+
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 346 |
+
row_idx = t0ScS_mn[r, 0][ROW]
|
| 347 |
+
acc_S_mn[r, c] = (
|
| 348 |
+
-Float32.inf
|
| 349 |
+
if row_idx < row_limit_top or row_idx > row_limit_bot
|
| 350 |
+
else acc_S_mn[r, c]
|
| 351 |
+
)
|
| 352 |
+
|
| 353 |
+
@cute.jit
|
| 354 |
+
def apply_mask_sm100(
|
| 355 |
+
self,
|
| 356 |
+
acc_S: cute.Tensor,
|
| 357 |
+
m_block: Int32,
|
| 358 |
+
n_block: Int32,
|
| 359 |
+
thr_mma: cute.TiledMma,
|
| 360 |
+
thr_tmem_load: cute.TiledCopy,
|
| 361 |
+
mask_seqlen: cutlass.Constexpr[bool],
|
| 362 |
+
mask_causal: cutlass.Constexpr[bool],
|
| 363 |
+
mask_local: cutlass.Constexpr[bool] = False,
|
| 364 |
+
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 365 |
+
batch_idx: Int32 = None,
|
| 366 |
+
head_idx: Int32 = None,
|
| 367 |
+
aux_tensors: Optional[list] = None,
|
| 368 |
+
fastdiv_mods=(None, None),
|
| 369 |
+
head_divmod=None,
|
| 370 |
+
check_q_boundary: bool = False,
|
| 371 |
+
) -> None:
|
| 372 |
+
assert not (mask_causal and mask_local), "mask_causal and mask_local cannot be both True"
|
| 373 |
+
acc_shape = (self.tile_m, self.tile_n)
|
| 374 |
+
cS = cute.make_identity_tensor(acc_shape if not self.swap_AB else acc_shape[::-1])
|
| 375 |
+
tScS = thr_mma.partition_C(cS)
|
| 376 |
+
tScS_t2r = thr_tmem_load.partition_D(tScS)
|
| 377 |
+
# To handle edge cases of completely masked out rows where n_block_max = 0,
|
| 378 |
+
# we treat negative n_blocks as 0th n_block
|
| 379 |
+
# TODO: find more transparent solution
|
| 380 |
+
if n_block < 0:
|
| 381 |
+
n_block = 0
|
| 382 |
+
seqlenk_col_limit = self.seqlen_k - n_block * self.tile_n
|
| 383 |
+
r2p = True
|
| 384 |
+
if const_expr(not mask_causal and not mask_local and mask_mod is None):
|
| 385 |
+
if const_expr(mask_seqlen):
|
| 386 |
+
if const_expr(not r2p):
|
| 387 |
+
for i in cutlass.range(cute.size(tScS_t2r.shape), unroll_full=True):
|
| 388 |
+
# if tScS_t2r[i][1] >= seqlenk_col_limit:
|
| 389 |
+
# acc_S[i] = -Float32.inf
|
| 390 |
+
# For some reason the 2 lines above generate really bad SASS
|
| 391 |
+
acc_S[i] = -Float32.inf if tScS_t2r[i][1] >= seqlenk_col_limit else acc_S[i]
|
| 392 |
+
else:
|
| 393 |
+
mask_r2p(acc_S, seqlenk_col_limit, arch=100, rank1=True)
|
| 394 |
+
|
| 395 |
+
elif const_expr(not mask_causal and not mask_local and mask_mod is not None):
|
| 396 |
+
# Block sparse case w/ mask_mod
|
| 397 |
+
has_fastdiv = const_expr(
|
| 398 |
+
fastdiv_mods is not None
|
| 399 |
+
and fastdiv_mods[0] is not None
|
| 400 |
+
and fastdiv_mods[1] is not None
|
| 401 |
+
)
|
| 402 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32)
|
| 403 |
+
|
| 404 |
+
ncol = const_expr(cute.size(tScS_t2r.shape))
|
| 405 |
+
for i in cutlass.range_constexpr(ncol):
|
| 406 |
+
row_coord = tScS_t2r[i][0] if not self.swap_AB else tScS_t2r[i][1]
|
| 407 |
+
col_coord = tScS_t2r[i][1] if not self.swap_AB else tScS_t2r[i][0]
|
| 408 |
+
global_row = row_coord + m_block * self.tile_m
|
| 409 |
+
global_col = col_coord + n_block * self.tile_n
|
| 410 |
+
|
| 411 |
+
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 412 |
+
assert head_divmod is not None
|
| 413 |
+
mask_row, head_offset = divmod(global_row, head_divmod)
|
| 414 |
+
head_idx_for_mod = head_idx * self.qhead_per_kvhead_packgqa + head_offset
|
| 415 |
+
else:
|
| 416 |
+
head_idx_for_mod = head_idx
|
| 417 |
+
mask_row = global_row
|
| 418 |
+
|
| 419 |
+
mask_row_for_mod = mask_row
|
| 420 |
+
if const_expr(has_fastdiv and aux_tensors is not None):
|
| 421 |
+
if check_q_boundary:
|
| 422 |
+
_, mask_row_for_mod = divmod(mask_row, fastdiv_mods[0])
|
| 423 |
+
global_col_for_mod = global_col
|
| 424 |
+
if const_expr(has_fastdiv and mask_seqlen and aux_tensors is not None):
|
| 425 |
+
_, global_col_for_mod = divmod(global_col, fastdiv_mods[1])
|
| 426 |
+
|
| 427 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx_for_mod, cutlass.Int32)
|
| 428 |
+
mask_row_ssa = utils.scalar_to_ssa(mask_row_for_mod, cutlass.Int32)
|
| 429 |
+
kv_idx_ssa = utils.scalar_to_ssa(global_col_for_mod, cutlass.Int32)
|
| 430 |
+
mask_value = mask_mod(
|
| 431 |
+
batch_idx_ssa,
|
| 432 |
+
head_idx_ssa,
|
| 433 |
+
mask_row_ssa,
|
| 434 |
+
kv_idx_ssa,
|
| 435 |
+
self.seqlen_info,
|
| 436 |
+
aux_tensors,
|
| 437 |
+
)
|
| 438 |
+
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 439 |
+
acc_S[i] = acc_S[i] if cond else -Float32.inf
|
| 440 |
+
if const_expr(mask_seqlen):
|
| 441 |
+
acc_S[i] = -Float32.inf if global_col >= self.seqlen_k else acc_S[i]
|
| 442 |
+
if check_q_boundary:
|
| 443 |
+
acc_S[i] = -Float32.inf if mask_row >= self.seqlen_q else acc_S[i]
|
| 444 |
+
|
| 445 |
+
else: # Causal or local
|
| 446 |
+
causal_row_offset = 1 + self.seqlen_k - n_block * self.tile_n - self.seqlen_q
|
| 447 |
+
row_idx = tScS_t2r[0][0] + m_block * self.tile_m
|
| 448 |
+
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 449 |
+
row_idx = row_idx // self.qhead_per_kvhead_packgqa
|
| 450 |
+
if const_expr(mask_causal):
|
| 451 |
+
col_limit_right = row_idx + causal_row_offset
|
| 452 |
+
if const_expr(mask_seqlen):
|
| 453 |
+
col_limit_right = cutlass.min(col_limit_right, seqlenk_col_limit)
|
| 454 |
+
# if cute.arch.thread_idx()[0] % 32 == 0:
|
| 455 |
+
# cute.printf("tidx = %d, tidx tmem = %d, row_idx = %d, col_limit_right = %d, causal_row_offset = %d\n", cute.arch.thread_idx()[0], thr_tmem_load.thr_idx, row_idx, col_limit_right, causal_row_offset)
|
| 456 |
+
ncol = const_expr(cute.size(tScS_t2r.shape))
|
| 457 |
+
if const_expr(not r2p):
|
| 458 |
+
for i in cutlass.range(ncol, unroll_full=True):
|
| 459 |
+
acc_S[i] = -Float32.inf if tScS_t2r[i][1] >= col_limit_right else acc_S[i]
|
| 460 |
+
else:
|
| 461 |
+
mask_r2p(acc_S, col_limit_right, arch=100, rank1=True)
|
| 462 |
+
else:
|
| 463 |
+
local_row_offset_right = (
|
| 464 |
+
causal_row_offset + self.window_size_right
|
| 465 |
+
if const_expr(self.window_size_right is not None)
|
| 466 |
+
else None
|
| 467 |
+
)
|
| 468 |
+
local_row_offset_left = (
|
| 469 |
+
causal_row_offset - 1 - self.window_size_left
|
| 470 |
+
if const_expr(self.window_size_left is not None)
|
| 471 |
+
else None
|
| 472 |
+
)
|
| 473 |
+
if const_expr(self.window_size_right is not None):
|
| 474 |
+
col_limit_right = row_idx + local_row_offset_right
|
| 475 |
+
else:
|
| 476 |
+
col_limit_right = self.tile_n
|
| 477 |
+
if const_expr(mask_seqlen):
|
| 478 |
+
col_limit_right = cutlass.min(col_limit_right, seqlenk_col_limit)
|
| 479 |
+
col_limit_left = (
|
| 480 |
+
row_idx + local_row_offset_left
|
| 481 |
+
if const_expr(self.window_size_left is not None)
|
| 482 |
+
else 0
|
| 483 |
+
)
|
| 484 |
+
if const_expr(not r2p):
|
| 485 |
+
# if cute.arch.thread_idx()[0] == 0 or cute.arch.thread_idx()[0] == 128: cute.printf("m_block = {}, n_block = {}, row_idx = {}, causal_row_offset = {}, col_limit_right = {}, col_limit_left = {}", m_block, n_block, row_idx, causal_row_offset, col_limit_right, col_limit_left)
|
| 486 |
+
for i in cutlass.range(cute.size(tScS_t2r.shape), unroll_full=True):
|
| 487 |
+
col_idx = tScS_t2r[i][1]
|
| 488 |
+
acc_S[i] = (
|
| 489 |
+
-Float32.inf
|
| 490 |
+
if col_idx >= col_limit_right or col_idx < col_limit_left
|
| 491 |
+
else acc_S[i]
|
| 492 |
+
)
|
| 493 |
+
else:
|
| 494 |
+
# XOR-based R2P dual bound masking
|
| 495 |
+
mask_r2p_dual_bound(acc_S, col_limit_left, col_limit_right)
|
| 496 |
+
|
| 497 |
+
@cute.jit
|
| 498 |
+
def apply_mask_sm100_transposed(
|
| 499 |
+
self,
|
| 500 |
+
acc_S: cute.Tensor,
|
| 501 |
+
tScS_t2r: cute.Tensor,
|
| 502 |
+
t0ScS_t2r: cute.Tensor,
|
| 503 |
+
m_block: cutlass.Int32,
|
| 504 |
+
n_block: cutlass.Int32,
|
| 505 |
+
mask_seqlen: cutlass.Constexpr,
|
| 506 |
+
mask_causal: cutlass.Constexpr,
|
| 507 |
+
mask_local: cutlass.Constexpr,
|
| 508 |
+
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 509 |
+
batch_idx: Int32 = None,
|
| 510 |
+
head_idx: Int32 = None,
|
| 511 |
+
aux_tensors: Optional[list] = None,
|
| 512 |
+
fastdiv_mods=(None, None),
|
| 513 |
+
is_full_block: bool = False,
|
| 514 |
+
check_m_boundary: bool = True,
|
| 515 |
+
) -> None:
|
| 516 |
+
"""
|
| 517 |
+
Backward pass: mask S = K @ Q.T where n_block tiles seqlen_k and m_block tiles seqlen_q.
|
| 518 |
+
|
| 519 |
+
Coordinate conventio:
|
| 520 |
+
- ROW corresponds to Q (m_block)
|
| 521 |
+
- COL corresponds to KV (n_block)
|
| 522 |
+
|
| 523 |
+
is_full_block: If True, skip mask_mod (all elements valid). Only apply seqlen masking.
|
| 524 |
+
check_m_boundary: If False, skip seqlen_q boundary check (optimization for non-boundary m_blocks).
|
| 525 |
+
When iterating m_blocks in forward order, only the last m_block may be partial.
|
| 526 |
+
"""
|
| 527 |
+
assert not (mask_causal and mask_local), "mask_causal and mask_local cannot be both True"
|
| 528 |
+
ROW = 0 if const_expr(not self.swap_AB) else 1
|
| 529 |
+
COL = 1 if const_expr(not self.swap_AB) else 0
|
| 530 |
+
assert t0ScS_t2r[0][COL] == 0, "col0 == 0"
|
| 531 |
+
thr_col_offset = tScS_t2r[0][COL]
|
| 532 |
+
seqlenk_col_limit = self.seqlen_k - n_block * self.tile_n - thr_col_offset
|
| 533 |
+
|
| 534 |
+
if const_expr(not mask_causal and not mask_local and mask_mod is not None):
|
| 535 |
+
# Block sparse case with mask_mod (backward)
|
| 536 |
+
#
|
| 537 |
+
# Coordinate convention: ROW → Q (m_block), COL → KV (n_block).
|
| 538 |
+
# These already account for swap_AB.
|
| 539 |
+
#
|
| 540 |
+
# FULL blocks: mask_mod returns True for all elements, so skip it.
|
| 541 |
+
# Still need seqlen bounds check (elements may be OOB on last m_block).
|
| 542 |
+
# PARTIAL blocks: apply mask_mod element-wise, then seqlen bounds.
|
| 543 |
+
if is_full_block:
|
| 544 |
+
if const_expr(mask_seqlen):
|
| 545 |
+
if seqlenk_col_limit <= 0:
|
| 546 |
+
# Entire tile is OOB for K
|
| 547 |
+
for i in cutlass.range(cute.size(acc_S.shape), unroll_full=True):
|
| 548 |
+
acc_S[i] = -cutlass.Float32.inf
|
| 549 |
+
elif check_m_boundary:
|
| 550 |
+
# Last m_block: check Q and K boundaries
|
| 551 |
+
ncol = const_expr(cute.size(tScS_t2r.shape))
|
| 552 |
+
for i in cutlass.range_constexpr(ncol):
|
| 553 |
+
row_coord = tScS_t2r[i][ROW]
|
| 554 |
+
col_coord = tScS_t2r[i][COL]
|
| 555 |
+
global_q = row_coord + m_block * self.tile_m
|
| 556 |
+
global_kv = col_coord + n_block * self.tile_n
|
| 557 |
+
q_out_of_bounds = global_q >= self.seqlen_q
|
| 558 |
+
kv_out_of_bounds = global_kv >= self.seqlen_k
|
| 559 |
+
out_of_bounds = q_out_of_bounds or kv_out_of_bounds
|
| 560 |
+
acc_S[i] = -cutlass.Float32.inf if out_of_bounds else acc_S[i]
|
| 561 |
+
else:
|
| 562 |
+
# Partial block
|
| 563 |
+
has_fastdiv = const_expr(
|
| 564 |
+
fastdiv_mods is not None
|
| 565 |
+
and fastdiv_mods[0] is not None
|
| 566 |
+
and fastdiv_mods[1] is not None
|
| 567 |
+
)
|
| 568 |
+
wrap_aux_indices = const_expr(
|
| 569 |
+
has_fastdiv and mask_seqlen and const_expr(aux_tensors is not None)
|
| 570 |
+
)
|
| 571 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32)
|
| 572 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx, cutlass.Int32)
|
| 573 |
+
|
| 574 |
+
ncol = const_expr(cute.size(tScS_t2r.shape))
|
| 575 |
+
for i in cutlass.range_constexpr(ncol):
|
| 576 |
+
row_coord = tScS_t2r[i][ROW]
|
| 577 |
+
col_coord = tScS_t2r[i][COL]
|
| 578 |
+
global_q = row_coord + m_block * self.tile_m
|
| 579 |
+
global_kv = col_coord + n_block * self.tile_n
|
| 580 |
+
|
| 581 |
+
q_idx_for_mod = global_q
|
| 582 |
+
kv_idx_for_mod = global_kv
|
| 583 |
+
if const_expr(wrap_aux_indices):
|
| 584 |
+
_, q_idx_for_mod = divmod(global_q, fastdiv_mods[0])
|
| 585 |
+
_, kv_idx_for_mod = divmod(global_kv, fastdiv_mods[1])
|
| 586 |
+
|
| 587 |
+
q_idx_ssa = utils.scalar_to_ssa(q_idx_for_mod, cutlass.Int32)
|
| 588 |
+
kv_idx_ssa = utils.scalar_to_ssa(kv_idx_for_mod, cutlass.Int32)
|
| 589 |
+
|
| 590 |
+
mask_value = mask_mod(
|
| 591 |
+
batch_idx_ssa,
|
| 592 |
+
head_idx_ssa,
|
| 593 |
+
q_idx_ssa,
|
| 594 |
+
kv_idx_ssa,
|
| 595 |
+
self.seqlen_info,
|
| 596 |
+
aux_tensors,
|
| 597 |
+
)
|
| 598 |
+
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 599 |
+
acc_S[i] = acc_S[i] if cond else -cutlass.Float32.inf
|
| 600 |
+
|
| 601 |
+
if const_expr(mask_seqlen):
|
| 602 |
+
# check_m_boundary=False skips q check for non-boundary m_blocks
|
| 603 |
+
q_out_of_bounds = check_m_boundary and (global_q >= self.seqlen_q)
|
| 604 |
+
kv_out_of_bounds = global_kv >= self.seqlen_k
|
| 605 |
+
out_of_bounds = q_out_of_bounds or kv_out_of_bounds
|
| 606 |
+
acc_S[i] = -cutlass.Float32.inf if out_of_bounds else acc_S[i]
|
| 607 |
+
|
| 608 |
+
elif const_expr(not mask_causal and not mask_local):
|
| 609 |
+
if const_expr(mask_seqlen):
|
| 610 |
+
if seqlenk_col_limit <= 0:
|
| 611 |
+
for i in cutlass.range(cute.size(acc_S.shape), unroll_full=True):
|
| 612 |
+
acc_S[i] = -cutlass.Float32.inf
|
| 613 |
+
else: # Causal or local
|
| 614 |
+
thr_row_offset = tScS_t2r[0][ROW]
|
| 615 |
+
seqlenq_row_limit = self.seqlen_q - m_block * self.tile_m - thr_row_offset
|
| 616 |
+
causal_offset = seqlenq_row_limit - seqlenk_col_limit
|
| 617 |
+
if const_expr(mask_causal):
|
| 618 |
+
# tidx = cute.arch.thread_idx()[0] % 256
|
| 619 |
+
# if tidx < 32:
|
| 620 |
+
# cute.printf("tidx = {}, {} {}, {} {}", tidx, tScS_t2r[0][0], tScS_t2r[0][1], tScS_t2r[1][0], tScS_t2r[1][1])
|
| 621 |
+
row_limit_top = causal_offset
|
| 622 |
+
if const_expr(mask_seqlen):
|
| 623 |
+
# If col is beyond the column limit, we want to mask out the entire
|
| 624 |
+
# column, by setting row limit to be self.tile_m.
|
| 625 |
+
if seqlenk_col_limit <= 0:
|
| 626 |
+
row_limit_top = self.tile_m
|
| 627 |
+
r2p = True
|
| 628 |
+
if const_expr(not r2p):
|
| 629 |
+
for i in cutlass.range(cute.size(acc_S.shape), unroll_full=True):
|
| 630 |
+
acc_S[i] = (
|
| 631 |
+
-cutlass.Float32.inf if t0ScS_t2r[i][ROW] < row_limit_top else acc_S[i]
|
| 632 |
+
)
|
| 633 |
+
else:
|
| 634 |
+
num_rep = cute.size(tScS_t2r, mode=[0]) # 16 or 32
|
| 635 |
+
mask_r2p_transposed(acc_S, row_limit_top, num_rep)
|
| 636 |
+
else:
|
| 637 |
+
if const_expr(self.window_size_right is not None):
|
| 638 |
+
row_limit_top = causal_offset - self.window_size_right
|
| 639 |
+
else:
|
| 640 |
+
row_limit_top = 0
|
| 641 |
+
if const_expr(self.window_size_left is not None):
|
| 642 |
+
row_limit_bot = causal_offset + self.window_size_left
|
| 643 |
+
if const_expr(mask_seqlen):
|
| 644 |
+
if seqlenk_col_limit <= 0:
|
| 645 |
+
row_limit_top = self.tile_m
|
| 646 |
+
for i in cutlass.range(cute.size(acc_S.shape), unroll_full=True):
|
| 647 |
+
row_idx = t0ScS_t2r[i][ROW]
|
| 648 |
+
local_mask = row_idx < row_limit_top
|
| 649 |
+
if const_expr(self.window_size_left is not None):
|
| 650 |
+
local_mask |= row_idx > row_limit_bot
|
| 651 |
+
acc_S[i] = -cutlass.Float32.inf if local_mask else acc_S[i]
|
kernel/deps/flash_attn/cute/mma_sm100_desc.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
# Ported Cutlass code from C++ to Python:
|
| 3 |
+
# https://github.com/NVIDIA/cutlass/blob/main/include/cute/arch/mma_sm100_desc.hpp
|
| 4 |
+
# https://github.com/NVIDIA/cutlass/blob/main/include/cute/atom/mma_traits_sm100.hpp
|
| 5 |
+
|
| 6 |
+
from enum import IntEnum
|
| 7 |
+
|
| 8 |
+
import cutlass
|
| 9 |
+
import cutlass.cute as cute
|
| 10 |
+
|
| 11 |
+
# ---------------------------------------------------------------------------
|
| 12 |
+
# Enumerations that match the HW encodings (values MUST stay identical)
|
| 13 |
+
# ---------------------------------------------------------------------------
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class Major(IntEnum): # matrix “layout” in the ISA docs
|
| 17 |
+
K = 0
|
| 18 |
+
MN = 1
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class ScaleIn(IntEnum): # negate flags
|
| 22 |
+
One = 0
|
| 23 |
+
Neg = 1
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class Saturate(IntEnum):
|
| 27 |
+
False_ = 0
|
| 28 |
+
True_ = 1
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class CFormat(IntEnum): # 2-bit field (bits 4-5)
|
| 32 |
+
F16 = 0
|
| 33 |
+
F32 = 1
|
| 34 |
+
S32 = 2
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class F16F32Format(IntEnum): # 3-bit field (A/B element type)
|
| 38 |
+
F16 = 0
|
| 39 |
+
BF16 = 1
|
| 40 |
+
TF32 = 2
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class S8Format(IntEnum):
|
| 44 |
+
UINT8 = 0
|
| 45 |
+
INT8 = 1
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class MXF8F6F4Format(IntEnum):
|
| 49 |
+
E4M3 = 0
|
| 50 |
+
E5M2 = 1
|
| 51 |
+
E2M3 = 3
|
| 52 |
+
E3M2 = 4
|
| 53 |
+
E2M1 = 5
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class MaxShift(IntEnum):
|
| 57 |
+
NoShift = 0
|
| 58 |
+
MaxShift8 = 1
|
| 59 |
+
MaxShift16 = 2
|
| 60 |
+
MaxShift32 = 3
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
# ---------------------------------------------------------------------------
|
| 64 |
+
# CUTLASS-type → encoding helpers
|
| 65 |
+
# ---------------------------------------------------------------------------
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def to_UMMA_format(cutlass_type) -> int:
|
| 69 |
+
"""
|
| 70 |
+
Map a CUTLASS scalar class to the 3-bit encoding for Matrix A/B.
|
| 71 |
+
"""
|
| 72 |
+
if cutlass_type is cutlass.Int8:
|
| 73 |
+
return S8Format.INT8
|
| 74 |
+
# Unsigned 8-bit (if available in your CUTLASS build)
|
| 75 |
+
if cutlass_type is cutlass.Uint8:
|
| 76 |
+
return S8Format.UINT8
|
| 77 |
+
# FP-16 / BF-16
|
| 78 |
+
if cutlass_type is cutlass.Float16:
|
| 79 |
+
return F16F32Format.F16
|
| 80 |
+
if cutlass_type is cutlass.BFloat16:
|
| 81 |
+
return F16F32Format.BF16
|
| 82 |
+
# TensorFloat-32 (8-bit exponent, 10-bit mantissa packed in 19 bits)
|
| 83 |
+
if cutlass_type is cutlass.TFloat32:
|
| 84 |
+
return F16F32Format.TF32
|
| 85 |
+
# Float-8 / Float-6 / Float-4 – add whenever CUTLASS exposes them
|
| 86 |
+
if cutlass_type is cutlass.FloatE4M3FN:
|
| 87 |
+
return MXF8F6F4Format.E4M3
|
| 88 |
+
if cutlass_type is cutlass.FloatE5M2:
|
| 89 |
+
return MXF8F6F4Format.E5M2
|
| 90 |
+
raise TypeError(f"Unsupported CUTLASS scalar type for A/B: {cutlass_type!r}")
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def to_C_format(cutlass_type) -> int:
|
| 94 |
+
"""
|
| 95 |
+
Map a CUTLASS scalar class to the 2-bit accumulator encoding.
|
| 96 |
+
"""
|
| 97 |
+
if cutlass_type is cutlass.Float16:
|
| 98 |
+
return CFormat.F16
|
| 99 |
+
if cutlass_type is cutlass.Float32:
|
| 100 |
+
return CFormat.F32
|
| 101 |
+
if cutlass_type is cutlass.Int32:
|
| 102 |
+
return CFormat.S32
|
| 103 |
+
raise TypeError(f"Unsupported CUTLASS scalar type for accumulator: {cutlass_type!r}")
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
# ---------------------------------------------------------------------------
|
| 107 |
+
# The constructor – accepts only CUTLASS scalar classes
|
| 108 |
+
# ---------------------------------------------------------------------------
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def make_instr_desc(
|
| 112 |
+
a_type, # CUTLASS scalar class, e.g. cutlass.Int8
|
| 113 |
+
b_type,
|
| 114 |
+
c_type,
|
| 115 |
+
M: int, # 64, 128 or 256
|
| 116 |
+
N: int, # 8 … 256 (multiple of 8)
|
| 117 |
+
a_major: Major,
|
| 118 |
+
b_major: Major,
|
| 119 |
+
a_neg: ScaleIn = ScaleIn.One,
|
| 120 |
+
b_neg: ScaleIn = ScaleIn.One,
|
| 121 |
+
c_sat: Saturate = Saturate.False_,
|
| 122 |
+
is_sparse: bool = False,
|
| 123 |
+
max_shift: MaxShift = MaxShift.NoShift,
|
| 124 |
+
) -> int:
|
| 125 |
+
"""
|
| 126 |
+
Build the 32-bit instruction descriptor for Blackwell MMA.
|
| 127 |
+
All matrix/accumulator **types must be CUTLASS scalar classes** –
|
| 128 |
+
passing integers is forbidden.
|
| 129 |
+
"""
|
| 130 |
+
# --- encode element formats -------------------------------------------------
|
| 131 |
+
a_fmt = int(to_UMMA_format(a_type))
|
| 132 |
+
b_fmt = int(to_UMMA_format(b_type))
|
| 133 |
+
c_fmt = int(to_C_format(c_type))
|
| 134 |
+
|
| 135 |
+
# --- range checks on M/N -----------------------------------------------------
|
| 136 |
+
if M not in (64, 128, 256):
|
| 137 |
+
raise ValueError("M must be 64, 128 or 256")
|
| 138 |
+
if N < 8 or N > 256 or (N & 7):
|
| 139 |
+
raise ValueError("N must be a multiple of 8 in the range 8…256")
|
| 140 |
+
|
| 141 |
+
m_dim = M >> 4 # 5-bit field
|
| 142 |
+
n_dim = N >> 3 # 6-bit field
|
| 143 |
+
|
| 144 |
+
# fmt: off
|
| 145 |
+
# --- pack the bit-fields -----------------------------------------------------
|
| 146 |
+
desc = 0
|
| 147 |
+
desc |= (0 & 0x3) << 0 # sparse_id2 (always 0 here)
|
| 148 |
+
desc |= (int(is_sparse) & 0x1) << 2 # sparse_flag
|
| 149 |
+
desc |= (int(c_sat) & 0x1) << 3 # saturate
|
| 150 |
+
desc |= (c_fmt & 0x3) << 4 # c_format
|
| 151 |
+
desc |= (a_fmt & 0x7) << 7 # a_format
|
| 152 |
+
desc |= (b_fmt & 0x7) << 10 # b_format
|
| 153 |
+
desc |= (int(a_neg) & 0x1) << 13 # a_negate
|
| 154 |
+
desc |= (int(b_neg) & 0x1) << 14 # b_negate
|
| 155 |
+
desc |= (int(a_major) & 0x1) << 15 # a_major
|
| 156 |
+
desc |= (int(b_major) & 0x1) << 16 # b_major
|
| 157 |
+
desc |= (n_dim & 0x3F) << 17 # n_dim (6 bits)
|
| 158 |
+
desc |= (m_dim & 0x1F) << 24 # m_dim (5 bits)
|
| 159 |
+
desc |= (int(max_shift) & 0x3) << 30 # max_shift (2 bits)
|
| 160 |
+
# fmt: on
|
| 161 |
+
|
| 162 |
+
return desc & 0xFFFF_FFFF # ensure 32-bit result
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def mma_op_to_idesc(op: cute.nvgpu.tcgen05.mma.MmaOp):
|
| 166 |
+
return make_instr_desc(
|
| 167 |
+
op.a_dtype,
|
| 168 |
+
op.b_dtype,
|
| 169 |
+
op.acc_dtype,
|
| 170 |
+
op.shape_mnk[0],
|
| 171 |
+
op.shape_mnk[1],
|
| 172 |
+
Major.K if op.a_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K else Major.MN,
|
| 173 |
+
Major.K if op.b_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K else Major.MN,
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
class LayoutType(IntEnum): # occupies the top-3 bits [61:64)
|
| 178 |
+
SWIZZLE_NONE = 0 # (a.k.a. “INTERLEAVE” in older docs)
|
| 179 |
+
SWIZZLE_128B_BASE32B = 1
|
| 180 |
+
SWIZZLE_128B = 2
|
| 181 |
+
SWIZZLE_64B = 4
|
| 182 |
+
SWIZZLE_32B = 6
|
| 183 |
+
# values 3,5,7 are reserved / illegal for UMMA
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
# ---------------------------------------------------------------------------
|
| 187 |
+
# Helpers – figure out the SWIZZLE_* family from the tensor layout
|
| 188 |
+
# ---------------------------------------------------------------------------
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def _layout_type(swizzle: cute.Swizzle) -> LayoutType:
|
| 192 |
+
# No idea what the right way to get B, M, S is – so we're just parsing it from the __str__
|
| 193 |
+
# Swizzle string has the form "S<B,M,S>"
|
| 194 |
+
swz_str = str(swizzle)
|
| 195 |
+
inside = swz_str[swz_str.index("<") + 1 : swz_str.index(">")] # '3,4,3'
|
| 196 |
+
B, M, S = [int(x) for x in inside.split(",")] # [3, 4, 3]
|
| 197 |
+
|
| 198 |
+
if M == 4: # Swizzle<*,4,3>
|
| 199 |
+
if S != 3:
|
| 200 |
+
raise ValueError("Unexpected swizzle shift – want S==3 for M==4")
|
| 201 |
+
return {
|
| 202 |
+
0: LayoutType.SWIZZLE_NONE,
|
| 203 |
+
1: LayoutType.SWIZZLE_32B,
|
| 204 |
+
2: LayoutType.SWIZZLE_64B,
|
| 205 |
+
3: LayoutType.SWIZZLE_128B,
|
| 206 |
+
}[B] # KeyError ⇒ invalid B→ raise
|
| 207 |
+
if M == 5: # Swizzle<2,5,2> (the only legal triple for M==5)
|
| 208 |
+
if (B, S) != (2, 2):
|
| 209 |
+
raise ValueError("Only Swizzle<2,5,2> supported for 128B_BASE32B")
|
| 210 |
+
return LayoutType.SWIZZLE_128B_BASE32B
|
| 211 |
+
|
| 212 |
+
# Any other (M,B,S) triple is not a UMMA-legal shared-memory layout
|
| 213 |
+
raise ValueError("Unsupported swizzle triple for UMMA smem descriptor")
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def make_smem_desc_base(layout: cute.Layout, swizzle: cute.Swizzle, major: Major) -> int:
|
| 217 |
+
"""
|
| 218 |
+
Convert a 2-D *shared-memory* Cute layout into the Blackwell 64-bit
|
| 219 |
+
smem-descriptor, without the smem start address.
|
| 220 |
+
layout must correspond to layout of an uint128 tensor.
|
| 221 |
+
"""
|
| 222 |
+
# ------------------------------------------------------------------ meta
|
| 223 |
+
layout_type = _layout_type(swizzle) # resolve SWIZZLE_* family
|
| 224 |
+
|
| 225 |
+
VERSION = 1 # bits 46–47
|
| 226 |
+
LBO_MODE = 0 # bit 52
|
| 227 |
+
BASE_OFFSET = 0 # bits 49–51 (CUTLASS always 0)
|
| 228 |
+
|
| 229 |
+
# ---------------------------------------------------------- strides (units: uint128_t = 16 B)
|
| 230 |
+
swizzle_atom_mn_size = {
|
| 231 |
+
LayoutType.SWIZZLE_NONE: 1,
|
| 232 |
+
LayoutType.SWIZZLE_32B: 2,
|
| 233 |
+
LayoutType.SWIZZLE_64B: 4,
|
| 234 |
+
LayoutType.SWIZZLE_128B: 8,
|
| 235 |
+
LayoutType.SWIZZLE_128B_BASE32B: 8,
|
| 236 |
+
}[layout_type]
|
| 237 |
+
|
| 238 |
+
if major is Major.MN:
|
| 239 |
+
swizzle_atom_k_size = 4 if layout_type is LayoutType.SWIZZLE_128B_BASE32B else 8
|
| 240 |
+
canonical_layout = cute.logical_divide(layout, (swizzle_atom_mn_size, swizzle_atom_k_size))
|
| 241 |
+
if not cute.is_congruent(canonical_layout, ((1, 1), (1, 1))):
|
| 242 |
+
raise ValueError("Not a canonical UMMA_MN Layout: Expected profile failure.")
|
| 243 |
+
stride_00 = canonical_layout.stride[0][0]
|
| 244 |
+
if layout_type is not LayoutType.SWIZZLE_NONE and stride_00 != 1:
|
| 245 |
+
raise ValueError("Not a canonical UMMA_MN Layout: Expected stride failure.")
|
| 246 |
+
stride_10 = canonical_layout.stride[1][0]
|
| 247 |
+
if stride_10 != swizzle_atom_mn_size:
|
| 248 |
+
raise ValueError("Not a canonical UMMA_MN Layout: Expected stride failure.")
|
| 249 |
+
stride_01, stride_11 = canonical_layout.stride[0][1], canonical_layout.stride[1][1]
|
| 250 |
+
if layout_type is LayoutType.SWIZZLE_NONE:
|
| 251 |
+
stride_byte_offset, leading_byte_offset = stride_01, stride_11
|
| 252 |
+
else:
|
| 253 |
+
stride_byte_offset, leading_byte_offset = stride_11, stride_01
|
| 254 |
+
else:
|
| 255 |
+
if layout_type == LayoutType.SWIZZLE_128B_BASE32B:
|
| 256 |
+
raise ValueError("SWIZZLE_128B_BASE32B is invalid for Major-K")
|
| 257 |
+
if not cute.size(layout.shape[0]) % 8 == 0:
|
| 258 |
+
raise ValueError("Not a canonical UMMA_K Layout: Expected MN-size multiple of 8.")
|
| 259 |
+
canonical_layout = cute.logical_divide(layout, (8, 2))
|
| 260 |
+
if not cute.is_congruent(canonical_layout, ((1, 1), (1, 1))):
|
| 261 |
+
raise ValueError("Not a canonical UMMA_K Layout: Expected profile failure.")
|
| 262 |
+
stride_00 = canonical_layout.stride[0][0]
|
| 263 |
+
if stride_00 != swizzle_atom_mn_size:
|
| 264 |
+
raise ValueError("Not a canonical UMMA_K Layout: Expected stride failure.")
|
| 265 |
+
stride_10 = canonical_layout.stride[1][0]
|
| 266 |
+
if layout_type is not LayoutType.SWIZZLE_NONE and stride_10 != 1:
|
| 267 |
+
raise ValueError("Not a canonical UMMA_K Layout: Expected stride failure.")
|
| 268 |
+
stride_01 = canonical_layout.stride[0][1]
|
| 269 |
+
stride_byte_offset, leading_byte_offset = stride_01, stride_10
|
| 270 |
+
|
| 271 |
+
# ------------------------------------------------------------------ pack
|
| 272 |
+
desc = 0
|
| 273 |
+
# leading_byte_offset_ [16:30)
|
| 274 |
+
desc |= (leading_byte_offset & 0x3FFF) << 16
|
| 275 |
+
# stride_byte_offset_ [32:46)
|
| 276 |
+
desc |= (stride_byte_offset & 0x3FFF) << 32
|
| 277 |
+
# version_ [46:48)
|
| 278 |
+
desc |= (VERSION & 0x3) << 46
|
| 279 |
+
# base_offset_ [49:52)
|
| 280 |
+
desc |= (BASE_OFFSET & 0x7) << 49
|
| 281 |
+
# lbo_mode_ [52:53)
|
| 282 |
+
desc |= (LBO_MODE & 0x1) << 52
|
| 283 |
+
# layout_type_ [61:64)
|
| 284 |
+
desc |= (int(layout_type) & 0x7) << 61
|
| 285 |
+
|
| 286 |
+
return desc & 0xFFFF_FFFF_FFFF_FFFF # force 64-bit width
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def make_smem_desc_start_addr(start_addr: cute.Pointer) -> cutlass.Int32:
|
| 290 |
+
# 14 bits, remove 4 LSB (bits 0-13 in desc)
|
| 291 |
+
return (start_addr.toint() & 0x3FFFF) >> 4
|
kernel/deps/flash_attn/cute/named_barrier.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import enum
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class NamedBarrierFwd(enum.IntEnum):
|
| 7 |
+
Epilogue = enum.auto() # starts from 1 as barrier 0 is reserved for sync_threads()
|
| 8 |
+
WarpSchedulerWG1 = enum.auto()
|
| 9 |
+
WarpSchedulerWG2 = enum.auto()
|
| 10 |
+
WarpSchedulerWG3 = enum.auto()
|
| 11 |
+
PFull = enum.auto()
|
| 12 |
+
PEmpty = enum.auto()
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class NamedBarrierBwd(enum.IntEnum):
|
| 16 |
+
Epilogue = enum.auto()
|
| 17 |
+
WarpSchedulerWG1 = enum.auto()
|
| 18 |
+
WarpSchedulerWG2 = enum.auto()
|
| 19 |
+
WarpSchedulerWG3 = enum.auto()
|
| 20 |
+
PdS = enum.auto()
|
| 21 |
+
dQFullWG0 = enum.auto()
|
| 22 |
+
dQFullWG1 = enum.auto()
|
| 23 |
+
dQEmptyWG0 = enum.auto()
|
| 24 |
+
dQEmptyWG1 = enum.auto()
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class NamedBarrierBwdSm100(enum.IntEnum):
|
| 28 |
+
EpilogueWG1 = enum.auto()
|
| 29 |
+
EpilogueWG2 = enum.auto()
|
| 30 |
+
Compute = enum.auto()
|
| 31 |
+
dQaccReduce = enum.auto()
|
kernel/deps/flash_attn/cute/pack_gqa.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
|
| 7 |
+
import flash_attn.cute.utils as utils
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class PackGQA:
|
| 11 |
+
def __init__(
|
| 12 |
+
self,
|
| 13 |
+
m_block_size: cutlass.Constexpr[int],
|
| 14 |
+
head_dim_padded: cutlass.Constexpr[int],
|
| 15 |
+
check_hdim_oob: cutlass.Constexpr[bool],
|
| 16 |
+
qhead_per_kvhead: cutlass.Constexpr[bool],
|
| 17 |
+
):
|
| 18 |
+
self.m_block_size = m_block_size
|
| 19 |
+
self.head_dim_padded = head_dim_padded
|
| 20 |
+
self.check_hdim_oob = check_hdim_oob
|
| 21 |
+
self.qhead_per_kvhead = qhead_per_kvhead
|
| 22 |
+
|
| 23 |
+
@cute.jit
|
| 24 |
+
def compute_ptr(
|
| 25 |
+
self,
|
| 26 |
+
tensor: cute.Tensor,
|
| 27 |
+
cRows: cute.Tensor,
|
| 28 |
+
tidx: cutlass.Int32,
|
| 29 |
+
block: cutlass.Int32,
|
| 30 |
+
threads_per_row: cutlass.Constexpr[int],
|
| 31 |
+
num_threads: cutlass.Constexpr[int],
|
| 32 |
+
):
|
| 33 |
+
num_ptr_per_thread = cute.ceil_div(cute.size(cRows), threads_per_row)
|
| 34 |
+
tPrPtr = cute.make_fragment(num_ptr_per_thread, cutlass.Int64)
|
| 35 |
+
for i in cutlass.range_constexpr(num_ptr_per_thread):
|
| 36 |
+
row = i * num_threads + cRows[tidx % threads_per_row][0]
|
| 37 |
+
idx = block * self.m_block_size + row
|
| 38 |
+
m_idx = idx // self.qhead_per_kvhead
|
| 39 |
+
h_idx = idx - m_idx * self.qhead_per_kvhead
|
| 40 |
+
tPrPtr[i] = utils.elem_pointer(tensor, ((h_idx, m_idx),)).toint()
|
| 41 |
+
return tPrPtr
|
| 42 |
+
|
| 43 |
+
@cute.jit
|
| 44 |
+
def load_Q(
|
| 45 |
+
self,
|
| 46 |
+
mQ: cute.Tensor, # ((qhead_per_kvhead, seqlen_q), headdim)
|
| 47 |
+
sQ: cute.Tensor, # (m_block_size, head_dim_padded)
|
| 48 |
+
gmem_tiled_copy: cute.TiledCopy,
|
| 49 |
+
tidx: cutlass.Int32,
|
| 50 |
+
block: cutlass.Int32,
|
| 51 |
+
seqlen: cutlass.Int32,
|
| 52 |
+
):
|
| 53 |
+
gmem_thr_copy = gmem_tiled_copy.get_slice(tidx)
|
| 54 |
+
cQ = cute.make_identity_tensor((self.m_block_size, self.head_dim_padded))
|
| 55 |
+
tQsQ = gmem_thr_copy.partition_D(sQ)
|
| 56 |
+
tQcQ = gmem_thr_copy.partition_S(cQ)
|
| 57 |
+
t0QcQ = gmem_thr_copy.get_slice(0).partition_S(cQ)
|
| 58 |
+
tQpQ = utils.predicate_k(tQcQ, limit=mQ.shape[1])
|
| 59 |
+
tQcQ_row = tQcQ[0, None, 0]
|
| 60 |
+
threads_per_row = gmem_tiled_copy.layout_tv_tiled.shape[0][0]
|
| 61 |
+
assert cute.arch.WARP_SIZE % threads_per_row == 0, "threads_per_row must divide WARP_SIZE"
|
| 62 |
+
num_threads = gmem_tiled_copy.size
|
| 63 |
+
tPrQPtr = self.compute_ptr(mQ[None, 0], tQcQ_row, tidx, block, threads_per_row, num_threads)
|
| 64 |
+
for m in cutlass.range_constexpr(cute.size(tQsQ.shape[1])):
|
| 65 |
+
q_ptr_i64 = utils.shuffle_sync(
|
| 66 |
+
tPrQPtr[m // threads_per_row], m % threads_per_row, width=threads_per_row
|
| 67 |
+
)
|
| 68 |
+
q_gmem_ptr = cute.make_ptr(
|
| 69 |
+
mQ.element_type, q_ptr_i64, cute.AddressSpace.gmem, assumed_align=16
|
| 70 |
+
)
|
| 71 |
+
if (
|
| 72 |
+
t0QcQ[0, m, 0][0]
|
| 73 |
+
< seqlen * self.qhead_per_kvhead - block * self.m_block_size - tQcQ_row[0][0]
|
| 74 |
+
):
|
| 75 |
+
mQ_cur = cute.make_tensor(q_gmem_ptr, (self.head_dim_padded,))
|
| 76 |
+
elems_per_load = cute.size(tQsQ.shape[0][0])
|
| 77 |
+
mQ_cur_copy = cute.tiled_divide(mQ_cur, (elems_per_load,))
|
| 78 |
+
for k in cutlass.range_constexpr(cute.size(tQsQ.shape[2])):
|
| 79 |
+
ki = tQcQ[0, 0, k][1] // elems_per_load
|
| 80 |
+
cute.copy(
|
| 81 |
+
gmem_thr_copy,
|
| 82 |
+
mQ_cur_copy[None, ki],
|
| 83 |
+
tQsQ[None, m, k],
|
| 84 |
+
pred=tQpQ[None, m, k] if cutlass.const_expr(self.check_hdim_oob) else None,
|
| 85 |
+
)
|
| 86 |
+
# We don't need to clear the sQ smem tiles since we'll only write out the valid outputs
|
| 87 |
+
|
| 88 |
+
@cute.jit
|
| 89 |
+
def store_LSE(
|
| 90 |
+
self,
|
| 91 |
+
mLSE: cute.Tensor, # (qhead_per_kvhead, seqlen_q)
|
| 92 |
+
tLSErLSE: cute.Tensor, # (m_block_size, head_dim_padded)
|
| 93 |
+
tiled_mma: cute.TiledMma,
|
| 94 |
+
tidx: cutlass.Int32,
|
| 95 |
+
block: cutlass.Int32,
|
| 96 |
+
seqlen: cutlass.Int32,
|
| 97 |
+
):
|
| 98 |
+
thr_mma = tiled_mma.get_slice(tidx)
|
| 99 |
+
caccO = cute.make_identity_tensor((self.m_block_size, self.head_dim_padded))
|
| 100 |
+
taccOcO = thr_mma.partition_C(caccO)
|
| 101 |
+
taccOcO_row = utils.make_acc_tensor_mn_view(taccOcO)[None, 0]
|
| 102 |
+
assert cute.size(tLSErLSE) == cute.size(taccOcO_row)
|
| 103 |
+
threads_per_row = tiled_mma.tv_layout_C.shape[0][0]
|
| 104 |
+
assert cute.arch.WARP_SIZE % threads_per_row == 0, "threads_per_row must divide WARP_SIZE"
|
| 105 |
+
assert cute.size(tLSErLSE) <= threads_per_row
|
| 106 |
+
num_threads = tiled_mma.size
|
| 107 |
+
tPrLSEPtr = self.compute_ptr(mLSE, taccOcO_row, tidx, block, threads_per_row, num_threads)
|
| 108 |
+
for m in cutlass.range_constexpr(cute.size(tLSErLSE)):
|
| 109 |
+
lse_ptr_i64 = utils.shuffle_sync(
|
| 110 |
+
tPrLSEPtr[m // threads_per_row],
|
| 111 |
+
m % threads_per_row,
|
| 112 |
+
width=threads_per_row,
|
| 113 |
+
)
|
| 114 |
+
lse_gmem_ptr = cute.make_ptr(
|
| 115 |
+
mLSE.element_type, lse_ptr_i64, cute.AddressSpace.gmem, assumed_align=4
|
| 116 |
+
)
|
| 117 |
+
row = block * self.m_block_size + taccOcO_row[m][0]
|
| 118 |
+
# Only the thread corresponding to column 0 writes out the lse to gmem
|
| 119 |
+
if taccOcO[0][1] == 0 and row < seqlen * self.qhead_per_kvhead:
|
| 120 |
+
mLSE_copy = cute.make_tensor(lse_gmem_ptr, (1,))
|
| 121 |
+
mLSE_copy[0] = tLSErLSE[m]
|
| 122 |
+
|
| 123 |
+
@cute.jit
|
| 124 |
+
def store_O(
|
| 125 |
+
self,
|
| 126 |
+
mO: cute.Tensor, # ((qhead_per_kvhead, seqlen_q), headdim)
|
| 127 |
+
tOrO: cute.Tensor, # (m_block_size, head_dim_padded) split across threads according to gmem_tiled_copy
|
| 128 |
+
gmem_tiled_copy: cute.TiledCopy,
|
| 129 |
+
tidx: cutlass.Int32,
|
| 130 |
+
block: cutlass.Int32,
|
| 131 |
+
seqlen: cutlass.Int32,
|
| 132 |
+
):
|
| 133 |
+
gmem_thr_copy = gmem_tiled_copy.get_slice(tidx)
|
| 134 |
+
cO = cute.make_identity_tensor((self.m_block_size, self.head_dim_padded))
|
| 135 |
+
tOcO = gmem_thr_copy.partition_S(cO)
|
| 136 |
+
t0OcO = gmem_thr_copy.get_slice(0).partition_S(cO)
|
| 137 |
+
tOpO = utils.predicate_k(tOcO, limit=mO.shape[1])
|
| 138 |
+
tOcO_row = tOcO[0, None, 0]
|
| 139 |
+
threads_per_row = gmem_tiled_copy.layout_tv_tiled.shape[0][0]
|
| 140 |
+
assert cute.arch.WARP_SIZE % threads_per_row == 0, "threads_per_row must divide WARP_SIZE"
|
| 141 |
+
num_threads = gmem_tiled_copy.size
|
| 142 |
+
tPrOPtr = self.compute_ptr(mO[None, 0], tOcO_row, tidx, block, threads_per_row, num_threads)
|
| 143 |
+
for m in cutlass.range_constexpr(cute.size(tOrO.shape[1])):
|
| 144 |
+
o_ptr_i64 = utils.shuffle_sync(
|
| 145 |
+
tPrOPtr[m // threads_per_row], m % threads_per_row, width=threads_per_row
|
| 146 |
+
)
|
| 147 |
+
o_gmem_ptr = cute.make_ptr(
|
| 148 |
+
mO.element_type, o_ptr_i64, cute.AddressSpace.gmem, assumed_align=16
|
| 149 |
+
)
|
| 150 |
+
if (
|
| 151 |
+
t0OcO[0, m, 0][0]
|
| 152 |
+
< seqlen * self.qhead_per_kvhead - block * self.m_block_size - tOcO_row[0][0]
|
| 153 |
+
):
|
| 154 |
+
mO_cur = cute.make_tensor(o_gmem_ptr, (self.head_dim_padded,))
|
| 155 |
+
elems_per_load = cute.size(tOrO.shape[0][0])
|
| 156 |
+
mO_cur_copy = cute.tiled_divide(mO_cur, (elems_per_load,))
|
| 157 |
+
for k in cutlass.range_constexpr(cute.size(tOrO.shape[2])):
|
| 158 |
+
ki = tOcO[0, 0, k][1] // elems_per_load
|
| 159 |
+
cute.copy(
|
| 160 |
+
gmem_thr_copy,
|
| 161 |
+
tOrO[None, m, k],
|
| 162 |
+
mO_cur_copy[None, ki],
|
| 163 |
+
pred=tOpO[None, m, k] if cutlass.const_expr(self.check_hdim_oob) else None,
|
| 164 |
+
)
|
kernel/deps/flash_attn/cute/paged_kv.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Type
|
| 2 |
+
from dataclasses import dataclass
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
from cutlass.cute.nvgpu import cpasync
|
| 7 |
+
from cutlass import Int32, const_expr
|
| 8 |
+
|
| 9 |
+
from flash_attn.cute import utils
|
| 10 |
+
from flash_attn.cute.cute_dsl_utils import ParamsBase
|
| 11 |
+
from cutlass.cute import FastDivmodDivisor
|
| 12 |
+
|
| 13 |
+
import math
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class PagedKVManager(ParamsBase):
|
| 18 |
+
mPageTable: cute.Tensor
|
| 19 |
+
mK_paged: cute.Tensor
|
| 20 |
+
mV_paged: cute.Tensor
|
| 21 |
+
thread_idx: Int32
|
| 22 |
+
|
| 23 |
+
page_size_divmod: FastDivmodDivisor
|
| 24 |
+
seqlen_k: Int32
|
| 25 |
+
leftpad_k: Int32
|
| 26 |
+
n_block_size: Int32
|
| 27 |
+
num_threads: cutlass.Constexpr[Int32]
|
| 28 |
+
head_dim_padded: cutlass.Constexpr[Int32]
|
| 29 |
+
head_dim_v_padded: cutlass.Constexpr[Int32]
|
| 30 |
+
|
| 31 |
+
gmem_threads_per_row: cutlass.Constexpr[Int32]
|
| 32 |
+
page_entry_per_thread: Int32
|
| 33 |
+
async_copy_elems: Int32
|
| 34 |
+
|
| 35 |
+
gmem_tiled_copy_KV: cute.TiledCopy
|
| 36 |
+
gmem_thr_copy_KV: cute.TiledCopy
|
| 37 |
+
tPrPage: cute.Tensor
|
| 38 |
+
tPrPageOffset: cute.Tensor
|
| 39 |
+
tKpK: cute.Tensor
|
| 40 |
+
tVpV: cute.Tensor
|
| 41 |
+
|
| 42 |
+
@staticmethod
|
| 43 |
+
def create(
|
| 44 |
+
mPageTable: cute.Tensor,
|
| 45 |
+
mK_paged: cute.Tensor,
|
| 46 |
+
mV_paged: cute.Tensor,
|
| 47 |
+
page_size_divmod: FastDivmodDivisor,
|
| 48 |
+
bidb: Int32,
|
| 49 |
+
bidh: Int32,
|
| 50 |
+
thread_idx: Int32,
|
| 51 |
+
seqlen_k: Int32,
|
| 52 |
+
leftpad_k: Int32,
|
| 53 |
+
n_block_size: cutlass.Constexpr[Int32],
|
| 54 |
+
head_dim_padded: cutlass.Constexpr[Int32],
|
| 55 |
+
head_dim_v_padded: cutlass.Constexpr[Int32],
|
| 56 |
+
num_threads: cutlass.Constexpr[Int32],
|
| 57 |
+
dtype: Type[cutlass.Numeric],
|
| 58 |
+
):
|
| 59 |
+
universal_copy_bits = 128
|
| 60 |
+
async_copy_elems = universal_copy_bits // dtype.width
|
| 61 |
+
dtype_bytes = dtype.width // 8
|
| 62 |
+
gmem_k_block_size = math.gcd(
|
| 63 |
+
head_dim_padded,
|
| 64 |
+
head_dim_v_padded,
|
| 65 |
+
128 // dtype_bytes,
|
| 66 |
+
)
|
| 67 |
+
assert gmem_k_block_size % async_copy_elems == 0
|
| 68 |
+
gmem_threads_per_row = gmem_k_block_size // async_copy_elems
|
| 69 |
+
assert cute.arch.WARP_SIZE % gmem_threads_per_row == 0
|
| 70 |
+
atom_async_copy = cute.make_copy_atom(
|
| 71 |
+
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.GLOBAL),
|
| 72 |
+
dtype,
|
| 73 |
+
num_bits_per_copy=universal_copy_bits,
|
| 74 |
+
)
|
| 75 |
+
thr_layout = cute.make_ordered_layout(
|
| 76 |
+
(num_threads // gmem_threads_per_row, gmem_threads_per_row),
|
| 77 |
+
order=(1, 0),
|
| 78 |
+
)
|
| 79 |
+
val_layout = cute.make_layout((1, async_copy_elems))
|
| 80 |
+
gmem_tiled_copy_KV = cute.make_tiled_copy_tv(atom_async_copy, thr_layout, val_layout)
|
| 81 |
+
gmem_thr_copy_KV = gmem_tiled_copy_KV.get_slice(thread_idx)
|
| 82 |
+
page_entry_per_thread = n_block_size // num_threads
|
| 83 |
+
|
| 84 |
+
tPrPage = cute.make_rmem_tensor((page_entry_per_thread,), Int32)
|
| 85 |
+
tPrPageOffset = cute.make_rmem_tensor((page_entry_per_thread,), Int32)
|
| 86 |
+
|
| 87 |
+
mPageTable = mPageTable[bidb, None]
|
| 88 |
+
mK_paged = mK_paged[None, None, bidh, None]
|
| 89 |
+
mV_paged = mV_paged[None, None, bidh, None]
|
| 90 |
+
|
| 91 |
+
cK = cute.make_identity_tensor((n_block_size, head_dim_padded))
|
| 92 |
+
tKcK = gmem_thr_copy_KV.partition_S(cK)
|
| 93 |
+
tKpK = utils.predicate_k(tKcK, limit=mK_paged.shape[1])
|
| 94 |
+
|
| 95 |
+
if const_expr(head_dim_padded == head_dim_v_padded):
|
| 96 |
+
tVpV = tKpK
|
| 97 |
+
else:
|
| 98 |
+
cV = cute.make_identity_tensor((n_block_size, head_dim_v_padded))
|
| 99 |
+
tVcV = gmem_thr_copy_KV.partition_S(cV)
|
| 100 |
+
tVpV = utils.predicate_k(tVcV, limit=mV_paged.shape[0])
|
| 101 |
+
|
| 102 |
+
return PagedKVManager(
|
| 103 |
+
mPageTable,
|
| 104 |
+
mK_paged,
|
| 105 |
+
mV_paged,
|
| 106 |
+
thread_idx,
|
| 107 |
+
page_size_divmod,
|
| 108 |
+
seqlen_k,
|
| 109 |
+
leftpad_k,
|
| 110 |
+
n_block_size,
|
| 111 |
+
num_threads,
|
| 112 |
+
head_dim_padded,
|
| 113 |
+
head_dim_v_padded,
|
| 114 |
+
gmem_threads_per_row,
|
| 115 |
+
page_entry_per_thread,
|
| 116 |
+
async_copy_elems,
|
| 117 |
+
gmem_tiled_copy_KV,
|
| 118 |
+
gmem_thr_copy_KV,
|
| 119 |
+
tPrPage,
|
| 120 |
+
tPrPageOffset,
|
| 121 |
+
tKpK,
|
| 122 |
+
tVpV,
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
@cute.jit
|
| 126 |
+
def load_page_table(self, n_block: Int32):
|
| 127 |
+
for i in cutlass.range(self.page_entry_per_thread, unroll=1):
|
| 128 |
+
row = (
|
| 129 |
+
i * self.num_threads
|
| 130 |
+
+ (self.thread_idx % self.gmem_threads_per_row)
|
| 131 |
+
* (self.num_threads // self.gmem_threads_per_row)
|
| 132 |
+
+ (self.thread_idx // self.gmem_threads_per_row)
|
| 133 |
+
)
|
| 134 |
+
row_idx = n_block * self.n_block_size + row
|
| 135 |
+
|
| 136 |
+
page_idx, page_offset = divmod(row_idx + self.leftpad_k, self.page_size_divmod)
|
| 137 |
+
|
| 138 |
+
is_valid = (
|
| 139 |
+
(i + 1) * self.num_threads <= self.n_block_size or row < self.n_block_size
|
| 140 |
+
) and row_idx < self.seqlen_k
|
| 141 |
+
page = self.mPageTable[page_idx] if is_valid else 0
|
| 142 |
+
|
| 143 |
+
self.tPrPage[i] = page
|
| 144 |
+
self.tPrPageOffset[i] = page_offset
|
| 145 |
+
|
| 146 |
+
@cute.jit
|
| 147 |
+
def compute_X_ptr(self, K_or_V: str):
|
| 148 |
+
tPrXPtr = cute.make_rmem_tensor((self.page_entry_per_thread,), cutlass.Int64)
|
| 149 |
+
for i in cutlass.range(self.page_entry_per_thread, unroll=1):
|
| 150 |
+
page = self.tPrPage[i]
|
| 151 |
+
page_offset = self.tPrPageOffset[i]
|
| 152 |
+
if const_expr(K_or_V == "K"):
|
| 153 |
+
tPrXPtr[i] = utils.elem_pointer(self.mK_paged, (page_offset, 0, page)).toint()
|
| 154 |
+
else:
|
| 155 |
+
tPrXPtr[i] = utils.elem_pointer(self.mV_paged, (0, page_offset, page)).toint()
|
| 156 |
+
return tPrXPtr
|
| 157 |
+
|
| 158 |
+
@cute.jit
|
| 159 |
+
def load_KV(self, n_block: Int32, sX: cute.Tensor, K_or_V: str):
|
| 160 |
+
assert K_or_V in ("K", "V")
|
| 161 |
+
|
| 162 |
+
tPrXPtr = self.compute_X_ptr(K_or_V)
|
| 163 |
+
|
| 164 |
+
# Finesse sX layout to be (M, N).
|
| 165 |
+
sX_pi = cute.make_tensor(
|
| 166 |
+
sX.iterator,
|
| 167 |
+
cute.make_layout(
|
| 168 |
+
(sX.shape[0][0], (sX.shape[0][1], sX.shape[2])),
|
| 169 |
+
stride=(sX.stride[0][0], (sX.stride[0][1], sX.stride[2])),
|
| 170 |
+
),
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
if const_expr(K_or_V == "V"):
|
| 174 |
+
# Need to transpose V
|
| 175 |
+
sX_pi = cute.make_tensor(sX_pi.iterator, cute.select(sX_pi.layout, mode=[1, 0]))
|
| 176 |
+
|
| 177 |
+
head_dim = self.head_dim_v_padded if const_expr(K_or_V == "V") else self.head_dim_padded
|
| 178 |
+
cX = cute.make_identity_tensor((self.n_block_size, head_dim))
|
| 179 |
+
tXsX = self.gmem_thr_copy_KV.partition_D(sX_pi)
|
| 180 |
+
tXcX = self.gmem_thr_copy_KV.partition_S(cX)
|
| 181 |
+
tXc0X = self.gmem_thr_copy_KV.get_slice(0).partition_S(cX)
|
| 182 |
+
|
| 183 |
+
seqlenk_row_limit = (
|
| 184 |
+
self.seqlen_k - n_block * self.n_block_size - tXcX[0][0] if n_block >= 0 else 0
|
| 185 |
+
)
|
| 186 |
+
for m in cutlass.range_constexpr(cute.size(tXsX, mode=[1])):
|
| 187 |
+
row_valid = tXc0X[0, m, 0][0] < seqlenk_row_limit
|
| 188 |
+
should_load = cute.make_fragment_like(tXsX[(0, None), m, 0], cute.Boolean)
|
| 189 |
+
should_load.fill(row_valid)
|
| 190 |
+
|
| 191 |
+
x_ptr_i64 = utils.shuffle_sync(
|
| 192 |
+
tPrXPtr[m // self.gmem_threads_per_row],
|
| 193 |
+
m % self.gmem_threads_per_row,
|
| 194 |
+
width=self.gmem_threads_per_row,
|
| 195 |
+
)
|
| 196 |
+
x_gmem_ptr = cute.make_ptr(
|
| 197 |
+
self.mK_paged.element_type, x_ptr_i64, cute.AddressSpace.gmem, assumed_align=16
|
| 198 |
+
)
|
| 199 |
+
mX_paged_cur = cute.make_tensor(x_gmem_ptr, cute.make_layout((head_dim,)))
|
| 200 |
+
mX_paged_cur_copy = cute.tiled_divide(mX_paged_cur, (self.async_copy_elems,))
|
| 201 |
+
|
| 202 |
+
for k in cutlass.range_constexpr(cute.size(tXsX, mode=[2])):
|
| 203 |
+
ki = tXcX[0, 0, k][1] // self.async_copy_elems
|
| 204 |
+
mX_paged_cur_copy_ki = mX_paged_cur_copy[None, ki]
|
| 205 |
+
tXsX_k = tXsX[None, m, k]
|
| 206 |
+
mX_paged_cur_copy_ki = cute.make_tensor(
|
| 207 |
+
mX_paged_cur_copy_ki.iterator, tXsX_k.layout
|
| 208 |
+
)
|
| 209 |
+
cute.copy(
|
| 210 |
+
self.gmem_tiled_copy_KV,
|
| 211 |
+
mX_paged_cur_copy_ki,
|
| 212 |
+
tXsX_k,
|
| 213 |
+
pred=should_load,
|
| 214 |
+
)
|
kernel/deps/flash_attn/cute/pipeline.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
# import math
|
| 4 |
+
from typing import Optional
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
|
| 7 |
+
import cutlass
|
| 8 |
+
import cutlass.cute as cute
|
| 9 |
+
from cutlass import Boolean, Int32, const_expr
|
| 10 |
+
from cutlass.cutlass_dsl import if_generate
|
| 11 |
+
from cutlass.pipeline import PipelineAsync, PipelineState, Agent, CooperativeGroup
|
| 12 |
+
from cutlass.pipeline import PipelineUserType, PipelineOp
|
| 13 |
+
from cutlass.pipeline import PipelineTmaAsync as PipelineTmaAsyncOg
|
| 14 |
+
from cutlass.pipeline import PipelineTmaUmma as PipelineTmaUmmaOg
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
# We deviate from cute-dsl implementation to use cute.arch.cluster_arrive_relaxed
|
| 18 |
+
def pipeline_init_wait(cta_layout_vmnk: Optional[cute.Layout] = None):
|
| 19 |
+
"""
|
| 20 |
+
Fences the mbarrier init and syncs the threadblock or cluster
|
| 21 |
+
"""
|
| 22 |
+
cute.arch.mbarrier_init_fence()
|
| 23 |
+
|
| 24 |
+
if cta_layout_vmnk is None or cute.size(cta_layout_vmnk) == 1:
|
| 25 |
+
# If not using clusters, sync the threadblock
|
| 26 |
+
_sync(Agent.ThreadBlock)
|
| 27 |
+
else:
|
| 28 |
+
# If using clusters, sync the cluster
|
| 29 |
+
_sync(Agent.ThreadBlockCluster)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _sync(group: Agent):
|
| 33 |
+
"""
|
| 34 |
+
Syncs all threads within an agent.
|
| 35 |
+
"""
|
| 36 |
+
if group is Agent.Thread:
|
| 37 |
+
raise NotImplementedError("Error: Not supported.")
|
| 38 |
+
elif group is Agent.ThreadBlock:
|
| 39 |
+
cute.arch.sync_threads()
|
| 40 |
+
elif group is Agent.ThreadBlockCluster:
|
| 41 |
+
cute.arch.cluster_arrive_relaxed()
|
| 42 |
+
cute.arch.cluster_wait()
|
| 43 |
+
else:
|
| 44 |
+
assert False, (
|
| 45 |
+
"Error: No explicit sync instruction exists. Please use barriers (named / mbarrier) instead."
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class PipelineStateSimple:
|
| 50 |
+
"""
|
| 51 |
+
Pipeline state contains an index and phase bit corresponding to the current position in the circular buffer.
|
| 52 |
+
Use a single Int32 to store both the index and phase bit, then we use divmod to get the
|
| 53 |
+
index and phase. If stages is a power of 2, divmod turns into bit twiddling.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
def __init__(self, stages: int, phase_index: Int32):
|
| 57 |
+
# assert stages < 2**16
|
| 58 |
+
# self._log_stages = int(math.log2(stages))
|
| 59 |
+
# assert 1 << self._log_stages == stages, "Number of stages must be a power of 2."
|
| 60 |
+
self._stages = stages
|
| 61 |
+
self._phase_index = phase_index
|
| 62 |
+
|
| 63 |
+
def clone(self) -> "PipelineStateSimple":
|
| 64 |
+
return PipelineStateSimple(self.stages, self._phase_index)
|
| 65 |
+
|
| 66 |
+
@property
|
| 67 |
+
def stages(self) -> int:
|
| 68 |
+
# return 1 << self._log_stages
|
| 69 |
+
return self._stages
|
| 70 |
+
|
| 71 |
+
@property
|
| 72 |
+
def index(self) -> Int32:
|
| 73 |
+
# return self._phase_index & 0xFFFF
|
| 74 |
+
# return self._phase_index & ((1 << self._log_stages) - 1)
|
| 75 |
+
if const_expr(self._stages == 1):
|
| 76 |
+
return Int32(0)
|
| 77 |
+
else:
|
| 78 |
+
return self._phase_index % self._stages
|
| 79 |
+
|
| 80 |
+
@property
|
| 81 |
+
def phase(self) -> Int32:
|
| 82 |
+
# return self._phase_index >> 16
|
| 83 |
+
# PTX docs say that the phase parity needs to be 0 or 1, so by right we need to
|
| 84 |
+
# take modulo 2. But in practice just passing the phase in without modulo works fine.
|
| 85 |
+
# return (self._phase_index >> self._log_stages) % 2
|
| 86 |
+
# return self._phase_index >> self._log_stages
|
| 87 |
+
if const_expr(self._stages == 1):
|
| 88 |
+
return self._phase_index
|
| 89 |
+
else:
|
| 90 |
+
return self._phase_index // self._stages
|
| 91 |
+
|
| 92 |
+
def advance(self):
|
| 93 |
+
if const_expr(self._stages == 1):
|
| 94 |
+
self._phase_index ^= 1
|
| 95 |
+
else:
|
| 96 |
+
self._phase_index += 1
|
| 97 |
+
|
| 98 |
+
# def then_body(phase_index):
|
| 99 |
+
# # XOR the phase bit and set the index to 0
|
| 100 |
+
# return (phase_index & 0xFFFF0000) ^ (1 << 16)
|
| 101 |
+
|
| 102 |
+
# def else_body(phase_index):
|
| 103 |
+
# return phase_index
|
| 104 |
+
|
| 105 |
+
# self._phase_index = if_generate(
|
| 106 |
+
# (self._phase_index & 0xFFFF) == self.stages,
|
| 107 |
+
# then_body,
|
| 108 |
+
# else_body,
|
| 109 |
+
# [self._phase_index],
|
| 110 |
+
# [Int32],
|
| 111 |
+
# )
|
| 112 |
+
|
| 113 |
+
def __extract_mlir_values__(self):
|
| 114 |
+
phase_index = self._phase_index
|
| 115 |
+
return [phase_index.ir_value()]
|
| 116 |
+
|
| 117 |
+
def __new_from_mlir_values__(self, values):
|
| 118 |
+
return PipelineStateSimple(self.stages, Int32(values[0]))
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def make_pipeline_state(type: PipelineUserType, stages: int):
|
| 122 |
+
"""
|
| 123 |
+
Creates a pipeline state. Producers are assumed to start with an empty buffer and have a flipped phase bit of 1.
|
| 124 |
+
"""
|
| 125 |
+
if type is PipelineUserType.Producer:
|
| 126 |
+
# return PipelineStateSimple(stages, Int32(1 << 16))
|
| 127 |
+
return PipelineStateSimple(stages, Int32(stages))
|
| 128 |
+
elif type is PipelineUserType.Consumer:
|
| 129 |
+
return PipelineStateSimple(stages, Int32(0))
|
| 130 |
+
else:
|
| 131 |
+
assert False, "Error: invalid PipelineUserType specified for make_pipeline_state."
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
@dataclass(frozen=True)
|
| 135 |
+
class PipelineTmaAsync(PipelineTmaAsyncOg):
|
| 136 |
+
"""
|
| 137 |
+
Override producer_acquire to take in extra_tx_count parameter.
|
| 138 |
+
"""
|
| 139 |
+
|
| 140 |
+
@staticmethod
|
| 141 |
+
def create(*args, **kwargs):
|
| 142 |
+
obj = PipelineTmaAsyncOg.create(*args, **kwargs)
|
| 143 |
+
# Can't assign to __class__ directly since the dataclass is frozen
|
| 144 |
+
# obj.__class__ = PipelineTmaAsync
|
| 145 |
+
object.__setattr__(obj, "__class__", PipelineTmaAsync)
|
| 146 |
+
return obj
|
| 147 |
+
|
| 148 |
+
def producer_acquire(
|
| 149 |
+
self,
|
| 150 |
+
state: PipelineState,
|
| 151 |
+
try_acquire_token: Optional[Boolean] = None,
|
| 152 |
+
extra_tx_count: int = 0,
|
| 153 |
+
):
|
| 154 |
+
"""
|
| 155 |
+
TMA producer commit conditionally waits on buffer empty and sets the transaction barrier for leader threadblocks.
|
| 156 |
+
"""
|
| 157 |
+
if_generate(
|
| 158 |
+
try_acquire_token is None or try_acquire_token == 0,
|
| 159 |
+
lambda: self.sync_object_empty.wait(state.index, state.phase),
|
| 160 |
+
)
|
| 161 |
+
if const_expr(extra_tx_count == 0):
|
| 162 |
+
self.sync_object_full.arrive(state.index, self.producer_mask)
|
| 163 |
+
else:
|
| 164 |
+
tx_count = self.sync_object_full.tx_count + extra_tx_count
|
| 165 |
+
self.sync_object_full.arrive_and_expect_tx(state.index, tx_count)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
@dataclass(frozen=True)
|
| 169 |
+
class PipelineTmaUmma(PipelineTmaUmmaOg):
|
| 170 |
+
@staticmethod
|
| 171 |
+
def create(
|
| 172 |
+
*,
|
| 173 |
+
num_stages: int,
|
| 174 |
+
producer_group: CooperativeGroup,
|
| 175 |
+
consumer_group: CooperativeGroup,
|
| 176 |
+
tx_count: int,
|
| 177 |
+
barrier_storage: cute.Pointer = None,
|
| 178 |
+
cta_layout_vmnk: Optional[cute.Layout] = None,
|
| 179 |
+
mcast_mode_mn: tuple[int, int] = (1, 1),
|
| 180 |
+
init_wait: cutlass.Constexpr[bool] = True,
|
| 181 |
+
):
|
| 182 |
+
"""
|
| 183 |
+
This helper function computes any necessary attributes and returns an instance of PipelineTmaUmma.
|
| 184 |
+
:param barrier_storage: Pointer to the smem address for this pipeline's mbarriers
|
| 185 |
+
:type barrier_storage: cute.Pointer
|
| 186 |
+
:param num_stages: Number of buffer stages for this pipeline
|
| 187 |
+
:type num_stages: Int32
|
| 188 |
+
:param producer_group: `CooperativeGroup` for the producer agent
|
| 189 |
+
:type producer_group: CooperativeGroup
|
| 190 |
+
:param consumer_group: `CooperativeGroup` for the consumer agent
|
| 191 |
+
:type consumer_group: CooperativeGroup
|
| 192 |
+
:param tx_count: Number of bytes expected to be written to the transaction barrier for one stage
|
| 193 |
+
:type tx_count: int
|
| 194 |
+
:param cta_layout_vmnk: Layout of the cluster shape
|
| 195 |
+
:type cta_layout_vmnk: cute.Layout | None
|
| 196 |
+
:param mcast_mode_mn: Tuple of two integers, specifying whether mcast is enabled for the m and n modes. At least one of the two integers must be 1.
|
| 197 |
+
:type mcast_mode_mn: tuple[int, int]
|
| 198 |
+
"""
|
| 199 |
+
if not isinstance(barrier_storage, cute.Pointer):
|
| 200 |
+
raise ValueError(
|
| 201 |
+
f"Expected barrier_storage to be a cute.Pointer, but got {type(barrier_storage)}"
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
producer_type = PipelineOp.TmaLoad
|
| 205 |
+
consumer_type = PipelineOp.TCGen05Mma
|
| 206 |
+
|
| 207 |
+
producer = (producer_type, producer_group)
|
| 208 |
+
consumer = (consumer_type, consumer_group)
|
| 209 |
+
|
| 210 |
+
sync_object_full = PipelineAsync._make_sync_object(
|
| 211 |
+
barrier_storage.align(min_align=8), num_stages, producer, tx_count
|
| 212 |
+
)
|
| 213 |
+
sync_object_empty = PipelineAsync._make_sync_object(
|
| 214 |
+
barrier_storage.align(min_align=8) + num_stages, num_stages, consumer
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
if cta_layout_vmnk is None or cute.size(cta_layout_vmnk) == 1:
|
| 218 |
+
# No mcast mask if not using clusters
|
| 219 |
+
producer_mask = None
|
| 220 |
+
# All threadblocks are leaders if not using clusters
|
| 221 |
+
is_leader_cta = True
|
| 222 |
+
else:
|
| 223 |
+
producer_mask = PipelineTmaUmma._compute_mcast_arrival_mask(
|
| 224 |
+
cta_layout_vmnk, mcast_mode_mn
|
| 225 |
+
)
|
| 226 |
+
is_leader_cta = PipelineTmaUmma._compute_is_leader_cta(cta_layout_vmnk)
|
| 227 |
+
|
| 228 |
+
cta_group = (
|
| 229 |
+
cute.nvgpu.tcgen05.CtaGroup.ONE
|
| 230 |
+
if cta_layout_vmnk is None or cute.size(cta_layout_vmnk, mode=[0]) == 1
|
| 231 |
+
else cute.nvgpu.tcgen05.CtaGroup.TWO
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
consumer_mask = producer_mask
|
| 235 |
+
|
| 236 |
+
if const_expr(init_wait):
|
| 237 |
+
pipeline_init_wait(cta_layout_vmnk)
|
| 238 |
+
|
| 239 |
+
return PipelineTmaUmma(
|
| 240 |
+
sync_object_full,
|
| 241 |
+
sync_object_empty,
|
| 242 |
+
num_stages,
|
| 243 |
+
producer_mask,
|
| 244 |
+
consumer_mask,
|
| 245 |
+
is_leader_cta,
|
| 246 |
+
cta_group,
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
def producer_acquire(
|
| 250 |
+
self,
|
| 251 |
+
state: PipelineState,
|
| 252 |
+
try_acquire_token: Optional[Boolean] = None,
|
| 253 |
+
extra_tx_count: int = 0,
|
| 254 |
+
):
|
| 255 |
+
"""
|
| 256 |
+
TMA producer commit conditionally waits on buffer empty and sets the transaction barrier for leader threadblocks.
|
| 257 |
+
"""
|
| 258 |
+
if_generate(
|
| 259 |
+
try_acquire_token is None or try_acquire_token == 0,
|
| 260 |
+
lambda: self.sync_object_empty.wait(state.index, state.phase),
|
| 261 |
+
)
|
| 262 |
+
if const_expr(extra_tx_count == 0):
|
| 263 |
+
if_generate(
|
| 264 |
+
self.is_leader_cta,
|
| 265 |
+
lambda: self.sync_object_full.arrive(state.index, self.producer_mask),
|
| 266 |
+
)
|
| 267 |
+
else:
|
| 268 |
+
tx_count = self.sync_object_full.tx_count + extra_tx_count
|
| 269 |
+
if_generate(
|
| 270 |
+
self.is_leader_cta,
|
| 271 |
+
lambda: self.sync_object_full.arrive_and_expect_tx(state.index, tx_count),
|
| 272 |
+
)
|
kernel/deps/flash_attn/cute/seqlen_info.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
from dataclasses import dataclass
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
from cutlass import Int32, const_expr
|
| 7 |
+
|
| 8 |
+
"""
|
| 9 |
+
This consolidates all the info related to sequence length. This is so that we can do all
|
| 10 |
+
the gmem reads once at the beginning of each tile, rather than having to repeat these reads
|
| 11 |
+
to compute various things like n_block_min, n_block_max, etc.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@dataclass(frozen=True)
|
| 16 |
+
class SeqlenInfo:
|
| 17 |
+
offset: cutlass.Int32
|
| 18 |
+
seqlen: cutlass.Int32
|
| 19 |
+
|
| 20 |
+
@staticmethod
|
| 21 |
+
def create(
|
| 22 |
+
batch_idx: cutlass.Int32,
|
| 23 |
+
seqlen_static: cutlass.Int32,
|
| 24 |
+
cu_seqlens: Optional[cute.Tensor] = None,
|
| 25 |
+
seqused: Optional[cute.Tensor] = None,
|
| 26 |
+
):
|
| 27 |
+
offset = 0 if const_expr(cu_seqlens is None) else cu_seqlens[batch_idx]
|
| 28 |
+
if const_expr(seqused is not None):
|
| 29 |
+
seqlen = seqused[batch_idx]
|
| 30 |
+
elif const_expr(cu_seqlens is not None):
|
| 31 |
+
seqlen = cu_seqlens[batch_idx + 1] - cu_seqlens[batch_idx]
|
| 32 |
+
else:
|
| 33 |
+
seqlen = seqlen_static
|
| 34 |
+
return SeqlenInfo(offset, seqlen)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@dataclass(frozen=True)
|
| 38 |
+
class SeqlenInfoQK:
|
| 39 |
+
offset_q: cutlass.Int32
|
| 40 |
+
offset_k: cutlass.Int32
|
| 41 |
+
padded_offset_q: cutlass.Int32
|
| 42 |
+
padded_offset_k: cutlass.Int32
|
| 43 |
+
seqlen_q: cutlass.Int32
|
| 44 |
+
seqlen_k: cutlass.Int32
|
| 45 |
+
has_cu_seqlens_q: cutlass.Constexpr[bool]
|
| 46 |
+
has_cu_seqlens_k: cutlass.Constexpr[bool]
|
| 47 |
+
has_seqused_q: cutlass.Constexpr[bool]
|
| 48 |
+
has_seqused_k: cutlass.Constexpr[bool]
|
| 49 |
+
|
| 50 |
+
@staticmethod
|
| 51 |
+
def create(
|
| 52 |
+
batch_idx: cutlass.Int32,
|
| 53 |
+
seqlen_q_static: cutlass.Int32,
|
| 54 |
+
seqlen_k_static: cutlass.Int32,
|
| 55 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 56 |
+
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 57 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 58 |
+
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 59 |
+
tile_m: cutlass.Constexpr[cutlass.Int32] = 128,
|
| 60 |
+
tile_n: cutlass.Constexpr[cutlass.Int32] = 128,
|
| 61 |
+
):
|
| 62 |
+
offset_q = 0 if const_expr(mCuSeqlensQ is None) else mCuSeqlensQ[batch_idx]
|
| 63 |
+
offset_k = 0 if const_expr(mCuSeqlensK is None) else mCuSeqlensK[batch_idx]
|
| 64 |
+
padded_offset_q = (
|
| 65 |
+
0
|
| 66 |
+
if const_expr(mCuSeqlensQ is None)
|
| 67 |
+
else (offset_q + batch_idx * tile_m) // tile_m * tile_m
|
| 68 |
+
)
|
| 69 |
+
padded_offset_k = (
|
| 70 |
+
0
|
| 71 |
+
if const_expr(mCuSeqlensK is None)
|
| 72 |
+
else (offset_k + batch_idx * tile_n) // tile_n * tile_n
|
| 73 |
+
)
|
| 74 |
+
if const_expr(mSeqUsedQ is not None):
|
| 75 |
+
seqlen_q = mSeqUsedQ[batch_idx]
|
| 76 |
+
else:
|
| 77 |
+
seqlen_q = (
|
| 78 |
+
seqlen_q_static
|
| 79 |
+
if const_expr(mCuSeqlensQ is None)
|
| 80 |
+
else mCuSeqlensQ[batch_idx + 1] - offset_q
|
| 81 |
+
)
|
| 82 |
+
if const_expr(mSeqUsedK is not None):
|
| 83 |
+
seqlen_k = mSeqUsedK[batch_idx]
|
| 84 |
+
else:
|
| 85 |
+
seqlen_k = (
|
| 86 |
+
seqlen_k_static
|
| 87 |
+
if const_expr(mCuSeqlensK is None)
|
| 88 |
+
else mCuSeqlensK[batch_idx + 1] - offset_k
|
| 89 |
+
)
|
| 90 |
+
has_cu_seqlens_q: int = mCuSeqlensQ is not None
|
| 91 |
+
has_cu_seqlens_k: int = mCuSeqlensK is not None
|
| 92 |
+
has_seqused_q: int = mSeqUsedQ is not None
|
| 93 |
+
has_seqused_k: int = mSeqUsedK is not None
|
| 94 |
+
return SeqlenInfoQK(
|
| 95 |
+
offset_q,
|
| 96 |
+
offset_k,
|
| 97 |
+
padded_offset_q,
|
| 98 |
+
padded_offset_k,
|
| 99 |
+
seqlen_q,
|
| 100 |
+
seqlen_k,
|
| 101 |
+
has_cu_seqlens_q,
|
| 102 |
+
has_cu_seqlens_k,
|
| 103 |
+
has_seqused_q,
|
| 104 |
+
has_seqused_k,
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
def offset_batch_Q(
|
| 108 |
+
self,
|
| 109 |
+
mQ: cute.Tensor,
|
| 110 |
+
batch_idx: Int32,
|
| 111 |
+
dim: int,
|
| 112 |
+
padded: cutlass.Constexpr[bool] = False,
|
| 113 |
+
) -> cute.Tensor:
|
| 114 |
+
"""Seqlen must be the first dimension of mQ"""
|
| 115 |
+
if const_expr(not self.has_cu_seqlens_q):
|
| 116 |
+
idx = (None,) * dim + (batch_idx,) + (None,) * (cute.rank(mQ) - 1 - dim)
|
| 117 |
+
return mQ[idx]
|
| 118 |
+
else:
|
| 119 |
+
offset_q = self.offset_q if const_expr(not padded) else self.padded_offset_q
|
| 120 |
+
offset = offset_q if const_expr(cute.rank(mQ.shape[0]) == 1) else (0, offset_q)
|
| 121 |
+
idx = (offset,) + (0,) * (cute.rank(mQ) - 1)
|
| 122 |
+
return cute.domain_offset(idx, mQ)
|
| 123 |
+
|
| 124 |
+
def offset_batch_K(
|
| 125 |
+
self,
|
| 126 |
+
mK: cute.Tensor,
|
| 127 |
+
batch_idx: Int32,
|
| 128 |
+
dim: int,
|
| 129 |
+
padded: cutlass.Constexpr[bool] = False,
|
| 130 |
+
) -> cute.Tensor:
|
| 131 |
+
"""Seqlen must be the first dimension of mK"""
|
| 132 |
+
if const_expr(not self.has_cu_seqlens_k):
|
| 133 |
+
idx = (None,) * dim + (batch_idx,) + (None,) * (cute.rank(mK) - 1 - dim)
|
| 134 |
+
return mK[idx]
|
| 135 |
+
else:
|
| 136 |
+
offset_k = self.offset_k if const_expr(not padded) else self.padded_offset_k
|
| 137 |
+
idx = (offset_k,) + (0,) * (cute.rank(mK) - 1)
|
| 138 |
+
return cute.domain_offset(idx, mK)
|
kernel/deps/flash_attn/cute/softmax.py
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
import operator
|
| 5 |
+
from typing import Tuple
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
|
| 8 |
+
import cutlass
|
| 9 |
+
import cutlass.cute as cute
|
| 10 |
+
from cutlass import Float32
|
| 11 |
+
|
| 12 |
+
import flash_attn.cute.utils as utils
|
| 13 |
+
from flash_attn.cute.cute_dsl_utils import ParamsBase
|
| 14 |
+
from flash_attn.cute.seqlen_info import SeqlenInfoQK
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@dataclass
|
| 18 |
+
class Softmax(ParamsBase):
|
| 19 |
+
scale_log2: Float32
|
| 20 |
+
num_rows: cutlass.Constexpr[int]
|
| 21 |
+
row_max: cute.Tensor
|
| 22 |
+
row_sum: cute.Tensor
|
| 23 |
+
arch: cutlass.Constexpr[int] = 80
|
| 24 |
+
softmax_scale: Float32 | None = None
|
| 25 |
+
|
| 26 |
+
@staticmethod
|
| 27 |
+
def create(
|
| 28 |
+
scale_log2: Float32,
|
| 29 |
+
num_rows: cutlass.Constexpr[int],
|
| 30 |
+
arch: cutlass.Constexpr[int] = 80,
|
| 31 |
+
softmax_scale: Float32 | None = None,
|
| 32 |
+
):
|
| 33 |
+
row_max = cute.make_rmem_tensor(num_rows, Float32)
|
| 34 |
+
row_sum = cute.make_rmem_tensor(num_rows, Float32)
|
| 35 |
+
return Softmax(scale_log2, num_rows, row_max, row_sum, arch, softmax_scale)
|
| 36 |
+
|
| 37 |
+
def reset(self) -> None:
|
| 38 |
+
self.row_max.fill(-Float32.inf)
|
| 39 |
+
self.row_sum.fill(0.0)
|
| 40 |
+
|
| 41 |
+
def _compute_row_max(
|
| 42 |
+
self, acc_S_row: cute.TensorSSA, init_val: float | Float32 | None = None
|
| 43 |
+
) -> Float32:
|
| 44 |
+
return utils.fmax_reduce(acc_S_row, init_val, arch=self.arch)
|
| 45 |
+
|
| 46 |
+
def _compute_row_sum(
|
| 47 |
+
self, acc_S_row_exp: cute.TensorSSA, init_val: float | Float32 | None = None
|
| 48 |
+
) -> Float32:
|
| 49 |
+
return utils.fadd_reduce(acc_S_row_exp, init_val, arch=self.arch)
|
| 50 |
+
|
| 51 |
+
@cute.jit
|
| 52 |
+
def online_softmax(
|
| 53 |
+
self,
|
| 54 |
+
acc_S: cute.Tensor,
|
| 55 |
+
is_first: cutlass.Constexpr[bool] = False,
|
| 56 |
+
check_inf: cutlass.Constexpr[bool] = True,
|
| 57 |
+
) -> cute.Tensor:
|
| 58 |
+
"""Apply online softmax and return the row_scale to rescale O.
|
| 59 |
+
|
| 60 |
+
:param acc_S: acc_S tensor
|
| 61 |
+
:type acc_S: cute.Tensor
|
| 62 |
+
:param is_first: is first n_block
|
| 63 |
+
:type is_first: cutlass.Constexpr
|
| 64 |
+
"""
|
| 65 |
+
# Change acc_S to M,N layout view.
|
| 66 |
+
acc_S_mn = utils.make_acc_tensor_mn_view(acc_S)
|
| 67 |
+
row_scale = cute.make_fragment_like(self.row_max, Float32)
|
| 68 |
+
|
| 69 |
+
row_max = self.row_max
|
| 70 |
+
row_sum = self.row_sum
|
| 71 |
+
scale_log2 = self.scale_log2
|
| 72 |
+
arch = self.arch
|
| 73 |
+
|
| 74 |
+
# Each iteration processes one row of acc_S
|
| 75 |
+
for r in cutlass.range(cute.size(row_max), unroll_full=True):
|
| 76 |
+
acc_S_row = acc_S_mn[r, None].load() # (n_block_size)
|
| 77 |
+
|
| 78 |
+
row_max_cur = utils.fmax_reduce(
|
| 79 |
+
acc_S_row,
|
| 80 |
+
init_val=row_max[r] if cutlass.const_expr(not is_first) else None,
|
| 81 |
+
arch=arch,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
row_max_cur = utils.warp_reduce(row_max_cur, cute.arch.fmax, width=4)
|
| 85 |
+
# Update row_max before changing row_max_cur to safe value for -inf
|
| 86 |
+
row_max_prev = row_max[r]
|
| 87 |
+
row_max[r] = row_max_cur
|
| 88 |
+
|
| 89 |
+
if cutlass.const_expr(check_inf):
|
| 90 |
+
row_max_cur = 0.0 if row_max_cur == -Float32.inf else row_max_cur
|
| 91 |
+
|
| 92 |
+
if cutlass.const_expr(is_first):
|
| 93 |
+
row_max_cur_scaled = row_max_cur * scale_log2
|
| 94 |
+
acc_S_row_exp = utils.exp2f(acc_S_row * scale_log2 - row_max_cur_scaled)
|
| 95 |
+
|
| 96 |
+
acc_S_row_sum = utils.fadd_reduce(acc_S_row_exp, init_val=None, arch=arch)
|
| 97 |
+
row_scale[r] = 1.0
|
| 98 |
+
else:
|
| 99 |
+
row_max_cur_scaled = row_max_cur * scale_log2
|
| 100 |
+
acc_S_row_exp = utils.exp2f(acc_S_row * scale_log2 - row_max_cur_scaled)
|
| 101 |
+
# row_scale[r] = utils.exp2f(row_max_prev * self.scale_log2 - row_max_cur_scaled)
|
| 102 |
+
row_scale[r] = utils.exp2f((row_max_prev - row_max_cur) * scale_log2)
|
| 103 |
+
|
| 104 |
+
acc_S_row_sum = utils.fadd_reduce(
|
| 105 |
+
acc_S_row_exp, init_val=row_sum[r] * row_scale[r], arch=arch
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
row_sum[r] = acc_S_row_sum
|
| 109 |
+
acc_S_mn[r, None].store(acc_S_row_exp)
|
| 110 |
+
|
| 111 |
+
return row_scale
|
| 112 |
+
|
| 113 |
+
@cute.jit
|
| 114 |
+
def finalize(
|
| 115 |
+
self, final_scale: Float32 = 1.0, sink_val: Float32 | cute.Tensor | None = None
|
| 116 |
+
) -> cute.Tensor:
|
| 117 |
+
"""Finalize the online softmax by computing the scale and logsumexp."""
|
| 118 |
+
if cutlass.const_expr(sink_val is not None and isinstance(sink_val, cute.Tensor)):
|
| 119 |
+
assert cute.size(sink_val) == cute.size(self.row_sum)
|
| 120 |
+
row_sum = self.row_sum
|
| 121 |
+
row_max = self.row_max
|
| 122 |
+
scale_log2 = self.scale_log2
|
| 123 |
+
|
| 124 |
+
# quad reduction for row_sum as we didn't do it during each iteration of online softmax
|
| 125 |
+
row_sum.store(utils.warp_reduce(row_sum.load(), operator.add, width=4))
|
| 126 |
+
row_scale = cute.make_fragment_like(row_max, Float32)
|
| 127 |
+
|
| 128 |
+
for r in cutlass.range(cute.size(row_sum), unroll_full=True):
|
| 129 |
+
if cutlass.const_expr(sink_val is not None):
|
| 130 |
+
sink_val_cur = sink_val if not isinstance(sink_val, cute.Tensor) else sink_val[r]
|
| 131 |
+
LOG2_E = math.log2(math.e)
|
| 132 |
+
row_sum[r] += utils.exp2f(sink_val_cur * LOG2_E - row_max[r] * scale_log2)
|
| 133 |
+
|
| 134 |
+
# if row_sum is zero or nan, set acc_O_mn_row to 1.0
|
| 135 |
+
acc_O_mn_row_is_zero_or_nan = row_sum[r] == 0.0 or row_sum[r] != row_sum[r]
|
| 136 |
+
row_scale[r] = (
|
| 137 |
+
cute.arch.rcp_approx(row_sum[r] if not acc_O_mn_row_is_zero_or_nan else 1.0)
|
| 138 |
+
) * final_scale
|
| 139 |
+
row_sum_cur = row_sum[r]
|
| 140 |
+
LN2 = math.log(2.0)
|
| 141 |
+
row_sum[r] = (
|
| 142 |
+
(row_max[r] * scale_log2 + utils.log2f(row_sum_cur)) * LN2
|
| 143 |
+
if not acc_O_mn_row_is_zero_or_nan
|
| 144 |
+
else -Float32.inf
|
| 145 |
+
)
|
| 146 |
+
return row_scale
|
| 147 |
+
|
| 148 |
+
@cute.jit
|
| 149 |
+
def rescale_O(self, acc_O: cute.Tensor, row_scale: cute.Tensor) -> None:
|
| 150 |
+
"""Scale each row of acc_O by the given scale tensor.
|
| 151 |
+
:param acc_O: input tensor
|
| 152 |
+
:type acc_O: cute.Tensor
|
| 153 |
+
:param row_scale: row_scale tensor
|
| 154 |
+
:type row_scale: cute.Tensor
|
| 155 |
+
"""
|
| 156 |
+
acc_O_mn = utils.make_acc_tensor_mn_view(acc_O)
|
| 157 |
+
assert cute.size(row_scale) == cute.size(acc_O_mn, mode=[0])
|
| 158 |
+
for r in cutlass.range(cute.size(row_scale), unroll_full=True):
|
| 159 |
+
acc_O_mn[r, None].store(acc_O_mn[r, None].load() * row_scale[r])
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
@dataclass
|
| 163 |
+
class SoftmaxSm100(Softmax):
|
| 164 |
+
rescale_threshold: cutlass.Constexpr[float] = 0.0
|
| 165 |
+
|
| 166 |
+
@staticmethod
|
| 167 |
+
def create(
|
| 168 |
+
scale_log2: Float32,
|
| 169 |
+
rescale_threshold: cutlass.Constexpr[float] = 0.0,
|
| 170 |
+
softmax_scale: Float32 | None = None,
|
| 171 |
+
):
|
| 172 |
+
num_rows = 1
|
| 173 |
+
arch = 100
|
| 174 |
+
row_max = cute.make_rmem_tensor(num_rows, Float32)
|
| 175 |
+
row_sum = cute.make_rmem_tensor(num_rows, Float32)
|
| 176 |
+
return SoftmaxSm100(
|
| 177 |
+
scale_log2,
|
| 178 |
+
num_rows,
|
| 179 |
+
row_max,
|
| 180 |
+
row_sum,
|
| 181 |
+
arch,
|
| 182 |
+
softmax_scale,
|
| 183 |
+
rescale_threshold=rescale_threshold,
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
@cute.jit
|
| 187 |
+
def update_row_max(self, acc_S_row: cute.TensorSSA, is_first: int) -> Tuple[Float32, Float32]:
|
| 188 |
+
if cutlass.const_expr(is_first):
|
| 189 |
+
row_max_new = self._compute_row_max(acc_S_row)
|
| 190 |
+
row_max_safe = row_max_new if row_max_new != -cutlass.Float32.inf else 0.0
|
| 191 |
+
acc_scale = 0.0
|
| 192 |
+
else:
|
| 193 |
+
row_max_old = self.row_max[0]
|
| 194 |
+
row_max_new = self._compute_row_max(acc_S_row, init_val=row_max_old)
|
| 195 |
+
row_max_safe = row_max_new if row_max_new != -cutlass.Float32.inf else 0.0
|
| 196 |
+
acc_scale_ = (row_max_old - row_max_safe) * self.scale_log2
|
| 197 |
+
acc_scale = utils.exp2f(acc_scale_)
|
| 198 |
+
if cutlass.const_expr(self.rescale_threshold > 0.0):
|
| 199 |
+
if acc_scale_ >= -self.rescale_threshold:
|
| 200 |
+
row_max_new = row_max_old
|
| 201 |
+
row_max_safe = row_max_old
|
| 202 |
+
acc_scale = 1.0
|
| 203 |
+
self.row_max[0] = row_max_new
|
| 204 |
+
return row_max_safe, acc_scale
|
| 205 |
+
|
| 206 |
+
def update_row_sum(
|
| 207 |
+
self, acc_S_row_exp: cute.TensorSSA, row_scale: Float32, is_first: int = False
|
| 208 |
+
) -> None:
|
| 209 |
+
init_val = self.row_sum[0] * row_scale if cutlass.const_expr(not is_first) else None
|
| 210 |
+
# self.row_sum[0] = self._compute_row_sum(acc_S_row_exp, init_val=self.row_sum[0] * row_scale)
|
| 211 |
+
self.row_sum[0] = self._compute_row_sum(acc_S_row_exp, init_val=init_val)
|
| 212 |
+
# tmp = self._compute_row_sum(acc_S_row_exp)
|
| 213 |
+
# self.row_sum[0] = self.row_sum[0] * row_scale + tmp
|
| 214 |
+
|
| 215 |
+
@cute.jit
|
| 216 |
+
def scale_subtract_rowmax(
|
| 217 |
+
self,
|
| 218 |
+
acc_S_row: cute.Tensor,
|
| 219 |
+
row_max: Float32,
|
| 220 |
+
):
|
| 221 |
+
assert cute.size(acc_S_row.shape) % 2 == 0, "acc_S_row must have an even number of elements"
|
| 222 |
+
row_max_scaled = row_max * self.scale_log2
|
| 223 |
+
for i in cutlass.range(0, cute.size(acc_S_row.shape), 2, unroll_full=True):
|
| 224 |
+
acc_S_row[i], acc_S_row[i + 1] = utils.fma_packed_f32x2(
|
| 225 |
+
(acc_S_row[i], acc_S_row[i + 1]),
|
| 226 |
+
(self.scale_log2, self.scale_log2),
|
| 227 |
+
(-row_max_scaled, -row_max_scaled),
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
@cute.jit
|
| 231 |
+
def apply_exp2_convert(
|
| 232 |
+
self,
|
| 233 |
+
acc_S_row: cute.Tensor,
|
| 234 |
+
acc_S_row_converted: cute.Tensor,
|
| 235 |
+
e2e: cutlass.Constexpr[bool] = False,
|
| 236 |
+
e2e_freq: cutlass.Constexpr[int] = 16,
|
| 237 |
+
e2e_res: cutlass.Constexpr[int] = 4,
|
| 238 |
+
e2e_frg_limit: cutlass.Constexpr[int] = 1,
|
| 239 |
+
):
|
| 240 |
+
assert cute.size(acc_S_row.shape) % 2 == 0, "acc_S_row must have an even number of elements"
|
| 241 |
+
frg_tile = 32
|
| 242 |
+
assert frg_tile % 2 == 0
|
| 243 |
+
frg_cnt = cute.size(acc_S_row) // frg_tile
|
| 244 |
+
assert cute.size(acc_S_row) % frg_tile == 0
|
| 245 |
+
acc_S_row_frg = cute.logical_divide(acc_S_row, cute.make_layout(frg_tile))
|
| 246 |
+
acc_S_row_converted_frg = cute.logical_divide(
|
| 247 |
+
acc_S_row_converted, cute.make_layout(frg_tile)
|
| 248 |
+
)
|
| 249 |
+
for j in cutlass.range_constexpr(frg_cnt):
|
| 250 |
+
for k in cutlass.range_constexpr(0, cute.size(acc_S_row_frg, mode=[0]), 2):
|
| 251 |
+
# acc_S_row_frg[k, j] = utils.exp2f(acc_S_row_frg[k, j])
|
| 252 |
+
# acc_S_row_frg[k + 1, j] = utils.exp2f(acc_S_row_frg[k + 1, j])
|
| 253 |
+
if cutlass.const_expr(not e2e):
|
| 254 |
+
acc_S_row_frg[k, j] = cute.arch.exp2(acc_S_row_frg[k, j])
|
| 255 |
+
acc_S_row_frg[k + 1, j] = cute.arch.exp2(acc_S_row_frg[k + 1, j])
|
| 256 |
+
else:
|
| 257 |
+
if cutlass.const_expr(
|
| 258 |
+
k % e2e_freq < e2e_freq - e2e_res or j >= frg_cnt - e2e_frg_limit
|
| 259 |
+
):
|
| 260 |
+
acc_S_row_frg[k, j] = cute.arch.exp2(acc_S_row_frg[k, j])
|
| 261 |
+
acc_S_row_frg[k + 1, j] = cute.arch.exp2(acc_S_row_frg[k + 1, j])
|
| 262 |
+
else:
|
| 263 |
+
# acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j] = utils.e2e_asm2(acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j])
|
| 264 |
+
acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j] = utils.ex2_emulation_2(
|
| 265 |
+
acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j]
|
| 266 |
+
)
|
| 267 |
+
acc_S_row_converted_frg[None, j].store(
|
| 268 |
+
acc_S_row_frg[None, j].load().to(acc_S_row_converted.element_type)
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
@cute.jit
|
| 272 |
+
def scale_apply_exp2_convert(
|
| 273 |
+
self,
|
| 274 |
+
acc_S_row: cute.Tensor,
|
| 275 |
+
row_max: Float32,
|
| 276 |
+
acc_S_row_converted: cute.Tensor,
|
| 277 |
+
):
|
| 278 |
+
assert cute.size(acc_S_row.shape) % 2 == 0, "acc_S_row must have an even number of elements"
|
| 279 |
+
minus_row_max_scaled = -row_max * self.scale_log2
|
| 280 |
+
for i in cutlass.range_constexpr(0, cute.size(acc_S_row.shape), 2):
|
| 281 |
+
acc_S_row[i], acc_S_row[i + 1] = utils.fma_packed_f32x2(
|
| 282 |
+
(acc_S_row[i], acc_S_row[i + 1]),
|
| 283 |
+
(self.scale_log2, self.scale_log2),
|
| 284 |
+
(minus_row_max_scaled, minus_row_max_scaled),
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
# for i in cutlass.range_constexpr(0, cute.size(acc_S_row.shape), 2):
|
| 288 |
+
# acc_S_row[i], acc_S_row[i + 1] = utils.fma_packed_f32x2(
|
| 289 |
+
# (acc_S_row[i], acc_S_row[i + 1]),
|
| 290 |
+
# (self.scale_log2, self.scale_log2),
|
| 291 |
+
# (minus_row_max_scaled, minus_row_max_scaled),
|
| 292 |
+
# )
|
| 293 |
+
# acc_S_row[i] = cute.arch.exp2(acc_S_row[i])
|
| 294 |
+
# acc_S_row[i + 1] = cute.arch.exp2(acc_S_row[i + 1])
|
| 295 |
+
|
| 296 |
+
frg_tile = 32
|
| 297 |
+
assert frg_tile % 2 == 0
|
| 298 |
+
frg_cnt = cute.size(acc_S_row) // frg_tile
|
| 299 |
+
assert cute.size(acc_S_row) % frg_tile == 0
|
| 300 |
+
acc_S_row_frg = cute.logical_divide(acc_S_row, cute.make_layout(frg_tile))
|
| 301 |
+
acc_S_row_converted_frg = cute.logical_divide(
|
| 302 |
+
acc_S_row_converted, cute.make_layout(frg_tile)
|
| 303 |
+
)
|
| 304 |
+
for j in cutlass.range_constexpr(frg_cnt):
|
| 305 |
+
for k in cutlass.range_constexpr(0, cute.size(acc_S_row_frg, mode=[0]), 2):
|
| 306 |
+
# acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j] = (
|
| 307 |
+
# utils.fma_packed_f32x2(
|
| 308 |
+
# (acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j]),
|
| 309 |
+
# (self.scale_log2, self.scale_log2),
|
| 310 |
+
# (minus_row_max_scaled, minus_row_max_scaled),
|
| 311 |
+
# )
|
| 312 |
+
# )
|
| 313 |
+
# acc_S_row_frg[k, j] = utils.exp2f(acc_S_row_frg[k, j])
|
| 314 |
+
# acc_S_row_frg[k + 1, j] = utils.exp2f(acc_S_row_frg[k + 1, j])
|
| 315 |
+
acc_S_row_frg[k, j] = cute.arch.exp2(acc_S_row_frg[k, j])
|
| 316 |
+
acc_S_row_frg[k + 1, j] = cute.arch.exp2(acc_S_row_frg[k + 1, j])
|
| 317 |
+
acc_S_row_converted_frg[None, j].store(
|
| 318 |
+
acc_S_row_frg[None, j].load().to(acc_S_row_converted.element_type)
|
| 319 |
+
)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
@cute.jit
|
| 323 |
+
def floor_if_packed(
|
| 324 |
+
q_idx,
|
| 325 |
+
qhead_per_kvhead: cutlass.Constexpr[int],
|
| 326 |
+
) -> cute.Tensor:
|
| 327 |
+
"""Convert q_idx to packed format for Pack-GQA."""
|
| 328 |
+
if cutlass.const_expr(qhead_per_kvhead == 1):
|
| 329 |
+
return q_idx
|
| 330 |
+
return q_idx // qhead_per_kvhead
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
@cute.jit
|
| 334 |
+
def apply_score_mod_inner(
|
| 335 |
+
score_tensor,
|
| 336 |
+
index_tensor,
|
| 337 |
+
score_mod: cutlass.Constexpr,
|
| 338 |
+
batch_idx,
|
| 339 |
+
head_idx,
|
| 340 |
+
softmax_scale,
|
| 341 |
+
vec_size: cutlass.Constexpr,
|
| 342 |
+
qk_acc_dtype: cutlass.Constexpr,
|
| 343 |
+
aux_tensors,
|
| 344 |
+
fastdiv_mods,
|
| 345 |
+
seqlen_info: SeqlenInfoQK,
|
| 346 |
+
constant_q_idx: cutlass.Constexpr,
|
| 347 |
+
qhead_per_kvhead: cutlass.Constexpr[int] = 1,
|
| 348 |
+
transpose_indices: cutlass.Constexpr[bool] = False,
|
| 349 |
+
):
|
| 350 |
+
"""Shared implementation for applying score modification.
|
| 351 |
+
|
| 352 |
+
Args:
|
| 353 |
+
score_tensor: The scores to modify (acc_S for flash_fwd, tSrS_t2r for sm100)
|
| 354 |
+
index_tensor: Index positions (tScS for flash_fwd, tScS_t2r for sm100)
|
| 355 |
+
score_mod: The score modification function to apply
|
| 356 |
+
batch_idx: Batch index
|
| 357 |
+
head_idx: Head index
|
| 358 |
+
softmax_scale: Scale to apply
|
| 359 |
+
vec_size: Vector size for processing elements
|
| 360 |
+
qk_acc_dtype: Data type for accumulator
|
| 361 |
+
aux_tensors: Optional aux_tensors for FlexAttention
|
| 362 |
+
fastdiv_mods: Tuple of (seqlen_q_divmod, seqlen_k_divmod) for wrapping
|
| 363 |
+
seqlen_info: Sequence length info
|
| 364 |
+
constant_q_idx: If provided, use this constant for all q_idx values
|
| 365 |
+
If None, compute q_idx per-element
|
| 366 |
+
qhead_per_kvhead_packgqa: Pack-GQA replication factor. Divide q_idx by this
|
| 367 |
+
when greater than 1 so score mods see logical heads.
|
| 368 |
+
transpose_indices: If True, swap q_idx/kv_idx in index_tensor (for bwd kernel where S is transposed)
|
| 369 |
+
"""
|
| 370 |
+
# Index positions in the index_tensor tuple
|
| 371 |
+
# Forward: index_tensor[...][0] = q_idx, index_tensor[...][1] = kv_idx
|
| 372 |
+
# Backward (transposed): index_tensor[...][0] = kv_idx, index_tensor[...][1] = q_idx
|
| 373 |
+
if cutlass.const_expr(transpose_indices):
|
| 374 |
+
q_idx_pos = cutlass.const_expr(1)
|
| 375 |
+
kv_idx_pos = cutlass.const_expr(0)
|
| 376 |
+
else:
|
| 377 |
+
q_idx_pos = cutlass.const_expr(0)
|
| 378 |
+
kv_idx_pos = cutlass.const_expr(1)
|
| 379 |
+
|
| 380 |
+
n_vals = cutlass.const_expr(cute.size(score_tensor.shape))
|
| 381 |
+
score_vec = cute.make_rmem_tensor(vec_size, qk_acc_dtype)
|
| 382 |
+
kv_idx_vec = cute.make_rmem_tensor(vec_size, cutlass.Int32)
|
| 383 |
+
|
| 384 |
+
# SSA values for batch (constant across all elements)
|
| 385 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32).broadcast_to((vec_size,))
|
| 386 |
+
|
| 387 |
+
# Handle q_idx based on whether it's constant
|
| 388 |
+
q_idx_vec = cute.make_rmem_tensor(vec_size, cutlass.Int32)
|
| 389 |
+
|
| 390 |
+
# For Pack-GQA with non-constant q_idx, we need per-element head indices
|
| 391 |
+
# since a thread my process multiple query head indices
|
| 392 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 393 |
+
head_idx_vec = cute.make_rmem_tensor(vec_size, cutlass.Int32)
|
| 394 |
+
|
| 395 |
+
for i in cutlass.range(0, n_vals, vec_size, unroll_full=True):
|
| 396 |
+
for j in cutlass.range(vec_size, unroll_full=True):
|
| 397 |
+
score_vec[j] = score_tensor[i + j] * softmax_scale
|
| 398 |
+
|
| 399 |
+
# Extract head offset from packed q_idx for Pack-GQA
|
| 400 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 401 |
+
q_idx_packed = index_tensor[i + j][q_idx_pos]
|
| 402 |
+
# Building up the logical q_head idx: final_q_head = kv_head * qhead_per_kvhead + (q_physical % qhead_per_kvhead)
|
| 403 |
+
q_idx_logical = q_idx_packed // qhead_per_kvhead
|
| 404 |
+
head_offset = q_idx_packed - q_idx_logical * qhead_per_kvhead
|
| 405 |
+
head_idx_vec[j] = head_idx * qhead_per_kvhead + head_offset
|
| 406 |
+
|
| 407 |
+
# If we will do loads we mod, in order to not read OOB
|
| 408 |
+
if cutlass.const_expr(aux_tensors is not None and fastdiv_mods is not None):
|
| 409 |
+
if cutlass.const_expr(constant_q_idx is None):
|
| 410 |
+
seqlen_q_divmod, seqlen_k_divmod = fastdiv_mods
|
| 411 |
+
q_idx_floored = floor_if_packed(
|
| 412 |
+
index_tensor[i + j][q_idx_pos], qhead_per_kvhead
|
| 413 |
+
)
|
| 414 |
+
_, q_idx_wrapped = divmod(q_idx_floored, seqlen_q_divmod)
|
| 415 |
+
q_idx_vec[j] = q_idx_wrapped
|
| 416 |
+
else:
|
| 417 |
+
_, seqlen_k_divmod = fastdiv_mods
|
| 418 |
+
|
| 419 |
+
_, kv_idx_wrapped = divmod(index_tensor[i + j][kv_idx_pos], seqlen_k_divmod)
|
| 420 |
+
kv_idx_vec[j] = kv_idx_wrapped
|
| 421 |
+
else:
|
| 422 |
+
# No bounds checking - direct indexing
|
| 423 |
+
if constant_q_idx is None:
|
| 424 |
+
q_idx_vec[j] = floor_if_packed(index_tensor[i + j][q_idx_pos], qhead_per_kvhead)
|
| 425 |
+
kv_idx_vec[j] = index_tensor[i + j][kv_idx_pos]
|
| 426 |
+
|
| 427 |
+
# Convert to SSA for score_mod call
|
| 428 |
+
score_ssa = score_vec.load()
|
| 429 |
+
kv_idx_ssa = kv_idx_vec.load()
|
| 430 |
+
if cutlass.const_expr(constant_q_idx is None):
|
| 431 |
+
q_idx_ssa = q_idx_vec.load()
|
| 432 |
+
else:
|
| 433 |
+
# NB we do not apply Pack-GQA division here, as constant_q_idx is assumed to already be logical
|
| 434 |
+
q_idx_const = constant_q_idx
|
| 435 |
+
q_idx_ssa = utils.scalar_to_ssa(q_idx_const, cutlass.Int32).broadcast_to((vec_size,))
|
| 436 |
+
|
| 437 |
+
# Compute head_idx_ssa: per-element for Pack-GQA with non-constant q_idx, constant otherwise
|
| 438 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 439 |
+
head_idx_ssa = head_idx_vec.load()
|
| 440 |
+
else:
|
| 441 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx, cutlass.Int32).broadcast_to((vec_size,))
|
| 442 |
+
|
| 443 |
+
aux_args = []
|
| 444 |
+
if cutlass.const_expr(aux_tensors is not None):
|
| 445 |
+
aux_args = aux_tensors
|
| 446 |
+
|
| 447 |
+
post_mod_scores = score_mod(
|
| 448 |
+
score_ssa,
|
| 449 |
+
batch_idx_ssa,
|
| 450 |
+
head_idx_ssa,
|
| 451 |
+
q_idx=q_idx_ssa,
|
| 452 |
+
kv_idx=kv_idx_ssa,
|
| 453 |
+
seqlen_info=seqlen_info,
|
| 454 |
+
aux_tensors=aux_args,
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
# Write back modified scores
|
| 458 |
+
score_vec.store(post_mod_scores)
|
| 459 |
+
for j in cutlass.range(vec_size, unroll_full=True):
|
| 460 |
+
score_tensor[i + j] = score_vec[j]
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
@cute.jit
|
| 464 |
+
def apply_score_mod_bwd_inner(
|
| 465 |
+
grad_tensor,
|
| 466 |
+
score_tensor,
|
| 467 |
+
index_tensor,
|
| 468 |
+
score_mod_bwd: cutlass.Constexpr,
|
| 469 |
+
batch_idx,
|
| 470 |
+
head_idx,
|
| 471 |
+
softmax_scale,
|
| 472 |
+
vec_size: cutlass.Constexpr,
|
| 473 |
+
qk_acc_dtype: cutlass.Constexpr,
|
| 474 |
+
aux_tensors,
|
| 475 |
+
fastdiv_mods,
|
| 476 |
+
seqlen_info,
|
| 477 |
+
constant_q_idx: cutlass.Constexpr,
|
| 478 |
+
qhead_per_kvhead: cutlass.Constexpr[int] = 1,
|
| 479 |
+
transpose_indices: cutlass.Constexpr[bool] = False,
|
| 480 |
+
):
|
| 481 |
+
"""Apply backward score modification (joint graph).
|
| 482 |
+
|
| 483 |
+
Args:
|
| 484 |
+
grad_tensor: in/out: dlogits rewritten in-place with d(scaled_scores)
|
| 485 |
+
score_tensor: pre-mod scores (unscaled QK tile), scaled by softmax_scale internally
|
| 486 |
+
index_tensor: Index positions (same as forward)
|
| 487 |
+
score_mod_bwd: The backward score modification function (joint graph)
|
| 488 |
+
batch_idx: Batch index
|
| 489 |
+
head_idx: Head index
|
| 490 |
+
softmax_scale: Scale to apply to score_tensor
|
| 491 |
+
vec_size: Vector size for processing elements
|
| 492 |
+
qk_acc_dtype: Data type for accumulator
|
| 493 |
+
aux_tensors: Optional aux_tensors for FlexAttention
|
| 494 |
+
fastdiv_mods: Tuple of (seqlen_q_divmod, seqlen_k_divmod) for wrapping
|
| 495 |
+
seqlen_info: Sequence length info
|
| 496 |
+
constant_q_idx: If provided, use this constant for all q_idx values
|
| 497 |
+
qhead_per_kvhead: Pack-GQA replication factor
|
| 498 |
+
transpose_indices: If True, swap q_idx/kv_idx in index_tensor
|
| 499 |
+
"""
|
| 500 |
+
# Index positions in the index_tensor tuple
|
| 501 |
+
# Forward: index_tensor[...][0] = q_idx, index_tensor[...][1] = kv_idx
|
| 502 |
+
# Backward (transposed): index_tensor[...][0] = kv_idx, index_tensor[...][1] = q_idx
|
| 503 |
+
if cutlass.const_expr(transpose_indices):
|
| 504 |
+
q_idx_pos = cutlass.const_expr(1)
|
| 505 |
+
kv_idx_pos = cutlass.const_expr(0)
|
| 506 |
+
else:
|
| 507 |
+
q_idx_pos = cutlass.const_expr(0)
|
| 508 |
+
kv_idx_pos = cutlass.const_expr(1)
|
| 509 |
+
n_vals = cutlass.const_expr(cute.size(grad_tensor.shape))
|
| 510 |
+
grad_vec = cute.make_fragment(vec_size, qk_acc_dtype)
|
| 511 |
+
score_vec = cute.make_fragment(vec_size, qk_acc_dtype)
|
| 512 |
+
kv_idx_vec = cute.make_fragment(vec_size, cutlass.Int32)
|
| 513 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32).broadcast_to((vec_size,))
|
| 514 |
+
q_idx_vec = cute.make_fragment(vec_size, cutlass.Int32)
|
| 515 |
+
|
| 516 |
+
# For Pack-GQA with non-constant q_idx, we need per-element head indices
|
| 517 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 518 |
+
head_idx_vec = cute.make_fragment(vec_size, cutlass.Int32)
|
| 519 |
+
|
| 520 |
+
for i in cutlass.range(0, n_vals, vec_size, unroll_full=True):
|
| 521 |
+
for j in cutlass.range(vec_size, unroll_full=True):
|
| 522 |
+
grad_vec[j] = grad_tensor[i + j]
|
| 523 |
+
# Scale score so joint graph sees same value as forward score_mod
|
| 524 |
+
score_vec[j] = score_tensor[i + j] * softmax_scale
|
| 525 |
+
|
| 526 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 527 |
+
q_idx_packed = index_tensor[i + j][q_idx_pos]
|
| 528 |
+
q_idx_logical = q_idx_packed // qhead_per_kvhead
|
| 529 |
+
head_offset = q_idx_packed - q_idx_logical * qhead_per_kvhead
|
| 530 |
+
head_idx_vec[j] = head_idx * qhead_per_kvhead + head_offset
|
| 531 |
+
|
| 532 |
+
if cutlass.const_expr(aux_tensors is not None and fastdiv_mods is not None):
|
| 533 |
+
if cutlass.const_expr(constant_q_idx is None):
|
| 534 |
+
seqlen_q_divmod, seqlen_k_divmod = fastdiv_mods
|
| 535 |
+
q_idx_floored = floor_if_packed(
|
| 536 |
+
index_tensor[i + j][q_idx_pos], qhead_per_kvhead
|
| 537 |
+
)
|
| 538 |
+
_, q_idx_wrapped = divmod(q_idx_floored, seqlen_q_divmod)
|
| 539 |
+
q_idx_vec[j] = q_idx_wrapped
|
| 540 |
+
else:
|
| 541 |
+
_, seqlen_k_divmod = fastdiv_mods
|
| 542 |
+
|
| 543 |
+
_, kv_idx_wrapped = divmod(index_tensor[i + j][kv_idx_pos], seqlen_k_divmod)
|
| 544 |
+
kv_idx_vec[j] = kv_idx_wrapped
|
| 545 |
+
else:
|
| 546 |
+
# No bounds checking - direct indexing
|
| 547 |
+
if constant_q_idx is None:
|
| 548 |
+
q_idx_vec[j] = floor_if_packed(index_tensor[i + j][q_idx_pos], qhead_per_kvhead)
|
| 549 |
+
kv_idx_vec[j] = index_tensor[i + j][kv_idx_pos]
|
| 550 |
+
|
| 551 |
+
grad_ssa = grad_vec.load()
|
| 552 |
+
score_ssa = score_vec.load()
|
| 553 |
+
kv_idx_ssa = kv_idx_vec.load()
|
| 554 |
+
|
| 555 |
+
if cutlass.const_expr(constant_q_idx is None):
|
| 556 |
+
q_idx_ssa = q_idx_vec.load()
|
| 557 |
+
else:
|
| 558 |
+
q_idx_ssa = utils.scalar_to_ssa(constant_q_idx, cutlass.Int32).broadcast_to((vec_size,))
|
| 559 |
+
|
| 560 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 561 |
+
head_idx_ssa = head_idx_vec.load()
|
| 562 |
+
else:
|
| 563 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx, cutlass.Int32).broadcast_to((vec_size,))
|
| 564 |
+
|
| 565 |
+
aux_args = []
|
| 566 |
+
if cutlass.const_expr(aux_tensors is not None):
|
| 567 |
+
aux_args = aux_tensors
|
| 568 |
+
|
| 569 |
+
grad_out_ssa = score_mod_bwd(
|
| 570 |
+
grad_ssa,
|
| 571 |
+
score_ssa,
|
| 572 |
+
batch_idx_ssa,
|
| 573 |
+
head_idx_ssa,
|
| 574 |
+
q_idx=q_idx_ssa,
|
| 575 |
+
kv_idx=kv_idx_ssa,
|
| 576 |
+
seqlen_info=seqlen_info,
|
| 577 |
+
aux_tensors=aux_args,
|
| 578 |
+
)
|
| 579 |
+
|
| 580 |
+
grad_vec.store(grad_out_ssa)
|
| 581 |
+
for j in cutlass.range(vec_size, unroll_full=True):
|
| 582 |
+
grad_tensor[i + j] = grad_vec[j]
|
kernel/deps/flash_attn/cute/tile_scheduler.py
ADDED
|
@@ -0,0 +1,719 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
from typing import Optional, Tuple
|
| 4 |
+
from dataclasses import dataclass, fields
|
| 5 |
+
|
| 6 |
+
try:
|
| 7 |
+
from typing import override
|
| 8 |
+
except ImportError: # Python < 3.12
|
| 9 |
+
from typing_extensions import override
|
| 10 |
+
|
| 11 |
+
import cutlass
|
| 12 |
+
from cutlass._mlir import ir
|
| 13 |
+
import cutlass.cute as cute
|
| 14 |
+
from cutlass import Int32, const_expr
|
| 15 |
+
|
| 16 |
+
import flash_attn.cute.utils as utils
|
| 17 |
+
from flash_attn.cute.fast_math import clz
|
| 18 |
+
from cutlass.cute import FastDivmodDivisor
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class WorkTileInfo(cutlass.utils.WorkTileInfo):
|
| 22 |
+
"""Altered WorkTileInfo which includes four axes: (block, head, batch, split)"""
|
| 23 |
+
|
| 24 |
+
@override
|
| 25 |
+
def __new_from_mlir_values__(self, values: list[ir.Value]) -> "WorkTileInfo":
|
| 26 |
+
assert len(values) == 5
|
| 27 |
+
new_tile_idx = cutlass.new_from_mlir_values(self._tile_idx, values[:-1])
|
| 28 |
+
new_is_valid_tile = cutlass.new_from_mlir_values(self._is_valid_tile, [values[-1]])
|
| 29 |
+
return WorkTileInfo(new_tile_idx, new_is_valid_tile)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@dataclass
|
| 33 |
+
class ParamsBase:
|
| 34 |
+
def __extract_mlir_values__(self):
|
| 35 |
+
all_fields = [getattr(self, field.name) for field in fields(self)]
|
| 36 |
+
non_constexpr_fields = [f for f in all_fields if not isinstance(f, cutlass.Constexpr)]
|
| 37 |
+
values, self._values_pos = [], []
|
| 38 |
+
for obj in non_constexpr_fields:
|
| 39 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 40 |
+
values += obj_values
|
| 41 |
+
self._values_pos.append(len(obj_values))
|
| 42 |
+
return values
|
| 43 |
+
|
| 44 |
+
def __new_from_mlir_values__(self, values):
|
| 45 |
+
all_fields = {field.name: getattr(self, field.name) for field in fields(self)}
|
| 46 |
+
constexpr_fields = {n: f for n, f in all_fields.items() if isinstance(f, cutlass.Constexpr)}
|
| 47 |
+
non_constexpr_fields = {
|
| 48 |
+
n: f for n, f in all_fields.items() if not isinstance(f, cutlass.Constexpr)
|
| 49 |
+
}
|
| 50 |
+
for (name, field), n_items in zip(non_constexpr_fields.items(), self._values_pos):
|
| 51 |
+
non_constexpr_fields[name] = cutlass.new_from_mlir_values(field, values[:n_items])
|
| 52 |
+
values = values[n_items:]
|
| 53 |
+
return self.__class__(**non_constexpr_fields, **constexpr_fields)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
@dataclass
|
| 57 |
+
class TileSchedulerArguments(ParamsBase):
|
| 58 |
+
num_block: Int32
|
| 59 |
+
num_head: Int32
|
| 60 |
+
num_batch: Int32
|
| 61 |
+
num_splits: Int32
|
| 62 |
+
seqlen_k: Int32
|
| 63 |
+
headdim: Int32
|
| 64 |
+
headdim_v: Int32
|
| 65 |
+
total_q: Int32
|
| 66 |
+
tile_shape_mn: cutlass.Constexpr[Tuple[int, int]]
|
| 67 |
+
cluster_shape_mn: cutlass.Constexpr[Tuple[int, int]] = (1, 1)
|
| 68 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None
|
| 69 |
+
mSeqUsedQ: Optional[cute.Tensor] = None
|
| 70 |
+
qhead_per_kvhead_packgqa: cutlass.Constexpr[int] = 1
|
| 71 |
+
element_size: cutlass.Constexpr[int] = 2
|
| 72 |
+
is_persistent: cutlass.Constexpr[bool] = False
|
| 73 |
+
lpt: cutlass.Constexpr[bool] = False
|
| 74 |
+
is_split_kv: cutlass.Constexpr[bool] = False
|
| 75 |
+
head_swizzle: cutlass.Constexpr[bool] = False
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class SingleTileScheduler:
|
| 79 |
+
@dataclass
|
| 80 |
+
class Params(ParamsBase):
|
| 81 |
+
num_block: Int32
|
| 82 |
+
num_head: Int32
|
| 83 |
+
num_batch: Int32
|
| 84 |
+
num_splits: Int32
|
| 85 |
+
num_splits_divmod: FastDivmodDivisor
|
| 86 |
+
is_split_kv: cutlass.Constexpr[bool] = False
|
| 87 |
+
cluster_shape_mn: cutlass.Constexpr[Tuple[int, int]] = (1, 1)
|
| 88 |
+
|
| 89 |
+
@staticmethod
|
| 90 |
+
def create(
|
| 91 |
+
args: TileSchedulerArguments, *, loc=None, ip=None
|
| 92 |
+
) -> "SingleTileScheduler.Params":
|
| 93 |
+
return SingleTileScheduler.Params(
|
| 94 |
+
args.num_block,
|
| 95 |
+
args.num_head,
|
| 96 |
+
args.num_batch,
|
| 97 |
+
args.num_splits,
|
| 98 |
+
FastDivmodDivisor(args.num_splits),
|
| 99 |
+
args.is_split_kv,
|
| 100 |
+
args.cluster_shape_mn,
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
def __init__(self, params: Params, blk_coord: cute.Coord, *, loc=None, ip=None):
|
| 104 |
+
self.params = params
|
| 105 |
+
self._blk_coord = blk_coord
|
| 106 |
+
self._is_first_block = True
|
| 107 |
+
self._loc = loc
|
| 108 |
+
self._ip = ip
|
| 109 |
+
|
| 110 |
+
@staticmethod
|
| 111 |
+
def to_underlying_arguments(args: TileSchedulerArguments, *, loc=None, ip=None) -> Params:
|
| 112 |
+
return SingleTileScheduler.Params.create(args, loc=loc, ip=ip)
|
| 113 |
+
|
| 114 |
+
@staticmethod
|
| 115 |
+
def create(params: Params, *, loc=None, ip=None) -> "SingleTileScheduler":
|
| 116 |
+
blk_coord = cute.arch.block_idx()
|
| 117 |
+
return SingleTileScheduler(params, blk_coord, loc=loc, ip=ip)
|
| 118 |
+
|
| 119 |
+
# called by host
|
| 120 |
+
@staticmethod
|
| 121 |
+
def get_grid_shape(
|
| 122 |
+
params: Params,
|
| 123 |
+
*,
|
| 124 |
+
loc=None,
|
| 125 |
+
ip=None,
|
| 126 |
+
) -> Tuple[Int32, Int32, Int32]:
|
| 127 |
+
# TODO: this hard-codes the fact that we only use cluster = (1, 1) or (2, 1)
|
| 128 |
+
assert params.cluster_shape_mn[1] == 1, "Only cluster_shape_mn[1] == 1 is supported"
|
| 129 |
+
return (
|
| 130 |
+
cute.round_up(params.num_block, params.cluster_shape_mn[0]),
|
| 131 |
+
params.num_head * params.num_splits,
|
| 132 |
+
params.num_batch,
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
def get_current_work(self, *, loc=None, ip=None) -> WorkTileInfo:
|
| 136 |
+
block_idx, head_idx, batch_idx = self._blk_coord
|
| 137 |
+
if const_expr(self.params.is_split_kv):
|
| 138 |
+
head_idx, split_idx = divmod(head_idx, self.params.num_splits_divmod)
|
| 139 |
+
else:
|
| 140 |
+
split_idx = Int32(0)
|
| 141 |
+
return WorkTileInfo(
|
| 142 |
+
(block_idx, head_idx, batch_idx, split_idx),
|
| 143 |
+
self._is_first_block,
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 147 |
+
return self.get_current_work(loc=loc, ip=ip)
|
| 148 |
+
|
| 149 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 150 |
+
pass
|
| 151 |
+
|
| 152 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 153 |
+
self._is_first_block = False
|
| 154 |
+
|
| 155 |
+
def __extract_mlir_values__(self):
|
| 156 |
+
values, self._values_pos = [], []
|
| 157 |
+
for obj in [self.params, self._blk_coord]:
|
| 158 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 159 |
+
values += obj_values
|
| 160 |
+
self._values_pos.append(len(obj_values))
|
| 161 |
+
return values
|
| 162 |
+
|
| 163 |
+
def __new_from_mlir_values__(self, values):
|
| 164 |
+
obj_list = []
|
| 165 |
+
for obj, n_items in zip([self.params, self._blk_coord], self._values_pos):
|
| 166 |
+
obj_list.append(cutlass.new_from_mlir_values(obj, values[:n_items]))
|
| 167 |
+
values = values[n_items:]
|
| 168 |
+
return SingleTileScheduler(*(tuple(obj_list)), loc=self._loc)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
class StaticPersistentTileScheduler:
|
| 172 |
+
@dataclass
|
| 173 |
+
class Params(ParamsBase):
|
| 174 |
+
num_block_divmod: FastDivmodDivisor
|
| 175 |
+
num_head_divmod: FastDivmodDivisor
|
| 176 |
+
total_blocks: Int32
|
| 177 |
+
|
| 178 |
+
@staticmethod
|
| 179 |
+
def create(
|
| 180 |
+
args: TileSchedulerArguments, *, loc=None, ip=None
|
| 181 |
+
) -> "StaticPersistentTileScheduler.Params":
|
| 182 |
+
total_blocks = args.num_block * args.num_head * args.num_batch
|
| 183 |
+
return StaticPersistentTileScheduler.Params(
|
| 184 |
+
FastDivmodDivisor(args.num_block), FastDivmodDivisor(args.num_head), total_blocks
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
def __init__(self, params: Params, tile_idx: Int32, *, loc=None, ip=None):
|
| 188 |
+
self.params = params
|
| 189 |
+
self._tile_idx = tile_idx
|
| 190 |
+
self._loc = loc
|
| 191 |
+
self._ip = ip
|
| 192 |
+
|
| 193 |
+
@staticmethod
|
| 194 |
+
def to_underlying_arguments(args: TileSchedulerArguments, *, loc=None, ip=None) -> Params:
|
| 195 |
+
return StaticPersistentTileScheduler.Params.create(args, loc=loc, ip=ip)
|
| 196 |
+
|
| 197 |
+
@staticmethod
|
| 198 |
+
def create(params: Params, *, loc=None, ip=None) -> "StaticPersistentTileScheduler":
|
| 199 |
+
tile_idx = cute.arch.block_idx()[0]
|
| 200 |
+
return StaticPersistentTileScheduler(params, tile_idx, loc=loc, ip=ip)
|
| 201 |
+
|
| 202 |
+
# called by host
|
| 203 |
+
@staticmethod
|
| 204 |
+
def get_grid_shape(
|
| 205 |
+
params: Params,
|
| 206 |
+
*,
|
| 207 |
+
loc=None,
|
| 208 |
+
ip=None,
|
| 209 |
+
) -> Tuple[Int32, Int32, Int32]:
|
| 210 |
+
hardware_info = cutlass.utils.HardwareInfo()
|
| 211 |
+
sm_count = hardware_info.get_device_multiprocessor_count()
|
| 212 |
+
return (cutlass.min(sm_count, params.total_blocks), Int32(1), Int32(1))
|
| 213 |
+
|
| 214 |
+
# @cute.jit
|
| 215 |
+
def get_current_work(self, *, loc=None, ip=None) -> WorkTileInfo:
|
| 216 |
+
hn_idx, block_idx = divmod(self._tile_idx, self.params.num_block_divmod)
|
| 217 |
+
batch_idx, head_idx = divmod(hn_idx, self.params.num_head_divmod)
|
| 218 |
+
is_valid = self._tile_idx < self.params.total_blocks
|
| 219 |
+
# if cute.arch.thread_idx()[0] == 0:
|
| 220 |
+
# cute.printf("TileScheduler: tile_idx=%d, hn_idx=%d, block_idx=%d, batch_idx=%d, head_idx=%d, is_valid=%d", self._tile_idx, hn_idx, block_idx, batch_idx, head_idx, is_valid)
|
| 221 |
+
return WorkTileInfo(
|
| 222 |
+
(Int32(block_idx), Int32(head_idx), Int32(batch_idx), Int32(0)), is_valid
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 226 |
+
return self.get_current_work(loc=loc, ip=ip)
|
| 227 |
+
|
| 228 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 229 |
+
pass
|
| 230 |
+
|
| 231 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 232 |
+
self._tile_idx += cute.arch.grid_dim()[0]
|
| 233 |
+
|
| 234 |
+
def __extract_mlir_values__(self):
|
| 235 |
+
values, self._values_pos = [], []
|
| 236 |
+
for obj in [self.params, self._tile_idx]:
|
| 237 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 238 |
+
values += obj_values
|
| 239 |
+
self._values_pos.append(len(obj_values))
|
| 240 |
+
return values
|
| 241 |
+
|
| 242 |
+
def __new_from_mlir_values__(self, values):
|
| 243 |
+
obj_list = []
|
| 244 |
+
for obj, n_items in zip(
|
| 245 |
+
[self.params, self._tile_idx],
|
| 246 |
+
self._values_pos,
|
| 247 |
+
):
|
| 248 |
+
obj_list.append(cutlass.new_from_mlir_values(obj, values[:n_items]))
|
| 249 |
+
values = values[n_items:]
|
| 250 |
+
return StaticPersistentTileScheduler(*(tuple(obj_list)), loc=self._loc)
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
class SingleTileLPTScheduler:
|
| 254 |
+
@dataclass
|
| 255 |
+
class Params(ParamsBase):
|
| 256 |
+
total_blocks: Int32
|
| 257 |
+
num_splits: Int32
|
| 258 |
+
num_block: Int32
|
| 259 |
+
l2_minor: Int32
|
| 260 |
+
num_block_divmod: FastDivmodDivisor
|
| 261 |
+
num_head_divmod: FastDivmodDivisor
|
| 262 |
+
l2_minor_divmod: FastDivmodDivisor
|
| 263 |
+
l2_major_divmod: FastDivmodDivisor
|
| 264 |
+
l2_minor_residual_divmod: FastDivmodDivisor
|
| 265 |
+
num_hb_quotient: Int32
|
| 266 |
+
is_split_kv: cutlass.Constexpr[bool] = False
|
| 267 |
+
|
| 268 |
+
@staticmethod
|
| 269 |
+
@cute.jit
|
| 270 |
+
def create(
|
| 271 |
+
args: TileSchedulerArguments, *, loc=None, ip=None
|
| 272 |
+
) -> "SingleTileLPTScheduler.Params":
|
| 273 |
+
# cute.printf(args.num_block, args.num_head, args.num_batch, args.seqlen_k, args.headdim, args.headdim_v, args.total_q, args.tile_shape_mn, args.qhead_per_kvhead_packgqa, args.element_size)
|
| 274 |
+
size_one_kv_head = args.seqlen_k * (args.headdim + args.headdim_v) * args.element_size
|
| 275 |
+
size_one_head = size_one_kv_head
|
| 276 |
+
size_l2 = 50 * 1024 * 1024 # 40 MB for K & V
|
| 277 |
+
# Swizzle is the size of each "section". Round swizzle to a power of 2
|
| 278 |
+
# Need to be careful about the case where only one head will fit
|
| 279 |
+
# swizzle is how many heads can fit in L2
|
| 280 |
+
# swizzle = 1 if size_l2 < size_one_head else (size_l2 // size_one_head)
|
| 281 |
+
# Seems faster if swizzle if a power of 2
|
| 282 |
+
log2_floor = lambda n: 31 - clz(n)
|
| 283 |
+
swizzle = 1 if size_l2 < size_one_head else (1 << log2_floor(size_l2 // size_one_head))
|
| 284 |
+
# swizzle = 1 if size_l2 < size_one_head else (size_l2 // size_one_head)
|
| 285 |
+
# If we're in the last section (called residual), we don't want to divide by
|
| 286 |
+
# swizzle. Instead we want to divide by the remainder.
|
| 287 |
+
num_hb_quotient = (args.num_head * args.num_batch) // swizzle
|
| 288 |
+
num_hb_remainder = (args.num_head * args.num_batch) % swizzle
|
| 289 |
+
return SingleTileLPTScheduler.Params(
|
| 290 |
+
total_blocks=args.num_block * args.num_head * args.num_batch,
|
| 291 |
+
num_block=args.num_block,
|
| 292 |
+
l2_minor=Int32(swizzle),
|
| 293 |
+
num_block_divmod=FastDivmodDivisor(args.num_block),
|
| 294 |
+
num_head_divmod=FastDivmodDivisor(args.num_head),
|
| 295 |
+
l2_minor_divmod=FastDivmodDivisor(swizzle),
|
| 296 |
+
l2_major_divmod=FastDivmodDivisor(swizzle * args.num_block),
|
| 297 |
+
l2_minor_residual_divmod=FastDivmodDivisor(
|
| 298 |
+
max(num_hb_remainder, 1)
|
| 299 |
+
), # don't divide by 0
|
| 300 |
+
num_hb_quotient=Int32(num_hb_quotient),
|
| 301 |
+
num_splits=args.num_splits,
|
| 302 |
+
is_split_kv=args.is_split_kv,
|
| 303 |
+
)
|
| 304 |
+
|
| 305 |
+
def __init__(self, params: Params, tile_idx: Int32, split_idx: Int32, *, loc=None, ip=None):
|
| 306 |
+
self.params = params
|
| 307 |
+
self._tile_idx = tile_idx
|
| 308 |
+
self._split_idx = split_idx
|
| 309 |
+
self._loc = loc
|
| 310 |
+
self._ip = ip
|
| 311 |
+
|
| 312 |
+
@staticmethod
|
| 313 |
+
def to_underlying_arguments(args: TileSchedulerArguments, *, loc=None, ip=None) -> Params:
|
| 314 |
+
return SingleTileLPTScheduler.Params.create(args, loc=loc, ip=ip)
|
| 315 |
+
|
| 316 |
+
@staticmethod
|
| 317 |
+
@cute.jit
|
| 318 |
+
def create(params: Params, *, loc=None, ip=None) -> "SingleTileLPTScheduler":
|
| 319 |
+
tile_idx, split_idx, _ = cute.arch.block_idx()
|
| 320 |
+
return SingleTileLPTScheduler(params, tile_idx, split_idx, loc=loc, ip=ip)
|
| 321 |
+
|
| 322 |
+
# called by host
|
| 323 |
+
@staticmethod
|
| 324 |
+
def get_grid_shape(
|
| 325 |
+
params: Params,
|
| 326 |
+
*,
|
| 327 |
+
loc=None,
|
| 328 |
+
ip=None,
|
| 329 |
+
) -> Tuple[Int32, Int32, Int32]:
|
| 330 |
+
return (params.total_blocks, params.num_splits, Int32(1))
|
| 331 |
+
|
| 332 |
+
@cute.jit
|
| 333 |
+
def get_current_work(self, *, loc=None, ip=None) -> WorkTileInfo:
|
| 334 |
+
params = self.params
|
| 335 |
+
# Implement LPT scheduling coordinate calculation
|
| 336 |
+
bidhb, l2_mod = divmod(self._tile_idx, params.l2_major_divmod)
|
| 337 |
+
# If we're in the last section (called residual), we don't want to divide by
|
| 338 |
+
# swizzle. Instead we want to divide by the remainder.
|
| 339 |
+
block, bidhb_residual = 0, 0
|
| 340 |
+
if bidhb < params.num_hb_quotient:
|
| 341 |
+
block, bidhb_residual = divmod(l2_mod, params.l2_minor_divmod)
|
| 342 |
+
else:
|
| 343 |
+
block, bidhb_residual = divmod(l2_mod, params.l2_minor_residual_divmod)
|
| 344 |
+
bidhb_actual = bidhb * params.l2_minor + bidhb_residual
|
| 345 |
+
batch_idx, head_idx = divmod(bidhb_actual, params.num_head_divmod)
|
| 346 |
+
# Longest-processing-time-first
|
| 347 |
+
block = params.num_block - 1 - block
|
| 348 |
+
is_valid = self._tile_idx < params.total_blocks
|
| 349 |
+
return WorkTileInfo(
|
| 350 |
+
(Int32(block), Int32(head_idx), Int32(batch_idx), Int32(self._split_idx)), is_valid
|
| 351 |
+
)
|
| 352 |
+
|
| 353 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 354 |
+
return self.get_current_work(loc=loc, ip=ip)
|
| 355 |
+
|
| 356 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 357 |
+
pass
|
| 358 |
+
|
| 359 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 360 |
+
# Single tile scheduler - set to invalid tile_idx to indicate no more work
|
| 361 |
+
self._tile_idx = self.params.total_blocks
|
| 362 |
+
|
| 363 |
+
def __extract_mlir_values__(self):
|
| 364 |
+
values, self._values_pos = [], []
|
| 365 |
+
for obj in [self.params, self._tile_idx, self._split_idx]:
|
| 366 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 367 |
+
values += obj_values
|
| 368 |
+
self._values_pos.append(len(obj_values))
|
| 369 |
+
return values
|
| 370 |
+
|
| 371 |
+
def __new_from_mlir_values__(self, values):
|
| 372 |
+
obj_list = []
|
| 373 |
+
for obj, n_items in zip([self.params, self._tile_idx, self._split_idx], self._values_pos):
|
| 374 |
+
obj_list.append(cutlass.new_from_mlir_values(obj, values[:n_items]))
|
| 375 |
+
values = values[n_items:]
|
| 376 |
+
return self.__class__(*(tuple(obj_list)), loc=self._loc)
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
class SingleTileLPTBwdScheduler:
|
| 380 |
+
@dataclass
|
| 381 |
+
class Params(ParamsBase):
|
| 382 |
+
total_blocks: Int32
|
| 383 |
+
num_block: Int32
|
| 384 |
+
l2_minor: Int32
|
| 385 |
+
num_head_divmod: FastDivmodDivisor
|
| 386 |
+
l2_minor_divmod: FastDivmodDivisor
|
| 387 |
+
l2_major_divmod: FastDivmodDivisor
|
| 388 |
+
l2_minor_residual_divmod: FastDivmodDivisor
|
| 389 |
+
num_hb_quotient: Int32
|
| 390 |
+
cluster_shape_mn: cutlass.Constexpr[Tuple[int, int]] = (1, 1)
|
| 391 |
+
spt: cutlass.Constexpr[bool] = True
|
| 392 |
+
|
| 393 |
+
@staticmethod
|
| 394 |
+
@cute.jit
|
| 395 |
+
def create(
|
| 396 |
+
args: TileSchedulerArguments, *, loc=None, ip=None
|
| 397 |
+
) -> "SingleTileLPTBwdScheduler.Params":
|
| 398 |
+
size_l2 = 50 * 1024 * 1024
|
| 399 |
+
size_one_qdo_head = args.seqlen_k * (args.headdim + args.headdim_v) * args.element_size
|
| 400 |
+
# size_one_dqaccum_head = args.seqlen_k * (args.headdim) * 4
|
| 401 |
+
size_one_dqaccum_head = 0
|
| 402 |
+
size_one_head = size_one_qdo_head + size_one_dqaccum_head
|
| 403 |
+
log2_floor = lambda n: 31 - clz(n)
|
| 404 |
+
swizzle = 1 if size_l2 < size_one_head else (1 << log2_floor(size_l2 // size_one_head))
|
| 405 |
+
# swizzle = 8
|
| 406 |
+
# If we're in the last section (called residual), we don't want to divide by
|
| 407 |
+
# swizzle. Instead we want to divide by the remainder.
|
| 408 |
+
num_hb_quotient = (args.num_head * args.num_batch) // swizzle
|
| 409 |
+
num_hb_remainder = (args.num_head * args.num_batch) % swizzle
|
| 410 |
+
num_block = cute.ceil_div(args.num_block, args.cluster_shape_mn[0])
|
| 411 |
+
return SingleTileLPTBwdScheduler.Params(
|
| 412 |
+
total_blocks=(num_block * args.cluster_shape_mn[0])
|
| 413 |
+
* args.num_head
|
| 414 |
+
* args.num_batch,
|
| 415 |
+
num_block=num_block,
|
| 416 |
+
l2_minor=Int32(swizzle),
|
| 417 |
+
num_head_divmod=FastDivmodDivisor(args.num_head),
|
| 418 |
+
l2_minor_divmod=FastDivmodDivisor(swizzle),
|
| 419 |
+
l2_major_divmod=FastDivmodDivisor(swizzle * num_block),
|
| 420 |
+
l2_minor_residual_divmod=FastDivmodDivisor(
|
| 421 |
+
max(num_hb_remainder, 1)
|
| 422 |
+
), # don't divide by 0
|
| 423 |
+
num_hb_quotient=Int32(num_hb_quotient),
|
| 424 |
+
cluster_shape_mn=args.cluster_shape_mn,
|
| 425 |
+
spt=args.lpt,
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
def __init__(self, params: Params, tile_idx: Int32, *, loc=None, ip=None):
|
| 429 |
+
self.params = params
|
| 430 |
+
self._tile_idx = tile_idx
|
| 431 |
+
self._loc = loc
|
| 432 |
+
self._ip = ip
|
| 433 |
+
|
| 434 |
+
@staticmethod
|
| 435 |
+
def to_underlying_arguments(args: TileSchedulerArguments, *, loc=None, ip=None) -> Params:
|
| 436 |
+
return SingleTileLPTBwdScheduler.Params.create(args, loc=loc, ip=ip)
|
| 437 |
+
|
| 438 |
+
@staticmethod
|
| 439 |
+
@cute.jit
|
| 440 |
+
def create(params: Params, *, loc=None, ip=None) -> "SingleTileLPTBwdScheduler":
|
| 441 |
+
tile_idx = cute.arch.block_idx()[0]
|
| 442 |
+
return SingleTileLPTBwdScheduler(params, tile_idx, loc=loc, ip=ip)
|
| 443 |
+
|
| 444 |
+
# called by host
|
| 445 |
+
@staticmethod
|
| 446 |
+
def get_grid_shape(
|
| 447 |
+
params: Params,
|
| 448 |
+
*,
|
| 449 |
+
loc=None,
|
| 450 |
+
ip=None,
|
| 451 |
+
) -> Tuple[Int32, Int32, Int32]:
|
| 452 |
+
return (params.total_blocks, Int32(1), Int32(1))
|
| 453 |
+
|
| 454 |
+
@cute.jit
|
| 455 |
+
def get_current_work(self, *, loc=None, ip=None) -> cutlass.utils.WorkTileInfo:
|
| 456 |
+
cluster_idx = self._tile_idx // self.params.cluster_shape_mn[0]
|
| 457 |
+
params = self.params
|
| 458 |
+
# Implement LPT scheduling coordinate calculation
|
| 459 |
+
bidhb, l2_mod = divmod(cluster_idx, params.l2_major_divmod)
|
| 460 |
+
# If we're in the last section (called residual), we don't want to divide by
|
| 461 |
+
# swizzle. Instead we want to divide by the remainder.
|
| 462 |
+
block, bidhb_residual = 0, 0
|
| 463 |
+
if bidhb < params.num_hb_quotient:
|
| 464 |
+
block, bidhb_residual = divmod(l2_mod, params.l2_minor_divmod)
|
| 465 |
+
else:
|
| 466 |
+
block, bidhb_residual = divmod(l2_mod, params.l2_minor_residual_divmod)
|
| 467 |
+
bidhb_actual = bidhb * params.l2_minor + bidhb_residual
|
| 468 |
+
batch_idx, head_idx = divmod(bidhb_actual, params.num_head_divmod)
|
| 469 |
+
is_valid = self._tile_idx < params.total_blocks
|
| 470 |
+
bidx_in_cluster = cute.arch.block_in_cluster_idx()
|
| 471 |
+
block = block * params.cluster_shape_mn[0] + bidx_in_cluster[0]
|
| 472 |
+
if cutlass.const_expr(params.spt):
|
| 473 |
+
block = params.num_block - 1 - block
|
| 474 |
+
return WorkTileInfo((Int32(block), Int32(head_idx), Int32(batch_idx), Int32(0)), is_valid)
|
| 475 |
+
|
| 476 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 477 |
+
return self.get_current_work(loc=loc, ip=ip)
|
| 478 |
+
|
| 479 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 480 |
+
pass
|
| 481 |
+
|
| 482 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 483 |
+
# Single tile scheduler - set to invalid tile_idx to indicate no more work
|
| 484 |
+
self._tile_idx = self.params.total_blocks
|
| 485 |
+
|
| 486 |
+
def __extract_mlir_values__(self):
|
| 487 |
+
values, self._values_pos = [], []
|
| 488 |
+
for obj in [self.params, self._tile_idx]:
|
| 489 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 490 |
+
values += obj_values
|
| 491 |
+
self._values_pos.append(len(obj_values))
|
| 492 |
+
return values
|
| 493 |
+
|
| 494 |
+
def __new_from_mlir_values__(self, values):
|
| 495 |
+
obj_list = []
|
| 496 |
+
for obj, n_items in zip([self.params, self._tile_idx], self._values_pos):
|
| 497 |
+
obj_list.append(cutlass.new_from_mlir_values(obj, values[:n_items]))
|
| 498 |
+
values = values[n_items:]
|
| 499 |
+
return self.__class__(*(tuple(obj_list)), loc=self._loc)
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
class SingleTileVarlenScheduler:
|
| 503 |
+
@dataclass
|
| 504 |
+
class Params(ParamsBase):
|
| 505 |
+
num_head: Int32
|
| 506 |
+
num_batch: Int32
|
| 507 |
+
total_q: Int32
|
| 508 |
+
num_splits: Int32
|
| 509 |
+
max_kvblock_in_l2: Int32
|
| 510 |
+
tile_shape_mn: cutlass.Constexpr[Tuple[int, int]]
|
| 511 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None
|
| 512 |
+
mSeqUsedQ: Optional[cute.Tensor] = None
|
| 513 |
+
qhead_per_kvhead_packgqa: cutlass.Constexpr[int] = 1
|
| 514 |
+
lpt: cutlass.Constexpr[bool] = False
|
| 515 |
+
is_split_kv: cutlass.Constexpr[bool] = False
|
| 516 |
+
head_swizzle: cutlass.Constexpr[bool] = False
|
| 517 |
+
|
| 518 |
+
@staticmethod
|
| 519 |
+
@cute.jit
|
| 520 |
+
def create(
|
| 521 |
+
args: TileSchedulerArguments, *, loc=None, ip=None
|
| 522 |
+
) -> "SingleTileVarlenScheduler.Params":
|
| 523 |
+
size_l2 = 50 * 1024 * 1024 # 50 MB for K & V
|
| 524 |
+
max_kvblock_in_l2 = size_l2 // (
|
| 525 |
+
(args.headdim + args.headdim_v) * args.element_size * args.tile_shape_mn[1]
|
| 526 |
+
)
|
| 527 |
+
assert args.mCuSeqlensQ is not None or args.mSeqUsedQ is not None, (
|
| 528 |
+
"At least one of mCuSeqlensQ or mSeqUsedQ must be provided"
|
| 529 |
+
)
|
| 530 |
+
return SingleTileVarlenScheduler.Params(
|
| 531 |
+
num_head=args.num_head,
|
| 532 |
+
num_batch=args.num_batch,
|
| 533 |
+
total_q=args.total_q,
|
| 534 |
+
num_splits=args.num_splits,
|
| 535 |
+
max_kvblock_in_l2=max_kvblock_in_l2,
|
| 536 |
+
tile_shape_mn=args.tile_shape_mn,
|
| 537 |
+
mCuSeqlensQ=args.mCuSeqlensQ,
|
| 538 |
+
mSeqUsedQ=args.mSeqUsedQ,
|
| 539 |
+
qhead_per_kvhead_packgqa=args.qhead_per_kvhead_packgqa,
|
| 540 |
+
lpt=args.lpt,
|
| 541 |
+
is_split_kv=args.is_split_kv,
|
| 542 |
+
head_swizzle=args.head_swizzle,
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
def __init__(self, params: Params, tile_idx: Int32, split_idx: Int32, *, loc=None, ip=None):
|
| 546 |
+
self.params = params
|
| 547 |
+
self._tile_idx = tile_idx
|
| 548 |
+
self._split_idx = split_idx
|
| 549 |
+
self._is_first_block = True
|
| 550 |
+
self._loc = loc
|
| 551 |
+
self._ip = ip
|
| 552 |
+
|
| 553 |
+
@staticmethod
|
| 554 |
+
def to_underlying_arguments(args: TileSchedulerArguments, *, loc=None, ip=None) -> Params:
|
| 555 |
+
return SingleTileVarlenScheduler.Params.create(args, loc=loc, ip=ip)
|
| 556 |
+
|
| 557 |
+
@staticmethod
|
| 558 |
+
def create(params: Params, *, loc=None, ip=None) -> "SingleTileVarlenScheduler":
|
| 559 |
+
tile_idx, split_idx, _ = cute.arch.block_idx()
|
| 560 |
+
return SingleTileVarlenScheduler(params, tile_idx, split_idx, loc=loc, ip=ip)
|
| 561 |
+
|
| 562 |
+
# called by host
|
| 563 |
+
@staticmethod
|
| 564 |
+
def get_grid_shape(
|
| 565 |
+
params: Params,
|
| 566 |
+
*,
|
| 567 |
+
loc=None,
|
| 568 |
+
ip=None,
|
| 569 |
+
) -> Tuple[Int32, Int32, Int32]:
|
| 570 |
+
total_blocks_max = (
|
| 571 |
+
params.total_q + params.num_batch * (params.tile_shape_mn[0] - 1)
|
| 572 |
+
) // params.tile_shape_mn[0]
|
| 573 |
+
return (total_blocks_max * params.num_head, params.num_splits, Int32(1))
|
| 574 |
+
|
| 575 |
+
@cute.jit
|
| 576 |
+
def _get_num_m_blocks(self, lane: Int32, bidb_start: Int32) -> Int32:
|
| 577 |
+
params = self.params
|
| 578 |
+
batch_idx = lane + bidb_start
|
| 579 |
+
if cutlass.const_expr(params.mSeqUsedQ is not None):
|
| 580 |
+
seqlen = Int32(0)
|
| 581 |
+
if batch_idx < params.num_batch:
|
| 582 |
+
seqlen = params.mSeqUsedQ[batch_idx]
|
| 583 |
+
else:
|
| 584 |
+
assert params.mCuSeqlensQ is not None
|
| 585 |
+
cur_cu_seqlen = Int32(0)
|
| 586 |
+
if batch_idx <= params.num_batch:
|
| 587 |
+
cur_cu_seqlen = params.mCuSeqlensQ[batch_idx]
|
| 588 |
+
next_cu_seqlen = cute.arch.shuffle_sync_down(cur_cu_seqlen, offset=1)
|
| 589 |
+
seqlen = next_cu_seqlen - cur_cu_seqlen
|
| 590 |
+
if cutlass.const_expr(params.qhead_per_kvhead_packgqa > 1):
|
| 591 |
+
seqlen *= params.qhead_per_kvhead_packgqa
|
| 592 |
+
return (
|
| 593 |
+
cute.ceil_div(seqlen, params.tile_shape_mn[0])
|
| 594 |
+
if batch_idx < params.num_batch and lane < cute.arch.WARP_SIZE - 1
|
| 595 |
+
else Int32(0)
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
@cute.jit
|
| 599 |
+
def get_current_work(self, *, loc=None, ip=None) -> WorkTileInfo:
|
| 600 |
+
params = self.params
|
| 601 |
+
lane_idx = cute.arch.lane_idx()
|
| 602 |
+
num_m_blocks = self._get_num_m_blocks(lane_idx, bidb_start=0)
|
| 603 |
+
num_m_blocks_cumulative = utils.warp_prefix_sum(num_m_blocks, lane_idx)
|
| 604 |
+
# Total number of blocks for the next 31 batches
|
| 605 |
+
m_blocks_in_group = cute.arch.shuffle_sync(num_m_blocks_cumulative, cute.arch.WARP_SIZE - 1)
|
| 606 |
+
# Same for all lanes
|
| 607 |
+
group_end_tile = m_blocks_in_group * params.num_head
|
| 608 |
+
# if cute.arch.thread_idx()[0] == 128 + 31: cute.printf("SingleTileVarlenScheduler: tile_idx=%d, group_end_tile = %d, num_m_blocks=%d, num_m_blocks_cumulative = %d, m_blocks_in_group = %d", self._tile_idx, group_end_tile, num_m_blocks, num_m_blocks_cumulative, m_blocks_in_group)
|
| 609 |
+
block, head_idx, batch_idx = Int32(0), Int32(0), Int32(0)
|
| 610 |
+
next_tile_idx = self._tile_idx
|
| 611 |
+
while group_end_tile <= next_tile_idx:
|
| 612 |
+
batch_idx += cute.arch.WARP_SIZE - 1
|
| 613 |
+
if batch_idx >= params.num_batch:
|
| 614 |
+
batch_idx = Int32(params.num_batch)
|
| 615 |
+
group_end_tile = next_tile_idx + 1
|
| 616 |
+
else:
|
| 617 |
+
num_m_blocks = self._get_num_m_blocks(lane_idx, bidb_start=batch_idx)
|
| 618 |
+
num_m_blocks_cumulative = utils.warp_prefix_sum(num_m_blocks, lane_idx)
|
| 619 |
+
m_blocks_in_group = cute.arch.shuffle_sync(
|
| 620 |
+
num_m_blocks_cumulative, cute.arch.WARP_SIZE - 1
|
| 621 |
+
)
|
| 622 |
+
group_end_tile += m_blocks_in_group * params.num_head
|
| 623 |
+
is_valid = False
|
| 624 |
+
if batch_idx >= params.num_batch:
|
| 625 |
+
block, head_idx, batch_idx = Int32(0), Int32(0), Int32(params.num_batch)
|
| 626 |
+
else:
|
| 627 |
+
group_start_tile = group_end_tile - m_blocks_in_group * params.num_head
|
| 628 |
+
# if cute.arch.thread_idx()[0] == 128 + 31: cute.printf("SingleTileVarlenScheduler: tile_idx=%d, group_end_tile = %d, num_m_blocks=%d, batch_idx = %d", self._tile_idx, group_end_tile, num_m_blocks, batch_idx)
|
| 629 |
+
# The next problem to process is the first one that does not have ending tile position
|
| 630 |
+
# that is greater than or equal to tile index.
|
| 631 |
+
batch_idx_in_group = cute.arch.popc(
|
| 632 |
+
cute.arch.vote_ballot_sync(
|
| 633 |
+
group_start_tile + num_m_blocks_cumulative * params.num_head <= next_tile_idx
|
| 634 |
+
)
|
| 635 |
+
)
|
| 636 |
+
batch_idx += batch_idx_in_group
|
| 637 |
+
num_m_blocks_prev_lane = (
|
| 638 |
+
0
|
| 639 |
+
if batch_idx_in_group == 0
|
| 640 |
+
else cute.arch.shuffle_sync(num_m_blocks_cumulative, batch_idx_in_group - 1)
|
| 641 |
+
)
|
| 642 |
+
num_m_blocks = cute.arch.shuffle_sync(num_m_blocks, batch_idx_in_group)
|
| 643 |
+
mh_block = next_tile_idx - group_start_tile - num_m_blocks_prev_lane * params.num_head
|
| 644 |
+
if cutlass.const_expr(params.lpt or params.head_swizzle):
|
| 645 |
+
# This is a version of the SingleTileLPTScheduler, complicated by the fact that
|
| 646 |
+
# the seqlen can vary per batch.
|
| 647 |
+
# TODO: is there any case where num_m_blocks is 0?
|
| 648 |
+
# TODO: by right we should read the seqlen_kv but we're assuming seqlen_q == seqlen_k here
|
| 649 |
+
num_n_blocks = (
|
| 650 |
+
num_m_blocks
|
| 651 |
+
* params.tile_shape_mn[0]
|
| 652 |
+
// params.qhead_per_kvhead_packgqa
|
| 653 |
+
// params.tile_shape_mn[1]
|
| 654 |
+
)
|
| 655 |
+
# nheads_in_l2 = min(max(self.max_kvblock_in_l2 // num_n_blocks, 1), self.num_head)
|
| 656 |
+
# Seems faster to have this be a power of 2
|
| 657 |
+
nheads_in_l2 = (
|
| 658 |
+
16
|
| 659 |
+
if num_n_blocks * 16 <= params.max_kvblock_in_l2
|
| 660 |
+
else (
|
| 661 |
+
8
|
| 662 |
+
if num_n_blocks * 8 <= params.max_kvblock_in_l2
|
| 663 |
+
else (
|
| 664 |
+
4
|
| 665 |
+
if num_n_blocks * 4 <= params.max_kvblock_in_l2
|
| 666 |
+
else (2 if num_n_blocks * 2 <= params.max_kvblock_in_l2 else 1)
|
| 667 |
+
)
|
| 668 |
+
)
|
| 669 |
+
)
|
| 670 |
+
nheads_in_l2 = min(nheads_in_l2, params.num_head)
|
| 671 |
+
mh_in_l2 = nheads_in_l2 * num_m_blocks
|
| 672 |
+
section_idx = mh_block // mh_in_l2
|
| 673 |
+
l2_mod = mh_block - section_idx * mh_in_l2
|
| 674 |
+
# Deal with tail section
|
| 675 |
+
nheads_in_this_section = (
|
| 676 |
+
nheads_in_l2
|
| 677 |
+
if nheads_in_l2 * (section_idx + 1) <= params.num_head
|
| 678 |
+
else params.num_head - section_idx * nheads_in_l2
|
| 679 |
+
)
|
| 680 |
+
block = l2_mod // nheads_in_this_section
|
| 681 |
+
head_idx_residual = l2_mod - block * nheads_in_this_section
|
| 682 |
+
head_idx = section_idx * nheads_in_l2 + head_idx_residual
|
| 683 |
+
if cutlass.const_expr(params.lpt):
|
| 684 |
+
block = num_m_blocks - 1 - block
|
| 685 |
+
else:
|
| 686 |
+
head_idx = mh_block // num_m_blocks
|
| 687 |
+
block = mh_block - head_idx * num_m_blocks
|
| 688 |
+
is_valid = self._is_first_block and batch_idx < params.num_batch
|
| 689 |
+
# if cute.arch.thread_idx()[0] == 128: cute.printf("SingleTileVarlenScheduler: tile_idx=%d, batch_idx=%d, head_idx=%d, block=%d, is_valid = %d", self._tile_idx, batch_idx, head_idx, block, is_valid)
|
| 690 |
+
split_idx = self._split_idx if const_expr(params.is_split_kv) else Int32(0)
|
| 691 |
+
return WorkTileInfo((Int32(block), Int32(head_idx), Int32(batch_idx), split_idx), is_valid)
|
| 692 |
+
|
| 693 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 694 |
+
return self.get_current_work(loc=loc, ip=ip)
|
| 695 |
+
|
| 696 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 697 |
+
pass
|
| 698 |
+
|
| 699 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 700 |
+
# Single tile scheduler - set to invalid tile_idx to indicate no more work
|
| 701 |
+
self._is_first_block = False
|
| 702 |
+
|
| 703 |
+
def __extract_mlir_values__(self):
|
| 704 |
+
values, self._values_pos = [], []
|
| 705 |
+
for obj in [self.params, self._tile_idx, self._split_idx]:
|
| 706 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 707 |
+
values += obj_values
|
| 708 |
+
self._values_pos.append(len(obj_values))
|
| 709 |
+
return values
|
| 710 |
+
|
| 711 |
+
def __new_from_mlir_values__(self, values):
|
| 712 |
+
obj_list = []
|
| 713 |
+
for obj, n_items in zip(
|
| 714 |
+
[self.params, self._tile_idx, self._split_idx],
|
| 715 |
+
self._values_pos,
|
| 716 |
+
):
|
| 717 |
+
obj_list.append(cutlass.new_from_mlir_values(obj, values[:n_items]))
|
| 718 |
+
values = values[n_items:]
|
| 719 |
+
return SingleTileVarlenScheduler(*(tuple(obj_list)), loc=self._loc)
|
kernel/deps/flash_attn/cute/utils.py
ADDED
|
@@ -0,0 +1,859 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
import hashlib
|
| 5 |
+
import inspect
|
| 6 |
+
import re
|
| 7 |
+
from typing import Type, Callable, Optional, Tuple, overload
|
| 8 |
+
from functools import partial
|
| 9 |
+
|
| 10 |
+
import cutlass
|
| 11 |
+
import cutlass.cute as cute
|
| 12 |
+
|
| 13 |
+
from cutlass import Float32, const_expr
|
| 14 |
+
from cutlass.cutlass_dsl import T, dsl_user_op
|
| 15 |
+
from cutlass._mlir.dialects import nvvm, llvm
|
| 16 |
+
from cutlass.cute.runtime import from_dlpack
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# cute.arch.{fma,mul,add}_packed_f32x2 uses RZ rounding mode by default
|
| 20 |
+
fma_packed_f32x2 = partial(cute.arch.fma_packed_f32x2, rnd=nvvm.RoundingModeKind.RN)
|
| 21 |
+
mul_packed_f32x2 = partial(cute.arch.mul_packed_f32x2, rnd=nvvm.RoundingModeKind.RN)
|
| 22 |
+
add_packed_f32x2 = partial(cute.arch.add_packed_f32x2, rnd=nvvm.RoundingModeKind.RN)
|
| 23 |
+
sub_packed_f32x2 = partial(
|
| 24 |
+
cute.arch.calc_packed_f32x2_op,
|
| 25 |
+
src_c=None,
|
| 26 |
+
calc_func=nvvm.sub_packed_f32x2,
|
| 27 |
+
rnd=nvvm.RoundingModeKind.RN,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def hash_callable(func: Callable, set_cute_hash=True) -> str:
|
| 32 |
+
"""Hash a callable based on the source code or bytecode and closure values.
|
| 33 |
+
|
| 34 |
+
Fast-path: if the callable (or its __wrapped__ base) has a ``__cute_hash__``
|
| 35 |
+
attribute, that value is returned immediately. Code-generation backends such
|
| 36 |
+
as Inductor can set this attribute to avoid expensive runtime hashing.
|
| 37 |
+
|
| 38 |
+
set_cute_hash: whether or not to set func.__cute_hash__ if not present
|
| 39 |
+
"""
|
| 40 |
+
if hasattr(func, "__cute_hash__"):
|
| 41 |
+
return func.__cute_hash__
|
| 42 |
+
|
| 43 |
+
# Unwrap decorated functions (e.g., cute.jit wrappers).
|
| 44 |
+
if hasattr(func, "__wrapped__"):
|
| 45 |
+
base_func = func.__wrapped__
|
| 46 |
+
if hasattr(base_func, "__cute_hash__"):
|
| 47 |
+
return base_func.__cute_hash__
|
| 48 |
+
func = base_func
|
| 49 |
+
|
| 50 |
+
try:
|
| 51 |
+
data = inspect.getsource(func).encode()
|
| 52 |
+
except (OSError, TypeError):
|
| 53 |
+
if hasattr(func, "__code__") and func.__code__ is not None:
|
| 54 |
+
data = func.__code__.co_code
|
| 55 |
+
else:
|
| 56 |
+
data = repr(func).encode()
|
| 57 |
+
|
| 58 |
+
hasher = hashlib.sha256(data)
|
| 59 |
+
|
| 60 |
+
if hasattr(func, "__closure__") and func.__closure__ is not None:
|
| 61 |
+
for idx, cell in enumerate(func.__closure__):
|
| 62 |
+
cell_value = cell.cell_contents
|
| 63 |
+
hasher.update(repr(cell_value).encode())
|
| 64 |
+
|
| 65 |
+
hash = hasher.hexdigest()
|
| 66 |
+
|
| 67 |
+
if set_cute_hash:
|
| 68 |
+
func.__cute_hash__ = hash
|
| 69 |
+
|
| 70 |
+
return hash
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def create_softcap_scoremod(softcap_val):
|
| 74 |
+
inv_softcap = 1.0 / softcap_val
|
| 75 |
+
|
| 76 |
+
@cute.jit
|
| 77 |
+
def scoremod_premask_fn(acc_S_SSA, batch_idx, head_idx, q_idx, kv_idx, aux_tensors):
|
| 78 |
+
scores = acc_S_SSA * inv_softcap
|
| 79 |
+
return scores * cute.math.tanh(scores, fastmath=True)
|
| 80 |
+
|
| 81 |
+
return scoremod_premask_fn
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def convert_from_dlpack(x, leading_dim, alignment=16, divisibility=1) -> cute.Tensor:
|
| 85 |
+
return (
|
| 86 |
+
from_dlpack(x, assumed_align=alignment)
|
| 87 |
+
.mark_layout_dynamic(leading_dim=leading_dim)
|
| 88 |
+
.mark_compact_shape_dynamic(
|
| 89 |
+
mode=leading_dim, stride_order=x.dim_order(), divisibility=divisibility
|
| 90 |
+
)
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def convert_from_dlpack_leading_static(
|
| 95 |
+
x, leading_dim, alignment=16, static_modes=None, stride_order=None
|
| 96 |
+
) -> cute.Tensor:
|
| 97 |
+
if stride_order is None:
|
| 98 |
+
stride_order = x.dim_order()
|
| 99 |
+
x_ = from_dlpack(x, assumed_align=alignment)
|
| 100 |
+
for i in range(x.ndim):
|
| 101 |
+
if i != leading_dim and (static_modes is None or i not in static_modes):
|
| 102 |
+
x_ = x_.mark_compact_shape_dynamic(mode=i, stride_order=stride_order)
|
| 103 |
+
return x_
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def make_tiled_copy_A(
|
| 107 |
+
copy_atom: cute.CopyAtom, tiled_mma: cute.TiledMma, swapAB: cutlass.Constexpr[bool] = False
|
| 108 |
+
) -> cute.TiledCopy:
|
| 109 |
+
if const_expr(swapAB):
|
| 110 |
+
return cute.make_tiled_copy_B(copy_atom, tiled_mma)
|
| 111 |
+
else:
|
| 112 |
+
return cute.make_tiled_copy_A(copy_atom, tiled_mma)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def make_tiled_copy_B(
|
| 116 |
+
copy_atom: cute.CopyAtom, tiled_mma: cute.TiledMma, swapAB: cutlass.Constexpr[bool] = False
|
| 117 |
+
) -> cute.TiledCopy:
|
| 118 |
+
if const_expr(swapAB):
|
| 119 |
+
return cute.make_tiled_copy_A(copy_atom, tiled_mma)
|
| 120 |
+
else:
|
| 121 |
+
return cute.make_tiled_copy_B(copy_atom, tiled_mma)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def mma_make_fragment_A(
|
| 125 |
+
smem: cute.Tensor, thr_mma: cute.core.ThrMma, swapAB: cutlass.Constexpr[bool] = False
|
| 126 |
+
) -> cute.Tensor:
|
| 127 |
+
if const_expr(swapAB):
|
| 128 |
+
return mma_make_fragment_B(smem, thr_mma)
|
| 129 |
+
else:
|
| 130 |
+
return thr_mma.make_fragment_A(thr_mma.partition_A(smem))
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def mma_make_fragment_B(
|
| 134 |
+
smem: cute.Tensor, thr_mma: cute.core.ThrMma, swapAB: cutlass.Constexpr[bool] = False
|
| 135 |
+
) -> cute.Tensor:
|
| 136 |
+
if const_expr(swapAB):
|
| 137 |
+
return mma_make_fragment_A(smem, thr_mma)
|
| 138 |
+
else:
|
| 139 |
+
return thr_mma.make_fragment_B(thr_mma.partition_B(smem))
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def get_smem_store_atom(
|
| 143 |
+
arch: cutlass.Constexpr[int], element_type: Type[cute.Numeric], transpose: bool = False
|
| 144 |
+
) -> cute.CopyAtom:
|
| 145 |
+
if const_expr(arch < 90 or element_type.width != 16):
|
| 146 |
+
return cute.make_copy_atom(
|
| 147 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 148 |
+
element_type,
|
| 149 |
+
num_bits_per_copy=2 * element_type.width,
|
| 150 |
+
)
|
| 151 |
+
else:
|
| 152 |
+
return cute.make_copy_atom(
|
| 153 |
+
cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=transpose, num_matrices=4),
|
| 154 |
+
element_type,
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
@cute.jit
|
| 159 |
+
def warp_reduce(
|
| 160 |
+
val: cute.TensorSSA | cute.Numeric,
|
| 161 |
+
op: Callable,
|
| 162 |
+
width: cutlass.Constexpr[int] = cute.arch.WARP_SIZE,
|
| 163 |
+
) -> cute.TensorSSA | cute.Numeric:
|
| 164 |
+
if const_expr(isinstance(val, cute.TensorSSA)):
|
| 165 |
+
res = cute.make_fragment(val.shape, val.dtype)
|
| 166 |
+
res.store(val)
|
| 167 |
+
for i in cutlass.range_constexpr(cute.size(val.shape)):
|
| 168 |
+
res[i] = warp_reduce(res[i], op, width)
|
| 169 |
+
return res.load()
|
| 170 |
+
else:
|
| 171 |
+
for i in cutlass.range_constexpr(int(math.log2(width))):
|
| 172 |
+
val = op(val, cute.arch.shuffle_sync_bfly(val, offset=1 << i))
|
| 173 |
+
return val
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def convert_layout_acc_mn(acc_layout: cute.Layout, transpose: bool = False) -> cute.Layout:
|
| 177 |
+
"""
|
| 178 |
+
For Sm80, convert ((2, 2), MMA_M, MMA_N, ...) to ((2, MMA_M), (2, MMA_N), ...).
|
| 179 |
+
For Sm90, convert ((2, 2, V), MMA_M, MMA_N, ...) to ((2, MMA_M), (2, V, MMA_N), ...).
|
| 180 |
+
"""
|
| 181 |
+
acc_layout_col_major = cute.make_layout(acc_layout.shape)
|
| 182 |
+
shape = (
|
| 183 |
+
(acc_layout_col_major.shape[0][1], acc_layout_col_major.shape[1]), # MMA_M
|
| 184 |
+
(
|
| 185 |
+
acc_layout_col_major.shape[0][0],
|
| 186 |
+
*acc_layout_col_major.shape[0][2:],
|
| 187 |
+
acc_layout_col_major.shape[2],
|
| 188 |
+
), # MMA_N
|
| 189 |
+
*acc_layout_col_major.shape[3:],
|
| 190 |
+
)
|
| 191 |
+
stride = (
|
| 192 |
+
(acc_layout_col_major.stride[0][1], acc_layout_col_major.stride[1]), # MMA_M
|
| 193 |
+
(
|
| 194 |
+
acc_layout_col_major.stride[0][0],
|
| 195 |
+
*acc_layout_col_major.stride[0][2:],
|
| 196 |
+
acc_layout_col_major.stride[2],
|
| 197 |
+
), # MMA_N
|
| 198 |
+
*acc_layout_col_major.stride[3:],
|
| 199 |
+
)
|
| 200 |
+
if const_expr(transpose):
|
| 201 |
+
shape = (shape[1], shape[0], *shape[2:])
|
| 202 |
+
stride = (stride[1], stride[0], *stride[2:])
|
| 203 |
+
acc_layout_mn = cute.make_layout(shape, stride=stride)
|
| 204 |
+
return cute.composition(acc_layout, acc_layout_mn)
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def make_acc_tensor_mn_view(acc: cute.Tensor, transpose: bool = False) -> cute.Tensor:
|
| 208 |
+
return cute.make_tensor(acc.iterator, convert_layout_acc_mn(acc.layout, transpose=transpose))
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
@cute.jit
|
| 212 |
+
def convert_layout_acc_frgA(acc_layout: cute.Layout) -> cute.Layout:
|
| 213 |
+
# For back to back gemm, convert layout of acc0 to gemm 1 accept layout.
|
| 214 |
+
# For Sm80, as the mma instruction shape is 16x8x16, we need to convert from (4, MMA_M, MMA_N) to ((4, 2), MMA_M, MMA_N / 2)
|
| 215 |
+
# For Sm90, FP16/BF16, convert acc_layout from ((2, 2, N / 8), MMA_M, MMA_N) to ((2, 2, 2), MMA_M, (N / 16, MMA_N))
|
| 216 |
+
# TODO: Sm90 FP8
|
| 217 |
+
if const_expr(cute.rank(acc_layout.shape[0]) == 3): # Sm90
|
| 218 |
+
l = cute.logical_divide(
|
| 219 |
+
acc_layout, ((None, None, 2), None, None)
|
| 220 |
+
) # ((2, 2, (2, N / 16)), MMA_M, MMA_N)
|
| 221 |
+
rA_mma_view = cute.make_layout(
|
| 222 |
+
(
|
| 223 |
+
(l.shape[0][0], l.shape[0][1], l.shape[0][2][0]),
|
| 224 |
+
l.shape[1],
|
| 225 |
+
(l.shape[0][2][1], l.shape[2]),
|
| 226 |
+
),
|
| 227 |
+
stride=(
|
| 228 |
+
(l.stride[0][0], l.stride[0][1], l.stride[0][2][0]),
|
| 229 |
+
l.stride[1],
|
| 230 |
+
(l.stride[0][2][1], l.stride[2]),
|
| 231 |
+
),
|
| 232 |
+
)
|
| 233 |
+
else: # Sm80
|
| 234 |
+
# (4, MMA_M, MMA_N) -> (4, MMA_M, (2, MMA_N / 2))
|
| 235 |
+
l = cute.logical_divide(acc_layout, (None, None, 2))
|
| 236 |
+
rA_mma_view = cute.make_layout(
|
| 237 |
+
(
|
| 238 |
+
(l.shape[0], l.shape[2][0]),
|
| 239 |
+
l.shape[1],
|
| 240 |
+
l.shape[2][1],
|
| 241 |
+
),
|
| 242 |
+
stride=(
|
| 243 |
+
(l.stride[0], l.stride[2][0]),
|
| 244 |
+
l.stride[1],
|
| 245 |
+
l.stride[2][1],
|
| 246 |
+
),
|
| 247 |
+
)
|
| 248 |
+
return rA_mma_view
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def make_acc_tensor_frgA_view(acc: cute.Tensor) -> cute.Tensor:
|
| 252 |
+
return cute.make_tensor(acc.iterator, convert_layout_acc_frgA(acc.layout))
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def select(a: cute.Tensor, mode: list[int]) -> cute.Tensor:
|
| 256 |
+
return cute.make_tensor(a.iterator, cute.select(a.layout, mode))
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
def transpose_view(a: cute.Tensor) -> cute.Tensor:
|
| 260 |
+
"""Transpose the first two dimensions of a tensor on smem."""
|
| 261 |
+
shape = (a.shape[1], a.shape[0], *a.shape[2:])
|
| 262 |
+
order = (1, 0, *range(2, cute.rank(a)))
|
| 263 |
+
return cute.composition(a, cute.make_ordered_layout(shape, order=order))
|
| 264 |
+
# stride = (a.layout.stride[1], a.layout.stride[0], *a.layout.stride[2:])
|
| 265 |
+
# return cute.make_tensor(a.iterator, cute.make_layout(shape, stride=stride))
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def parse_swizzle_from_pointer(ptr: cute.Pointer) -> cute.Swizzle:
|
| 269 |
+
"""Extract swizzle parameters from a pointer's swizzle_type.
|
| 270 |
+
|
| 271 |
+
The swizzle_type string has the form '!cute.swizzle<"S<b,m,s>">' where
|
| 272 |
+
b, m, s are the swizzle parameters (bits, base, shift).
|
| 273 |
+
|
| 274 |
+
Returns:
|
| 275 |
+
A cute.Swizzle object constructed from the extracted parameters
|
| 276 |
+
|
| 277 |
+
Raises:
|
| 278 |
+
ValueError: If the swizzle_type string cannot be parsed
|
| 279 |
+
"""
|
| 280 |
+
# Ideally there should be a better API to get swizzle parameters, but we'll just parse
|
| 281 |
+
# the string here.
|
| 282 |
+
swizzle_str = str(ptr.type.swizzle_type)
|
| 283 |
+
# Extract the inner part "S<b,m,s>"
|
| 284 |
+
match = re.search(r"S<(\d+),(\d+),(\d+)>", swizzle_str)
|
| 285 |
+
if match:
|
| 286 |
+
b, m, s = int(match.group(1)), int(match.group(2)), int(match.group(3))
|
| 287 |
+
return cute.make_swizzle(b, m, s)
|
| 288 |
+
else:
|
| 289 |
+
raise ValueError(f"Could not parse swizzle_type: {swizzle_str}")
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
@cute.jit
|
| 293 |
+
def exp2f(x: cute.TensorSSA | Float32) -> cute.TensorSSA | Float32:
|
| 294 |
+
"""exp2f calculation for both vector and scalar.
|
| 295 |
+
:param x: input value
|
| 296 |
+
:type x: cute.TensorSSA or Float32
|
| 297 |
+
:return: exp2 value
|
| 298 |
+
:rtype: cute.TensorSSA or Float32
|
| 299 |
+
"""
|
| 300 |
+
if const_expr(isinstance(x, cute.TensorSSA)):
|
| 301 |
+
res = cute.make_fragment(x.shape, Float32)
|
| 302 |
+
res.store(x)
|
| 303 |
+
for i in cutlass.range_constexpr(cute.size(x.shape)):
|
| 304 |
+
res[i] = cute.arch.exp2(res[i])
|
| 305 |
+
return res.load()
|
| 306 |
+
else:
|
| 307 |
+
return cute.arch.exp2(x)
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
@dsl_user_op
|
| 311 |
+
def log2f(a: float | Float32, *, loc=None, ip=None) -> Float32:
|
| 312 |
+
return Float32(
|
| 313 |
+
llvm.inline_asm(
|
| 314 |
+
T.f32(),
|
| 315 |
+
[Float32(a).ir_value(loc=loc, ip=ip)],
|
| 316 |
+
"lg2.approx.ftz.f32 $0, $1;",
|
| 317 |
+
"=f,f",
|
| 318 |
+
has_side_effects=False,
|
| 319 |
+
is_align_stack=False,
|
| 320 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 321 |
+
)
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
@dsl_user_op
|
| 326 |
+
def logf(a: float | Float32, *, loc=None, ip=None) -> Float32:
|
| 327 |
+
return log2f(a, loc=loc, ip=ip) * math.log(2.0)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
@dsl_user_op
|
| 331 |
+
def fmax(
|
| 332 |
+
a: float | Float32, b: float | Float32, c: float | Float32 | None = None, *, loc=None, ip=None
|
| 333 |
+
) -> Float32:
|
| 334 |
+
return Float32(
|
| 335 |
+
nvvm.fmax(
|
| 336 |
+
T.f32(),
|
| 337 |
+
Float32(a).ir_value(loc=loc, ip=ip),
|
| 338 |
+
Float32(b).ir_value(loc=loc, ip=ip),
|
| 339 |
+
c=Float32(c).ir_value(loc=loc, ip=ip) if c is not None else None,
|
| 340 |
+
loc=loc,
|
| 341 |
+
ip=ip,
|
| 342 |
+
)
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
@cute.jit
|
| 347 |
+
def fmax_reduce(
|
| 348 |
+
x: cute.TensorSSA, init_val: float | Float32 | None = None, arch: cutlass.Constexpr[int] = 80
|
| 349 |
+
) -> Float32:
|
| 350 |
+
if const_expr(arch < 100 or cute.size(x.shape) % 8 != 0):
|
| 351 |
+
# if const_expr(init_val is None):
|
| 352 |
+
# init_val = -cutlass.Float32.if
|
| 353 |
+
# return x.reduce(cute.ReductionOp.MAX, init_val, 0)
|
| 354 |
+
res = cute.make_fragment(x.shape, Float32)
|
| 355 |
+
res.store(x)
|
| 356 |
+
# local_max = [res[0], res[1]]
|
| 357 |
+
# for i in cutlass.range_constexpr(2, cute.size(x.shape), 2):
|
| 358 |
+
# local_max[0] = fmax(local_max[0], res[i + 0])
|
| 359 |
+
# local_max[1] = fmax(local_max[1], res[i + 1])
|
| 360 |
+
# local_max[0] = fmax(local_max[0], local_max[1])
|
| 361 |
+
# return local_max[0] if const_expr(init_val is None) else fmax(local_max[0], init_val)
|
| 362 |
+
local_max = [res[0], res[1], res[2], res[3]]
|
| 363 |
+
for i in cutlass.range_constexpr(4, cute.size(x.shape), 4):
|
| 364 |
+
local_max[0] = fmax(local_max[0], res[i + 0])
|
| 365 |
+
local_max[1] = fmax(local_max[1], res[i + 1])
|
| 366 |
+
local_max[2] = fmax(local_max[2], res[i + 2])
|
| 367 |
+
local_max[3] = fmax(local_max[3], res[i + 3])
|
| 368 |
+
local_max[0] = fmax(local_max[0], local_max[1])
|
| 369 |
+
local_max[2] = fmax(local_max[2], local_max[3])
|
| 370 |
+
local_max[0] = fmax(local_max[0], local_max[2])
|
| 371 |
+
return local_max[0] if const_expr(init_val is None) else fmax(local_max[0], init_val)
|
| 372 |
+
else:
|
| 373 |
+
# [2025-06-15] x.reduce only seems to use 50% 3-input max and 50% 2-input max
|
| 374 |
+
# We instead force the 3-input max.
|
| 375 |
+
res = cute.make_fragment(x.shape, Float32)
|
| 376 |
+
res.store(x)
|
| 377 |
+
local_max_0 = (
|
| 378 |
+
fmax(init_val, res[0], res[1])
|
| 379 |
+
if const_expr(init_val is not None)
|
| 380 |
+
else fmax(res[0], res[1])
|
| 381 |
+
)
|
| 382 |
+
local_max = [
|
| 383 |
+
local_max_0,
|
| 384 |
+
fmax(res[2], res[3]),
|
| 385 |
+
fmax(res[4], res[5]),
|
| 386 |
+
fmax(res[6], res[7]),
|
| 387 |
+
]
|
| 388 |
+
for i in cutlass.range_constexpr(8, cute.size(x.shape), 8):
|
| 389 |
+
local_max[0] = fmax(local_max[0], res[i], res[i + 1])
|
| 390 |
+
local_max[1] = fmax(local_max[1], res[i + 2], res[i + 3])
|
| 391 |
+
local_max[2] = fmax(local_max[2], res[i + 4], res[i + 5])
|
| 392 |
+
local_max[3] = fmax(local_max[3], res[i + 6], res[i + 7])
|
| 393 |
+
local_max[0] = fmax(local_max[0], local_max[1])
|
| 394 |
+
return fmax(local_max[0], local_max[2], local_max[3])
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
@cute.jit
|
| 398 |
+
def fadd_reduce(
|
| 399 |
+
x: cute.TensorSSA, init_val: float | Float32 | None = None, arch: cutlass.Constexpr[int] = 80
|
| 400 |
+
) -> Float32:
|
| 401 |
+
if const_expr(arch < 100 or cute.size(x.shape) % 8 != 0):
|
| 402 |
+
if const_expr(init_val is None):
|
| 403 |
+
init_val = Float32.zero
|
| 404 |
+
return x.reduce(cute.ReductionOp.ADD, init_val, 0)
|
| 405 |
+
# res = cute.make_fragment(x.shape, Float32)
|
| 406 |
+
# res.store(x)
|
| 407 |
+
# local_sum = [res[0], res[1], res[2], res[3]]
|
| 408 |
+
# for i in cutlass.range_constexpr(4, cute.size(x.shape), 4):
|
| 409 |
+
# local_sum[0] += res[i + 0]
|
| 410 |
+
# local_sum[1] += res[i + 1]
|
| 411 |
+
# local_sum[2] += res[i + 2]
|
| 412 |
+
# local_sum[3] += res[i + 3]
|
| 413 |
+
# local_sum[0] += local_sum[1]
|
| 414 |
+
# local_sum[2] += local_sum[3]
|
| 415 |
+
# local_sum[0] += local_sum[2]
|
| 416 |
+
# return local_sum[0] if const_expr(init_val is None) else local_sum[0] + init_val
|
| 417 |
+
else:
|
| 418 |
+
res = cute.make_fragment(x.shape, Float32)
|
| 419 |
+
res.store(x)
|
| 420 |
+
local_sum_0 = (
|
| 421 |
+
add_packed_f32x2((init_val, 0.0), (res[0], res[1]))
|
| 422 |
+
# add_packed_f32x2((init_val / 2, init_val / 2), (res[0], res[1]))
|
| 423 |
+
if const_expr(init_val is not None)
|
| 424 |
+
else (res[0], res[1])
|
| 425 |
+
)
|
| 426 |
+
local_sum = [local_sum_0, (res[2], res[3]), (res[4], res[5]), (res[6], res[7])]
|
| 427 |
+
for i in cutlass.range_constexpr(8, cute.size(x.shape), 8):
|
| 428 |
+
local_sum[0] = add_packed_f32x2(local_sum[0], (res[i + 0], res[i + 1]))
|
| 429 |
+
local_sum[1] = add_packed_f32x2(local_sum[1], (res[i + 2], res[i + 3]))
|
| 430 |
+
local_sum[2] = add_packed_f32x2(local_sum[2], (res[i + 4], res[i + 5]))
|
| 431 |
+
local_sum[3] = add_packed_f32x2(local_sum[3], (res[i + 6], res[i + 7]))
|
| 432 |
+
local_sum[0] = add_packed_f32x2(local_sum[0], local_sum[1])
|
| 433 |
+
local_sum[2] = add_packed_f32x2(local_sum[2], local_sum[3])
|
| 434 |
+
local_sum[0] = add_packed_f32x2(local_sum[0], local_sum[2])
|
| 435 |
+
return local_sum[0][0] + local_sum[0][1]
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
@dsl_user_op
|
| 439 |
+
def atomic_add_fp32(a: float | Float32, gmem_ptr: cute.Pointer, *, loc=None, ip=None) -> None:
|
| 440 |
+
# gmem_ptr_i64 = gmem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 441 |
+
# # cache_hint = cutlass.Int64(0x12F0000000000000)
|
| 442 |
+
# llvm.inline_asm(
|
| 443 |
+
# None,
|
| 444 |
+
# [gmem_ptr_i64, Float32(a).ir_value(loc=loc, ip=ip)],
|
| 445 |
+
# # [gmem_ptr_i64, Float32(a).ir_value(loc=loc, ip=ip), cache_hint.ir_value()],
|
| 446 |
+
# "red.global.add.f32 [$0], $1;",
|
| 447 |
+
# # "red.global.add.L2::cache_hint.f32 [$0], $1, 0x12F0000000000000;",
|
| 448 |
+
# # "red.global.add.L2::cache_hint.f32 [$0], $1, $2;",
|
| 449 |
+
# "l,f",
|
| 450 |
+
# # "l,f,l",
|
| 451 |
+
# has_side_effects=True,
|
| 452 |
+
# is_align_stack=False,
|
| 453 |
+
# asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 454 |
+
# )
|
| 455 |
+
nvvm.atomicrmw(
|
| 456 |
+
res=T.f32(), op=nvvm.AtomicOpKind.FADD, ptr=gmem_ptr.llvm_ptr, a=Float32(a).ir_value()
|
| 457 |
+
)
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
@dsl_user_op
|
| 461 |
+
def elem_pointer(x: cute.Tensor, coord: cute.Coord, *, loc=None, ip=None) -> cute.Pointer:
|
| 462 |
+
return x.iterator + cute.crd2idx(coord, x.layout, loc=loc, ip=ip)
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
@dsl_user_op
|
| 466 |
+
def elem_pointer_i64(x: cute.Tensor, coord: cute.Coord, *, loc=None, ip=None) -> cute.Pointer:
|
| 467 |
+
flat_coord_i64 = tuple(cutlass.Int64(c) for c in cute.flatten(coord))
|
| 468 |
+
flat_stride = cute.flatten_to_tuple(x.stride)
|
| 469 |
+
assert len(flat_coord_i64) == len(flat_stride), (
|
| 470 |
+
"Coordinate and stride must have the same length"
|
| 471 |
+
)
|
| 472 |
+
offset = sum(c * s for c, s in zip(flat_coord_i64, flat_stride))
|
| 473 |
+
# HACK: we assume that applying the offset does not change the pointer alignment
|
| 474 |
+
byte_offset = offset * x.element_type.width // 8
|
| 475 |
+
return cute.make_ptr(
|
| 476 |
+
x.element_type,
|
| 477 |
+
x.iterator.toint() + byte_offset,
|
| 478 |
+
x.memspace,
|
| 479 |
+
assumed_align=x.iterator.alignment,
|
| 480 |
+
)
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
@cute.jit
|
| 484 |
+
def predicate_k(tAcA: cute.Tensor, limit: cutlass.Int32) -> cute.Tensor:
|
| 485 |
+
# Only compute predicates for the "k" dimension. For the mn dimension, we will use "if"
|
| 486 |
+
tApA = cute.make_fragment(
|
| 487 |
+
cute.make_layout(
|
| 488 |
+
(cute.size(tAcA, mode=[0, 1]), cute.size(tAcA, mode=[1]), cute.size(tAcA, mode=[2])),
|
| 489 |
+
stride=(cute.size(tAcA, mode=[2]), 0, 1),
|
| 490 |
+
),
|
| 491 |
+
cutlass.Boolean,
|
| 492 |
+
)
|
| 493 |
+
for rest_v in cutlass.range_constexpr(tApA.shape[0]):
|
| 494 |
+
for rest_k in cutlass.range_constexpr(tApA.shape[2]):
|
| 495 |
+
tApA[rest_v, 0, rest_k] = cute.elem_less(tAcA[(0, rest_v), 0, rest_k][1], limit)
|
| 496 |
+
return tApA
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
def canonical_warp_group_idx(sync: bool = True) -> cutlass.Int32:
|
| 500 |
+
warp_group_idx = cute.arch.thread_idx()[0] // 128
|
| 501 |
+
if const_expr(sync):
|
| 502 |
+
warp_group_idx = cute.arch.make_warp_uniform(warp_group_idx)
|
| 503 |
+
return warp_group_idx
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
# @dsl_user_op
|
| 507 |
+
# def warp_vote_any_lt(a: float | Float32, b: float | Float32, *, loc=None, ip=None) -> cutlass.Boolean:
|
| 508 |
+
# mask = cutlass.Int32(-1)
|
| 509 |
+
# return cutlass.Boolean(
|
| 510 |
+
# llvm.inline_asm(
|
| 511 |
+
# T.i32(),
|
| 512 |
+
# [Float32(a).ir_value(loc=loc, ip=ip), Float32(b).ir_value(loc=loc, ip=ip), mask.ir_value(loc=loc, ip=ip)],
|
| 513 |
+
# ".pred p1, p2;\n"
|
| 514 |
+
# "setp.lt.f32 p1, $1, $2;\n"
|
| 515 |
+
# "vote.sync.any.pred p2, p1, $3;\n"
|
| 516 |
+
# "selp.u32 $0, 1, 0, p2;",
|
| 517 |
+
# # "selp.u32 $0, 1, 0, p1;",
|
| 518 |
+
# "=r,f,f,r",
|
| 519 |
+
# has_side_effects=False,
|
| 520 |
+
# is_align_stack=False,
|
| 521 |
+
# asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 522 |
+
# )
|
| 523 |
+
# )
|
| 524 |
+
|
| 525 |
+
|
| 526 |
+
@cute.jit
|
| 527 |
+
def shuffle_sync(
|
| 528 |
+
value: cute.Numeric,
|
| 529 |
+
offset: cute.typing.Int,
|
| 530 |
+
width: cutlass.Constexpr[int] = cute.arch.WARP_SIZE,
|
| 531 |
+
) -> cute.Numeric:
|
| 532 |
+
assert value.width % 32 == 0, "value type must be a multiple of 32 bits"
|
| 533 |
+
# 1 -> 0b11111, 2 -> 0b11110, 4 -> 0b11100, 8 -> 0b11000, 16 -> 0b10000, 32 -> 0b00000
|
| 534 |
+
mask = cute.arch.WARP_SIZE - width
|
| 535 |
+
clamp = cute.arch.WARP_SIZE - 1
|
| 536 |
+
mask_and_clamp = mask << 8 | clamp
|
| 537 |
+
# important: need stride 1 and not 0 for recast_tensor to work
|
| 538 |
+
val = cute.make_rmem_tensor(cute.make_layout((1,), stride=(1,)), type(value))
|
| 539 |
+
val[0] = value
|
| 540 |
+
val_i32 = cute.recast_tensor(val, cutlass.Int32)
|
| 541 |
+
for i in cutlass.range_constexpr(cute.size(val_i32)):
|
| 542 |
+
val_i32[i] = cute.arch.shuffle_sync(val_i32[i], offset, mask_and_clamp=mask_and_clamp)
|
| 543 |
+
return val[0]
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
@dsl_user_op
|
| 547 |
+
def shr_u32(val: cutlass.Uint32, shift: cutlass.Uint32, *, loc=None, ip=None) -> cutlass.Uint32:
|
| 548 |
+
return cutlass.Uint32(
|
| 549 |
+
llvm.inline_asm(
|
| 550 |
+
T.i32(),
|
| 551 |
+
[
|
| 552 |
+
cutlass.Uint32(val).ir_value(loc=loc, ip=ip),
|
| 553 |
+
cutlass.Uint32(shift).ir_value(loc=loc, ip=ip),
|
| 554 |
+
],
|
| 555 |
+
"shr.s32 $0, $1, $2;",
|
| 556 |
+
"=r,r,r",
|
| 557 |
+
has_side_effects=False,
|
| 558 |
+
is_align_stack=False,
|
| 559 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 560 |
+
)
|
| 561 |
+
)
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
@cute.jit
|
| 565 |
+
def warp_prefix_sum(val: cutlass.Int32, lane: Optional[cutlass.Int32] = None) -> cutlass.Int32:
|
| 566 |
+
if const_expr(lane is None):
|
| 567 |
+
lane = cute.arch.lane_idx()
|
| 568 |
+
# if cute.arch.thread_idx()[0] >= 128 and cute.arch.thread_idx()[0] < 128 + 32 and cute.arch.block_idx()[0] == 0: cute.printf("tidx = %d, val = %d", cute.arch.thread_idx()[0] % 32, val)
|
| 569 |
+
for i in cutlass.range_constexpr(int(math.log2(cute.arch.WARP_SIZE))):
|
| 570 |
+
offset = 1 << i
|
| 571 |
+
# Very important that we set mask_and_clamp to 0
|
| 572 |
+
partial_sum = cute.arch.shuffle_sync_up(val, offset=offset, mask_and_clamp=0)
|
| 573 |
+
if lane >= offset:
|
| 574 |
+
val += partial_sum
|
| 575 |
+
# if cute.arch.thread_idx()[0] >= 128 and cute.arch.thread_idx()[0] < 128 + 32 and cute.arch.block_idx()[0] == 0: cute.printf("tidx = %d, partial_sum = %d, val = %d", cute.arch.thread_idx()[0] % 32, partial_sum, val)
|
| 576 |
+
return val
|
| 577 |
+
|
| 578 |
+
|
| 579 |
+
@dsl_user_op
|
| 580 |
+
def cvt_f16x2_f32(
|
| 581 |
+
a: float | Float32, b: float | Float32, to_dtype: Type, *, loc=None, ip=None
|
| 582 |
+
) -> cutlass.Int32:
|
| 583 |
+
assert to_dtype in [cutlass.BFloat16, cutlass.Float16], "to_dtype must be BFloat16 or Float16"
|
| 584 |
+
return cutlass.Int32(
|
| 585 |
+
llvm.inline_asm(
|
| 586 |
+
T.i32(),
|
| 587 |
+
[Float32(a).ir_value(loc=loc, ip=ip), Float32(b).ir_value(loc=loc, ip=ip)],
|
| 588 |
+
f"cvt.rn.{'bf16x2' if to_dtype is cutlass.BFloat16 else 'f16x2'}.f32 $0, $2, $1;",
|
| 589 |
+
"=r,f,f",
|
| 590 |
+
has_side_effects=False,
|
| 591 |
+
is_align_stack=False,
|
| 592 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 593 |
+
)
|
| 594 |
+
)
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
@overload
|
| 598 |
+
def cvt_f16(src: cute.Tensor, dst: cute.Tensor) -> None: ...
|
| 599 |
+
|
| 600 |
+
|
| 601 |
+
@overload
|
| 602 |
+
def cvt_f16(src: cute.Tensor, dtype: Type[cute.Numeric]) -> cute.Tensor: ...
|
| 603 |
+
|
| 604 |
+
|
| 605 |
+
@cute.jit
|
| 606 |
+
def cvt_f16(src: cute.Tensor, dst_or_dtype):
|
| 607 |
+
"""Convert Float32 tensor to Float16/BFloat16.
|
| 608 |
+
|
| 609 |
+
Args:
|
| 610 |
+
src: Source tensor with Float32 element type
|
| 611 |
+
dst_or_dtype: Either a destination tensor or a dtype (Float16/BFloat16)
|
| 612 |
+
|
| 613 |
+
Returns:
|
| 614 |
+
None if dst is a tensor, or a new tensor if dtype is provided
|
| 615 |
+
"""
|
| 616 |
+
if const_expr(isinstance(dst_or_dtype, type)):
|
| 617 |
+
# dtype variant: create new tensor and call the tensor variant
|
| 618 |
+
dtype = dst_or_dtype
|
| 619 |
+
dst = cute.make_fragment(src.shape, dtype)
|
| 620 |
+
cvt_f16(src, dst)
|
| 621 |
+
return dst
|
| 622 |
+
else:
|
| 623 |
+
# tensor variant: write to dst
|
| 624 |
+
dst = dst_or_dtype
|
| 625 |
+
assert cute.size(dst.shape) == cute.size(src.shape), "dst and src must have the same size"
|
| 626 |
+
assert cute.size(src.shape) % 2 == 0, "src must have an even number of elements"
|
| 627 |
+
assert dst.element_type in [cutlass.BFloat16, cutlass.Float16], (
|
| 628 |
+
"dst must be BFloat16 or Float16"
|
| 629 |
+
)
|
| 630 |
+
assert src.element_type is Float32, "src must be Float32"
|
| 631 |
+
dst_i32 = cute.recast_tensor(dst, cutlass.Int32)
|
| 632 |
+
assert cute.size(dst_i32.shape) * 2 == cute.size(src.shape)
|
| 633 |
+
for i in cutlass.range_constexpr(cute.size(dst_i32)):
|
| 634 |
+
dst_i32[i] = cvt_f16x2_f32(src[2 * i], src[2 * i + 1], dst.element_type)
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
@dsl_user_op
|
| 638 |
+
@cute.jit
|
| 639 |
+
def evaluate_polynomial(x: Float32, poly: Tuple[Float32, ...], *, loc=None, ip=None) -> Float32:
|
| 640 |
+
deg = len(poly) - 1
|
| 641 |
+
out = poly[deg]
|
| 642 |
+
for i in cutlass.range_constexpr(deg - 1, -1, -1):
|
| 643 |
+
out = out * x + poly[i]
|
| 644 |
+
return out
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
@dsl_user_op
|
| 648 |
+
@cute.jit
|
| 649 |
+
def evaluate_polynomial_2(
|
| 650 |
+
x: Float32, y: Float32, poly: Tuple[Float32, ...], *, loc=None, ip=None
|
| 651 |
+
) -> Tuple[Float32, Float32]:
|
| 652 |
+
deg = len(poly) - 1
|
| 653 |
+
out = (poly[deg], poly[deg])
|
| 654 |
+
for i in cutlass.range_constexpr(deg - 1, -1, -1):
|
| 655 |
+
out = fma_packed_f32x2(out, (x, y), (poly[i], poly[i]))
|
| 656 |
+
return out
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
@dsl_user_op
|
| 660 |
+
def add_round_down(x: float | Float32, y: float | Float32, *, loc=None, ip=None) -> Float32:
|
| 661 |
+
# There's probably a way to call llvm or nvvm to do this instead of ptx
|
| 662 |
+
return cutlass.Float32(
|
| 663 |
+
llvm.inline_asm(
|
| 664 |
+
T.f32(),
|
| 665 |
+
[Float32(x).ir_value(loc=loc, ip=ip), Float32(y).ir_value(loc=loc, ip=ip)],
|
| 666 |
+
"add.rm.ftz.f32 $0, $1, $2;",
|
| 667 |
+
"=f,f,f",
|
| 668 |
+
has_side_effects=False,
|
| 669 |
+
is_align_stack=False,
|
| 670 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 671 |
+
)
|
| 672 |
+
)
|
| 673 |
+
|
| 674 |
+
|
| 675 |
+
@dsl_user_op
|
| 676 |
+
def combine_int_frac_ex2(x_rounded: Float32, frac_ex2: Float32, *, loc=None, ip=None) -> Float32:
|
| 677 |
+
return cutlass.Float32(
|
| 678 |
+
llvm.inline_asm(
|
| 679 |
+
T.f32(),
|
| 680 |
+
[
|
| 681 |
+
Float32(x_rounded).ir_value(loc=loc, ip=ip),
|
| 682 |
+
Float32(frac_ex2).ir_value(loc=loc, ip=ip),
|
| 683 |
+
],
|
| 684 |
+
"{\n\t"
|
| 685 |
+
".reg .s32 x_rounded_i, frac_ex_i, x_rounded_e, out_i;\n\t"
|
| 686 |
+
"mov.b32 x_rounded_i, $1;\n\t"
|
| 687 |
+
"mov.b32 frac_ex_i, $2;\n\t"
|
| 688 |
+
"shl.b32 x_rounded_e, x_rounded_i, 23;\n\t"
|
| 689 |
+
# add.u32 generates IMAD instruction and add.s32 generates LEA instruction
|
| 690 |
+
# IMAD uses the FMA pipeline and LEA uses the ALU pipeline, afaik
|
| 691 |
+
"add.s32 out_i, x_rounded_e, frac_ex_i;\n\t"
|
| 692 |
+
"mov.b32 $0, out_i;\n\t"
|
| 693 |
+
"}\n",
|
| 694 |
+
"=f,f,f",
|
| 695 |
+
has_side_effects=False,
|
| 696 |
+
is_align_stack=False,
|
| 697 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 698 |
+
)
|
| 699 |
+
)
|
| 700 |
+
|
| 701 |
+
|
| 702 |
+
@dsl_user_op
|
| 703 |
+
def ex2_emulation(x: Float32, *, loc=None, ip=None) -> Float32:
|
| 704 |
+
# We assume x <= 127.0
|
| 705 |
+
poly_ex2_deg3 = (
|
| 706 |
+
1.0,
|
| 707 |
+
0.695146143436431884765625,
|
| 708 |
+
0.227564394474029541015625,
|
| 709 |
+
0.077119089663028717041015625,
|
| 710 |
+
)
|
| 711 |
+
fp32_round_int = float(2**23 + 2**22)
|
| 712 |
+
x_clamped = cute.arch.fmax(x, -127.0)
|
| 713 |
+
# We want to round down here, so that the fractional part is in [0, 1)
|
| 714 |
+
x_rounded = add_round_down(x_clamped, fp32_round_int, loc=loc, ip=ip)
|
| 715 |
+
# The integer floor of x is now in the last 8 bits of x_rounded
|
| 716 |
+
# We assume the next 2 ops round to nearest even. The rounding mode is important.
|
| 717 |
+
x_rounded_back = x_rounded - fp32_round_int
|
| 718 |
+
x_frac = x_clamped - x_rounded_back
|
| 719 |
+
x_frac_ex2 = evaluate_polynomial(x_frac, poly_ex2_deg3, loc=loc, ip=ip)
|
| 720 |
+
return combine_int_frac_ex2(x_rounded, x_frac_ex2, loc=loc, ip=ip)
|
| 721 |
+
|
| 722 |
+
|
| 723 |
+
# TODO: check that the ex2_emulation_2 produces the same SASS as the ptx version
|
| 724 |
+
@dsl_user_op
|
| 725 |
+
def ex2_emulation_2(x: Float32, y: Float32, *, loc=None, ip=None) -> Tuple[Float32, Float32]:
|
| 726 |
+
# We assume x <= 127.0 and y <= 127.0
|
| 727 |
+
poly_ex2_deg3 = (
|
| 728 |
+
1.0,
|
| 729 |
+
0.695146143436431884765625,
|
| 730 |
+
0.227564394474029541015625,
|
| 731 |
+
0.077119089663028717041015625,
|
| 732 |
+
)
|
| 733 |
+
fp32_round_int = float(2**23 + 2**22)
|
| 734 |
+
xy_clamped = (cute.arch.fmax(x, -127.0), cute.arch.fmax(y, -127.0))
|
| 735 |
+
# We want to round down here, so that the fractional part is in [0, 1)
|
| 736 |
+
xy_rounded = cute.arch.add_packed_f32x2(
|
| 737 |
+
xy_clamped, (fp32_round_int, fp32_round_int), rnd=nvvm.RoundingModeKind.RM
|
| 738 |
+
)
|
| 739 |
+
# The integer floor of x & y are now in the last 8 bits of xy_rounded
|
| 740 |
+
# We want the next 2 ops to round to nearest even. The rounding mode is important.
|
| 741 |
+
xy_rounded_back = sub_packed_f32x2(xy_rounded, (fp32_round_int, fp32_round_int))
|
| 742 |
+
xy_frac = sub_packed_f32x2(xy_clamped, xy_rounded_back)
|
| 743 |
+
xy_frac_ex2 = evaluate_polynomial_2(*xy_frac, poly_ex2_deg3, loc=loc, ip=ip)
|
| 744 |
+
x_out = combine_int_frac_ex2(xy_rounded[0], xy_frac_ex2[0], loc=loc, ip=ip)
|
| 745 |
+
y_out = combine_int_frac_ex2(xy_rounded[1], xy_frac_ex2[1], loc=loc, ip=ip)
|
| 746 |
+
return x_out, y_out
|
| 747 |
+
|
| 748 |
+
|
| 749 |
+
@dsl_user_op
|
| 750 |
+
def e2e_asm2(x: Float32, y: Float32, *, loc=None, ip=None) -> Tuple[Float32, Float32]:
|
| 751 |
+
out_f32x2 = llvm.inline_asm(
|
| 752 |
+
llvm.StructType.get_literal([T.f32(), T.f32()]),
|
| 753 |
+
[Float32(x).ir_value(loc=loc, ip=ip), Float32(y, loc=loc, ip=ip).ir_value()],
|
| 754 |
+
"{\n\t"
|
| 755 |
+
".reg .f32 f1, f2, f3, f4, f5, f6, f7;\n\t"
|
| 756 |
+
".reg .b64 l1, l2, l3, l4, l5, l6, l7, l8, l9, l10;\n\t"
|
| 757 |
+
".reg .s32 r1, r2, r3, r4, r5, r6, r7, r8;\n\t"
|
| 758 |
+
"max.ftz.f32 f1, $2, 0fC2FE0000;\n\t"
|
| 759 |
+
"max.ftz.f32 f2, $3, 0fC2FE0000;\n\t"
|
| 760 |
+
"mov.b64 l1, {f1, f2};\n\t"
|
| 761 |
+
"mov.f32 f3, 0f4B400000;\n\t"
|
| 762 |
+
"mov.b64 l2, {f3, f3};\n\t"
|
| 763 |
+
"add.rm.ftz.f32x2 l7, l1, l2;\n\t"
|
| 764 |
+
"sub.rn.ftz.f32x2 l8, l7, l2;\n\t"
|
| 765 |
+
"sub.rn.ftz.f32x2 l9, l1, l8;\n\t"
|
| 766 |
+
"mov.f32 f7, 0f3D9DF09D;\n\t"
|
| 767 |
+
"mov.b64 l6, {f7, f7};\n\t"
|
| 768 |
+
"mov.f32 f6, 0f3E6906A4;\n\t"
|
| 769 |
+
"mov.b64 l5, {f6, f6};\n\t"
|
| 770 |
+
"mov.f32 f5, 0f3F31F519;\n\t"
|
| 771 |
+
"mov.b64 l4, {f5, f5};\n\t"
|
| 772 |
+
"mov.f32 f4, 0f3F800000;\n\t"
|
| 773 |
+
"mov.b64 l3, {f4, f4};\n\t"
|
| 774 |
+
"fma.rn.ftz.f32x2 l10, l9, l6, l5;\n\t"
|
| 775 |
+
"fma.rn.ftz.f32x2 l10, l10, l9, l4;\n\t"
|
| 776 |
+
"fma.rn.ftz.f32x2 l10, l10, l9, l3;\n\t"
|
| 777 |
+
"mov.b64 {r1, r2}, l7;\n\t"
|
| 778 |
+
"mov.b64 {r3, r4}, l10;\n\t"
|
| 779 |
+
"shl.b32 r5, r1, 23;\n\t"
|
| 780 |
+
"add.s32 r7, r5, r3;\n\t"
|
| 781 |
+
"shl.b32 r6, r2, 23;\n\t"
|
| 782 |
+
"add.s32 r8, r6, r4;\n\t"
|
| 783 |
+
"mov.b32 $0, r7;\n\t"
|
| 784 |
+
"mov.b32 $1, r8;\n\t"
|
| 785 |
+
"}\n",
|
| 786 |
+
"=r,=r,f,f",
|
| 787 |
+
has_side_effects=False,
|
| 788 |
+
is_align_stack=False,
|
| 789 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 790 |
+
)
|
| 791 |
+
out0 = Float32(llvm.extractvalue(T.f32(), out_f32x2, [0], loc=loc, ip=ip))
|
| 792 |
+
out1 = Float32(llvm.extractvalue(T.f32(), out_f32x2, [1], loc=loc, ip=ip))
|
| 793 |
+
return out0, out1
|
| 794 |
+
|
| 795 |
+
|
| 796 |
+
@dsl_user_op
|
| 797 |
+
def domain_offset_aligned(
|
| 798 |
+
coord: cute.Coord, tensor: cute.Tensor, *, loc=None, ip=None
|
| 799 |
+
) -> cute.Tensor:
|
| 800 |
+
assert isinstance(tensor.iterator, cute.Pointer)
|
| 801 |
+
# We assume that applying the offset does not change the pointer alignment
|
| 802 |
+
new_ptr = cute.make_ptr(
|
| 803 |
+
tensor.element_type,
|
| 804 |
+
elem_pointer(tensor, coord).toint(),
|
| 805 |
+
tensor.memspace,
|
| 806 |
+
assumed_align=tensor.iterator.alignment,
|
| 807 |
+
)
|
| 808 |
+
return cute.make_tensor(new_ptr, tensor.layout)
|
| 809 |
+
|
| 810 |
+
|
| 811 |
+
@dsl_user_op
|
| 812 |
+
def domain_offset_i64(coord: cute.Coord, tensor: cute.Tensor, *, loc=None, ip=None) -> cute.Tensor:
|
| 813 |
+
flat_coord_i64 = tuple(cutlass.Int64(c) for c in cute.flatten(coord))
|
| 814 |
+
flat_stride = cute.flatten_to_tuple(tensor.stride)
|
| 815 |
+
assert len(flat_coord_i64) == len(flat_stride), (
|
| 816 |
+
"Coordinate and stride must have the same length"
|
| 817 |
+
)
|
| 818 |
+
offset = sum(c * s for c, s in zip(flat_coord_i64, flat_stride))
|
| 819 |
+
assert isinstance(tensor.iterator, cute.Pointer)
|
| 820 |
+
# HACK: we assume that applying the offset does not change the pointer alignment
|
| 821 |
+
new_ptr = cute.make_ptr(
|
| 822 |
+
tensor.element_type,
|
| 823 |
+
tensor.iterator.toint() + offset * tensor.element_type.width // 8,
|
| 824 |
+
tensor.memspace,
|
| 825 |
+
assumed_align=tensor.iterator.max_alignment,
|
| 826 |
+
)
|
| 827 |
+
return cute.make_tensor(new_ptr, tensor.layout)
|
| 828 |
+
|
| 829 |
+
|
| 830 |
+
@dsl_user_op
|
| 831 |
+
def coord_offset_i64(
|
| 832 |
+
tensor: cute.Tensor, idx: cute.typing.Int, dim: int, *, loc=None, ip=None
|
| 833 |
+
) -> cute.Tensor:
|
| 834 |
+
offset = cutlass.Int64(idx) * cute.size(tensor.stride[dim])
|
| 835 |
+
assert isinstance(tensor.iterator, cute.Pointer)
|
| 836 |
+
# HACK: we assume that applying the offset does not change the pointer alignment
|
| 837 |
+
new_ptr = cute.make_ptr(
|
| 838 |
+
tensor.element_type,
|
| 839 |
+
tensor.iterator.toint() + offset * tensor.element_type.width // 8,
|
| 840 |
+
tensor.memspace,
|
| 841 |
+
assumed_align=tensor.iterator.max_alignment,
|
| 842 |
+
)
|
| 843 |
+
new_layout = cute.slice_(
|
| 844 |
+
tensor.layout, (*[None] * dim, 0, *[None] * (cute.rank(tensor) - dim - 1))
|
| 845 |
+
)
|
| 846 |
+
return cute.make_tensor(new_ptr, new_layout)
|
| 847 |
+
|
| 848 |
+
|
| 849 |
+
@cute.jit
|
| 850 |
+
def scalar_to_ssa(a: cute.Numeric, dtype) -> cute.TensorSSA:
|
| 851 |
+
"""Convert a scalar to a cute TensorSSA of shape (1,) and given dtype"""
|
| 852 |
+
vec = cute.make_fragment(1, dtype)
|
| 853 |
+
vec[0] = a
|
| 854 |
+
return vec.load()
|
| 855 |
+
|
| 856 |
+
|
| 857 |
+
def ssa_to_scalar(val):
|
| 858 |
+
"""Could inline but nice for reflecting the above api"""
|
| 859 |
+
return val[0]
|
kernel/ksa_attn/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Summary attention kernels for KSA inference."""
|
| 2 |
+
from .interface import summary_attn_func
|
kernel/ksa_attn/interface.py
ADDED
|
@@ -0,0 +1,996 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import os
|
| 3 |
+
import torch
|
| 4 |
+
from typing import Tuple, Optional, Dict
|
| 5 |
+
from functools import partial
|
| 6 |
+
|
| 7 |
+
# Lazy import: CuTe kernel only available on Hopper (sm90+)
|
| 8 |
+
_CUTE_AVAILABLE = None
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def _check_cute_available():
|
| 12 |
+
global _CUTE_AVAILABLE
|
| 13 |
+
if _CUTE_AVAILABLE is not None:
|
| 14 |
+
return _CUTE_AVAILABLE
|
| 15 |
+
try:
|
| 16 |
+
from flash_attn.cute.interface import _flash_attn_fwd
|
| 17 |
+
from flash_attn.cute.compute_block_sparsity import compute_block_sparsity
|
| 18 |
+
cap = torch.cuda.get_device_capability()
|
| 19 |
+
if cap[0] >= 9: # sm90+ (Hopper)
|
| 20 |
+
_CUTE_AVAILABLE = True
|
| 21 |
+
else:
|
| 22 |
+
_CUTE_AVAILABLE = False
|
| 23 |
+
except (ImportError, ModuleNotFoundError):
|
| 24 |
+
_CUTE_AVAILABLE = False
|
| 25 |
+
return _CUTE_AVAILABLE
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
SUMMARY_ATTN_CACHE: Dict[Tuple, Tuple] = {}
|
| 29 |
+
HIERARCHICAL_SUMMARY_ATTN_CACHE: Dict[Tuple, Tuple] = {}
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
# ---------------------------------------------------------------------------
|
| 33 |
+
# CuTe kernel path (Hopper only)
|
| 34 |
+
# ---------------------------------------------------------------------------
|
| 35 |
+
|
| 36 |
+
class SummaryAttnFunc(torch.autograd.Function):
|
| 37 |
+
@staticmethod
|
| 38 |
+
def _get_cached_value(
|
| 39 |
+
seqlen_q, seqlen_k, chunk_size, summary_num, sliding_chunk_num,
|
| 40 |
+
skip_old_summary=False, is_fwd=True, m_block_size=128, n_block_size=128,
|
| 41 |
+
):
|
| 42 |
+
from flash_attn.cute.compute_block_sparsity import compute_block_sparsity
|
| 43 |
+
from .mask import (
|
| 44 |
+
summary_attn_mask_func, summary_attn_contiguous_mask_func,
|
| 45 |
+
sliding_chunk_mask_func, sliding_chunk_contiguous_mask_func,
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
prefix_summary_length = seqlen_k - seqlen_q
|
| 49 |
+
cache_key = (seqlen_q, chunk_size, summary_num, sliding_chunk_num,
|
| 50 |
+
prefix_summary_length, skip_old_summary, is_fwd)
|
| 51 |
+
if cache_key not in SUMMARY_ATTN_CACHE:
|
| 52 |
+
if skip_old_summary:
|
| 53 |
+
func = sliding_chunk_contiguous_mask_func if prefix_summary_length > 0 else sliding_chunk_mask_func
|
| 54 |
+
else:
|
| 55 |
+
func = summary_attn_contiguous_mask_func if prefix_summary_length > 0 else summary_attn_mask_func
|
| 56 |
+
mask_mod = partial(func, chunk_size=chunk_size, summary_num=summary_num,
|
| 57 |
+
sliding_chunk_num=sliding_chunk_num,
|
| 58 |
+
prefix_summary_length=prefix_summary_length, swap_indices=False)
|
| 59 |
+
mask_swap_mod = mask_mod
|
| 60 |
+
if not is_fwd:
|
| 61 |
+
mask_swap_mod = partial(func, chunk_size=chunk_size, summary_num=summary_num,
|
| 62 |
+
sliding_chunk_num=sliding_chunk_num,
|
| 63 |
+
prefix_summary_length=prefix_summary_length, swap_indices=True)
|
| 64 |
+
m_block_size, n_block_size = n_block_size, m_block_size
|
| 65 |
+
seqlen_q, seqlen_k = seqlen_k, seqlen_q
|
| 66 |
+
_, block_sparse = compute_block_sparsity(
|
| 67 |
+
m_block_size, n_block_size, 1, 1, seqlen_q, seqlen_k,
|
| 68 |
+
mask_swap_mod, None, "cuda",
|
| 69 |
+
)
|
| 70 |
+
SUMMARY_ATTN_CACHE[cache_key] = (block_sparse, mask_mod)
|
| 71 |
+
return SUMMARY_ATTN_CACHE[cache_key]
|
| 72 |
+
|
| 73 |
+
@staticmethod
|
| 74 |
+
def forward(ctx, q, k, v, chunk_size, summary_num, sliding_chunk_num,
|
| 75 |
+
softmax_scale=None, causal=False, pack_gqa=None,
|
| 76 |
+
deterministic=False, skip_old_summary=False,
|
| 77 |
+
force_return_lse=False):
|
| 78 |
+
from flash_attn.cute.interface import _flash_attn_fwd
|
| 79 |
+
block_sparse, mask_mod = SummaryAttnFunc._get_cached_value(
|
| 80 |
+
q.shape[1], k.shape[1], chunk_size, summary_num, sliding_chunk_num,
|
| 81 |
+
skip_old_summary=skip_old_summary, is_fwd=True, m_block_size=128, n_block_size=64,
|
| 82 |
+
)
|
| 83 |
+
out, lse = _flash_attn_fwd(q, k, v, softmax_scale=softmax_scale, causal=causal,
|
| 84 |
+
pack_gqa=pack_gqa, mask_mod=mask_mod,
|
| 85 |
+
block_sparse_tensors=block_sparse,
|
| 86 |
+
m_block_size=128, n_block_size=64,
|
| 87 |
+
return_lse=force_return_lse)
|
| 88 |
+
return out, lse
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# ---------------------------------------------------------------------------
|
| 93 |
+
# Compressed-KSA CuTe path (Hopper only)
|
| 94 |
+
# ---------------------------------------------------------------------------
|
| 95 |
+
|
| 96 |
+
class HierarchicalSummaryAttnFunc(torch.autograd.Function):
|
| 97 |
+
@staticmethod
|
| 98 |
+
def _get_cached_value(
|
| 99 |
+
device_index, seqlen_q, seqlen_k, chunk_size, sliding_chunk_num,
|
| 100 |
+
group_size, meta_length, *, is_fwd,
|
| 101 |
+
m_block_size, n_block_size,
|
| 102 |
+
):
|
| 103 |
+
from flash_attn.cute.compute_block_sparsity import compute_block_sparsity
|
| 104 |
+
from .mask import hierarchical_summary_attn_mask_func
|
| 105 |
+
|
| 106 |
+
cache_key = (
|
| 107 |
+
device_index, seqlen_q, seqlen_k, chunk_size, sliding_chunk_num,
|
| 108 |
+
group_size, meta_length, is_fwd,
|
| 109 |
+
)
|
| 110 |
+
cached = HIERARCHICAL_SUMMARY_ATTN_CACHE.get(cache_key)
|
| 111 |
+
if cached is not None:
|
| 112 |
+
return cached
|
| 113 |
+
|
| 114 |
+
mask_mod = partial(
|
| 115 |
+
hierarchical_summary_attn_mask_func,
|
| 116 |
+
chunk_size=chunk_size,
|
| 117 |
+
sliding_chunk_num=sliding_chunk_num,
|
| 118 |
+
group_size=group_size,
|
| 119 |
+
meta_length=meta_length,
|
| 120 |
+
swap_indices=False,
|
| 121 |
+
)
|
| 122 |
+
sparsity_mask_mod = mask_mod
|
| 123 |
+
sparsity_seqlen_q, sparsity_seqlen_k = seqlen_q, seqlen_k
|
| 124 |
+
if not is_fwd:
|
| 125 |
+
sparsity_mask_mod = partial(
|
| 126 |
+
hierarchical_summary_attn_mask_func,
|
| 127 |
+
chunk_size=chunk_size,
|
| 128 |
+
sliding_chunk_num=sliding_chunk_num,
|
| 129 |
+
group_size=group_size,
|
| 130 |
+
meta_length=meta_length,
|
| 131 |
+
swap_indices=True,
|
| 132 |
+
)
|
| 133 |
+
m_block_size, n_block_size = n_block_size, m_block_size
|
| 134 |
+
sparsity_seqlen_q, sparsity_seqlen_k = seqlen_k, seqlen_q
|
| 135 |
+
|
| 136 |
+
_, block_sparse = compute_block_sparsity(
|
| 137 |
+
m_block_size,
|
| 138 |
+
n_block_size,
|
| 139 |
+
1,
|
| 140 |
+
1,
|
| 141 |
+
sparsity_seqlen_q,
|
| 142 |
+
sparsity_seqlen_k,
|
| 143 |
+
sparsity_mask_mod,
|
| 144 |
+
None,
|
| 145 |
+
"cuda",
|
| 146 |
+
)
|
| 147 |
+
cached = (block_sparse, mask_mod)
|
| 148 |
+
HIERARCHICAL_SUMMARY_ATTN_CACHE[cache_key] = cached
|
| 149 |
+
return cached
|
| 150 |
+
|
| 151 |
+
@staticmethod
|
| 152 |
+
def forward(
|
| 153 |
+
ctx, q, k, v, meta_k, meta_v,
|
| 154 |
+
chunk_size, sliding_chunk_num, group_size,
|
| 155 |
+
softmax_scale=None, deterministic=False, mass_correction=True,
|
| 156 |
+
force_return_lse=False,
|
| 157 |
+
):
|
| 158 |
+
from flash_attn.cute.interface import _flash_attn_fwd
|
| 159 |
+
|
| 160 |
+
from .mask import (
|
| 161 |
+
hierarchical_summary_attn_score_mod,
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
# Keep one physical entry per meta K/V and apply log(G) directly to
|
| 165 |
+
# its score. This preserves the mass correction without undoing the
|
| 166 |
+
# sequence compression inside the attention kernel.
|
| 167 |
+
needs_backward = any(
|
| 168 |
+
tensor.requires_grad for tensor in (q, k, v, meta_k, meta_v)
|
| 169 |
+
)
|
| 170 |
+
meta_length = meta_k.shape[1]
|
| 171 |
+
full_k = torch.cat((meta_k, k), dim=1).contiguous()
|
| 172 |
+
full_v = torch.cat((meta_v, v), dim=1).contiguous()
|
| 173 |
+
meta_logit_bias = math.log(group_size)
|
| 174 |
+
score_mod = None
|
| 175 |
+
if mass_correction:
|
| 176 |
+
score_mod = partial(
|
| 177 |
+
hierarchical_summary_attn_score_mod,
|
| 178 |
+
meta_length=meta_length,
|
| 179 |
+
meta_logit_bias=meta_logit_bias,
|
| 180 |
+
)
|
| 181 |
+
block_sparse, mask_mod = HierarchicalSummaryAttnFunc._get_cached_value(
|
| 182 |
+
q.device.index,
|
| 183 |
+
q.shape[1],
|
| 184 |
+
full_k.shape[1],
|
| 185 |
+
chunk_size,
|
| 186 |
+
sliding_chunk_num,
|
| 187 |
+
group_size,
|
| 188 |
+
meta_length,
|
| 189 |
+
is_fwd=True,
|
| 190 |
+
m_block_size=128,
|
| 191 |
+
n_block_size=64,
|
| 192 |
+
)
|
| 193 |
+
out, lse = _flash_attn_fwd(
|
| 194 |
+
q,
|
| 195 |
+
full_k,
|
| 196 |
+
full_v,
|
| 197 |
+
softmax_scale=softmax_scale,
|
| 198 |
+
causal=False,
|
| 199 |
+
pack_gqa=None,
|
| 200 |
+
score_mod=score_mod,
|
| 201 |
+
mask_mod=mask_mod,
|
| 202 |
+
block_sparse_tensors=block_sparse,
|
| 203 |
+
m_block_size=128,
|
| 204 |
+
n_block_size=64,
|
| 205 |
+
# torch.autograd.Function.forward runs with grad recording
|
| 206 |
+
# disabled. Therefore full_k/full_v created above do not carry
|
| 207 |
+
# requires_grad even when meta_k/meta_v are the only trainable
|
| 208 |
+
# inputs (the meta-pool-only warm-up case). Force LSE retention
|
| 209 |
+
# based on the original inputs so custom backward has the state
|
| 210 |
+
# it needs without adding inference-only storage.
|
| 211 |
+
return_lse=needs_backward or force_return_lse,
|
| 212 |
+
)
|
| 213 |
+
return out, lse
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
# ---------------------------------------------------------------------------
|
| 218 |
+
# Per-query attention-reuse S-KSA CuTe path (Hopper only)
|
| 219 |
+
# ---------------------------------------------------------------------------
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
# ---------------------------------------------------------------------------
|
| 226 |
+
# Summary-indexed, 8-token block sparse Full attention (Hopper only)
|
| 227 |
+
# ---------------------------------------------------------------------------
|
| 228 |
+
|
| 229 |
+
def _validate_summary_block_sparse_inputs(
|
| 230 |
+
q,
|
| 231 |
+
k,
|
| 232 |
+
v,
|
| 233 |
+
selected_block_indices,
|
| 234 |
+
block_size_tokens,
|
| 235 |
+
local_window_tokens,
|
| 236 |
+
local_alignment_tokens=None,
|
| 237 |
+
):
|
| 238 |
+
if q.ndim != 4 or k.ndim != 4 or v.ndim != 4:
|
| 239 |
+
raise ValueError("q, k, and v must have [batch, text, heads, head_dim]")
|
| 240 |
+
if q.shape[0] != k.shape[0] or k.shape != v.shape:
|
| 241 |
+
raise ValueError("q, k, and v batch/KV shapes must agree")
|
| 242 |
+
if q.shape[1] != k.shape[1]:
|
| 243 |
+
raise ValueError(
|
| 244 |
+
"summary_block_sparse_attn_func currently requires equal Q/K text lengths"
|
| 245 |
+
)
|
| 246 |
+
if selected_block_indices.ndim != 3:
|
| 247 |
+
raise ValueError("selected_block_indices must have [batch, query, topk]")
|
| 248 |
+
if tuple(selected_block_indices.shape[:2]) != tuple(q.shape[:2]):
|
| 249 |
+
raise ValueError("one selected-block row is required per text query")
|
| 250 |
+
if selected_block_indices.dtype not in (torch.int32, torch.int64):
|
| 251 |
+
raise ValueError("selected_block_indices must be int32 or int64")
|
| 252 |
+
if not isinstance(block_size_tokens, int) or block_size_tokens <= 0:
|
| 253 |
+
raise ValueError("block_size_tokens must be a positive integer")
|
| 254 |
+
if block_size_tokens not in (1, 2, 4, 8):
|
| 255 |
+
raise ValueError("route span must be 1, 2, 4, or 8 tokens")
|
| 256 |
+
if not isinstance(local_window_tokens, int) or local_window_tokens <= 0:
|
| 257 |
+
raise ValueError("local_window_tokens must be a positive integer")
|
| 258 |
+
if local_alignment_tokens is None:
|
| 259 |
+
local_alignment_tokens = block_size_tokens
|
| 260 |
+
if (
|
| 261 |
+
not isinstance(local_alignment_tokens, int)
|
| 262 |
+
or local_alignment_tokens <= 0
|
| 263 |
+
or local_alignment_tokens % block_size_tokens
|
| 264 |
+
):
|
| 265 |
+
raise ValueError(
|
| 266 |
+
"local_alignment_tokens must be a positive multiple of the "
|
| 267 |
+
"route span"
|
| 268 |
+
)
|
| 269 |
+
if q.shape[-1] != k.shape[-1]:
|
| 270 |
+
raise ValueError("Q and K head dimensions must agree")
|
| 271 |
+
if q.shape[2] % k.shape[2] != 0:
|
| 272 |
+
raise ValueError("the number of Q heads must be divisible by KV heads")
|
| 273 |
+
if not all(t.is_cuda for t in (q, k, v, selected_block_indices)):
|
| 274 |
+
raise RuntimeError("summary_block_sparse_attn_func requires CUDA tensors")
|
| 275 |
+
if not (q.device == k.device == v.device == selected_block_indices.device):
|
| 276 |
+
raise ValueError("q, k, v, and selected_block_indices must share a device")
|
| 277 |
+
|
| 278 |
+
if os.environ.get("SKSA_VALIDATE_BLOCK_INDICES", "0") == "1":
|
| 279 |
+
raw_block_count = math.ceil(k.shape[1] / block_size_tokens)
|
| 280 |
+
# This diagnostic intentionally synchronises. The production path
|
| 281 |
+
# relies on the indexer's TopK contract and remains asynchronous.
|
| 282 |
+
invalid = (selected_block_indices < -1) | (
|
| 283 |
+
selected_block_indices >= raw_block_count
|
| 284 |
+
)
|
| 285 |
+
if bool(invalid.any().item()):
|
| 286 |
+
raise ValueError(
|
| 287 |
+
f"selected block IDs must be -1 or in [0, {raw_block_count})"
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def _pack_summary_block_indices(
|
| 292 |
+
selected_block_indices,
|
| 293 |
+
*,
|
| 294 |
+
raw_block_count,
|
| 295 |
+
max_workspace_elements=16 * 1024 * 1024,
|
| 296 |
+
):
|
| 297 |
+
"""Pack ``[B,Q,K]`` unique block IDs into an 8x smaller bit route.
|
| 298 |
+
|
| 299 |
+
Top-k is expected to produce unique non-negative IDs per row. Repeated
|
| 300 |
+
IDs would make scatter-add carry into a neighbouring bit and are rejected
|
| 301 |
+
by the public wrapper before this helper is called.
|
| 302 |
+
"""
|
| 303 |
+
batch, query_count, _ = selected_block_indices.shape
|
| 304 |
+
# CuTe's DLPack bridge cannot infer a unique leading dimension for a
|
| 305 |
+
# rank-3 auxiliary tensor whose final extent is 1 (both trailing strides
|
| 306 |
+
# then equal one). One unused padding byte keeps short smoke/decode-like
|
| 307 |
+
# sequences on the same code path without changing any route bit.
|
| 308 |
+
route_byte_count = max(2, math.ceil(raw_block_count / 8))
|
| 309 |
+
packed = torch.zeros(
|
| 310 |
+
(batch, query_count, route_byte_count),
|
| 311 |
+
dtype=torch.uint8,
|
| 312 |
+
device=selected_block_indices.device,
|
| 313 |
+
)
|
| 314 |
+
query_chunk = max(
|
| 315 |
+
1,
|
| 316 |
+
max_workspace_elements // max(batch * route_byte_count, 1),
|
| 317 |
+
)
|
| 318 |
+
for query_start in range(0, query_count, query_chunk):
|
| 319 |
+
query_end = min(query_start + query_chunk, query_count)
|
| 320 |
+
indices = selected_block_indices[:, query_start:query_end]
|
| 321 |
+
valid = indices >= 0
|
| 322 |
+
safe_indices = indices.clamp_min(0).to(torch.long)
|
| 323 |
+
byte_indices = safe_indices // 8
|
| 324 |
+
bit_values = (
|
| 325 |
+
torch.ones_like(safe_indices, dtype=torch.int32)
|
| 326 |
+
<< (safe_indices % 8).to(torch.int32)
|
| 327 |
+
)
|
| 328 |
+
bit_values = bit_values * valid.to(torch.int32)
|
| 329 |
+
workspace = torch.zeros(
|
| 330 |
+
(batch, query_end - query_start, route_byte_count),
|
| 331 |
+
dtype=torch.int32,
|
| 332 |
+
device=indices.device,
|
| 333 |
+
)
|
| 334 |
+
workspace.scatter_add_(2, byte_indices, bit_values)
|
| 335 |
+
packed[:, query_start:query_end] = workspace.to(torch.uint8)
|
| 336 |
+
return packed.contiguous()
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
def _check_unique_summary_block_indices(selected_block_indices):
|
| 340 |
+
"""Reject duplicate valid IDs because the packed route uses scatter-add."""
|
| 341 |
+
if selected_block_indices.shape[-1] <= 1:
|
| 342 |
+
return
|
| 343 |
+
sorted_indices = selected_block_indices.sort(dim=-1).values
|
| 344 |
+
duplicate = (
|
| 345 |
+
(sorted_indices[..., 1:] == sorted_indices[..., :-1])
|
| 346 |
+
& (sorted_indices[..., 1:] >= 0)
|
| 347 |
+
)
|
| 348 |
+
if bool(duplicate.any().item()):
|
| 349 |
+
raise ValueError("valid selected block IDs must be unique within each query")
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def _summary_block_sparse_tensors(
|
| 353 |
+
selected_block_indices,
|
| 354 |
+
*,
|
| 355 |
+
text_length,
|
| 356 |
+
block_size_tokens,
|
| 357 |
+
local_window_tokens,
|
| 358 |
+
local_alignment_tokens=None,
|
| 359 |
+
query_tile_size=128,
|
| 360 |
+
forward_kv_tile_size=64,
|
| 361 |
+
backward_kv_tile_size=64,
|
| 362 |
+
packed_route=None,
|
| 363 |
+
):
|
| 364 |
+
"""Build conservative CuTe schedules from compact block IDs.
|
| 365 |
+
|
| 366 |
+
Forward uses a narrower 64-token KV execution tile. This retains the
|
| 367 |
+
cross-query reuse of a 128-token Q tile while reducing over-computation
|
| 368 |
+
when only some of the eight 8-token raw blocks in a 64-token KV tile are
|
| 369 |
+
selected. The model-local FlashAttention wheel supports a 64-token SM90
|
| 370 |
+
backward KV tile, so the same schedule can be transposed directly instead
|
| 371 |
+
of conservatively merging adjacent forward tiles. Passing 128 remains
|
| 372 |
+
available for numerical and performance A/B tests.
|
| 373 |
+
|
| 374 |
+
Remote tiles are always marked partial because every query row may have a
|
| 375 |
+
different route. The exact per-query mask is still applied inside both
|
| 376 |
+
kernels; these schedules only decide which execution tiles are launched.
|
| 377 |
+
"""
|
| 378 |
+
from flash_attn.cute.block_sparsity import BlockSparseTensorsTorch
|
| 379 |
+
|
| 380 |
+
if query_tile_size != 128:
|
| 381 |
+
raise ValueError("SM90 S-KSA schedules currently require 128-token Q tiles")
|
| 382 |
+
if forward_kv_tile_size != 64:
|
| 383 |
+
raise ValueError("SM90 S-KSA forward currently requires 64-token KV tiles")
|
| 384 |
+
if backward_kv_tile_size not in (64, 128):
|
| 385 |
+
raise ValueError("SM90 S-KSA backward requires a 64- or 128-token KV tile")
|
| 386 |
+
if backward_kv_tile_size % forward_kv_tile_size != 0:
|
| 387 |
+
raise ValueError("backward KV tile must be a multiple of forward KV tile")
|
| 388 |
+
if local_alignment_tokens is None:
|
| 389 |
+
local_alignment_tokens = block_size_tokens
|
| 390 |
+
|
| 391 |
+
batch, route_length, _ = selected_block_indices.shape
|
| 392 |
+
if route_length != text_length:
|
| 393 |
+
raise ValueError("one selected-block row is required per text query")
|
| 394 |
+
q_tile_count = math.ceil(text_length / query_tile_size)
|
| 395 |
+
forward_kv_tile_count = math.ceil(text_length / forward_kv_tile_size)
|
| 396 |
+
backward_kv_tile_count = math.ceil(text_length / backward_kv_tile_size)
|
| 397 |
+
padded_query_count = q_tile_count * query_tile_size
|
| 398 |
+
|
| 399 |
+
if packed_route is not None:
|
| 400 |
+
# Reuse the already-built bitmap instead of traversing
|
| 401 |
+
# [B,Q,TopK] again. An 8-token route uses one byte per 64-token KV
|
| 402 |
+
# tile; a token route uses eight bytes per tile.
|
| 403 |
+
if forward_kv_tile_size % (block_size_tokens * 8):
|
| 404 |
+
raise ValueError(
|
| 405 |
+
"a forward KV tile must contain a whole number of route bytes"
|
| 406 |
+
)
|
| 407 |
+
route_bytes_per_kv_tile = (
|
| 408 |
+
forward_kv_tile_size // (block_size_tokens * 8)
|
| 409 |
+
)
|
| 410 |
+
required_route_bytes = (
|
| 411 |
+
forward_kv_tile_count * route_bytes_per_kv_tile
|
| 412 |
+
)
|
| 413 |
+
route_byte_count = math.ceil(math.ceil(text_length / block_size_tokens) / 8)
|
| 414 |
+
if (
|
| 415 |
+
packed_route.ndim != 3
|
| 416 |
+
or packed_route.shape[0] != batch
|
| 417 |
+
or packed_route.shape[1] != text_length
|
| 418 |
+
or packed_route.shape[2] < route_byte_count
|
| 419 |
+
or packed_route.dtype != torch.uint8
|
| 420 |
+
or packed_route.device != selected_block_indices.device
|
| 421 |
+
):
|
| 422 |
+
raise ValueError(
|
| 423 |
+
"packed S-KSA route must be uint8 [batch, query, route_byte]"
|
| 424 |
+
)
|
| 425 |
+
if padded_query_count != text_length:
|
| 426 |
+
packed_padding = packed_route.new_zeros(
|
| 427 |
+
(
|
| 428 |
+
batch,
|
| 429 |
+
padded_query_count - text_length,
|
| 430 |
+
packed_route.shape[2],
|
| 431 |
+
)
|
| 432 |
+
)
|
| 433 |
+
packed_padded = torch.cat((packed_route, packed_padding), dim=1)
|
| 434 |
+
else:
|
| 435 |
+
packed_padded = packed_route
|
| 436 |
+
packed_padded = packed_padded[..., :required_route_bytes]
|
| 437 |
+
if packed_padded.shape[-1] < required_route_bytes:
|
| 438 |
+
packed_padded = torch.nn.functional.pad(
|
| 439 |
+
packed_padded, (0, required_route_bytes - packed_padded.shape[-1])
|
| 440 |
+
)
|
| 441 |
+
allowed = packed_padded[..., :required_route_bytes].view(
|
| 442 |
+
batch,
|
| 443 |
+
q_tile_count,
|
| 444 |
+
query_tile_size,
|
| 445 |
+
forward_kv_tile_count,
|
| 446 |
+
route_bytes_per_kv_tile,
|
| 447 |
+
).any(dim=(2, 4)).to(torch.bool).contiguous()
|
| 448 |
+
else:
|
| 449 |
+
# Compatibility/oracle path for callers that only have selected IDs.
|
| 450 |
+
if padded_query_count != text_length:
|
| 451 |
+
padding = selected_block_indices.new_full(
|
| 452 |
+
(
|
| 453 |
+
batch,
|
| 454 |
+
padded_query_count - text_length,
|
| 455 |
+
selected_block_indices.shape[-1],
|
| 456 |
+
),
|
| 457 |
+
-1,
|
| 458 |
+
)
|
| 459 |
+
selected_padded = torch.cat(
|
| 460 |
+
(selected_block_indices, padding), dim=1
|
| 461 |
+
)
|
| 462 |
+
else:
|
| 463 |
+
selected_padded = selected_block_indices
|
| 464 |
+
allowed_with_sentinel = torch.zeros(
|
| 465 |
+
(batch, q_tile_count, forward_kv_tile_count + 1),
|
| 466 |
+
dtype=torch.bool,
|
| 467 |
+
device=selected_block_indices.device,
|
| 468 |
+
)
|
| 469 |
+
# Chunk the int64 scatter indices so 128K x large-topk routes do not
|
| 470 |
+
# create another sequence-sized temporary tensor.
|
| 471 |
+
tile_chunk = 8
|
| 472 |
+
for q_tile_start in range(0, q_tile_count, tile_chunk):
|
| 473 |
+
q_tile_end = min(q_tile_start + tile_chunk, q_tile_count)
|
| 474 |
+
query_start = q_tile_start * query_tile_size
|
| 475 |
+
query_end = q_tile_end * query_tile_size
|
| 476 |
+
logical = selected_padded[:, query_start:query_end]
|
| 477 |
+
valid = logical >= 0
|
| 478 |
+
physical = torch.where(
|
| 479 |
+
valid,
|
| 480 |
+
(logical * block_size_tokens) // forward_kv_tile_size,
|
| 481 |
+
logical.new_full((), forward_kv_tile_count),
|
| 482 |
+
)
|
| 483 |
+
physical = physical.reshape(
|
| 484 |
+
batch, q_tile_end - q_tile_start, -1
|
| 485 |
+
)
|
| 486 |
+
allowed_with_sentinel[:, q_tile_start:q_tile_end].scatter_(
|
| 487 |
+
2,
|
| 488 |
+
physical.to(torch.long),
|
| 489 |
+
True,
|
| 490 |
+
)
|
| 491 |
+
allowed = allowed_with_sentinel[
|
| 492 |
+
..., :forward_kv_tile_count
|
| 493 |
+
].contiguous()
|
| 494 |
+
|
| 495 |
+
q_tiles = torch.arange(q_tile_count, device=allowed.device)
|
| 496 |
+
kv_tiles = torch.arange(forward_kv_tile_count, device=allowed.device)
|
| 497 |
+
q_first_token = q_tiles * query_tile_size
|
| 498 |
+
q_last_token = ((q_tiles + 1) * query_tile_size - 1).clamp_max(
|
| 499 |
+
text_length - 1
|
| 500 |
+
)
|
| 501 |
+
kv_first_token = kv_tiles * forward_kv_tile_size
|
| 502 |
+
kv_last_token = ((kv_tiles + 1) * forward_kv_tile_size - 1).clamp_max(
|
| 503 |
+
text_length - 1
|
| 504 |
+
)
|
| 505 |
+
|
| 506 |
+
# Schedule every tile with at least one causal-local (q, k) pair. Each
|
| 507 |
+
# query's local start is clamped at zero and aligned down to a raw summary
|
| 508 |
+
# block boundary. The token mask still clips the two boundary tiles
|
| 509 |
+
# exactly.
|
| 510 |
+
q_first_local_start = (
|
| 511 |
+
(q_first_token - local_window_tokens + 1).clamp_min(0)
|
| 512 |
+
// local_alignment_tokens
|
| 513 |
+
) * local_alignment_tokens
|
| 514 |
+
local_overlap = (
|
| 515 |
+
(kv_first_token[None, :] <= q_last_token[:, None])
|
| 516 |
+
& (kv_last_token[None, :] >= q_first_local_start[:, None])
|
| 517 |
+
)
|
| 518 |
+
causal_overlap = kv_first_token[None, :] <= q_last_token[:, None]
|
| 519 |
+
allowed |= local_overlap.unsqueeze(0)
|
| 520 |
+
allowed &= causal_overlap.unsqueeze(0)
|
| 521 |
+
|
| 522 |
+
# A tile can skip mask_mod only when every actual q/k pair is causal and
|
| 523 |
+
# inside the local window. Selected remote tiles always stay partial.
|
| 524 |
+
q_last_local_start = (
|
| 525 |
+
(q_last_token - local_window_tokens + 1).clamp_min(0)
|
| 526 |
+
// local_alignment_tokens
|
| 527 |
+
) * local_alignment_tokens
|
| 528 |
+
local_full = (
|
| 529 |
+
(kv_last_token[None, :] <= q_first_token[:, None])
|
| 530 |
+
& (kv_first_token[None, :] >= q_last_local_start[:, None])
|
| 531 |
+
)
|
| 532 |
+
full_allowed = allowed & local_full.unsqueeze(0)
|
| 533 |
+
partial_allowed = allowed & ~full_allowed
|
| 534 |
+
|
| 535 |
+
def compact(partial, full, *, block_size):
|
| 536 |
+
counts = partial.sum(dim=-1).to(torch.int32).unsqueeze(1)
|
| 537 |
+
indices = torch.argsort(
|
| 538 |
+
partial.to(torch.int8), dim=-1, descending=True, stable=True
|
| 539 |
+
).to(torch.int32).unsqueeze(1)
|
| 540 |
+
full_counts = full.sum(dim=-1).to(torch.int32).unsqueeze(1)
|
| 541 |
+
full_indices = torch.argsort(
|
| 542 |
+
full.to(torch.int8), dim=-1, descending=True, stable=True
|
| 543 |
+
).to(torch.int32).unsqueeze(1)
|
| 544 |
+
return BlockSparseTensorsTorch(
|
| 545 |
+
mask_block_cnt=counts,
|
| 546 |
+
mask_block_idx=indices,
|
| 547 |
+
full_block_cnt=full_counts,
|
| 548 |
+
full_block_idx=full_indices,
|
| 549 |
+
block_size=block_size,
|
| 550 |
+
)
|
| 551 |
+
|
| 552 |
+
forward_sparse = compact(
|
| 553 |
+
partial_allowed,
|
| 554 |
+
full_allowed,
|
| 555 |
+
block_size=(query_tile_size, forward_kv_tile_size),
|
| 556 |
+
)
|
| 557 |
+
|
| 558 |
+
# Backward consumes the KV-major transpose of the forward Q-major schedule.
|
| 559 |
+
# With the default 64-token tile this is a direct transpose. The generic
|
| 560 |
+
# merge below also keeps the old 128-token schedule available for A/B:
|
| 561 |
+
# a merged tile is full only if both halves are full.
|
| 562 |
+
return forward_sparse, None
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
class SummaryBlockSparseAttentionFunc(torch.autograd.Function):
|
| 566 |
+
"""Exact per-query Full attention over selected raw-token route units."""
|
| 567 |
+
|
| 568 |
+
@staticmethod
|
| 569 |
+
def forward(
|
| 570 |
+
ctx,
|
| 571 |
+
q,
|
| 572 |
+
k,
|
| 573 |
+
v,
|
| 574 |
+
packed_route,
|
| 575 |
+
selected_block_indices,
|
| 576 |
+
block_size_tokens,
|
| 577 |
+
local_window_tokens,
|
| 578 |
+
local_alignment_tokens,
|
| 579 |
+
softmax_scale=None,
|
| 580 |
+
deterministic=False,
|
| 581 |
+
cached_output=None,
|
| 582 |
+
cached_lse=None,
|
| 583 |
+
):
|
| 584 |
+
from flash_attn.cute.interface import _flash_attn_fwd
|
| 585 |
+
from .mask import summary_block_sparse_attn_mask_func
|
| 586 |
+
|
| 587 |
+
fwd_sparse, bwd_sparse = _summary_block_sparse_tensors(
|
| 588 |
+
selected_block_indices,
|
| 589 |
+
text_length=q.shape[1],
|
| 590 |
+
block_size_tokens=block_size_tokens,
|
| 591 |
+
local_window_tokens=local_window_tokens,
|
| 592 |
+
local_alignment_tokens=local_alignment_tokens,
|
| 593 |
+
packed_route=packed_route,
|
| 594 |
+
)
|
| 595 |
+
mask_mod = partial(
|
| 596 |
+
summary_block_sparse_attn_mask_func,
|
| 597 |
+
block_size_tokens=block_size_tokens,
|
| 598 |
+
local_window_tokens=local_window_tokens,
|
| 599 |
+
local_alignment_tokens=local_alignment_tokens,
|
| 600 |
+
)
|
| 601 |
+
if (cached_output is None) != (cached_lse is None):
|
| 602 |
+
raise ValueError("cached sparse output and LSE must be provided together")
|
| 603 |
+
if cached_output is None:
|
| 604 |
+
out, lse = _flash_attn_fwd(
|
| 605 |
+
q,
|
| 606 |
+
k,
|
| 607 |
+
v,
|
| 608 |
+
softmax_scale=softmax_scale,
|
| 609 |
+
causal=False,
|
| 610 |
+
pack_gqa=None,
|
| 611 |
+
mask_mod=mask_mod,
|
| 612 |
+
block_sparse_tensors=fwd_sparse,
|
| 613 |
+
aux_tensors=[packed_route],
|
| 614 |
+
m_block_size=128,
|
| 615 |
+
n_block_size=64,
|
| 616 |
+
return_lse=any(t.requires_grad for t in (q, k, v)),
|
| 617 |
+
)
|
| 618 |
+
else:
|
| 619 |
+
expected_lse_shape = (q.shape[0], q.shape[2], q.shape[1])
|
| 620 |
+
if (
|
| 621 |
+
cached_output.shape != q.shape
|
| 622 |
+
or cached_output.dtype != q.dtype
|
| 623 |
+
or cached_output.device != q.device
|
| 624 |
+
or cached_lse.shape != expected_lse_shape
|
| 625 |
+
or cached_lse.dtype != torch.float32
|
| 626 |
+
or cached_lse.device != q.device
|
| 627 |
+
):
|
| 628 |
+
raise ValueError("cached sparse attention tensor metadata mismatch")
|
| 629 |
+
# The enclosing non-reentrant layer checkpoint only needs to
|
| 630 |
+
# rebuild tensors saved for this Function's backward. Reuse the
|
| 631 |
+
# detached original-forward values instead of executing the same
|
| 632 |
+
# sparse forward kernel a second time.
|
| 633 |
+
out, lse = cached_output, cached_lse
|
| 634 |
+
if lse is not None:
|
| 635 |
+
ctx.mark_non_differentiable(lse)
|
| 636 |
+
return out, lse
|
| 637 |
+
|
| 638 |
+
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
|
| 642 |
+
# ---------------------------------------------------------------------------
|
| 643 |
+
# FlexAttention path (Ampere / any GPU with PyTorch >= 2.5)
|
| 644 |
+
# ---------------------------------------------------------------------------
|
| 645 |
+
|
| 646 |
+
_FLEX_BLOCK_MASK_CACHE: Dict[Tuple, object] = {}
|
| 647 |
+
|
| 648 |
+
|
| 649 |
+
def _create_block_mask_no_materialize(mask_mod, seq_len_q, seq_len_k, device, BLOCK_SIZE=128):
|
| 650 |
+
"""Create BlockMask without materializing the full (seq_len_q, seq_len_k) dense mask.
|
| 651 |
+
|
| 652 |
+
Processes one q_block row at a time, reducing peak memory from
|
| 653 |
+
O(seq_q * seq_k) to O(BLOCK_SIZE * seq_k).
|
| 654 |
+
"""
|
| 655 |
+
from torch.nn.attention.flex_attention import BlockMask
|
| 656 |
+
|
| 657 |
+
num_q_blocks = (seq_len_q + BLOCK_SIZE - 1) // BLOCK_SIZE
|
| 658 |
+
num_kv_blocks = (seq_len_k + BLOCK_SIZE - 1) // BLOCK_SIZE
|
| 659 |
+
|
| 660 |
+
# Block-level sparsity: (num_q_blocks, num_kv_blocks)
|
| 661 |
+
any_mask = torch.zeros(num_q_blocks, num_kv_blocks, dtype=torch.bool, device=device)
|
| 662 |
+
full_mask = torch.zeros(num_q_blocks, num_kv_blocks, dtype=torch.bool, device=device)
|
| 663 |
+
|
| 664 |
+
kv_arange = torch.arange(seq_len_k, device=device)
|
| 665 |
+
pad_k = (-seq_len_k) % BLOCK_SIZE
|
| 666 |
+
last_kv_len = seq_len_k - (num_kv_blocks - 1) * BLOCK_SIZE if seq_len_k % BLOCK_SIZE else BLOCK_SIZE
|
| 667 |
+
|
| 668 |
+
for q_blk in range(num_q_blocks):
|
| 669 |
+
q_start = q_blk * BLOCK_SIZE
|
| 670 |
+
q_end = min(q_start + BLOCK_SIZE, seq_len_q)
|
| 671 |
+
q_len = q_end - q_start
|
| 672 |
+
q_idx = torch.arange(q_start, q_end, device=device)
|
| 673 |
+
|
| 674 |
+
# (q_len, seq_len_k) bool
|
| 675 |
+
mask = mask_mod(0, 0, q_idx[:, None], kv_arange[None, :])
|
| 676 |
+
|
| 677 |
+
if pad_k > 0:
|
| 678 |
+
mask = torch.nn.functional.pad(mask, (0, pad_k), value=False)
|
| 679 |
+
|
| 680 |
+
# (q_len, num_kv_blocks, BLOCK_SIZE) -> sum per block
|
| 681 |
+
block_sums = mask.reshape(q_len, num_kv_blocks, BLOCK_SIZE).sum(dim=(0, 2))
|
| 682 |
+
|
| 683 |
+
any_mask[q_blk] = block_sums > 0
|
| 684 |
+
full_mask[q_blk] = block_sums == (q_len * BLOCK_SIZE)
|
| 685 |
+
# Last kv block may be shorter
|
| 686 |
+
if pad_k > 0:
|
| 687 |
+
full_mask[q_blk, -1] = block_sums[-1] == (q_len * last_kv_len)
|
| 688 |
+
|
| 689 |
+
def _bool_mask_to_compact(mask_2d):
|
| 690 |
+
"""Convert (rows, cols) bool mask to (num_per_row, indices) format."""
|
| 691 |
+
num_per_row = mask_2d.sum(dim=1).to(torch.int32)
|
| 692 |
+
max_num = max(num_per_row.max().item(), 1)
|
| 693 |
+
sorted_idx = mask_2d.to(torch.int32).argsort(dim=1, descending=True, stable=True)
|
| 694 |
+
indices = sorted_idx[:, :max_num].to(torch.int32)
|
| 695 |
+
return num_per_row, indices
|
| 696 |
+
|
| 697 |
+
kv_num_blocks, kv_indices = _bool_mask_to_compact(any_mask)
|
| 698 |
+
full_kv_num_blocks, full_kv_indices = _bool_mask_to_compact(full_mask)
|
| 699 |
+
q_num_blocks, q_indices = _bool_mask_to_compact(any_mask.t().contiguous())
|
| 700 |
+
full_q_num_blocks, full_q_indices = _bool_mask_to_compact(full_mask.t().contiguous())
|
| 701 |
+
|
| 702 |
+
return BlockMask(
|
| 703 |
+
kv_num_blocks=kv_num_blocks[None, None],
|
| 704 |
+
kv_indices=kv_indices[None, None],
|
| 705 |
+
full_kv_num_blocks=full_kv_num_blocks[None, None],
|
| 706 |
+
full_kv_indices=full_kv_indices[None, None],
|
| 707 |
+
q_num_blocks=q_num_blocks[None, None],
|
| 708 |
+
q_indices=q_indices[None, None],
|
| 709 |
+
full_q_num_blocks=full_q_num_blocks[None, None],
|
| 710 |
+
full_q_indices=full_q_indices[None, None],
|
| 711 |
+
BLOCK_SIZE=(BLOCK_SIZE, BLOCK_SIZE),
|
| 712 |
+
mask_mod=mask_mod,
|
| 713 |
+
seq_lengths=(seq_len_q, seq_len_k),
|
| 714 |
+
)
|
| 715 |
+
|
| 716 |
+
|
| 717 |
+
def _get_flex_block_mask(seq_len_q, seq_len_k, chunk_size, summary_num, sliding_chunk_num,
|
| 718 |
+
skip_old_summary, device):
|
| 719 |
+
cache_key = (seq_len_q, seq_len_k, chunk_size, summary_num, sliding_chunk_num, skip_old_summary)
|
| 720 |
+
if cache_key not in _FLEX_BLOCK_MASK_CACHE:
|
| 721 |
+
block = chunk_size + summary_num
|
| 722 |
+
prefix_summary_length = seq_len_k - seq_len_q
|
| 723 |
+
|
| 724 |
+
if prefix_summary_length > 0:
|
| 725 |
+
def mask_mod(b, h, q_idx, kv_idx):
|
| 726 |
+
q_chunk_id = q_idx // block
|
| 727 |
+
kv_chunk_id = (kv_idx - prefix_summary_length) // block
|
| 728 |
+
is_sliding = (q_chunk_id - kv_chunk_id) <= sliding_chunk_num
|
| 729 |
+
is_q_summary = (q_idx % block) >= chunk_size
|
| 730 |
+
is_kv_summary = ((kv_idx - prefix_summary_length) % block) >= chunk_size
|
| 731 |
+
is_causal = q_idx >= (kv_idx - prefix_summary_length)
|
| 732 |
+
valid_kv_q_summary = is_q_summary & (kv_chunk_id == q_chunk_id) & is_causal
|
| 733 |
+
valid_sliding_q_text = is_sliding & (~is_kv_summary) & (kv_idx >= prefix_summary_length)
|
| 734 |
+
if skip_old_summary:
|
| 735 |
+
valid_kv_q_text = (~is_q_summary) & is_causal & valid_sliding_q_text
|
| 736 |
+
else:
|
| 737 |
+
valid_summary_q_text = ((kv_idx // summary_num) + sliding_chunk_num) < q_chunk_id
|
| 738 |
+
valid_kv_q_text = (~is_q_summary) & ((is_causal & valid_sliding_q_text) | valid_summary_q_text)
|
| 739 |
+
return valid_kv_q_summary | valid_kv_q_text
|
| 740 |
+
else:
|
| 741 |
+
def mask_mod(b, h, q_idx, kv_idx):
|
| 742 |
+
q_chunk_id = q_idx // block
|
| 743 |
+
kv_chunk_id = kv_idx // block
|
| 744 |
+
is_sliding = (q_chunk_id - kv_chunk_id) <= sliding_chunk_num
|
| 745 |
+
is_q_summary = (q_idx % block) >= chunk_size
|
| 746 |
+
is_kv_summary = (kv_idx % block) >= chunk_size
|
| 747 |
+
is_causal = q_idx >= kv_idx
|
| 748 |
+
valid_kv_q_summary = is_q_summary & (kv_chunk_id == q_chunk_id) & is_causal
|
| 749 |
+
valid_sliding_q_text = is_sliding & (~is_kv_summary)
|
| 750 |
+
if skip_old_summary:
|
| 751 |
+
valid_kv_q_text = (~is_q_summary) & is_causal & valid_sliding_q_text
|
| 752 |
+
else:
|
| 753 |
+
valid_summary_q_text = is_kv_summary & ((q_chunk_id - kv_chunk_id) > sliding_chunk_num)
|
| 754 |
+
valid_kv_q_text = (~is_q_summary) & is_causal & (valid_sliding_q_text | valid_summary_q_text)
|
| 755 |
+
return valid_kv_q_summary | valid_kv_q_text
|
| 756 |
+
|
| 757 |
+
block_mask = _create_block_mask_no_materialize(
|
| 758 |
+
mask_mod, seq_len_q, seq_len_k, device,
|
| 759 |
+
)
|
| 760 |
+
_FLEX_BLOCK_MASK_CACHE[cache_key] = block_mask
|
| 761 |
+
return _FLEX_BLOCK_MASK_CACHE[cache_key]
|
| 762 |
+
|
| 763 |
+
|
| 764 |
+
_compiled_flex_attention = None
|
| 765 |
+
|
| 766 |
+
|
| 767 |
+
def _get_compiled_flex_attention():
|
| 768 |
+
global _compiled_flex_attention
|
| 769 |
+
if _compiled_flex_attention is None:
|
| 770 |
+
from torch.nn.attention.flex_attention import flex_attention
|
| 771 |
+
_compiled_flex_attention = torch.compile(flex_attention)
|
| 772 |
+
return _compiled_flex_attention
|
| 773 |
+
|
| 774 |
+
|
| 775 |
+
def _summary_attn_flex(q, k, v, chunk_size, summary_num, sliding_chunk_num, skip_old_summary):
|
| 776 |
+
"""FlexAttention-based implementation. Auto-compiles to efficient Triton block-sparse kernel.
|
| 777 |
+
|
| 778 |
+
Works on any GPU with PyTorch >= 2.5 (Ampere, Hopper, etc).
|
| 779 |
+
No KV duplication, no Python loops — single fused kernel call.
|
| 780 |
+
"""
|
| 781 |
+
b, seq_len_q, h_q, d = q.shape
|
| 782 |
+
_, seq_len_k, h_kv, _ = k.shape
|
| 783 |
+
enable_gqa = (h_q != h_kv)
|
| 784 |
+
|
| 785 |
+
block_mask = _get_flex_block_mask(seq_len_q, seq_len_k, chunk_size, summary_num,
|
| 786 |
+
sliding_chunk_num, skip_old_summary, q.device)
|
| 787 |
+
|
| 788 |
+
# flex_attention expects [B, H, S, D]
|
| 789 |
+
q_flex = q.transpose(1, 2) # [B, H, S, D]
|
| 790 |
+
k_flex = k.transpose(1, 2)
|
| 791 |
+
v_flex = v.transpose(1, 2)
|
| 792 |
+
|
| 793 |
+
compiled_fn = _get_compiled_flex_attention()
|
| 794 |
+
out = compiled_fn(q_flex, k_flex, v_flex, block_mask=block_mask, enable_gqa=enable_gqa)
|
| 795 |
+
out = out.transpose(1, 2) # [B, S, H, D]
|
| 796 |
+
|
| 797 |
+
return out, None
|
| 798 |
+
|
| 799 |
+
|
| 800 |
+
# ---------------------------------------------------------------------------
|
| 801 |
+
# Public API
|
| 802 |
+
# ---------------------------------------------------------------------------
|
| 803 |
+
|
| 804 |
+
def summary_attn_func(
|
| 805 |
+
q, k, v, chunk_size, summary_num, sliding_chunk_num,
|
| 806 |
+
summary_pos: Optional[torch.Tensor] = None,
|
| 807 |
+
skip_old_summary: bool = False,
|
| 808 |
+
force_return_lse: bool = False,
|
| 809 |
+
):
|
| 810 |
+
if summary_pos is not None:
|
| 811 |
+
k = torch.cat((k[:, summary_pos, :], k), dim=1)
|
| 812 |
+
v = torch.cat((v[:, summary_pos, :], v), dim=1)
|
| 813 |
+
|
| 814 |
+
if _check_cute_available():
|
| 815 |
+
out = SummaryAttnFunc.apply(
|
| 816 |
+
q, k, v, chunk_size, summary_num, sliding_chunk_num,
|
| 817 |
+
None, False, None, False, skip_old_summary, force_return_lse,
|
| 818 |
+
)
|
| 819 |
+
else:
|
| 820 |
+
out = _summary_attn_flex(
|
| 821 |
+
q, k, v, chunk_size, summary_num, sliding_chunk_num, skip_old_summary,
|
| 822 |
+
)
|
| 823 |
+
return out
|
| 824 |
+
|
| 825 |
+
|
| 826 |
+
def hierarchical_summary_attn_func(
|
| 827 |
+
q, k, v, meta_k, meta_v,
|
| 828 |
+
chunk_size, sliding_chunk_num, group_size,
|
| 829 |
+
softmax_scale=None, mass_correction=True, force_return_lse=False,
|
| 830 |
+
):
|
| 831 |
+
"""Compressed-KSA attention; tensors use [batch, sequence, heads, dim]."""
|
| 832 |
+
tensors = (q, k, v, meta_k, meta_v)
|
| 833 |
+
if not all(t.is_cuda for t in tensors):
|
| 834 |
+
raise RuntimeError("hierarchical_summary_attn_func requires CUDA tensors")
|
| 835 |
+
if any(t.ndim != 4 for t in tensors):
|
| 836 |
+
raise ValueError("q/k/v/meta_k/meta_v must be rank-4 tensors")
|
| 837 |
+
if k.shape != v.shape or meta_k.shape != meta_v.shape:
|
| 838 |
+
raise ValueError("key/value tensor shapes must match")
|
| 839 |
+
if q.shape[0] != k.shape[0] or q.shape[0] != meta_k.shape[0]:
|
| 840 |
+
raise ValueError("batch sizes must match")
|
| 841 |
+
if q.shape[1] != k.shape[1] or k.shape[2:] != meta_k.shape[2:]:
|
| 842 |
+
raise ValueError("base and meta K/V layouts are incompatible")
|
| 843 |
+
if q.shape[2] % k.shape[2]:
|
| 844 |
+
raise ValueError("query heads must be divisible by KV heads")
|
| 845 |
+
if group_size <= 1 or chunk_size <= 0 or sliding_chunk_num <= 0:
|
| 846 |
+
raise ValueError("group_size must exceed one and chunk/window sizes must be positive")
|
| 847 |
+
with torch.cuda.device(q.device):
|
| 848 |
+
if not _check_cute_available():
|
| 849 |
+
raise RuntimeError("hierarchical_summary_attn_func requires CuTe on sm90+")
|
| 850 |
+
return HierarchicalSummaryAttnFunc.apply(
|
| 851 |
+
q, k, v, meta_k, meta_v,
|
| 852 |
+
chunk_size, sliding_chunk_num, group_size,
|
| 853 |
+
softmax_scale, False, mass_correction, force_return_lse,
|
| 854 |
+
)
|
| 855 |
+
|
| 856 |
+
|
| 857 |
+
|
| 858 |
+
|
| 859 |
+
|
| 860 |
+
|
| 861 |
+
|
| 862 |
+
|
| 863 |
+
def _pad_summary_block_sparse_cute_inputs(
|
| 864 |
+
q,
|
| 865 |
+
k,
|
| 866 |
+
v,
|
| 867 |
+
selected_block_indices,
|
| 868 |
+
*,
|
| 869 |
+
query_tile_size=128,
|
| 870 |
+
):
|
| 871 |
+
"""Pad CuTe's physical Q tile without changing logical attention.
|
| 872 |
+
|
| 873 |
+
The block-sparse CuTe consumer may evaluate ``mask_mod`` for every
|
| 874 |
+
physical row in its final Q tile before applying the sequence-length
|
| 875 |
+
predicate to later partial KV blocks. Supplying real all-invalid route
|
| 876 |
+
rows for those physical lanes keeps the auxiliary lookup in bounds.
|
| 877 |
+
Future zero K/V rows remain invisible to every real query through the
|
| 878 |
+
kernel's causal guard, and the padded query outputs are discarded by the
|
| 879 |
+
public wrapper.
|
| 880 |
+
"""
|
| 881 |
+
text_length = q.shape[1]
|
| 882 |
+
padding = (-text_length) % query_tile_size
|
| 883 |
+
if not padding:
|
| 884 |
+
return q, k, v, selected_block_indices, text_length
|
| 885 |
+
|
| 886 |
+
def pad_sequence(tensor):
|
| 887 |
+
return torch.cat(
|
| 888 |
+
(
|
| 889 |
+
tensor,
|
| 890 |
+
tensor.new_zeros(
|
| 891 |
+
tensor.shape[0], padding, *tensor.shape[2:]
|
| 892 |
+
),
|
| 893 |
+
),
|
| 894 |
+
dim=1,
|
| 895 |
+
)
|
| 896 |
+
|
| 897 |
+
padded_route = torch.cat(
|
| 898 |
+
(
|
| 899 |
+
selected_block_indices,
|
| 900 |
+
selected_block_indices.new_full(
|
| 901 |
+
(
|
| 902 |
+
selected_block_indices.shape[0],
|
| 903 |
+
padding,
|
| 904 |
+
selected_block_indices.shape[2],
|
| 905 |
+
),
|
| 906 |
+
-1,
|
| 907 |
+
),
|
| 908 |
+
),
|
| 909 |
+
dim=1,
|
| 910 |
+
)
|
| 911 |
+
return (
|
| 912 |
+
pad_sequence(q),
|
| 913 |
+
pad_sequence(k),
|
| 914 |
+
pad_sequence(v),
|
| 915 |
+
padded_route,
|
| 916 |
+
text_length,
|
| 917 |
+
)
|
| 918 |
+
|
| 919 |
+
|
| 920 |
+
def summary_block_sparse_attn_func(
|
| 921 |
+
q,
|
| 922 |
+
k,
|
| 923 |
+
v,
|
| 924 |
+
selected_block_indices,
|
| 925 |
+
block_size_tokens,
|
| 926 |
+
local_window_tokens,
|
| 927 |
+
softmax_scale=None,
|
| 928 |
+
deterministic=False,
|
| 929 |
+
):
|
| 930 |
+
"""Run exact per-query Full attention over selected raw-token blocks.
|
| 931 |
+
|
| 932 |
+
``selected_block_indices[b, q]`` contains the summary/raw-block IDs chosen
|
| 933 |
+
by query ``q``; ``-1`` pads rows with fewer candidates. A selected ID
|
| 934 |
+
exposes the corresponding raw K/V block, never the summary K/V itself.
|
| 935 |
+
Local causal tokens are added independently. The caller is responsible
|
| 936 |
+
for reserving block 0 inside the TopK budget when an attention sink is
|
| 937 |
+
desired; this kernel always applies a final causal guard.
|
| 938 |
+
|
| 939 |
+
The selected IDs must be unique within each row. This is naturally true
|
| 940 |
+
for ``torch.topk``. Set ``SKSA_VALIDATE_BLOCK_INDICES=1`` to perform the
|
| 941 |
+
synchronising bounds/duplicate checks during diagnostics.
|
| 942 |
+
"""
|
| 943 |
+
_validate_summary_block_sparse_inputs(
|
| 944 |
+
q,
|
| 945 |
+
k,
|
| 946 |
+
v,
|
| 947 |
+
selected_block_indices,
|
| 948 |
+
block_size_tokens,
|
| 949 |
+
local_window_tokens,
|
| 950 |
+
block_size_tokens,
|
| 951 |
+
)
|
| 952 |
+
if os.environ.get("SKSA_VALIDATE_BLOCK_INDICES", "0") == "1":
|
| 953 |
+
_check_unique_summary_block_indices(selected_block_indices)
|
| 954 |
+
route_indices = selected_block_indices.to(torch.int32).contiguous()
|
| 955 |
+
with torch.cuda.device(q.device):
|
| 956 |
+
if not _check_cute_available():
|
| 957 |
+
raise RuntimeError(
|
| 958 |
+
"summary_block_sparse_attn_func requires CuTe on sm90+"
|
| 959 |
+
)
|
| 960 |
+
# Qwen's ordinary multi-head raw K/V is much wider than DeepSeek MLA's
|
| 961 |
+
# shared latent KV. Direct per-query gather therefore loses crucial
|
| 962 |
+
# cross-query KV reuse and is bandwidth-bound. Keep the exact CuTe
|
| 963 |
+
# tiled implementation as production default; ``direct`` is an
|
| 964 |
+
# explicit diagnostic prototype only.
|
| 965 |
+
q, k, v, route_indices, text_length = (
|
| 966 |
+
_pad_summary_block_sparse_cute_inputs(
|
| 967 |
+
q,
|
| 968 |
+
k,
|
| 969 |
+
v,
|
| 970 |
+
route_indices,
|
| 971 |
+
)
|
| 972 |
+
)
|
| 973 |
+
raw_block_count = math.ceil(k.shape[1] / block_size_tokens)
|
| 974 |
+
packed_route = _pack_summary_block_indices(
|
| 975 |
+
route_indices,
|
| 976 |
+
raw_block_count=raw_block_count,
|
| 977 |
+
)
|
| 978 |
+
output, _ = SummaryBlockSparseAttentionFunc.apply(
|
| 979 |
+
q,
|
| 980 |
+
k,
|
| 981 |
+
v,
|
| 982 |
+
packed_route,
|
| 983 |
+
route_indices,
|
| 984 |
+
block_size_tokens,
|
| 985 |
+
local_window_tokens,
|
| 986 |
+
block_size_tokens,
|
| 987 |
+
softmax_scale,
|
| 988 |
+
deterministic,
|
| 989 |
+
None,
|
| 990 |
+
None,
|
| 991 |
+
)
|
| 992 |
+
return (
|
| 993 |
+
output
|
| 994 |
+
if output.shape[1] == text_length
|
| 995 |
+
else output[:, :text_length].contiguous()
|
| 996 |
+
)
|
kernel/ksa_attn/mask.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cutlass
|
| 2 |
+
from cutlass import cute
|
| 3 |
+
from flash_attn.cute.seqlen_info import SeqlenInfoQK
|
| 4 |
+
from flash_attn.cute.utils import scalar_to_ssa, ssa_to_scalar
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@cute.jit
|
| 8 |
+
def summary_block_sparse_attn_mask_func(
|
| 9 |
+
batch_idx_ssa: cute.TensorSSA,
|
| 10 |
+
head_idx_ssa: cute.TensorSSA,
|
| 11 |
+
q_idx_ssa: cute.TensorSSA,
|
| 12 |
+
kv_idx_ssa: cute.TensorSSA,
|
| 13 |
+
seqlen_info: SeqlenInfoQK,
|
| 14 |
+
aux_tensors,
|
| 15 |
+
block_size_tokens: cutlass.Constexpr[int],
|
| 16 |
+
local_window_tokens: cutlass.Constexpr[int],
|
| 17 |
+
local_alignment_tokens: cutlass.Constexpr[int] = 0,
|
| 18 |
+
):
|
| 19 |
+
"""Per-query raw-token mask selected through summary-block indices.
|
| 20 |
+
|
| 21 |
+
``aux_tensors[0]`` is a packed uint8 tensor with layout
|
| 22 |
+
``[batch, query, ceil(raw_block_count / 8)]``. Bit ``j`` is set exactly
|
| 23 |
+
when the query selected summary/raw block ``j``. Packing the dynamic
|
| 24 |
+
``[B,Q,K]`` indices before launching attention avoids materialising the
|
| 25 |
+
quadratic ``[B,Q,raw_block_count]`` boolean route.
|
| 26 |
+
|
| 27 |
+
The CuTe scheduler still works in 128-token tiles. A scheduled tile is
|
| 28 |
+
only a conservative union; this function is evaluated for every score and
|
| 29 |
+
preserves exact per-query, per-``block_size_tokens`` semantics.
|
| 30 |
+
"""
|
| 31 |
+
del head_idx_ssa, seqlen_info
|
| 32 |
+
raw_block_idx_ssa = kv_idx_ssa // block_size_tokens
|
| 33 |
+
route_byte_idx_ssa = raw_block_idx_ssa // 8
|
| 34 |
+
route_bit_idx_ssa = raw_block_idx_ssa % 8
|
| 35 |
+
packed_route = aux_tensors[0]
|
| 36 |
+
route_byte = packed_route[
|
| 37 |
+
ssa_to_scalar(batch_idx_ssa),
|
| 38 |
+
ssa_to_scalar(q_idx_ssa),
|
| 39 |
+
ssa_to_scalar(route_byte_idx_ssa),
|
| 40 |
+
]
|
| 41 |
+
route_bit = 1 << ssa_to_scalar(route_bit_idx_ssa)
|
| 42 |
+
selected = (route_byte & route_bit) != 0
|
| 43 |
+
selected_ssa = scalar_to_ssa(selected, cutlass.Boolean)
|
| 44 |
+
causal = kv_idx_ssa <= q_idx_ssa
|
| 45 |
+
raw_local_start = q_idx_ssa - local_window_tokens + 1
|
| 46 |
+
clamped_local_start = cute.where(
|
| 47 |
+
raw_local_start > 0,
|
| 48 |
+
raw_local_start,
|
| 49 |
+
cute.full_like(raw_local_start, 0),
|
| 50 |
+
)
|
| 51 |
+
# Preserve the original summary-block API: callers that do not provide a
|
| 52 |
+
# separate local alignment retain ``block_size_tokens`` alignment. Raw
|
| 53 |
+
# token routing explicitly supplies eight here while its route span is one.
|
| 54 |
+
aligned_local_start = (
|
| 55 |
+
clamped_local_start // block_size_tokens
|
| 56 |
+
) * block_size_tokens
|
| 57 |
+
if local_alignment_tokens > 0:
|
| 58 |
+
aligned_local_start = (
|
| 59 |
+
clamped_local_start // local_alignment_tokens
|
| 60 |
+
) * local_alignment_tokens
|
| 61 |
+
local = kv_idx_ssa >= aligned_local_start
|
| 62 |
+
return causal & (local | selected_ssa)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
@cute.jit
|
| 68 |
+
def hierarchical_summary_attn_score_mod(
|
| 69 |
+
score_ssa: cute.TensorSSA,
|
| 70 |
+
batch_idx_ssa: cute.TensorSSA,
|
| 71 |
+
head_idx_ssa: cute.TensorSSA,
|
| 72 |
+
q_idx: cute.TensorSSA,
|
| 73 |
+
kv_idx: cute.TensorSSA,
|
| 74 |
+
seqlen_info: SeqlenInfoQK,
|
| 75 |
+
aux_tensors,
|
| 76 |
+
meta_length: cutlass.Constexpr[int],
|
| 77 |
+
meta_logit_bias: cutlass.Constexpr[float],
|
| 78 |
+
):
|
| 79 |
+
"""Add log(G) to each physical meta entry before softmax."""
|
| 80 |
+
return cute.where(
|
| 81 |
+
kv_idx < meta_length,
|
| 82 |
+
score_ssa + meta_logit_bias,
|
| 83 |
+
score_ssa,
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
@cute.jit
|
| 90 |
+
def hierarchical_summary_attn_mask_func(
|
| 91 |
+
batch_idx_ssa: cute.TensorSSA,
|
| 92 |
+
head_idx_ssa: cute.TensorSSA,
|
| 93 |
+
q_idx_ssa: cute.TensorSSA,
|
| 94 |
+
kv_idx_ssa: cute.TensorSSA,
|
| 95 |
+
seqlen_info: SeqlenInfoQK,
|
| 96 |
+
aux_tensors,
|
| 97 |
+
chunk_size: cutlass.Constexpr[int],
|
| 98 |
+
sliding_chunk_num: cutlass.Constexpr[int],
|
| 99 |
+
group_size: cutlass.Constexpr[int],
|
| 100 |
+
meta_length: cutlass.Constexpr[int],
|
| 101 |
+
swap_indices: cutlass.Constexpr[bool],
|
| 102 |
+
):
|
| 103 |
+
"""Compressed-KSA mask for K/V laid out as ``[meta, base]``."""
|
| 104 |
+
if swap_indices:
|
| 105 |
+
q_idx_ssa, kv_idx_ssa = kv_idx_ssa, q_idx_ssa
|
| 106 |
+
block_size = chunk_size + 1
|
| 107 |
+
q_chunk_id = q_idx_ssa // block_size
|
| 108 |
+
q_offset = q_idx_ssa % block_size
|
| 109 |
+
is_q_text = q_offset < chunk_size
|
| 110 |
+
raw_window_start = q_chunk_id - sliding_chunk_num
|
| 111 |
+
window_start = cute.where(
|
| 112 |
+
raw_window_start > 0,
|
| 113 |
+
raw_window_start,
|
| 114 |
+
cute.full_like(raw_window_start, 0),
|
| 115 |
+
)
|
| 116 |
+
complete_group_count = window_start // group_size
|
| 117 |
+
complete_end = complete_group_count * group_size
|
| 118 |
+
is_meta = kv_idx_ssa < meta_length
|
| 119 |
+
base_idx = kv_idx_ssa - meta_length
|
| 120 |
+
kv_chunk_id = base_idx // block_size
|
| 121 |
+
kv_offset = base_idx % block_size
|
| 122 |
+
is_kv_text = kv_offset < chunk_size
|
| 123 |
+
is_kv_summary = kv_offset == chunk_size
|
| 124 |
+
meta_group_id = kv_idx_ssa
|
| 125 |
+
valid_meta = is_q_text & is_meta & (meta_group_id < complete_group_count)
|
| 126 |
+
valid_text = (
|
| 127 |
+
is_q_text & (is_meta ^ 1) & is_kv_text
|
| 128 |
+
& (((kv_chunk_id >= window_start) & (kv_chunk_id < q_chunk_id))
|
| 129 |
+
| ((kv_chunk_id == q_chunk_id) & (base_idx <= q_idx_ssa)))
|
| 130 |
+
)
|
| 131 |
+
valid_residual = (
|
| 132 |
+
is_q_text & (is_meta ^ 1) & is_kv_summary
|
| 133 |
+
& (kv_chunk_id >= complete_end) & (kv_chunk_id < window_start)
|
| 134 |
+
)
|
| 135 |
+
valid_summary = (
|
| 136 |
+
(is_q_text ^ 1) & (is_meta ^ 1) & (kv_chunk_id == q_chunk_id)
|
| 137 |
+
& (is_kv_text | (is_kv_summary & (base_idx <= q_idx_ssa)))
|
| 138 |
+
)
|
| 139 |
+
return valid_meta | valid_text | valid_residual | valid_summary
|
| 140 |
+
|
| 141 |
+
@cute.jit
|
| 142 |
+
def summary_attn_mask_func(
|
| 143 |
+
batch_idx_ssa: cute.TensorSSA,
|
| 144 |
+
head_idx_ssa: cute.TensorSSA,
|
| 145 |
+
q_idx_ssa: cute.TensorSSA,
|
| 146 |
+
kv_idx_ssa: cute.TensorSSA,
|
| 147 |
+
seqlen_info: SeqlenInfoQK,
|
| 148 |
+
aux_tensors,
|
| 149 |
+
chunk_size: cutlass.Constexpr[int],
|
| 150 |
+
summary_num: cutlass.Constexpr[int],
|
| 151 |
+
sliding_chunk_num: cutlass.Constexpr[int],
|
| 152 |
+
prefix_summary_length: cutlass.Constexpr[int],
|
| 153 |
+
swap_indices: cutlass.Constexpr[bool],
|
| 154 |
+
):
|
| 155 |
+
if swap_indices:
|
| 156 |
+
q_idx_ssa, kv_idx_ssa = kv_idx_ssa, q_idx_ssa
|
| 157 |
+
q_chunk_id = q_idx_ssa // (chunk_size + summary_num)
|
| 158 |
+
kv_chunk_id = kv_idx_ssa // (chunk_size + summary_num)
|
| 159 |
+
is_sliding_chunk = (q_chunk_id - kv_chunk_id) <= sliding_chunk_num
|
| 160 |
+
is_q_summary = (q_idx_ssa % (chunk_size + summary_num)) >= chunk_size
|
| 161 |
+
is_kv_summary = (kv_idx_ssa % (chunk_size + summary_num)) >= chunk_size
|
| 162 |
+
is_causal = q_idx_ssa >= kv_idx_ssa
|
| 163 |
+
valid_kv_q_summary = is_q_summary & (kv_chunk_id == q_chunk_id) & is_causal
|
| 164 |
+
valid_sliding_q_text = is_sliding_chunk & (is_kv_summary ^ 1)
|
| 165 |
+
valid_summary_q_text = is_kv_summary & ((q_chunk_id - kv_chunk_id) > sliding_chunk_num)
|
| 166 |
+
valid_kv_q_text = (is_q_summary ^ 1) & is_causal & (valid_sliding_q_text | valid_summary_q_text)
|
| 167 |
+
return valid_kv_q_summary | valid_kv_q_text
|
| 168 |
+
|
| 169 |
+
@cute.jit
|
| 170 |
+
def summary_attn_contiguous_mask_func(
|
| 171 |
+
batch_idx_ssa: cute.TensorSSA,
|
| 172 |
+
head_idx_ssa: cute.TensorSSA,
|
| 173 |
+
q_idx_ssa: cute.TensorSSA,
|
| 174 |
+
kv_idx_ssa: cute.TensorSSA,
|
| 175 |
+
seqlen_info: SeqlenInfoQK,
|
| 176 |
+
aux_tensors,
|
| 177 |
+
chunk_size: cutlass.Constexpr[int],
|
| 178 |
+
summary_num: cutlass.Constexpr[int],
|
| 179 |
+
sliding_chunk_num: cutlass.Constexpr[int],
|
| 180 |
+
prefix_summary_length: cutlass.Constexpr[int],
|
| 181 |
+
swap_indices: cutlass.Constexpr[bool],
|
| 182 |
+
):
|
| 183 |
+
if swap_indices:
|
| 184 |
+
q_idx_ssa, kv_idx_ssa = kv_idx_ssa, q_idx_ssa
|
| 185 |
+
q_chunk_id = q_idx_ssa // (chunk_size + summary_num)
|
| 186 |
+
kv_chunk_id = (kv_idx_ssa - prefix_summary_length) // (chunk_size + summary_num)
|
| 187 |
+
is_sliding_chunk = (q_chunk_id - kv_chunk_id) <= sliding_chunk_num
|
| 188 |
+
is_q_summary = (q_idx_ssa % (chunk_size + summary_num)) >= chunk_size
|
| 189 |
+
is_kv_summary = ((kv_idx_ssa - prefix_summary_length) % (chunk_size + summary_num)) >= chunk_size
|
| 190 |
+
is_causal = q_idx_ssa >= (kv_idx_ssa - prefix_summary_length)
|
| 191 |
+
valid_kv_q_summary = is_q_summary & (kv_chunk_id == q_chunk_id) & is_causal
|
| 192 |
+
valid_sliding_q_text = is_sliding_chunk & (is_kv_summary ^ 1) & (kv_idx_ssa >= prefix_summary_length)
|
| 193 |
+
valid_summary_q_text = ((kv_idx_ssa // summary_num) + sliding_chunk_num) < q_chunk_id
|
| 194 |
+
valid_kv_q_text = (is_q_summary ^ 1) & ((is_causal & valid_sliding_q_text) | valid_summary_q_text)
|
| 195 |
+
return valid_kv_q_summary | valid_kv_q_text
|
| 196 |
+
|
| 197 |
+
# ---------------------------------------------------------------------------
|
| 198 |
+
# Sliding-chunk-only variants: text tokens never attend to old summary tokens
|
| 199 |
+
# ---------------------------------------------------------------------------
|
| 200 |
+
|
| 201 |
+
@cute.jit
|
| 202 |
+
def sliding_chunk_mask_func(
|
| 203 |
+
batch_idx_ssa: cute.TensorSSA,
|
| 204 |
+
head_idx_ssa: cute.TensorSSA,
|
| 205 |
+
q_idx_ssa: cute.TensorSSA,
|
| 206 |
+
kv_idx_ssa: cute.TensorSSA,
|
| 207 |
+
seqlen_info: SeqlenInfoQK,
|
| 208 |
+
aux_tensors,
|
| 209 |
+
chunk_size: cutlass.Constexpr[int],
|
| 210 |
+
summary_num: cutlass.Constexpr[int],
|
| 211 |
+
sliding_chunk_num: cutlass.Constexpr[int],
|
| 212 |
+
prefix_summary_length: cutlass.Constexpr[int],
|
| 213 |
+
swap_indices: cutlass.Constexpr[bool],
|
| 214 |
+
):
|
| 215 |
+
if swap_indices:
|
| 216 |
+
q_idx_ssa, kv_idx_ssa = kv_idx_ssa, q_idx_ssa
|
| 217 |
+
q_chunk_id = q_idx_ssa // (chunk_size + summary_num)
|
| 218 |
+
kv_chunk_id = kv_idx_ssa // (chunk_size + summary_num)
|
| 219 |
+
is_sliding_chunk = (q_chunk_id - kv_chunk_id) <= sliding_chunk_num
|
| 220 |
+
is_q_summary = (q_idx_ssa % (chunk_size + summary_num)) >= chunk_size
|
| 221 |
+
is_kv_summary = (kv_idx_ssa % (chunk_size + summary_num)) >= chunk_size
|
| 222 |
+
is_causal = q_idx_ssa >= kv_idx_ssa
|
| 223 |
+
valid_kv_q_summary = is_q_summary & (kv_chunk_id == q_chunk_id) & is_causal
|
| 224 |
+
valid_sliding_q_text = is_sliding_chunk & (is_kv_summary ^ 1)
|
| 225 |
+
valid_kv_q_text = (is_q_summary ^ 1) & is_causal & valid_sliding_q_text
|
| 226 |
+
return valid_kv_q_summary | valid_kv_q_text
|
| 227 |
+
|
| 228 |
+
@cute.jit
|
| 229 |
+
def sliding_chunk_contiguous_mask_func(
|
| 230 |
+
batch_idx_ssa: cute.TensorSSA,
|
| 231 |
+
head_idx_ssa: cute.TensorSSA,
|
| 232 |
+
q_idx_ssa: cute.TensorSSA,
|
| 233 |
+
kv_idx_ssa: cute.TensorSSA,
|
| 234 |
+
seqlen_info: SeqlenInfoQK,
|
| 235 |
+
aux_tensors,
|
| 236 |
+
chunk_size: cutlass.Constexpr[int],
|
| 237 |
+
summary_num: cutlass.Constexpr[int],
|
| 238 |
+
sliding_chunk_num: cutlass.Constexpr[int],
|
| 239 |
+
prefix_summary_length: cutlass.Constexpr[int],
|
| 240 |
+
swap_indices: cutlass.Constexpr[bool],
|
| 241 |
+
):
|
| 242 |
+
if swap_indices:
|
| 243 |
+
q_idx_ssa, kv_idx_ssa = kv_idx_ssa, q_idx_ssa
|
| 244 |
+
q_chunk_id = q_idx_ssa // (chunk_size + summary_num)
|
| 245 |
+
kv_chunk_id = (kv_idx_ssa - prefix_summary_length) // (chunk_size + summary_num)
|
| 246 |
+
is_sliding_chunk = (q_chunk_id - kv_chunk_id) <= sliding_chunk_num
|
| 247 |
+
is_q_summary = (q_idx_ssa % (chunk_size + summary_num)) >= chunk_size
|
| 248 |
+
is_kv_summary = ((kv_idx_ssa - prefix_summary_length) % (chunk_size + summary_num)) >= chunk_size
|
| 249 |
+
is_causal = q_idx_ssa >= (kv_idx_ssa - prefix_summary_length)
|
| 250 |
+
valid_kv_q_summary = is_q_summary & (kv_chunk_id == q_chunk_id) & is_causal
|
| 251 |
+
valid_sliding_q_text = is_sliding_chunk & (is_kv_summary ^ 1) & (kv_idx_ssa >= prefix_summary_length)
|
| 252 |
+
valid_kv_q_text = (is_q_summary ^ 1) & is_causal & valid_sliding_q_text
|
| 253 |
+
return valid_kv_q_summary | valid_kv_q_text
|
merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
model-00000-of-00001.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:14d4335fc7b6b09af233175ad4c9ca18537d188fed92d235203c265f1ac2240e
|
| 3 |
+
size 3862157336
|
model.safetensors.index.json
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"metadata": {
|
| 3 |
+
"total_size": 3862126592
|
| 4 |
+
},
|
| 5 |
+
"weight_map": {
|
| 6 |
+
"model.embed_tokens.weight": "model-00000-of-00001.safetensors",
|
| 7 |
+
"model.layers.0.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 8 |
+
"model.layers.0.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 9 |
+
"model.layers.0.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 10 |
+
"model.layers.0.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 11 |
+
"model.layers.0.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 12 |
+
"model.layers.0.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 13 |
+
"model.layers.0.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 14 |
+
"model.layers.0.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 15 |
+
"model.layers.0.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 16 |
+
"model.layers.0.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 17 |
+
"model.layers.0.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 18 |
+
"model.layers.1.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 19 |
+
"model.layers.1.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 20 |
+
"model.layers.1.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 21 |
+
"model.layers.1.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 22 |
+
"model.layers.1.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 23 |
+
"model.layers.1.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 24 |
+
"model.layers.1.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 25 |
+
"model.layers.1.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 26 |
+
"model.layers.1.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 27 |
+
"model.layers.1.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 28 |
+
"model.layers.1.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 29 |
+
"model.layers.2.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 30 |
+
"model.layers.2.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 31 |
+
"model.layers.2.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 32 |
+
"model.layers.2.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 33 |
+
"model.layers.2.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 34 |
+
"model.layers.2.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 35 |
+
"model.layers.2.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 36 |
+
"model.layers.2.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 37 |
+
"model.layers.2.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 38 |
+
"model.layers.2.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 39 |
+
"model.layers.2.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 40 |
+
"model.layers.3.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 41 |
+
"model.layers.3.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 42 |
+
"model.layers.3.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 43 |
+
"model.layers.3.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 44 |
+
"model.layers.3.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 45 |
+
"model.layers.3.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 46 |
+
"model.layers.3.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 47 |
+
"model.layers.3.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 48 |
+
"model.layers.3.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 49 |
+
"model.layers.3.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 50 |
+
"model.layers.3.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 51 |
+
"model.layers.4.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 52 |
+
"model.layers.4.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 53 |
+
"model.layers.4.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 54 |
+
"model.layers.4.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 55 |
+
"model.layers.4.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 56 |
+
"model.layers.4.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 57 |
+
"model.layers.4.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 58 |
+
"model.layers.4.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 59 |
+
"model.layers.4.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 60 |
+
"model.layers.4.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 61 |
+
"model.layers.4.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 62 |
+
"model.layers.5.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 63 |
+
"model.layers.5.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 64 |
+
"model.layers.5.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 65 |
+
"model.layers.5.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 66 |
+
"model.layers.5.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 67 |
+
"model.layers.5.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 68 |
+
"model.layers.5.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 69 |
+
"model.layers.5.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 70 |
+
"model.layers.5.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 71 |
+
"model.layers.5.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 72 |
+
"model.layers.5.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 73 |
+
"model.layers.6.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 74 |
+
"model.layers.6.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 75 |
+
"model.layers.6.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 76 |
+
"model.layers.6.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 77 |
+
"model.layers.6.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 78 |
+
"model.layers.6.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 79 |
+
"model.layers.6.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 80 |
+
"model.layers.6.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 81 |
+
"model.layers.6.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 82 |
+
"model.layers.6.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 83 |
+
"model.layers.6.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 84 |
+
"model.layers.7.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 85 |
+
"model.layers.7.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 86 |
+
"model.layers.7.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 87 |
+
"model.layers.7.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 88 |
+
"model.layers.7.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 89 |
+
"model.layers.7.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 90 |
+
"model.layers.7.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 91 |
+
"model.layers.7.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 92 |
+
"model.layers.7.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 93 |
+
"model.layers.7.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 94 |
+
"model.layers.7.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 95 |
+
"model.layers.8.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 96 |
+
"model.layers.8.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 97 |
+
"model.layers.8.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 98 |
+
"model.layers.8.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 99 |
+
"model.layers.8.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 100 |
+
"model.layers.8.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 101 |
+
"model.layers.8.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 102 |
+
"model.layers.8.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 103 |
+
"model.layers.8.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 104 |
+
"model.layers.8.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 105 |
+
"model.layers.8.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 106 |
+
"model.layers.9.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 107 |
+
"model.layers.9.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 108 |
+
"model.layers.9.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 109 |
+
"model.layers.9.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 110 |
+
"model.layers.9.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 111 |
+
"model.layers.9.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 112 |
+
"model.layers.9.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 113 |
+
"model.layers.9.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 114 |
+
"model.layers.9.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 115 |
+
"model.layers.9.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 116 |
+
"model.layers.9.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 117 |
+
"model.layers.10.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 118 |
+
"model.layers.10.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 119 |
+
"model.layers.10.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 120 |
+
"model.layers.10.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 121 |
+
"model.layers.10.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 122 |
+
"model.layers.10.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 123 |
+
"model.layers.10.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 124 |
+
"model.layers.10.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 125 |
+
"model.layers.10.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 126 |
+
"model.layers.10.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 127 |
+
"model.layers.10.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 128 |
+
"model.layers.11.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 129 |
+
"model.layers.11.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 130 |
+
"model.layers.11.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 131 |
+
"model.layers.11.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 132 |
+
"model.layers.11.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 133 |
+
"model.layers.11.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 134 |
+
"model.layers.11.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 135 |
+
"model.layers.11.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 136 |
+
"model.layers.11.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 137 |
+
"model.layers.11.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 138 |
+
"model.layers.11.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 139 |
+
"model.layers.12.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 140 |
+
"model.layers.12.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 141 |
+
"model.layers.12.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 142 |
+
"model.layers.12.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 143 |
+
"model.layers.12.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 144 |
+
"model.layers.12.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 145 |
+
"model.layers.12.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 146 |
+
"model.layers.12.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 147 |
+
"model.layers.12.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 148 |
+
"model.layers.12.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 149 |
+
"model.layers.12.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 150 |
+
"model.layers.13.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 151 |
+
"model.layers.13.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 152 |
+
"model.layers.13.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 153 |
+
"model.layers.13.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 154 |
+
"model.layers.13.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 155 |
+
"model.layers.13.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 156 |
+
"model.layers.13.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 157 |
+
"model.layers.13.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 158 |
+
"model.layers.13.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 159 |
+
"model.layers.13.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 160 |
+
"model.layers.13.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 161 |
+
"model.layers.14.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 162 |
+
"model.layers.14.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 163 |
+
"model.layers.14.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 164 |
+
"model.layers.14.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 165 |
+
"model.layers.14.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 166 |
+
"model.layers.14.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 167 |
+
"model.layers.14.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 168 |
+
"model.layers.14.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 169 |
+
"model.layers.14.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 170 |
+
"model.layers.14.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 171 |
+
"model.layers.14.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 172 |
+
"model.layers.15.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 173 |
+
"model.layers.15.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 174 |
+
"model.layers.15.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 175 |
+
"model.layers.15.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 176 |
+
"model.layers.15.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 177 |
+
"model.layers.15.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 178 |
+
"model.layers.15.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 179 |
+
"model.layers.15.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 180 |
+
"model.layers.15.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 181 |
+
"model.layers.15.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 182 |
+
"model.layers.15.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 183 |
+
"model.layers.16.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 184 |
+
"model.layers.16.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 185 |
+
"model.layers.16.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 186 |
+
"model.layers.16.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 187 |
+
"model.layers.16.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 188 |
+
"model.layers.16.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 189 |
+
"model.layers.16.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 190 |
+
"model.layers.16.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 191 |
+
"model.layers.16.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 192 |
+
"model.layers.16.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 193 |
+
"model.layers.16.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 194 |
+
"model.layers.17.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 195 |
+
"model.layers.17.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 196 |
+
"model.layers.17.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 197 |
+
"model.layers.17.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 198 |
+
"model.layers.17.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 199 |
+
"model.layers.17.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 200 |
+
"model.layers.17.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 201 |
+
"model.layers.17.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 202 |
+
"model.layers.17.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 203 |
+
"model.layers.17.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 204 |
+
"model.layers.17.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 205 |
+
"model.layers.18.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 206 |
+
"model.layers.18.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 207 |
+
"model.layers.18.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 208 |
+
"model.layers.18.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 209 |
+
"model.layers.18.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 210 |
+
"model.layers.18.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 211 |
+
"model.layers.18.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 212 |
+
"model.layers.18.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 213 |
+
"model.layers.18.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 214 |
+
"model.layers.18.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 215 |
+
"model.layers.18.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 216 |
+
"model.layers.19.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 217 |
+
"model.layers.19.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 218 |
+
"model.layers.19.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 219 |
+
"model.layers.19.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 220 |
+
"model.layers.19.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 221 |
+
"model.layers.19.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 222 |
+
"model.layers.19.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 223 |
+
"model.layers.19.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 224 |
+
"model.layers.19.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 225 |
+
"model.layers.19.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 226 |
+
"model.layers.19.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 227 |
+
"model.layers.20.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 228 |
+
"model.layers.20.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 229 |
+
"model.layers.20.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 230 |
+
"model.layers.20.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 231 |
+
"model.layers.20.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 232 |
+
"model.layers.20.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 233 |
+
"model.layers.20.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 234 |
+
"model.layers.20.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 235 |
+
"model.layers.20.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 236 |
+
"model.layers.20.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 237 |
+
"model.layers.20.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 238 |
+
"model.layers.21.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 239 |
+
"model.layers.21.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 240 |
+
"model.layers.21.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 241 |
+
"model.layers.21.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 242 |
+
"model.layers.21.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 243 |
+
"model.layers.21.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 244 |
+
"model.layers.21.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 245 |
+
"model.layers.21.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 246 |
+
"model.layers.21.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 247 |
+
"model.layers.21.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 248 |
+
"model.layers.21.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 249 |
+
"model.layers.22.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 250 |
+
"model.layers.22.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 251 |
+
"model.layers.22.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 252 |
+
"model.layers.22.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 253 |
+
"model.layers.22.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 254 |
+
"model.layers.22.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 255 |
+
"model.layers.22.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 256 |
+
"model.layers.22.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 257 |
+
"model.layers.22.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 258 |
+
"model.layers.22.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 259 |
+
"model.layers.22.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 260 |
+
"model.layers.23.self_attn.q_proj.weight": "model-00000-of-00001.safetensors",
|
| 261 |
+
"model.layers.23.self_attn.k_proj.weight": "model-00000-of-00001.safetensors",
|
| 262 |
+
"model.layers.23.self_attn.v_proj.weight": "model-00000-of-00001.safetensors",
|
| 263 |
+
"model.layers.23.self_attn.o_proj.weight": "model-00000-of-00001.safetensors",
|
| 264 |
+
"model.layers.23.self_attn.q_norm.weight": "model-00000-of-00001.safetensors",
|
| 265 |
+
"model.layers.23.self_attn.k_norm.weight": "model-00000-of-00001.safetensors",
|
| 266 |
+
"model.layers.23.mlp.gate_proj.weight": "model-00000-of-00001.safetensors",
|
| 267 |
+
"model.layers.23.mlp.down_proj.weight": "model-00000-of-00001.safetensors",
|
| 268 |
+
"model.layers.23.mlp.up_proj.weight": "model-00000-of-00001.safetensors",
|
| 269 |
+
"model.layers.23.input_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 270 |
+
"model.layers.23.post_attention_layernorm.weight": "model-00000-of-00001.safetensors",
|
| 271 |
+
"model.norm.weight": "model-00000-of-00001.safetensors",
|
| 272 |
+
"lm_head.weight": "model-00000-of-00001.safetensors"
|
| 273 |
+
}
|
| 274 |
+
}
|
modeling_qwen3.py
ADDED
|
@@ -0,0 +1,1570 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
| 2 |
+
# This file was automatically generated from src/transformers/models/qwen3/modular_qwen3.py.
|
| 3 |
+
# Do NOT edit this file manually as any edits will be overwritten by the generation of
|
| 4 |
+
# the file from the modular. If any change should be done, please apply the change to the
|
| 5 |
+
# modular_qwen3.py file directly. One of our CI enforces this.
|
| 6 |
+
# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
|
| 7 |
+
# coding=utf-8
|
| 8 |
+
# Copyright 2025 The Qwen team, Alibaba Group and the HuggingFace Inc. team. All rights reserved.
|
| 9 |
+
#
|
| 10 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 11 |
+
# you may not use this file except in compliance with the License.
|
| 12 |
+
# You may obtain a copy of the License at
|
| 13 |
+
#
|
| 14 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 15 |
+
#
|
| 16 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 17 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 18 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 19 |
+
# See the License for the specific language governing permissions and
|
| 20 |
+
# limitations under the License.
|
| 21 |
+
|
| 22 |
+
import ast
|
| 23 |
+
from typing import Any, Callable, Optional, Union
|
| 24 |
+
|
| 25 |
+
import torch
|
| 26 |
+
from torch import nn
|
| 27 |
+
import torch.nn.functional as F
|
| 28 |
+
#from flash_attn import flash_attn_func
|
| 29 |
+
|
| 30 |
+
from transformers.activations import ACT2FN
|
| 31 |
+
from transformers.cache_utils import Cache, DynamicCache
|
| 32 |
+
from transformers.generation import GenerationMixin
|
| 33 |
+
from transformers.integrations import use_kernel_forward_from_hub
|
| 34 |
+
from transformers.masking_utils import create_causal_mask, create_sliding_window_causal_mask
|
| 35 |
+
from transformers.modeling_flash_attention_utils import FlashAttentionKwargs
|
| 36 |
+
from transformers.modeling_layers import (
|
| 37 |
+
GenericForQuestionAnswering,
|
| 38 |
+
GenericForSequenceClassification,
|
| 39 |
+
GenericForTokenClassification,
|
| 40 |
+
GradientCheckpointingLayer,
|
| 41 |
+
)
|
| 42 |
+
from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
|
| 43 |
+
from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update
|
| 44 |
+
from transformers.modeling_utils import PreTrainedModel
|
| 45 |
+
from transformers.processing_utils import Unpack
|
| 46 |
+
from transformers.utils import TransformersKwargs, auto_docstring, can_return_tuple
|
| 47 |
+
from transformers.utils.deprecation import deprecate_kwarg
|
| 48 |
+
from transformers.utils.generic import check_model_inputs
|
| 49 |
+
from transformers.models.qwen3.configuration_qwen3 import Qwen3Config
|
| 50 |
+
# InfinityLM imports for summary attention
|
| 51 |
+
from .summary_context import SummaryBatchContext, build_summary_context, build_summary_sliding_context
|
| 52 |
+
from ksa_attn import summary_attn_func
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _parse_config_pattern(val):
|
| 57 |
+
"""Parse a layer setting stored as an integer or an explicit list."""
|
| 58 |
+
if isinstance(val, list):
|
| 59 |
+
return val
|
| 60 |
+
if isinstance(val, str):
|
| 61 |
+
return ast.literal_eval(val)
|
| 62 |
+
return val
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@use_kernel_forward_from_hub("RMSNorm")
|
| 66 |
+
class Qwen3RMSNorm(nn.Module):
|
| 67 |
+
def __init__(self, hidden_size, eps: float = 1e-6) -> None:
|
| 68 |
+
"""
|
| 69 |
+
Qwen3RMSNorm is equivalent to T5LayerNorm
|
| 70 |
+
"""
|
| 71 |
+
super().__init__()
|
| 72 |
+
self.weight = nn.Parameter(torch.ones(hidden_size))
|
| 73 |
+
self.variance_epsilon = eps
|
| 74 |
+
|
| 75 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 76 |
+
input_dtype = hidden_states.dtype
|
| 77 |
+
hidden_states = hidden_states.to(torch.float32)
|
| 78 |
+
variance = hidden_states.pow(2).mean(-1, keepdim=True)
|
| 79 |
+
hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
|
| 80 |
+
return self.weight * hidden_states.to(input_dtype)
|
| 81 |
+
|
| 82 |
+
def extra_repr(self):
|
| 83 |
+
return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class Qwen3RingBufferCache:
|
| 87 |
+
"""
|
| 88 |
+
Ring buffer KV cache with summary support.
|
| 89 |
+
|
| 90 |
+
Two strategies based on per-layer sliding_chunk_num:
|
| 91 |
+
- Large window layers (is_large_window=True): append-only buffer storing only text KV.
|
| 92 |
+
Summary KV is NOT stored since text tokens attend to all text KV directly.
|
| 93 |
+
- Small window layers (is_large_window=False):
|
| 94 |
+
Three buffers:
|
| 95 |
+
1. key_cache: [ring(ws) | old_summaries(growing) | chunk_mirror(≤C)]
|
| 96 |
+
→ attention input, steady state is a single contiguous slice
|
| 97 |
+
2. new_summary_buf: ring buffer of size scn, stores summaries whose text
|
| 98 |
+
is still in the window (not needed for attention)
|
| 99 |
+
3. chunk_buf: size C, holds current chunk's text KV
|
| 100 |
+
|
| 101 |
+
RoPE position information is baked into KV, so physical order doesn't matter.
|
| 102 |
+
"""
|
| 103 |
+
|
| 104 |
+
is_compileable = False
|
| 105 |
+
_SUMMARY_INIT_CAP = 512
|
| 106 |
+
_APPEND_HEADROOM = 1024
|
| 107 |
+
|
| 108 |
+
def __init__(self, config: Qwen3Config, sliding_chunk_nums: list[int]):
|
| 109 |
+
super().__init__()
|
| 110 |
+
self.summary_chunk_size = getattr(config, "summary_chunk_size", 0)
|
| 111 |
+
self.summary_token_num = getattr(config, "summary_token_num", 0)
|
| 112 |
+
self.num_hidden_layers = config.num_hidden_layers
|
| 113 |
+
|
| 114 |
+
self.sliding_chunk_nums = sliding_chunk_nums
|
| 115 |
+
large_window_threshold = min(sliding_chunk_nums) * self.summary_chunk_size
|
| 116 |
+
self.is_large_window = [sv * self.summary_chunk_size > large_window_threshold for sv in sliding_chunk_nums]
|
| 117 |
+
self.window_sizes = [sv * self.summary_chunk_size for sv in sliding_chunk_nums]
|
| 118 |
+
|
| 119 |
+
self.key_cache = [None for _ in range(config.num_hidden_layers)]
|
| 120 |
+
self.value_cache = [None for _ in range(config.num_hidden_layers)]
|
| 121 |
+
|
| 122 |
+
# Large window: append-only
|
| 123 |
+
self._text_len = [0] * config.num_hidden_layers
|
| 124 |
+
self._capacity = [0] * config.num_hidden_layers
|
| 125 |
+
|
| 126 |
+
# Small window: ring buffer + summary
|
| 127 |
+
self._window_write_ptr = [0] * config.num_hidden_layers
|
| 128 |
+
self._n_valid_window = [0] * config.num_hidden_layers
|
| 129 |
+
self._old_summary_len = [0] * config.num_hidden_layers # old summaries in key_cache
|
| 130 |
+
self._old_summary_cap = [0] * config.num_hidden_layers
|
| 131 |
+
|
| 132 |
+
# New summary ring buffer (small window only): summaries whose text is still in window
|
| 133 |
+
self._new_sum_key_buf = [None for _ in range(config.num_hidden_layers)]
|
| 134 |
+
self._new_sum_value_buf = [None for _ in range(config.num_hidden_layers)]
|
| 135 |
+
self._new_sum_len = [0] * config.num_hidden_layers # how many filled (≤ scn)
|
| 136 |
+
self._new_sum_write_ptr = [0] * config.num_hidden_layers # ring write pointer
|
| 137 |
+
|
| 138 |
+
# Current chunk buffer (small window only): holds partial chunk text KV
|
| 139 |
+
self._chunk_key_buf = [None for _ in range(config.num_hidden_layers)]
|
| 140 |
+
self._chunk_value_buf = [None for _ in range(config.num_hidden_layers)]
|
| 141 |
+
self._chunk_buf_len = [0] * config.num_hidden_layers
|
| 142 |
+
|
| 143 |
+
# Common
|
| 144 |
+
self.cur_chunk_sizes = [0] * config.num_hidden_layers
|
| 145 |
+
self.true_tokens = [0] * config.num_hidden_layers
|
| 146 |
+
self._total_chunks = [0] * config.num_hidden_layers # completed chunks count
|
| 147 |
+
self._reorganized = False
|
| 148 |
+
|
| 149 |
+
def __len__(self):
|
| 150 |
+
return self.num_hidden_layers
|
| 151 |
+
|
| 152 |
+
def get_seq_length(self, layer_idx: Optional[int] = 0) -> int:
|
| 153 |
+
"""Returns nonzero when cache is populated (used to detect prefill vs decode)."""
|
| 154 |
+
if layer_idx >= self.num_hidden_layers:
|
| 155 |
+
return 0
|
| 156 |
+
if self.is_large_window[layer_idx]:
|
| 157 |
+
return self._text_len[layer_idx]
|
| 158 |
+
else:
|
| 159 |
+
return (self._n_valid_window[layer_idx] + self._chunk_buf_len[layer_idx]
|
| 160 |
+
+ self._old_summary_len[layer_idx] + self._new_sum_len[layer_idx])
|
| 161 |
+
|
| 162 |
+
def get_cur_chunk_size(self, layer_idx: Optional[int] = None) -> int:
|
| 163 |
+
if layer_idx is None:
|
| 164 |
+
layer_idx = self.num_hidden_layers - 1
|
| 165 |
+
return self.cur_chunk_sizes[layer_idx]
|
| 166 |
+
|
| 167 |
+
def get_true_token_num(self, layer_idx: Optional[int] = None) -> int:
|
| 168 |
+
if layer_idx is None:
|
| 169 |
+
layer_idx = self.num_hidden_layers - 1
|
| 170 |
+
return self.true_tokens[layer_idx]
|
| 171 |
+
|
| 172 |
+
# ── Prefill: standard append (before reorganize) ──
|
| 173 |
+
|
| 174 |
+
def update(
|
| 175 |
+
self,
|
| 176 |
+
key_states: torch.Tensor,
|
| 177 |
+
value_states: torch.Tensor,
|
| 178 |
+
layer_idx: int,
|
| 179 |
+
cache_kwargs: Optional[dict[str, Any]] = None,
|
| 180 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 181 |
+
"""Append KV during prefill (before reorganize). Returns full KV for prefill attention."""
|
| 182 |
+
add_len = key_states.shape[-2]
|
| 183 |
+
cur_len = self._text_len[layer_idx]
|
| 184 |
+
new_len = cur_len + add_len
|
| 185 |
+
|
| 186 |
+
if self.key_cache[layer_idx] is None:
|
| 187 |
+
cap = new_len + self._APPEND_HEADROOM
|
| 188 |
+
bsz, heads, _, head_dim = key_states.shape
|
| 189 |
+
self.key_cache[layer_idx] = torch.empty(
|
| 190 |
+
bsz, heads, cap, head_dim, dtype=key_states.dtype, device=key_states.device)
|
| 191 |
+
self.value_cache[layer_idx] = torch.empty(
|
| 192 |
+
bsz, heads, cap, head_dim, dtype=value_states.dtype, device=value_states.device)
|
| 193 |
+
self._capacity[layer_idx] = cap
|
| 194 |
+
elif new_len > self._capacity[layer_idx]:
|
| 195 |
+
cap = max(new_len + self._APPEND_HEADROOM, self._capacity[layer_idx] * 2)
|
| 196 |
+
old_k, old_v = self.key_cache[layer_idx], self.value_cache[layer_idx]
|
| 197 |
+
bsz, heads, _, head_dim = old_k.shape
|
| 198 |
+
new_k = torch.empty(bsz, heads, cap, head_dim, dtype=old_k.dtype, device=old_k.device)
|
| 199 |
+
new_v = torch.empty(bsz, heads, cap, head_dim, dtype=old_v.dtype, device=old_v.device)
|
| 200 |
+
new_k[:, :, :cur_len, :].copy_(old_k[:, :, :cur_len, :])
|
| 201 |
+
new_v[:, :, :cur_len, :].copy_(old_v[:, :, :cur_len, :])
|
| 202 |
+
self.key_cache[layer_idx] = new_k
|
| 203 |
+
self.value_cache[layer_idx] = new_v
|
| 204 |
+
self._capacity[layer_idx] = cap
|
| 205 |
+
|
| 206 |
+
self.key_cache[layer_idx][:, :, cur_len:new_len, :].copy_(key_states)
|
| 207 |
+
self.value_cache[layer_idx][:, :, cur_len:new_len, :].copy_(value_states)
|
| 208 |
+
self._text_len[layer_idx] = new_len
|
| 209 |
+
|
| 210 |
+
if self.summary_chunk_size > 0:
|
| 211 |
+
if cache_kwargs and 'summary_mask' in cache_kwargs:
|
| 212 |
+
text_count = add_len - cache_kwargs['summary_mask'][0].sum().item()
|
| 213 |
+
else:
|
| 214 |
+
text_count = add_len
|
| 215 |
+
self.cur_chunk_sizes[layer_idx] += add_len
|
| 216 |
+
self.true_tokens[layer_idx] += text_count
|
| 217 |
+
|
| 218 |
+
return self.key_cache[layer_idx][:, :, :new_len, :], self.value_cache[layer_idx][:, :, :new_len, :]
|
| 219 |
+
|
| 220 |
+
# ── Reorganize after prefill ──
|
| 221 |
+
|
| 222 |
+
def reorganize_after_prefill(self, summary_mask: torch.Tensor):
|
| 223 |
+
"""Reorganize all layers from prefill block layout to ring buffer layout."""
|
| 224 |
+
if self._reorganized:
|
| 225 |
+
return
|
| 226 |
+
self._reorganized = True
|
| 227 |
+
|
| 228 |
+
text_mask = ~summary_mask[0]
|
| 229 |
+
|
| 230 |
+
for layer_idx in range(self.num_hidden_layers):
|
| 231 |
+
prefill_len = self._text_len[layer_idx]
|
| 232 |
+
prefill_k = self.key_cache[layer_idx][:, :, :prefill_len, :]
|
| 233 |
+
prefill_v = self.value_cache[layer_idx][:, :, :prefill_len, :]
|
| 234 |
+
bsz, heads, _, head_dim = prefill_k.shape
|
| 235 |
+
device, dtype = prefill_k.device, prefill_k.dtype
|
| 236 |
+
|
| 237 |
+
text_k = prefill_k[:, :, text_mask, :]
|
| 238 |
+
text_v = prefill_v[:, :, text_mask, :]
|
| 239 |
+
n_text = text_k.shape[2]
|
| 240 |
+
|
| 241 |
+
if self.is_large_window[layer_idx]:
|
| 242 |
+
# Large window: keep only text KV
|
| 243 |
+
cap = n_text + self._APPEND_HEADROOM
|
| 244 |
+
new_k = torch.empty(bsz, heads, cap, head_dim, dtype=dtype, device=device)
|
| 245 |
+
new_v = torch.empty(bsz, heads, cap, head_dim, dtype=dtype, device=device)
|
| 246 |
+
new_k[:, :, :n_text, :].copy_(text_k)
|
| 247 |
+
new_v[:, :, :n_text, :].copy_(text_v)
|
| 248 |
+
self.key_cache[layer_idx] = new_k
|
| 249 |
+
self.value_cache[layer_idx] = new_v
|
| 250 |
+
self._text_len[layer_idx] = n_text
|
| 251 |
+
self._capacity[layer_idx] = cap
|
| 252 |
+
else:
|
| 253 |
+
# Small window: split summaries into old (evicted) and new (in window)
|
| 254 |
+
all_summary_k = prefill_k[:, :, summary_mask[0], :]
|
| 255 |
+
all_summary_v = prefill_v[:, :, summary_mask[0], :]
|
| 256 |
+
n_summary = all_summary_k.shape[2]
|
| 257 |
+
|
| 258 |
+
C = self.summary_chunk_size
|
| 259 |
+
ws = self.window_sizes[layer_idx]
|
| 260 |
+
scn = self.sliding_chunk_nums[layer_idx]
|
| 261 |
+
|
| 262 |
+
# Split text into complete chunks + partial remainder
|
| 263 |
+
n_complete_chunks = n_text // C
|
| 264 |
+
n_partial = n_text % C
|
| 265 |
+
n_complete_text = n_complete_chunks * C
|
| 266 |
+
|
| 267 |
+
# Window: last scn complete chunks (or all if fewer)
|
| 268 |
+
n_window_chunks = min(scn, n_complete_chunks)
|
| 269 |
+
n_window_text = n_window_chunks * C
|
| 270 |
+
window_start = n_complete_text - n_window_text
|
| 271 |
+
|
| 272 |
+
# Split summaries: old (text evicted from ring) vs new (text in ring)
|
| 273 |
+
n_old = max(0, n_summary - n_window_chunks)
|
| 274 |
+
n_new = n_summary - n_old
|
| 275 |
+
|
| 276 |
+
# key_cache: [ring(ws) | old_summaries | chunk_mirror(≤C)]
|
| 277 |
+
old_s_cap = max(self._SUMMARY_INIT_CAP, (n_old + 1) * 2)
|
| 278 |
+
total_cap = ws + old_s_cap + C
|
| 279 |
+
new_k = torch.empty(bsz, heads, total_cap, head_dim, dtype=dtype, device=device)
|
| 280 |
+
new_v = torch.empty(bsz, heads, total_cap, head_dim, dtype=dtype, device=device)
|
| 281 |
+
|
| 282 |
+
if n_window_text > 0:
|
| 283 |
+
new_k[:, :, :n_window_text, :].copy_(text_k[:, :, window_start:n_complete_text, :])
|
| 284 |
+
new_v[:, :, :n_window_text, :].copy_(text_v[:, :, window_start:n_complete_text, :])
|
| 285 |
+
self._n_valid_window[layer_idx] = n_window_text
|
| 286 |
+
self._window_write_ptr[layer_idx] = n_window_text % ws
|
| 287 |
+
|
| 288 |
+
# Old summaries go into key_cache after ring
|
| 289 |
+
if n_old > 0:
|
| 290 |
+
new_k[:, :, ws:ws + n_old, :].copy_(all_summary_k[:, :, :n_old, :])
|
| 291 |
+
new_v[:, :, ws:ws + n_old, :].copy_(all_summary_v[:, :, :n_old, :])
|
| 292 |
+
self._old_summary_len[layer_idx] = n_old
|
| 293 |
+
self._old_summary_cap[layer_idx] = old_s_cap
|
| 294 |
+
|
| 295 |
+
# Mirror partial chunk into key_cache after old_summaries
|
| 296 |
+
if n_partial > 0:
|
| 297 |
+
mirror_start = ws + n_old
|
| 298 |
+
new_k[:, :, mirror_start:mirror_start + n_partial, :].copy_(
|
| 299 |
+
text_k[:, :, n_complete_text:, :])
|
| 300 |
+
new_v[:, :, mirror_start:mirror_start + n_partial, :].copy_(
|
| 301 |
+
text_v[:, :, n_complete_text:, :])
|
| 302 |
+
|
| 303 |
+
self.key_cache[layer_idx] = new_k
|
| 304 |
+
self.value_cache[layer_idx] = new_v
|
| 305 |
+
self._capacity[layer_idx] = total_cap
|
| 306 |
+
self._text_len[layer_idx] = 0
|
| 307 |
+
|
| 308 |
+
# New summary ring buffer
|
| 309 |
+
ns_buf_k = torch.empty(bsz, heads, scn, head_dim, dtype=dtype, device=device)
|
| 310 |
+
ns_buf_v = torch.empty(bsz, heads, scn, head_dim, dtype=dtype, device=device)
|
| 311 |
+
if n_new > 0:
|
| 312 |
+
ns_buf_k[:, :, :n_new, :].copy_(all_summary_k[:, :, n_old:, :])
|
| 313 |
+
ns_buf_v[:, :, :n_new, :].copy_(all_summary_v[:, :, n_old:, :])
|
| 314 |
+
self._new_sum_key_buf[layer_idx] = ns_buf_k
|
| 315 |
+
self._new_sum_value_buf[layer_idx] = ns_buf_v
|
| 316 |
+
self._new_sum_len[layer_idx] = n_new
|
| 317 |
+
self._new_sum_write_ptr[layer_idx] = n_new % scn
|
| 318 |
+
|
| 319 |
+
# Chunk buffer for partial remainder
|
| 320 |
+
chunk_buf_k = torch.empty(bsz, heads, C, head_dim, dtype=dtype, device=device)
|
| 321 |
+
chunk_buf_v = torch.empty(bsz, heads, C, head_dim, dtype=dtype, device=device)
|
| 322 |
+
if n_partial > 0:
|
| 323 |
+
chunk_buf_k[:, :, :n_partial, :].copy_(text_k[:, :, n_complete_text:, :])
|
| 324 |
+
chunk_buf_v[:, :, :n_partial, :].copy_(text_v[:, :, n_complete_text:, :])
|
| 325 |
+
self._chunk_key_buf[layer_idx] = chunk_buf_k
|
| 326 |
+
self._chunk_value_buf[layer_idx] = chunk_buf_v
|
| 327 |
+
self._chunk_buf_len[layer_idx] = n_partial
|
| 328 |
+
|
| 329 |
+
block = self.summary_chunk_size + self.summary_token_num
|
| 330 |
+
for layer_idx in range(self.num_hidden_layers):
|
| 331 |
+
self.cur_chunk_sizes[layer_idx] = self.cur_chunk_sizes[layer_idx] % block
|
| 332 |
+
self._total_chunks[layer_idx] = (
|
| 333 |
+
self._old_summary_len[layer_idx] + self._new_sum_len[layer_idx]
|
| 334 |
+
if not self.is_large_window[layer_idx]
|
| 335 |
+
else (self.true_tokens[layer_idx] // self.summary_chunk_size)
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
# ── Decode: text token update ──
|
| 339 |
+
|
| 340 |
+
def update_text(self, key_states: torch.Tensor, value_states: torch.Tensor, layer_idx: int):
|
| 341 |
+
"""Write a single text token KV during decode."""
|
| 342 |
+
if self.is_large_window[layer_idx]:
|
| 343 |
+
cur = self._text_len[layer_idx]
|
| 344 |
+
new_len = cur + 1
|
| 345 |
+
if new_len > self._capacity[layer_idx]:
|
| 346 |
+
cap = max(new_len + self._APPEND_HEADROOM, self._capacity[layer_idx] * 2)
|
| 347 |
+
old_k, old_v = self.key_cache[layer_idx], self.value_cache[layer_idx]
|
| 348 |
+
bsz, heads, _, head_dim = old_k.shape
|
| 349 |
+
new_k = torch.empty(bsz, heads, cap, head_dim, dtype=old_k.dtype, device=old_k.device)
|
| 350 |
+
new_v = torch.empty(bsz, heads, cap, head_dim, dtype=old_v.dtype, device=old_v.device)
|
| 351 |
+
new_k[:, :, :cur, :].copy_(old_k[:, :, :cur, :])
|
| 352 |
+
new_v[:, :, :cur, :].copy_(old_v[:, :, :cur, :])
|
| 353 |
+
self.key_cache[layer_idx] = new_k
|
| 354 |
+
self.value_cache[layer_idx] = new_v
|
| 355 |
+
self._capacity[layer_idx] = cap
|
| 356 |
+
self.key_cache[layer_idx][:, :, cur:new_len, :].copy_(key_states)
|
| 357 |
+
self.value_cache[layer_idx][:, :, cur:new_len, :].copy_(value_states)
|
| 358 |
+
self._text_len[layer_idx] = new_len
|
| 359 |
+
else:
|
| 360 |
+
# Write only to key_cache mirror region (chunk_buf eliminated)
|
| 361 |
+
ws = self.window_sizes[layer_idx]
|
| 362 |
+
n_old = self._old_summary_len[layer_idx]
|
| 363 |
+
pos = self._chunk_buf_len[layer_idx]
|
| 364 |
+
mirror_pos = ws + n_old + pos
|
| 365 |
+
self.key_cache[layer_idx][:, :, mirror_pos:mirror_pos+1, :].copy_(key_states)
|
| 366 |
+
self.value_cache[layer_idx][:, :, mirror_pos:mirror_pos+1, :].copy_(value_states)
|
| 367 |
+
self._chunk_buf_len[layer_idx] = pos + 1
|
| 368 |
+
|
| 369 |
+
self.cur_chunk_sizes[layer_idx] += 1
|
| 370 |
+
self.true_tokens[layer_idx] += 1
|
| 371 |
+
|
| 372 |
+
# ── Decode: summary token update ──
|
| 373 |
+
|
| 374 |
+
def update_summary(self, key_states: torch.Tensor, value_states: torch.Tensor, layer_idx: int):
|
| 375 |
+
"""Write summary token KV during decode (chunk boundary).
|
| 376 |
+
|
| 377 |
+
Large window: skip.
|
| 378 |
+
Small window (order matters — flush mirror before evict to avoid clobbering):
|
| 379 |
+
1. Flush mirror region → ring
|
| 380 |
+
2. Evict oldest new_summary → old_summary in key_cache (if full)
|
| 381 |
+
3. Write new summary → new_summary_buf
|
| 382 |
+
"""
|
| 383 |
+
n_summary = key_states.shape[2]
|
| 384 |
+
|
| 385 |
+
if self.is_large_window[layer_idx]:
|
| 386 |
+
self.cur_chunk_sizes[layer_idx] += n_summary
|
| 387 |
+
self._total_chunks[layer_idx] += n_summary
|
| 388 |
+
return
|
| 389 |
+
|
| 390 |
+
C = self.summary_chunk_size
|
| 391 |
+
ws = self.window_sizes[layer_idx]
|
| 392 |
+
scn = self.sliding_chunk_nums[layer_idx]
|
| 393 |
+
cbl = self._chunk_buf_len[layer_idx]
|
| 394 |
+
ptr = self._window_write_ptr[layer_idx]
|
| 395 |
+
n_old = self._old_summary_len[layer_idx]
|
| 396 |
+
|
| 397 |
+
# Step 1: Flush mirror region → ring (must happen before evict touches mirror[0])
|
| 398 |
+
mirror_start = ws + n_old
|
| 399 |
+
if ptr + cbl <= ws:
|
| 400 |
+
self.key_cache[layer_idx][:, :, ptr:ptr + cbl, :].copy_(
|
| 401 |
+
self.key_cache[layer_idx][:, :, mirror_start:mirror_start + cbl, :])
|
| 402 |
+
self.value_cache[layer_idx][:, :, ptr:ptr + cbl, :].copy_(
|
| 403 |
+
self.value_cache[layer_idx][:, :, mirror_start:mirror_start + cbl, :])
|
| 404 |
+
else:
|
| 405 |
+
first = ws - ptr
|
| 406 |
+
self.key_cache[layer_idx][:, :, ptr:ws, :].copy_(
|
| 407 |
+
self.key_cache[layer_idx][:, :, mirror_start:mirror_start + first, :])
|
| 408 |
+
self.value_cache[layer_idx][:, :, ptr:ws, :].copy_(
|
| 409 |
+
self.value_cache[layer_idx][:, :, mirror_start:mirror_start + first, :])
|
| 410 |
+
rest = cbl - first
|
| 411 |
+
self.key_cache[layer_idx][:, :, :rest, :].copy_(
|
| 412 |
+
self.key_cache[layer_idx][:, :, mirror_start + first:mirror_start + cbl, :])
|
| 413 |
+
self.value_cache[layer_idx][:, :, :rest, :].copy_(
|
| 414 |
+
self.value_cache[layer_idx][:, :, mirror_start + first:mirror_start + cbl, :])
|
| 415 |
+
|
| 416 |
+
self._window_write_ptr[layer_idx] = (ptr + cbl) % ws
|
| 417 |
+
if self._n_valid_window[layer_idx] < ws:
|
| 418 |
+
self._n_valid_window[layer_idx] = min(ws, self._n_valid_window[layer_idx] + cbl)
|
| 419 |
+
self._chunk_buf_len[layer_idx] = 0
|
| 420 |
+
|
| 421 |
+
# Step 2: Evict oldest new_summary → old_summary (now safe — mirror already flushed)
|
| 422 |
+
if self._new_sum_len[layer_idx] >= scn:
|
| 423 |
+
read_ptr = self._new_sum_write_ptr[layer_idx]
|
| 424 |
+
old_dst = ws + n_old # == mirror_start, but mirror data is already in ring
|
| 425 |
+
|
| 426 |
+
# Check capacity for old_summary growth
|
| 427 |
+
needed = old_dst + 1 + C # +1 for new old_sum, +C for future chunk mirror
|
| 428 |
+
if needed > self._capacity[layer_idx]:
|
| 429 |
+
new_s_cap = max(self._old_summary_cap[layer_idx] * 2, n_old + self._SUMMARY_INIT_CAP)
|
| 430 |
+
new_total = ws + new_s_cap + C
|
| 431 |
+
old_k, old_v = self.key_cache[layer_idx], self.value_cache[layer_idx]
|
| 432 |
+
bsz, heads, _, head_dim = old_k.shape
|
| 433 |
+
nk = torch.empty(bsz, heads, new_total, head_dim, dtype=old_k.dtype, device=old_k.device)
|
| 434 |
+
nv = torch.empty(bsz, heads, new_total, head_dim, dtype=old_v.dtype, device=old_v.device)
|
| 435 |
+
copy_len = ws + n_old
|
| 436 |
+
nk[:, :, :copy_len, :].copy_(old_k[:, :, :copy_len, :])
|
| 437 |
+
nv[:, :, :copy_len, :].copy_(old_v[:, :, :copy_len, :])
|
| 438 |
+
self.key_cache[layer_idx] = nk
|
| 439 |
+
self.value_cache[layer_idx] = nv
|
| 440 |
+
self._old_summary_cap[layer_idx] = new_s_cap
|
| 441 |
+
self._capacity[layer_idx] = new_total
|
| 442 |
+
|
| 443 |
+
self.key_cache[layer_idx][:, :, old_dst:old_dst+1, :].copy_(
|
| 444 |
+
self._new_sum_key_buf[layer_idx][:, :, read_ptr:read_ptr+1, :])
|
| 445 |
+
self.value_cache[layer_idx][:, :, old_dst:old_dst+1, :].copy_(
|
| 446 |
+
self._new_sum_value_buf[layer_idx][:, :, read_ptr:read_ptr+1, :])
|
| 447 |
+
self._old_summary_len[layer_idx] += 1
|
| 448 |
+
|
| 449 |
+
# Step 3: Write new summary to new_summary_buf (overwrite oldest slot)
|
| 450 |
+
w_ptr = self._new_sum_write_ptr[layer_idx]
|
| 451 |
+
self._new_sum_key_buf[layer_idx][:, :, w_ptr:w_ptr+1, :].copy_(key_states)
|
| 452 |
+
self._new_sum_value_buf[layer_idx][:, :, w_ptr:w_ptr+1, :].copy_(value_states)
|
| 453 |
+
self._new_sum_write_ptr[layer_idx] = (w_ptr + 1) % scn
|
| 454 |
+
if self._new_sum_len[layer_idx] < scn:
|
| 455 |
+
self._new_sum_len[layer_idx] += 1
|
| 456 |
+
|
| 457 |
+
self.cur_chunk_sizes[layer_idx] += n_summary
|
| 458 |
+
self._total_chunks[layer_idx] += n_summary
|
| 459 |
+
|
| 460 |
+
# ── Decode: get KV for attention ──
|
| 461 |
+
|
| 462 |
+
def get_attention_kv(self, layer_idx: int) -> tuple[torch.Tensor, torch.Tensor]:
|
| 463 |
+
"""Get full KV for text token attention.
|
| 464 |
+
|
| 465 |
+
Large window: buffer[:text_len]
|
| 466 |
+
Small window (steady state): key_cache[:ws + n_old + cbl] — single slice, zero cat
|
| 467 |
+
"""
|
| 468 |
+
if self.is_large_window[layer_idx]:
|
| 469 |
+
tl = self._text_len[layer_idx]
|
| 470 |
+
return (self.key_cache[layer_idx][:, :, :tl, :],
|
| 471 |
+
self.value_cache[layer_idx][:, :, :tl, :])
|
| 472 |
+
|
| 473 |
+
ws = self.window_sizes[layer_idx]
|
| 474 |
+
nv = self._n_valid_window[layer_idx]
|
| 475 |
+
cbl = self._chunk_buf_len[layer_idx]
|
| 476 |
+
n_old = self._old_summary_len[layer_idx]
|
| 477 |
+
|
| 478 |
+
# Steady state: ring full → [ring(ws) | old_sums(n_old) | chunk_mirror(cbl)] contiguous
|
| 479 |
+
if nv == ws:
|
| 480 |
+
end = ws + n_old + cbl
|
| 481 |
+
return (self.key_cache[layer_idx][:, :, :end, :],
|
| 482 |
+
self.value_cache[layer_idx][:, :, :end, :])
|
| 483 |
+
|
| 484 |
+
# Warmup: ring not full, [nv:ws] is gap → cat
|
| 485 |
+
parts_k, parts_v = [], []
|
| 486 |
+
if nv > 0:
|
| 487 |
+
parts_k.append(self.key_cache[layer_idx][:, :, :nv, :])
|
| 488 |
+
parts_v.append(self.value_cache[layer_idx][:, :, :nv, :])
|
| 489 |
+
if cbl > 0:
|
| 490 |
+
mirror_start = ws + n_old
|
| 491 |
+
parts_k.append(self.key_cache[layer_idx][:, :, mirror_start:mirror_start + cbl, :])
|
| 492 |
+
parts_v.append(self.value_cache[layer_idx][:, :, mirror_start:mirror_start + cbl, :])
|
| 493 |
+
if n_old > 0:
|
| 494 |
+
parts_k.append(self.key_cache[layer_idx][:, :, ws:ws + n_old, :])
|
| 495 |
+
parts_v.append(self.value_cache[layer_idx][:, :, ws:ws + n_old, :])
|
| 496 |
+
if len(parts_k) == 1:
|
| 497 |
+
return parts_k[0], parts_v[0]
|
| 498 |
+
return torch.cat(parts_k, dim=2), torch.cat(parts_v, dim=2)
|
| 499 |
+
|
| 500 |
+
def get_current_chunk_kv(self, layer_idx: int) -> tuple[torch.Tensor, torch.Tensor]:
|
| 501 |
+
"""Get KV of the current chunk's C text tokens for summary token attention."""
|
| 502 |
+
C = self.summary_chunk_size
|
| 503 |
+
if self.is_large_window[layer_idx]:
|
| 504 |
+
tl = self._text_len[layer_idx]
|
| 505 |
+
return (self.key_cache[layer_idx][:, :, tl - C:tl, :],
|
| 506 |
+
self.value_cache[layer_idx][:, :, tl - C:tl, :])
|
| 507 |
+
else:
|
| 508 |
+
ws = self.window_sizes[layer_idx]
|
| 509 |
+
n_old = self._old_summary_len[layer_idx]
|
| 510 |
+
cbl = self._chunk_buf_len[layer_idx]
|
| 511 |
+
mirror_start = ws + n_old
|
| 512 |
+
return (self.key_cache[layer_idx][:, :, mirror_start:mirror_start + cbl, :],
|
| 513 |
+
self.value_cache[layer_idx][:, :, mirror_start:mirror_start + cbl, :])
|
| 514 |
+
|
| 515 |
+
def reset_chunk_counter(self):
|
| 516 |
+
"""Reset chunk counters after a chunk boundary step completes."""
|
| 517 |
+
block = self.summary_chunk_size + self.summary_token_num
|
| 518 |
+
for layer_idx in range(self.num_hidden_layers):
|
| 519 |
+
if self.cur_chunk_sizes[layer_idx] >= block:
|
| 520 |
+
self.cur_chunk_sizes[layer_idx] %= block
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
class Qwen3MLP(nn.Module):
|
| 524 |
+
def __init__(self, config):
|
| 525 |
+
super().__init__()
|
| 526 |
+
self.config = config
|
| 527 |
+
self.hidden_size = config.hidden_size
|
| 528 |
+
self.intermediate_size = config.intermediate_size
|
| 529 |
+
self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
|
| 530 |
+
self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
|
| 531 |
+
self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
|
| 532 |
+
self.act_fn = ACT2FN[config.hidden_act]
|
| 533 |
+
|
| 534 |
+
def forward(self, x):
|
| 535 |
+
down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
|
| 536 |
+
return down_proj
|
| 537 |
+
|
| 538 |
+
|
| 539 |
+
def rotate_half(x):
|
| 540 |
+
"""Rotates half the hidden dims of the input."""
|
| 541 |
+
x1 = x[..., : x.shape[-1] // 2]
|
| 542 |
+
x2 = x[..., x.shape[-1] // 2 :]
|
| 543 |
+
return torch.cat((-x2, x1), dim=-1)
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
|
| 547 |
+
"""Applies Rotary Position Embedding to the query and key tensors.
|
| 548 |
+
|
| 549 |
+
Args:
|
| 550 |
+
q (`torch.Tensor`): The query tensor.
|
| 551 |
+
k (`torch.Tensor`): The key tensor.
|
| 552 |
+
cos (`torch.Tensor`): The cosine part of the rotary embedding.
|
| 553 |
+
sin (`torch.Tensor`): The sine part of the rotary embedding.
|
| 554 |
+
position_ids (`torch.Tensor`, *optional*):
|
| 555 |
+
Deprecated and unused.
|
| 556 |
+
unsqueeze_dim (`int`, *optional*, defaults to 1):
|
| 557 |
+
The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
|
| 558 |
+
sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
|
| 559 |
+
that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and
|
| 560 |
+
k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes
|
| 561 |
+
cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have
|
| 562 |
+
the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.
|
| 563 |
+
Returns:
|
| 564 |
+
`tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
|
| 565 |
+
"""
|
| 566 |
+
cos = cos.unsqueeze(unsqueeze_dim)
|
| 567 |
+
sin = sin.unsqueeze(unsqueeze_dim)
|
| 568 |
+
q_embed = (q * cos) + (rotate_half(q) * sin)
|
| 569 |
+
k_embed = (k * cos) + (rotate_half(k) * sin)
|
| 570 |
+
return q_embed, k_embed
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
|
| 574 |
+
"""
|
| 575 |
+
This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
|
| 576 |
+
num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
|
| 577 |
+
"""
|
| 578 |
+
batch, num_key_value_heads, slen, head_dim = hidden_states.shape
|
| 579 |
+
if n_rep == 1:
|
| 580 |
+
return hidden_states
|
| 581 |
+
hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
|
| 582 |
+
return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
|
| 583 |
+
|
| 584 |
+
|
| 585 |
+
def _sdpa_attention_forward(
|
| 586 |
+
module: nn.Module,
|
| 587 |
+
query: torch.Tensor,
|
| 588 |
+
key: torch.Tensor,
|
| 589 |
+
value: torch.Tensor,
|
| 590 |
+
attention_mask: Optional[torch.Tensor],
|
| 591 |
+
scaling: float,
|
| 592 |
+
dropout: float = 0.0,
|
| 593 |
+
**kwargs: Unpack[TransformersKwargs],
|
| 594 |
+
):
|
| 595 |
+
key_states = repeat_kv(key, module.num_key_value_groups)
|
| 596 |
+
value_states = repeat_kv(value, module.num_key_value_groups)
|
| 597 |
+
attn_output = F.scaled_dot_product_attention(
|
| 598 |
+
query,
|
| 599 |
+
key_states,
|
| 600 |
+
value_states,
|
| 601 |
+
attn_mask=None,
|
| 602 |
+
dropout_p=dropout,
|
| 603 |
+
is_causal=False,
|
| 604 |
+
)
|
| 605 |
+
attn_output = attn_output.transpose(1, 2).contiguous()
|
| 606 |
+
return attn_output, None
|
| 607 |
+
|
| 608 |
+
|
| 609 |
+
|
| 610 |
+
class Qwen3Attention(nn.Module):
|
| 611 |
+
"""Multi-headed attention from 'Attention Is All You Need' paper"""
|
| 612 |
+
|
| 613 |
+
def __init__(self, config: Qwen3Config, layer_idx: int):
|
| 614 |
+
super().__init__()
|
| 615 |
+
self.config = config
|
| 616 |
+
self.layer_idx = layer_idx
|
| 617 |
+
self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
|
| 618 |
+
self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
|
| 619 |
+
self.scaling = self.head_dim**-0.5
|
| 620 |
+
self.attention_dropout = config.attention_dropout
|
| 621 |
+
|
| 622 |
+
self.q_proj = nn.Linear(
|
| 623 |
+
config.hidden_size, config.num_attention_heads * self.head_dim, bias=config.attention_bias
|
| 624 |
+
)
|
| 625 |
+
self.k_proj = nn.Linear(
|
| 626 |
+
config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
|
| 627 |
+
)
|
| 628 |
+
self.v_proj = nn.Linear(
|
| 629 |
+
config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
|
| 630 |
+
)
|
| 631 |
+
self.o_proj = nn.Linear(
|
| 632 |
+
config.num_attention_heads * self.head_dim, config.hidden_size, bias=config.attention_bias
|
| 633 |
+
)
|
| 634 |
+
self.q_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps) # unlike olmo, only on the head dim!
|
| 635 |
+
self.k_norm = Qwen3RMSNorm(self.head_dim, eps=config.rms_norm_eps) # thus post q_norm does not need reshape
|
| 636 |
+
self.sliding_window = config.sliding_window if config.layer_types[layer_idx] == "sliding_attention" else None
|
| 637 |
+
|
| 638 |
+
@deprecate_kwarg("past_key_value", new_name="past_key_values", version="4.58")
|
| 639 |
+
def forward(
|
| 640 |
+
self,
|
| 641 |
+
hidden_states: torch.Tensor,
|
| 642 |
+
position_embeddings: tuple[torch.Tensor, torch.Tensor],
|
| 643 |
+
attention_mask: Optional[torch.Tensor],
|
| 644 |
+
past_key_values: Optional[Cache] = None,
|
| 645 |
+
cache_position: Optional[torch.LongTensor] = None,
|
| 646 |
+
**kwargs: Unpack[FlashAttentionKwargs],
|
| 647 |
+
) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 648 |
+
input_shape = hidden_states.shape[:-1]
|
| 649 |
+
hidden_shape = (*input_shape, -1, self.head_dim)
|
| 650 |
+
|
| 651 |
+
|
| 652 |
+
query_states = self.q_norm(self.q_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
|
| 653 |
+
key_states = self.k_norm(self.k_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
|
| 654 |
+
value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 655 |
+
|
| 656 |
+
cos, sin = position_embeddings
|
| 657 |
+
query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
|
| 658 |
+
|
| 659 |
+
if past_key_values is not None:
|
| 660 |
+
# sin and cos are specific to RoPE models; cache_position needed for the static cache
|
| 661 |
+
cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
|
| 662 |
+
key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx, cache_kwargs)
|
| 663 |
+
|
| 664 |
+
attn_output, attn_weights = _sdpa_attention_forward(
|
| 665 |
+
self,
|
| 666 |
+
query_states,
|
| 667 |
+
key_states,
|
| 668 |
+
value_states,
|
| 669 |
+
attention_mask,
|
| 670 |
+
dropout=0.0 if not self.training else self.attention_dropout,
|
| 671 |
+
scaling=self.scaling,
|
| 672 |
+
**kwargs,
|
| 673 |
+
)
|
| 674 |
+
|
| 675 |
+
attn_output = attn_output.reshape(*input_shape, -1).contiguous()
|
| 676 |
+
attn_output = self.o_proj(attn_output)
|
| 677 |
+
return attn_output, attn_weights
|
| 678 |
+
|
| 679 |
+
|
| 680 |
+
class Qwen3SummaryAttention(Qwen3Attention):
|
| 681 |
+
"""
|
| 682 |
+
Summary-aware variant of Qwen3Attention: uses a sliding summary mask.
|
| 683 |
+
"""
|
| 684 |
+
|
| 685 |
+
def __init__(self, config: Qwen3Config, layer_idx: int):
|
| 686 |
+
super().__init__(config, layer_idx)
|
| 687 |
+
self.summary_chunk_size = getattr(self.config, "summary_chunk_size", 0)
|
| 688 |
+
self.summary_token_num = getattr(self.config, "summary_token_num", 0)
|
| 689 |
+
|
| 690 |
+
# Cache sliding_chunk_num to avoid eval() on every forward call
|
| 691 |
+
val = getattr(config, "summary_sliding_chunk_num", 0) or 0
|
| 692 |
+
val = _parse_config_pattern(val)
|
| 693 |
+
if isinstance(val, list):
|
| 694 |
+
self._sliding_chunk_num = val[layer_idx]
|
| 695 |
+
else:
|
| 696 |
+
self._sliding_chunk_num = int(val)
|
| 697 |
+
|
| 698 |
+
if config.summary_independent_parameters and config.mix_coeff > 0:
|
| 699 |
+
self.q_proj_summary = nn.Linear(
|
| 700 |
+
config.hidden_size, config.num_attention_heads * self.head_dim, bias=config.attention_bias
|
| 701 |
+
)
|
| 702 |
+
self.k_proj_summary = nn.Linear(
|
| 703 |
+
config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
|
| 704 |
+
)
|
| 705 |
+
self.v_proj_summary = nn.Linear(
|
| 706 |
+
config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
|
| 707 |
+
)
|
| 708 |
+
|
| 709 |
+
def _get_sliding_chunk_num(self):
|
| 710 |
+
return self._sliding_chunk_num
|
| 711 |
+
|
| 712 |
+
def get_query_key_value_tensors(self, hidden_states):
|
| 713 |
+
input_shape = hidden_states.shape[:-1]
|
| 714 |
+
hidden_shape = (*input_shape, -1, self.head_dim)
|
| 715 |
+
query_states = self.q_norm(self.q_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
|
| 716 |
+
key_states = self.k_norm(self.k_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
|
| 717 |
+
value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 718 |
+
|
| 719 |
+
return query_states, key_states, value_states
|
| 720 |
+
|
| 721 |
+
def get_query_key_value_tensors_summary(self, hidden_states):
|
| 722 |
+
input_shape = hidden_states.shape[:-1]
|
| 723 |
+
hidden_shape = (*input_shape, -1, self.head_dim)
|
| 724 |
+
query_states = self.q_norm(self.q_proj_summary(hidden_states).view(hidden_shape)).transpose(1, 2)
|
| 725 |
+
key_states = self.k_norm(self.k_proj_summary(hidden_states).view(hidden_shape)).transpose(1, 2)
|
| 726 |
+
value_states = self.v_proj_summary(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 727 |
+
|
| 728 |
+
return query_states, key_states, value_states
|
| 729 |
+
|
| 730 |
+
@deprecate_kwarg("past_key_value", new_name="past_key_values", version="4.58")
|
| 731 |
+
def forward(
|
| 732 |
+
self,
|
| 733 |
+
hidden_states: torch.Tensor,
|
| 734 |
+
position_embeddings: tuple[torch.Tensor, torch.Tensor],
|
| 735 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 736 |
+
past_key_values: Optional[Cache] = None,
|
| 737 |
+
cache_position: Optional[torch.LongTensor] = None,
|
| 738 |
+
summary_ctx: Optional[SummaryBatchContext] = None,
|
| 739 |
+
**kwargs,
|
| 740 |
+
) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 741 |
+
input_shape = hidden_states.shape[:-1]
|
| 742 |
+
if hidden_states.size(0) != 1:
|
| 743 |
+
raise ValueError("Summary sliding attention only supports batch size=1.")
|
| 744 |
+
|
| 745 |
+
# Compute q/k/v for the full sequence once.
|
| 746 |
+
if self.config.summary_independent_parameters:
|
| 747 |
+
if summary_ctx is None:
|
| 748 |
+
raise ValueError("summary_ctx is required when using summary_independent_parameters.")
|
| 749 |
+
summary_mask = summary_ctx.summary_mask
|
| 750 |
+
summary_pos = summary_mask[0]
|
| 751 |
+
assert (summary_mask == summary_mask[0:1]).all()
|
| 752 |
+
|
| 753 |
+
if self.config.mix_coeff == 0:
|
| 754 |
+
# When mix_coeff=0, summary projections have no effect — skip clone + extra linear
|
| 755 |
+
query_states, key_states, value_states = self.get_query_key_value_tensors(hidden_states)
|
| 756 |
+
else:
|
| 757 |
+
query, key, value = self.get_query_key_value_tensors(hidden_states)
|
| 758 |
+
|
| 759 |
+
query_states = query.clone()
|
| 760 |
+
key_states = key.clone()
|
| 761 |
+
value_states = value.clone()
|
| 762 |
+
|
| 763 |
+
hs_summary = hidden_states[:, summary_pos, :]
|
| 764 |
+
if hs_summary.size(1) > 0:
|
| 765 |
+
base_q_summary = query[:, :, summary_pos, :]
|
| 766 |
+
base_k_summary = key[:, :, summary_pos, :]
|
| 767 |
+
base_v_summary = value[:, :, summary_pos, :]
|
| 768 |
+
|
| 769 |
+
q_s, k_s, v_s = self.get_query_key_value_tensors_summary(hs_summary)
|
| 770 |
+
|
| 771 |
+
q_s = self.config.mix_coeff * q_s + (1.0 - self.config.mix_coeff) * base_q_summary
|
| 772 |
+
k_s = self.config.mix_coeff * k_s + (1.0 - self.config.mix_coeff) * base_k_summary
|
| 773 |
+
v_s = self.config.mix_coeff * v_s + (1.0 - self.config.mix_coeff) * base_v_summary
|
| 774 |
+
|
| 775 |
+
query_states[:, :, summary_pos, :] = q_s
|
| 776 |
+
key_states[:, :, summary_pos, :] = k_s
|
| 777 |
+
value_states[:, :, summary_pos, :] = v_s
|
| 778 |
+
else:
|
| 779 |
+
query_states, key_states, value_states = self.get_query_key_value_tensors(hidden_states)
|
| 780 |
+
|
| 781 |
+
cos, sin = position_embeddings
|
| 782 |
+
query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
|
| 783 |
+
|
| 784 |
+
query_len = query_states.shape[2]
|
| 785 |
+
is_prefill = past_key_values is None or not past_key_values._reorganized
|
| 786 |
+
|
| 787 |
+
if is_prefill:
|
| 788 |
+
# Prefill: use standard append and summary_attn_func
|
| 789 |
+
if past_key_values is not None:
|
| 790 |
+
cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
|
| 791 |
+
if summary_ctx is not None:
|
| 792 |
+
cache_kwargs["summary_mask"] = summary_ctx.summary_mask
|
| 793 |
+
key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx, cache_kwargs)
|
| 794 |
+
|
| 795 |
+
with torch.cuda.device(query_states.device):
|
| 796 |
+
attn_output, attn_weights = summary_attn_func(
|
| 797 |
+
query_states.transpose(1,2).contiguous(),
|
| 798 |
+
key_states.transpose(1,2).contiguous(),
|
| 799 |
+
value_states.transpose(1,2).contiguous(),
|
| 800 |
+
self.summary_chunk_size,
|
| 801 |
+
self.summary_token_num,
|
| 802 |
+
self._get_sliding_chunk_num(),
|
| 803 |
+
summary_pos=summary_ctx.summary_mask.squeeze()
|
| 804 |
+
)
|
| 805 |
+
elif query_len == 1:
|
| 806 |
+
# Single text token decode: write to cache, attend to full buffer
|
| 807 |
+
past_key_values.update_text(key_states, value_states, self.layer_idx)
|
| 808 |
+
k_full, v_full = past_key_values.get_attention_kv(self.layer_idx)
|
| 809 |
+
attn_output, attn_weights = _sdpa_attention_forward(
|
| 810 |
+
self,
|
| 811 |
+
query_states,
|
| 812 |
+
k_full,
|
| 813 |
+
v_full,
|
| 814 |
+
None,
|
| 815 |
+
dropout=0.0 if not self.training else self.attention_dropout,
|
| 816 |
+
scaling=self.scaling,
|
| 817 |
+
sliding_window=self.sliding_window,
|
| 818 |
+
**kwargs,
|
| 819 |
+
)
|
| 820 |
+
else:
|
| 821 |
+
# Chunk boundary: query = [text_token, summary_token(s)]
|
| 822 |
+
# Split into text (first token) and summary (remaining tokens)
|
| 823 |
+
q_text = query_states[:, :, :1, :]
|
| 824 |
+
q_summary = query_states[:, :, 1:, :]
|
| 825 |
+
k_text = key_states[:, :, :1, :]
|
| 826 |
+
v_text = value_states[:, :, :1, :]
|
| 827 |
+
k_summary = key_states[:, :, 1:, :]
|
| 828 |
+
v_summary = value_states[:, :, 1:, :]
|
| 829 |
+
|
| 830 |
+
# 1. Write text token to cache, get full KV, run text attention
|
| 831 |
+
past_key_values.update_text(k_text, v_text, self.layer_idx)
|
| 832 |
+
k_full, v_full = past_key_values.get_attention_kv(self.layer_idx)
|
| 833 |
+
text_out, _ = _sdpa_attention_forward(
|
| 834 |
+
self,
|
| 835 |
+
q_text,
|
| 836 |
+
k_full,
|
| 837 |
+
v_full,
|
| 838 |
+
None,
|
| 839 |
+
dropout=0.0 if not self.training else self.attention_dropout,
|
| 840 |
+
scaling=self.scaling,
|
| 841 |
+
sliding_window=self.sliding_window,
|
| 842 |
+
**kwargs,
|
| 843 |
+
)
|
| 844 |
+
|
| 845 |
+
# 2. Summary attention: attend to current chunk's C text tokens + own KV (self-attention)
|
| 846 |
+
# The original model includes the summary token's own KV in its attention
|
| 847 |
+
# (causal within summary positions). With S=1, this is just self-attention.
|
| 848 |
+
k_chunk, v_chunk = past_key_values.get_current_chunk_kv(self.layer_idx)
|
| 849 |
+
k_chunk_with_self = torch.cat([k_chunk, k_summary], dim=2)
|
| 850 |
+
v_chunk_with_self = torch.cat([v_chunk, v_summary], dim=2)
|
| 851 |
+
summary_out, _ = _sdpa_attention_forward(
|
| 852 |
+
self,
|
| 853 |
+
q_summary,
|
| 854 |
+
k_chunk_with_self,
|
| 855 |
+
v_chunk_with_self,
|
| 856 |
+
None,
|
| 857 |
+
dropout=0.0 if not self.training else self.attention_dropout,
|
| 858 |
+
scaling=self.scaling,
|
| 859 |
+
sliding_window=self.sliding_window,
|
| 860 |
+
**kwargs,
|
| 861 |
+
)
|
| 862 |
+
|
| 863 |
+
# 3. Write summary KV to cache
|
| 864 |
+
past_key_values.update_summary(k_summary, v_summary, self.layer_idx)
|
| 865 |
+
|
| 866 |
+
attn_output = torch.cat([text_out, summary_out], dim=2)
|
| 867 |
+
attn_weights = None
|
| 868 |
+
|
| 869 |
+
attn_output = attn_output.reshape(*input_shape, -1).contiguous()
|
| 870 |
+
attn_output = self.o_proj(attn_output)
|
| 871 |
+
return attn_output, attn_weights
|
| 872 |
+
|
| 873 |
+
|
| 874 |
+
class Qwen3DecoderLayer(GradientCheckpointingLayer):
|
| 875 |
+
def __init__(self, config: Qwen3Config, layer_idx: int):
|
| 876 |
+
super().__init__()
|
| 877 |
+
self.config = config
|
| 878 |
+
self.hidden_size = config.hidden_size
|
| 879 |
+
|
| 880 |
+
# Use SummaryAttention if enabled in config
|
| 881 |
+
if getattr(config, "use_summary_attention", False) is True and config.summary_layer_freq[layer_idx] == 1:
|
| 882 |
+
self.self_attn = Qwen3SummaryAttention(config=config, layer_idx=layer_idx)
|
| 883 |
+
elif getattr(config, "use_summary_attention", False) is False and config.summary_layer_freq[layer_idx] == 0:
|
| 884 |
+
self.self_attn = Qwen3Attention(config=config, layer_idx=layer_idx)
|
| 885 |
+
else:
|
| 886 |
+
raise ValueError(f'Check config.summary_layer_freq {config.summary_layer_freq} and config.use_summary_attention {config.use_summary_attention}')
|
| 887 |
+
|
| 888 |
+
self.mlp = Qwen3MLP(config)
|
| 889 |
+
self.input_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 890 |
+
if getattr(config, "summary_independent_attention_layernorm", False):
|
| 891 |
+
self.input_layernorm_summary = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 892 |
+
self.post_attention_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 893 |
+
self.attention_type = config.layer_types[layer_idx]
|
| 894 |
+
|
| 895 |
+
@deprecate_kwarg("past_key_value", new_name="past_key_values", version="4.58")
|
| 896 |
+
def forward(
|
| 897 |
+
self,
|
| 898 |
+
hidden_states: torch.Tensor,
|
| 899 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 900 |
+
position_ids: Optional[torch.LongTensor] = None,
|
| 901 |
+
past_key_values: Optional[Cache] = None,
|
| 902 |
+
use_cache: Optional[bool] = False,
|
| 903 |
+
cache_position: Optional[torch.LongTensor] = None,
|
| 904 |
+
position_embeddings: Optional[tuple[torch.Tensor, torch.Tensor]] = None, # necessary, but kept here for BC
|
| 905 |
+
summary_ctx: Optional[SummaryBatchContext] = None,
|
| 906 |
+
**kwargs: Unpack[TransformersKwargs],
|
| 907 |
+
) -> torch.Tensor:
|
| 908 |
+
residual = hidden_states
|
| 909 |
+
if getattr(self.config, "summary_independent_attention_layernorm", False):
|
| 910 |
+
summary_mask = summary_ctx.summary_mask
|
| 911 |
+
assert (summary_mask == summary_mask[0:1]).all(), \
|
| 912 |
+
"summary_mask must be identical across batch"
|
| 913 |
+
hidden_states = self.input_layernorm(hidden_states)
|
| 914 |
+
if summary_mask.any():
|
| 915 |
+
hidden_summary = residual[:, summary_mask[0].to(residual.device), :]
|
| 916 |
+
hidden_summary = self.input_layernorm_summary(hidden_summary)
|
| 917 |
+
hidden_states[:, summary_mask[0], :] = hidden_summary
|
| 918 |
+
else:
|
| 919 |
+
hidden_states = self.input_layernorm(hidden_states)
|
| 920 |
+
|
| 921 |
+
# Self Attention - pass summary_ctx if using summary attention
|
| 922 |
+
attn_kwargs = {
|
| 923 |
+
"hidden_states": hidden_states,
|
| 924 |
+
"attention_mask": attention_mask,
|
| 925 |
+
"position_ids": position_ids,
|
| 926 |
+
"past_key_values": past_key_values,
|
| 927 |
+
"use_cache": use_cache,
|
| 928 |
+
"cache_position": cache_position,
|
| 929 |
+
"position_embeddings": position_embeddings,
|
| 930 |
+
**kwargs,
|
| 931 |
+
}
|
| 932 |
+
if isinstance(self.self_attn, Qwen3SummaryAttention):
|
| 933 |
+
attn_kwargs["summary_ctx"] = summary_ctx
|
| 934 |
+
|
| 935 |
+
hidden_states, _ = self.self_attn(**attn_kwargs)
|
| 936 |
+
hidden_states = residual + hidden_states
|
| 937 |
+
|
| 938 |
+
# Fully Connected
|
| 939 |
+
residual = hidden_states
|
| 940 |
+
hidden_states = self.post_attention_layernorm(hidden_states)
|
| 941 |
+
hidden_states = self.mlp(hidden_states)
|
| 942 |
+
hidden_states = residual + hidden_states
|
| 943 |
+
return hidden_states
|
| 944 |
+
|
| 945 |
+
|
| 946 |
+
@auto_docstring
|
| 947 |
+
class Qwen3PreTrainedModel(PreTrainedModel):
|
| 948 |
+
config: Qwen3Config
|
| 949 |
+
base_model_prefix = "model"
|
| 950 |
+
supports_gradient_checkpointing = True
|
| 951 |
+
_no_split_modules = ["Qwen3DecoderLayer"]
|
| 952 |
+
_skip_keys_device_placement = ["past_key_values"]
|
| 953 |
+
_supports_flash_attn = True
|
| 954 |
+
_supports_sdpa = True
|
| 955 |
+
_supports_flex_attn = True
|
| 956 |
+
|
| 957 |
+
_can_compile_fullgraph = True
|
| 958 |
+
_supports_attention_backend = True
|
| 959 |
+
_can_record_outputs = {
|
| 960 |
+
"hidden_states": Qwen3DecoderLayer,
|
| 961 |
+
"attentions": Qwen3Attention,
|
| 962 |
+
}
|
| 963 |
+
|
| 964 |
+
|
| 965 |
+
class Qwen3RotaryEmbedding(nn.Module):
|
| 966 |
+
inv_freq: torch.Tensor # fix linting for `register_buffer`
|
| 967 |
+
|
| 968 |
+
def __init__(self, config: Qwen3Config, device=None):
|
| 969 |
+
super().__init__()
|
| 970 |
+
self.max_seq_len_cached = config.max_position_embeddings
|
| 971 |
+
self.original_max_seq_len = config.max_position_embeddings
|
| 972 |
+
|
| 973 |
+
self.config = config
|
| 974 |
+
|
| 975 |
+
self.rope_type = self.config.rope_parameters["rope_type"]
|
| 976 |
+
rope_init_fn: Callable = self.compute_default_rope_parameters
|
| 977 |
+
if self.rope_type != "default":
|
| 978 |
+
rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type]
|
| 979 |
+
inv_freq, self.attention_scaling = rope_init_fn(self.config, device)
|
| 980 |
+
|
| 981 |
+
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
| 982 |
+
self.original_inv_freq = inv_freq
|
| 983 |
+
|
| 984 |
+
@staticmethod
|
| 985 |
+
def compute_default_rope_parameters(
|
| 986 |
+
config: Optional[Qwen3Config] = None,
|
| 987 |
+
device: Optional["torch.device"] = None,
|
| 988 |
+
seq_len: Optional[int] = None,
|
| 989 |
+
) -> tuple["torch.Tensor", float]:
|
| 990 |
+
"""
|
| 991 |
+
Computes the inverse frequencies according to the original RoPE implementation
|
| 992 |
+
Args:
|
| 993 |
+
config ([`~transformers.PreTrainedConfig`]):
|
| 994 |
+
The model configuration.
|
| 995 |
+
device (`torch.device`):
|
| 996 |
+
The device to use for initialization of the inverse frequencies.
|
| 997 |
+
seq_len (`int`, *optional*):
|
| 998 |
+
The current sequence length. Unused for this type of RoPE.
|
| 999 |
+
Returns:
|
| 1000 |
+
Tuple of (`torch.Tensor`, `float`), containing the inverse frequencies for the RoPE embeddings and the
|
| 1001 |
+
post-processing scaling factor applied to the computed cos/sin (unused in this type of RoPE).
|
| 1002 |
+
"""
|
| 1003 |
+
base = config.rope_parameters["rope_theta"]
|
| 1004 |
+
dim = getattr(config, "head_dim", None) or config.hidden_size // config.num_attention_heads
|
| 1005 |
+
|
| 1006 |
+
attention_factor = 1.0 # Unused in this type of RoPE
|
| 1007 |
+
|
| 1008 |
+
# Compute the inverse frequencies
|
| 1009 |
+
inv_freq = 1.0 / (
|
| 1010 |
+
base ** (torch.arange(0, dim, 2, dtype=torch.int64).to(device=device, dtype=torch.float) / dim)
|
| 1011 |
+
)
|
| 1012 |
+
return inv_freq, attention_factor
|
| 1013 |
+
|
| 1014 |
+
@torch.no_grad()
|
| 1015 |
+
@dynamic_rope_update # power user: used with advanced RoPE types (e.g. dynamic rope)
|
| 1016 |
+
def forward(self, x, position_ids):
|
| 1017 |
+
inv_freq_expanded = self.inv_freq[None, :, None].float().expand(position_ids.shape[0], -1, 1).to(x.device)
|
| 1018 |
+
position_ids_expanded = position_ids[:, None, :].float()
|
| 1019 |
+
|
| 1020 |
+
device_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu"
|
| 1021 |
+
with torch.autocast(device_type=device_type, enabled=False): # Force float32
|
| 1022 |
+
freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(1, 2)
|
| 1023 |
+
emb = torch.cat((freqs, freqs), dim=-1)
|
| 1024 |
+
cos = emb.cos() * self.attention_scaling
|
| 1025 |
+
sin = emb.sin() * self.attention_scaling
|
| 1026 |
+
|
| 1027 |
+
return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
|
| 1028 |
+
|
| 1029 |
+
|
| 1030 |
+
@auto_docstring
|
| 1031 |
+
class Qwen3Model(Qwen3PreTrainedModel):
|
| 1032 |
+
def __init__(self, config: Qwen3Config):
|
| 1033 |
+
super().__init__(config)
|
| 1034 |
+
self.padding_idx = config.pad_token_id
|
| 1035 |
+
self.vocab_size = config.vocab_size
|
| 1036 |
+
if not getattr(config, "summary_layer_freq", False):
|
| 1037 |
+
if config.use_summary_attention:
|
| 1038 |
+
config.summary_layer_freq = [1]*config.num_hidden_layers
|
| 1039 |
+
else:
|
| 1040 |
+
config.summary_layer_freq = [0]*config.num_hidden_layers
|
| 1041 |
+
Warning(f'Please set config.summary_layer_freq, temp set summary_layer_freq = {config.num_hidden_layers}')
|
| 1042 |
+
else:
|
| 1043 |
+
config.summary_layer_freq = _parse_config_pattern(config.summary_layer_freq)
|
| 1044 |
+
|
| 1045 |
+
self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
|
| 1046 |
+
self.layers = nn.ModuleList(
|
| 1047 |
+
[Qwen3DecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
|
| 1048 |
+
)
|
| 1049 |
+
self.norm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 1050 |
+
self.rotary_emb = Qwen3RotaryEmbedding(config=config)
|
| 1051 |
+
self.gradient_checkpointing = False
|
| 1052 |
+
self.has_sliding_layers = "sliding_attention" in self.config.layer_types
|
| 1053 |
+
|
| 1054 |
+
# Cache per-layer sliding_chunk_nums for KV cache eviction
|
| 1055 |
+
_sv = _parse_config_pattern(getattr(config, "summary_sliding_chunk_num", 0) or 0)
|
| 1056 |
+
if isinstance(_sv, list):
|
| 1057 |
+
self._sliding_chunk_nums = [int(v) for v in _sv]
|
| 1058 |
+
else:
|
| 1059 |
+
self._sliding_chunk_nums = [int(_sv)] * config.num_hidden_layers
|
| 1060 |
+
|
| 1061 |
+
# Initialize weights and apply final processing
|
| 1062 |
+
self.post_init()
|
| 1063 |
+
|
| 1064 |
+
def _expand_input_with_summary_tokens(self, input_ids):
|
| 1065 |
+
"""Expand input_ids with summary tokens for prefill phase (vectorized).
|
| 1066 |
+
|
| 1067 |
+
Returns:
|
| 1068 |
+
Tuple of (expanded_input_ids, position_ids, text_only_mask)
|
| 1069 |
+
"""
|
| 1070 |
+
summary_chunk = self.config.summary_chunk_size
|
| 1071 |
+
summary_num = self.config.summary_token_num
|
| 1072 |
+
summary_begin = self.config.summary_token_begin
|
| 1073 |
+
|
| 1074 |
+
if summary_chunk == 0 or summary_num == 0:
|
| 1075 |
+
return input_ids, None, None
|
| 1076 |
+
|
| 1077 |
+
batch_size, seq_len = input_ids.shape
|
| 1078 |
+
device = input_ids.device
|
| 1079 |
+
dtype = input_ids.dtype
|
| 1080 |
+
block = summary_chunk + summary_num
|
| 1081 |
+
|
| 1082 |
+
# Number of full chunks and remainder
|
| 1083 |
+
n_full_chunks = seq_len // summary_chunk
|
| 1084 |
+
remainder = seq_len % summary_chunk
|
| 1085 |
+
has_remainder = remainder > 0
|
| 1086 |
+
|
| 1087 |
+
# Total expanded length: full_chunks * block + remainder
|
| 1088 |
+
expanded_len = n_full_chunks * block + (remainder if has_remainder else 0)
|
| 1089 |
+
|
| 1090 |
+
# --- Build expanded_input_ids ---
|
| 1091 |
+
expanded_ids = torch.empty((batch_size, expanded_len), dtype=dtype, device=device)
|
| 1092 |
+
text_only_mask = torch.zeros((batch_size, expanded_len), dtype=torch.bool, device=device)
|
| 1093 |
+
|
| 1094 |
+
# Compute text positions: for chunk i, text goes to [i*block, i*block+summary_chunk)
|
| 1095 |
+
# Summary positions: [i*block+summary_chunk, (i+1)*block)
|
| 1096 |
+
if n_full_chunks > 0:
|
| 1097 |
+
chunk_indices = torch.arange(n_full_chunks, device=device)
|
| 1098 |
+
# Text source positions in original input_ids
|
| 1099 |
+
text_src_offsets = (chunk_indices * summary_chunk).unsqueeze(1) + torch.arange(summary_chunk, device=device).unsqueeze(0) # [n_full_chunks, summary_chunk]
|
| 1100 |
+
# Text dest positions in expanded
|
| 1101 |
+
text_dst_offsets = (chunk_indices * block).unsqueeze(1) + torch.arange(summary_chunk, device=device).unsqueeze(0) # [n_full_chunks, summary_chunk]
|
| 1102 |
+
# Summary dest positions
|
| 1103 |
+
summary_dst_offsets = (chunk_indices * block + summary_chunk).unsqueeze(1) + torch.arange(summary_num, device=device).unsqueeze(0) # [n_full_chunks, summary_num]
|
| 1104 |
+
|
| 1105 |
+
text_src_flat = text_src_offsets.reshape(-1)
|
| 1106 |
+
text_dst_flat = text_dst_offsets.reshape(-1)
|
| 1107 |
+
summary_dst_flat = summary_dst_offsets.reshape(-1)
|
| 1108 |
+
|
| 1109 |
+
# Copy text tokens
|
| 1110 |
+
expanded_ids[:, text_dst_flat] = input_ids[:, text_src_flat]
|
| 1111 |
+
text_only_mask[:, text_dst_flat] = True
|
| 1112 |
+
|
| 1113 |
+
# Fill summary tokens
|
| 1114 |
+
summary_ids_val = torch.arange(summary_num, device=device, dtype=dtype) + summary_begin
|
| 1115 |
+
expanded_ids[:, summary_dst_flat] = summary_ids_val.repeat(n_full_chunks).unsqueeze(0).expand(batch_size, -1)
|
| 1116 |
+
|
| 1117 |
+
# Handle remainder (last partial chunk, no summary tokens)
|
| 1118 |
+
if has_remainder:
|
| 1119 |
+
rem_start_src = n_full_chunks * summary_chunk
|
| 1120 |
+
rem_start_dst = n_full_chunks * block
|
| 1121 |
+
rem_offsets = torch.arange(remainder, device=device)
|
| 1122 |
+
expanded_ids[:, rem_start_dst + rem_offsets] = input_ids[:, rem_start_src + rem_offsets]
|
| 1123 |
+
text_only_mask[:, rem_start_dst + rem_offsets] = True
|
| 1124 |
+
|
| 1125 |
+
# --- Build position_ids ---
|
| 1126 |
+
position_ids = torch.empty((batch_size, expanded_len), dtype=torch.long, device=device)
|
| 1127 |
+
|
| 1128 |
+
if n_full_chunks > 0:
|
| 1129 |
+
# Text position IDs
|
| 1130 |
+
if self.config.summary_chunk_position_ids_type == 'origin':
|
| 1131 |
+
text_pos = text_src_flat.unsqueeze(0).expand(batch_size, -1)
|
| 1132 |
+
elif self.config.summary_chunk_position_ids_type == 'inner_chunk':
|
| 1133 |
+
inner_pos = torch.arange(summary_chunk, device=device).repeat(n_full_chunks)
|
| 1134 |
+
text_pos = inner_pos.unsqueeze(0).expand(batch_size, -1)
|
| 1135 |
+
else:
|
| 1136 |
+
raise ValueError(f'Check config.summary_chunk_position_ids_type: {self.config.summary_chunk_position_ids_type}')
|
| 1137 |
+
position_ids[:, text_dst_flat] = text_pos
|
| 1138 |
+
|
| 1139 |
+
# Summary position IDs
|
| 1140 |
+
if self.config.summary_token_position_ids_type == 'zeros':
|
| 1141 |
+
position_ids[:, summary_dst_flat] = 0
|
| 1142 |
+
elif self.config.summary_token_position_ids_type in ('last_chunk_slice_left', 'last_chunk_slice_right'):
|
| 1143 |
+
# Vectorized slice_ends computation for all chunks at once
|
| 1144 |
+
if self.config.summary_token_position_ids_type == 'last_chunk_slice_left':
|
| 1145 |
+
idx = torch.arange(0, summary_num, device=device, dtype=torch.long)
|
| 1146 |
+
else:
|
| 1147 |
+
idx = torch.arange(1, summary_num + 1, device=device, dtype=torch.long)
|
| 1148 |
+
# For each chunk i: prev_text_end = i * summary_chunk
|
| 1149 |
+
prev_ends = (chunk_indices * summary_chunk).unsqueeze(1) # [n_full_chunks, 1]
|
| 1150 |
+
slice_ends = prev_ends + (idx.unsqueeze(0) * summary_chunk) // summary_num - 1 # [n_full_chunks, summary_num]
|
| 1151 |
+
slice_ends = slice_ends.clamp(min=0)
|
| 1152 |
+
# Clamp per-chunk: min is prev_text_end for that chunk
|
| 1153 |
+
slice_ends = torch.max(slice_ends, prev_ends)
|
| 1154 |
+
position_ids[:, summary_dst_flat] = slice_ends.reshape(-1).unsqueeze(0).expand(batch_size, -1)
|
| 1155 |
+
else:
|
| 1156 |
+
raise ValueError(f'Unknown summary_token_position_ids_type: {self.config.summary_token_position_ids_type}')
|
| 1157 |
+
|
| 1158 |
+
# Remainder position IDs
|
| 1159 |
+
if has_remainder:
|
| 1160 |
+
if self.config.summary_chunk_position_ids_type == 'origin':
|
| 1161 |
+
rem_pos = (rem_start_src + rem_offsets).unsqueeze(0).expand(batch_size, -1)
|
| 1162 |
+
elif self.config.summary_chunk_position_ids_type == 'inner_chunk':
|
| 1163 |
+
rem_pos = rem_offsets.unsqueeze(0).expand(batch_size, -1)
|
| 1164 |
+
else:
|
| 1165 |
+
raise ValueError(f'Check config.summary_chunk_position_ids_type: {self.config.summary_chunk_position_ids_type}')
|
| 1166 |
+
position_ids[:, rem_start_dst + rem_offsets] = rem_pos
|
| 1167 |
+
|
| 1168 |
+
return expanded_ids, position_ids, text_only_mask
|
| 1169 |
+
|
| 1170 |
+
def _build_summary_context(self, input_ids, position_ids, is_prefill, use_cache):
|
| 1171 |
+
"""Build summary context for attention layers."""
|
| 1172 |
+
summary_chunk = self.config.summary_chunk_size
|
| 1173 |
+
summary_num = self.config.summary_token_num
|
| 1174 |
+
summary_begin = self.config.summary_token_begin
|
| 1175 |
+
|
| 1176 |
+
if summary_chunk > 0 and summary_num > 0:
|
| 1177 |
+
return build_summary_sliding_context(
|
| 1178 |
+
input_ids=input_ids,
|
| 1179 |
+
position_ids=position_ids,
|
| 1180 |
+
summary_token_num=summary_num,
|
| 1181 |
+
summary_token_begin=summary_begin,
|
| 1182 |
+
)
|
| 1183 |
+
return None
|
| 1184 |
+
|
| 1185 |
+
def _filter_summary_tokens(self, hidden_states, text_only_mask, use_summary, is_decode):
|
| 1186 |
+
"""Filter out summary tokens from output hidden states."""
|
| 1187 |
+
if text_only_mask is not None:
|
| 1188 |
+
# Prefill: vectorized filtering using boolean mask
|
| 1189 |
+
batch_size, _, hidden_size = hidden_states.shape
|
| 1190 |
+
text_length = text_only_mask[0].sum().item()
|
| 1191 |
+
return hidden_states[text_only_mask.to(hidden_states.device)].reshape(batch_size, text_length, hidden_size)
|
| 1192 |
+
elif use_summary and is_decode and hidden_states.size(1) > 1:
|
| 1193 |
+
# Decode: if we have multiple tokens, only return the first (text token)
|
| 1194 |
+
return hidden_states[:, :1, :]
|
| 1195 |
+
return hidden_states
|
| 1196 |
+
|
| 1197 |
+
@check_model_inputs
|
| 1198 |
+
@auto_docstring
|
| 1199 |
+
def forward(
|
| 1200 |
+
self,
|
| 1201 |
+
input_ids: Optional[torch.LongTensor] = None,
|
| 1202 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 1203 |
+
position_ids: Optional[torch.LongTensor] = None,
|
| 1204 |
+
past_key_values: Optional[Cache] = None,
|
| 1205 |
+
inputs_embeds: Optional[torch.FloatTensor] = None,
|
| 1206 |
+
use_cache: Optional[bool] = None,
|
| 1207 |
+
cache_position: Optional[torch.LongTensor] = None,
|
| 1208 |
+
summary_ctx: Optional[SummaryBatchContext] = None,
|
| 1209 |
+
**kwargs: Unpack[TransformersKwargs],
|
| 1210 |
+
) -> BaseModelOutputWithPast:
|
| 1211 |
+
"""
|
| 1212 |
+
summary_ctx (`SummaryBatchContext`, *optional*):
|
| 1213 |
+
Summary positions and chunk metadata for the current input.
|
| 1214 |
+
"""
|
| 1215 |
+
if (input_ids is None) ^ (inputs_embeds is not None):
|
| 1216 |
+
raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
|
| 1217 |
+
use_summary = getattr(self.config, "use_summary_attention", False)
|
| 1218 |
+
is_prefill = past_key_values is None or past_key_values.get_seq_length() == 0
|
| 1219 |
+
|
| 1220 |
+
# Prefill phase with summary attention: expand input_ids with summary tokens
|
| 1221 |
+
text_only_mask = None
|
| 1222 |
+
if use_summary and input_ids is not None and inputs_embeds is None and is_prefill:
|
| 1223 |
+
input_ids, position_ids, text_only_mask = self._expand_input_with_summary_tokens(input_ids)
|
| 1224 |
+
|
| 1225 |
+
if inputs_embeds is None:
|
| 1226 |
+
inputs_embeds = self.embed_tokens(input_ids)
|
| 1227 |
+
|
| 1228 |
+
# Initialize cache
|
| 1229 |
+
if use_cache and past_key_values is None:
|
| 1230 |
+
if use_summary:
|
| 1231 |
+
past_key_values = Qwen3RingBufferCache(
|
| 1232 |
+
config=self.config, sliding_chunk_nums=self._sliding_chunk_nums)
|
| 1233 |
+
else:
|
| 1234 |
+
past_key_values = DynamicCache(config=self.config)
|
| 1235 |
+
|
| 1236 |
+
if cache_position is None:
|
| 1237 |
+
past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
|
| 1238 |
+
cache_position = torch.arange(
|
| 1239 |
+
past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device
|
| 1240 |
+
)
|
| 1241 |
+
|
| 1242 |
+
if position_ids is None:
|
| 1243 |
+
position_ids = cache_position.unsqueeze(0)
|
| 1244 |
+
|
| 1245 |
+
# Build summary context if needed
|
| 1246 |
+
if use_summary and summary_ctx is None and input_ids is not None:
|
| 1247 |
+
summary_ctx = self._build_summary_context(input_ids, position_ids, is_prefill, use_cache)
|
| 1248 |
+
|
| 1249 |
+
causal_mask_mapping = attention_mask
|
| 1250 |
+
if not isinstance(causal_mask_mapping, (dict, list)):
|
| 1251 |
+
if summary_ctx and summary_ctx.enabled:
|
| 1252 |
+
seq_len = inputs_embeds.shape[1]
|
| 1253 |
+
# During prefill, Qwen3SummaryAttention uses summary_attn_func
|
| 1254 |
+
# which does not need a dense mask. Skip expensive mask construction.
|
| 1255 |
+
# During decode, prepare_inputs_for_generation already computed
|
| 1256 |
+
# per-layer keep_indices and passed them as attention_mask (list).
|
| 1257 |
+
# If we reach here with a non-list, it means no mask is needed.
|
| 1258 |
+
causal_mask_mapping = None
|
| 1259 |
+
else:
|
| 1260 |
+
# Prepare mask arguments
|
| 1261 |
+
mask_kwargs = {
|
| 1262 |
+
"config": self.config,
|
| 1263 |
+
"input_embeds": inputs_embeds,
|
| 1264 |
+
"attention_mask": attention_mask,
|
| 1265 |
+
"cache_position": cache_position,
|
| 1266 |
+
"past_key_values": past_key_values,
|
| 1267 |
+
"position_ids": position_ids,
|
| 1268 |
+
}
|
| 1269 |
+
# Create the masks - disable causal mask when summary context is enabled
|
| 1270 |
+
causal_mask_mapping = {
|
| 1271 |
+
"full_attention": create_causal_mask(**mask_kwargs),
|
| 1272 |
+
}
|
| 1273 |
+
# The sliding window alternating layers are not always activated depending on the config
|
| 1274 |
+
if self.has_sliding_layers:
|
| 1275 |
+
causal_mask_mapping["sliding_attention"] = create_sliding_window_causal_mask(**mask_kwargs)
|
| 1276 |
+
|
| 1277 |
+
hidden_states = inputs_embeds
|
| 1278 |
+
|
| 1279 |
+
# create position embeddings to be shared across the decoder layers
|
| 1280 |
+
position_embeddings = self.rotary_emb(hidden_states, position_ids)
|
| 1281 |
+
|
| 1282 |
+
for layer_idx, decoder_layer in enumerate(self.layers[: self.config.num_hidden_layers]):
|
| 1283 |
+
if causal_mask_mapping is None:
|
| 1284 |
+
layer_mask = None
|
| 1285 |
+
elif isinstance(causal_mask_mapping, list):
|
| 1286 |
+
layer_mask = causal_mask_mapping[layer_idx]
|
| 1287 |
+
else:
|
| 1288 |
+
layer_mask = causal_mask_mapping[decoder_layer.attention_type]
|
| 1289 |
+
hidden_states = decoder_layer(
|
| 1290 |
+
hidden_states,
|
| 1291 |
+
attention_mask=layer_mask,
|
| 1292 |
+
position_ids=position_ids,
|
| 1293 |
+
past_key_values=past_key_values,
|
| 1294 |
+
use_cache=use_cache,
|
| 1295 |
+
cache_position=cache_position,
|
| 1296 |
+
position_embeddings=position_embeddings,
|
| 1297 |
+
summary_ctx=summary_ctx,
|
| 1298 |
+
**kwargs,
|
| 1299 |
+
)
|
| 1300 |
+
|
| 1301 |
+
hidden_states = self.norm(hidden_states)
|
| 1302 |
+
|
| 1303 |
+
# After prefill: reorganize cache to ring buffer layout
|
| 1304 |
+
if use_cache and use_summary and past_key_values is not None and is_prefill:
|
| 1305 |
+
if hasattr(past_key_values, 'reorganize_after_prefill') and summary_ctx is not None:
|
| 1306 |
+
past_key_values.reorganize_after_prefill(summary_ctx.summary_mask)
|
| 1307 |
+
|
| 1308 |
+
# After chunk boundary decode: reset chunk counters
|
| 1309 |
+
if use_cache and use_summary and past_key_values is not None and not is_prefill:
|
| 1310 |
+
if hasattr(past_key_values, 'reset_chunk_counter'):
|
| 1311 |
+
past_key_values.reset_chunk_counter()
|
| 1312 |
+
|
| 1313 |
+
# Filter out summary tokens from output
|
| 1314 |
+
hidden_states = self._filter_summary_tokens(hidden_states, text_only_mask, use_summary,
|
| 1315 |
+
past_key_values is not None and past_key_values.get_seq_length() > 0)
|
| 1316 |
+
|
| 1317 |
+
return BaseModelOutputWithPast(
|
| 1318 |
+
last_hidden_state=hidden_states,
|
| 1319 |
+
past_key_values=past_key_values if use_cache else None,
|
| 1320 |
+
)
|
| 1321 |
+
|
| 1322 |
+
|
| 1323 |
+
@auto_docstring
|
| 1324 |
+
class Qwen3ForCausalLM(Qwen3PreTrainedModel, GenerationMixin):
|
| 1325 |
+
_is_stateful = True
|
| 1326 |
+
_tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
|
| 1327 |
+
_tp_plan = {"lm_head": "colwise_rep"}
|
| 1328 |
+
_pp_plan = {"lm_head": (["hidden_states"], ["logits"])}
|
| 1329 |
+
|
| 1330 |
+
def __init__(self, config):
|
| 1331 |
+
super().__init__(config)
|
| 1332 |
+
self.model = Qwen3Model(config)
|
| 1333 |
+
self.vocab_size = config.vocab_size
|
| 1334 |
+
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
|
| 1335 |
+
|
| 1336 |
+
# Initialize weights and apply final processing
|
| 1337 |
+
self.post_init()
|
| 1338 |
+
|
| 1339 |
+
@can_return_tuple
|
| 1340 |
+
@auto_docstring
|
| 1341 |
+
def forward(
|
| 1342 |
+
self,
|
| 1343 |
+
input_ids: Optional[torch.LongTensor] = None,
|
| 1344 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 1345 |
+
position_ids: Optional[torch.LongTensor] = None,
|
| 1346 |
+
past_key_values: Optional[Cache] = None,
|
| 1347 |
+
inputs_embeds: Optional[torch.FloatTensor] = None,
|
| 1348 |
+
labels: Optional[torch.LongTensor] = None,
|
| 1349 |
+
use_cache: Optional[bool] = None,
|
| 1350 |
+
cache_position: Optional[torch.LongTensor] = None,
|
| 1351 |
+
logits_to_keep: Union[int, torch.Tensor] = 0,
|
| 1352 |
+
summary_ctx: Optional[SummaryBatchContext] = None,
|
| 1353 |
+
**kwargs: Unpack[TransformersKwargs],
|
| 1354 |
+
) -> CausalLMOutputWithPast:
|
| 1355 |
+
r"""
|
| 1356 |
+
labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
|
| 1357 |
+
Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
|
| 1358 |
+
config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
|
| 1359 |
+
(masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
|
| 1360 |
+
|
| 1361 |
+
summary_ctx (`SummaryBatchContext`, *optional*):
|
| 1362 |
+
Summary positions and chunk metadata for the current input.
|
| 1363 |
+
"""
|
| 1364 |
+
outputs: BaseModelOutputWithPast = self.model(
|
| 1365 |
+
input_ids=input_ids,
|
| 1366 |
+
attention_mask=attention_mask,
|
| 1367 |
+
position_ids=position_ids,
|
| 1368 |
+
past_key_values=past_key_values,
|
| 1369 |
+
inputs_embeds=inputs_embeds,
|
| 1370 |
+
use_cache=use_cache,
|
| 1371 |
+
cache_position=cache_position,
|
| 1372 |
+
summary_ctx=summary_ctx,
|
| 1373 |
+
**kwargs,
|
| 1374 |
+
)
|
| 1375 |
+
|
| 1376 |
+
hidden_states = outputs.last_hidden_state
|
| 1377 |
+
# Only compute necessary logits, and do not upcast them to float if we are not computing the loss
|
| 1378 |
+
if isinstance(logits_to_keep, int) and logits_to_keep == 0 and labels is None:
|
| 1379 |
+
# Inference: only need last token's logits to avoid OOM from [seq_len, vocab_size]
|
| 1380 |
+
logits = self.lm_head(hidden_states[:, -1:, :])
|
| 1381 |
+
else:
|
| 1382 |
+
slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
|
| 1383 |
+
logits = self.lm_head(hidden_states[:, slice_indices, :])
|
| 1384 |
+
|
| 1385 |
+
truncate_n = getattr(self.config, "truncate_predict_nums", 151936)
|
| 1386 |
+
if truncate_n > 0:
|
| 1387 |
+
logits = logits[..., :truncate_n]
|
| 1388 |
+
|
| 1389 |
+
loss = None
|
| 1390 |
+
if labels is not None:
|
| 1391 |
+
loss = self.loss_function(logits=logits, labels=labels, vocab_size=logits.shape[-1], **kwargs)
|
| 1392 |
+
|
| 1393 |
+
return CausalLMOutputWithPast(
|
| 1394 |
+
loss=loss,
|
| 1395 |
+
logits=logits,
|
| 1396 |
+
past_key_values=outputs.past_key_values,
|
| 1397 |
+
hidden_states=outputs.hidden_states,
|
| 1398 |
+
attentions=outputs.attentions,
|
| 1399 |
+
)
|
| 1400 |
+
|
| 1401 |
+
def _build_summary_attention_mask_for_generation(
|
| 1402 |
+
self,
|
| 1403 |
+
*,
|
| 1404 |
+
input_ids: torch.LongTensor,
|
| 1405 |
+
past_key_values: Optional[Cache],
|
| 1406 |
+
attention_mask: Optional[torch.Tensor],
|
| 1407 |
+
) -> Optional[torch.Tensor]:
|
| 1408 |
+
"""Ring buffer cache handles attention internally — no mask needed for decode."""
|
| 1409 |
+
if isinstance(past_key_values, Qwen3RingBufferCache):
|
| 1410 |
+
return None
|
| 1411 |
+
return attention_mask
|
| 1412 |
+
|
| 1413 |
+
def prepare_inputs_for_generation(
|
| 1414 |
+
self,
|
| 1415 |
+
input_ids: torch.LongTensor,
|
| 1416 |
+
past_key_values: Optional[Cache] = None,
|
| 1417 |
+
attention_mask: Optional[torch.LongTensor] = None,
|
| 1418 |
+
inputs_embeds: Optional[torch.FloatTensor] = None,
|
| 1419 |
+
cache_position: Optional[torch.LongTensor] = None,
|
| 1420 |
+
position_ids: Optional[torch.LongTensor] = None,
|
| 1421 |
+
**kwargs,
|
| 1422 |
+
):
|
| 1423 |
+
use_summary = getattr(self.config, "use_summary_attention", False)
|
| 1424 |
+
|
| 1425 |
+
# If not using summary attention, use standard behavior
|
| 1426 |
+
if not use_summary:
|
| 1427 |
+
return super().prepare_inputs_for_generation(
|
| 1428 |
+
input_ids=input_ids,
|
| 1429 |
+
past_key_values=past_key_values,
|
| 1430 |
+
attention_mask=attention_mask,
|
| 1431 |
+
inputs_embeds=inputs_embeds,
|
| 1432 |
+
cache_position=cache_position,
|
| 1433 |
+
position_ids=position_ids,
|
| 1434 |
+
**kwargs,
|
| 1435 |
+
)
|
| 1436 |
+
|
| 1437 |
+
# For summary attention: handle cache-based input slicing
|
| 1438 |
+
summary_chunk_size = getattr(self.config, "summary_chunk_size", 0)
|
| 1439 |
+
summary_token_num = getattr(self.config, "summary_token_num", 0)
|
| 1440 |
+
summary_token_begin = getattr(self.config, "summary_token_begin", 0)
|
| 1441 |
+
|
| 1442 |
+
# Prefill phase: pass full sequence, forward() will handle summary token insertion
|
| 1443 |
+
if past_key_values is None or past_key_values.get_seq_length() == 0:
|
| 1444 |
+
if cache_position is None:
|
| 1445 |
+
cache_position = torch.arange(0, input_ids.shape[1], device=input_ids.device)
|
| 1446 |
+
|
| 1447 |
+
return {
|
| 1448 |
+
"input_ids": input_ids,
|
| 1449 |
+
"attention_mask": attention_mask,
|
| 1450 |
+
"position_ids": position_ids,
|
| 1451 |
+
"past_key_values": past_key_values,
|
| 1452 |
+
"cache_position": cache_position,
|
| 1453 |
+
"use_cache": kwargs.get("use_cache"),
|
| 1454 |
+
}
|
| 1455 |
+
|
| 1456 |
+
# Decode phase: only pass new tokens not in cache
|
| 1457 |
+
# Get current chunk size (number of text tokens in current chunk)
|
| 1458 |
+
cur_chunk = past_key_values.get_cur_chunk_size() if hasattr(past_key_values, "get_cur_chunk_size") else 0
|
| 1459 |
+
true_token_num = past_key_values.get_true_token_num()
|
| 1460 |
+
|
| 1461 |
+
# Only take the new tokens that haven't been processed
|
| 1462 |
+
if input_ids.shape[1] > 1:
|
| 1463 |
+
# Slice to get only new tokens
|
| 1464 |
+
new_token_count = input_ids.shape[1] - true_token_num
|
| 1465 |
+
assert new_token_count > 0, f'new_token_count={new_token_count} should be greater than 0'
|
| 1466 |
+
input_ids = input_ids[:, -new_token_count:]
|
| 1467 |
+
device = input_ids.device
|
| 1468 |
+
# Check if we need to insert summary tokens
|
| 1469 |
+
# If cur_chunk >= summary_chunk_size, we need to generate summary tokens
|
| 1470 |
+
if cur_chunk == summary_chunk_size - 1:
|
| 1471 |
+
# Insert summary tokens
|
| 1472 |
+
batch_size = input_ids.shape[0]
|
| 1473 |
+
summary_ids = (
|
| 1474 |
+
torch.arange(summary_token_num, device=device, dtype=input_ids.dtype)
|
| 1475 |
+
+ summary_token_begin
|
| 1476 |
+
).unsqueeze(0).repeat(batch_size, 1)
|
| 1477 |
+
|
| 1478 |
+
# Concatenate: [text_token, summary_tokens]
|
| 1479 |
+
input_ids = torch.cat([input_ids, summary_ids], dim=1)
|
| 1480 |
+
|
| 1481 |
+
# Position IDs: text token uses cur_chunk, summary tokens use 0
|
| 1482 |
+
if self.config.summary_chunk_position_ids_type == 'origin':
|
| 1483 |
+
text_pos = torch.full((batch_size, 1), past_key_values.get_true_token_num(), device=device, dtype=torch.long)
|
| 1484 |
+
elif self.config.summary_chunk_position_ids_type == 'inner_chunk':
|
| 1485 |
+
text_pos = torch.full((batch_size, 1), cur_chunk, device=device, dtype=torch.long)
|
| 1486 |
+
else:
|
| 1487 |
+
raise ValueError(f'Check config.summary_chunk_position_ids_type: {self.config.summary_chunk_position_ids_type}')
|
| 1488 |
+
|
| 1489 |
+
if self.config.summary_token_position_ids_type == 'zeros':
|
| 1490 |
+
summary_pos = torch.zeros((batch_size, summary_token_num), device=device, dtype=torch.long)
|
| 1491 |
+
elif self.config.summary_token_position_ids_type == 'last_chunk_slice_left':
|
| 1492 |
+
# 等分成 summary_num 份,每个 summary token 取对应 slice 的末尾
|
| 1493 |
+
prev_text_end = past_key_values.get_true_token_num()+1-summary_chunk_size
|
| 1494 |
+
cur_text_end = past_key_values.get_true_token_num()+1
|
| 1495 |
+
chunk_len = cur_text_end - prev_text_end
|
| 1496 |
+
|
| 1497 |
+
idx = torch.arange(0, summary_token_num, device=device, dtype=torch.long,)
|
| 1498 |
+
|
| 1499 |
+
# 每一份的末尾(全局 position)
|
| 1500 |
+
slice_ends = prev_text_end + (idx * chunk_len) // summary_token_num - 1
|
| 1501 |
+
slice_ends = slice_ends.clamp(min=prev_text_end)
|
| 1502 |
+
|
| 1503 |
+
summary_pos = slice_ends.to(dtype=torch.long, device=device).unsqueeze(0)
|
| 1504 |
+
elif self.config.summary_token_position_ids_type == 'last_chunk_slice_right':
|
| 1505 |
+
# 等分成 summary_num 份,每个 summary token 取对应 slice 的末尾
|
| 1506 |
+
prev_text_end = past_key_values.get_true_token_num()+1-summary_chunk_size
|
| 1507 |
+
cur_text_end = past_key_values.get_true_token_num()+1
|
| 1508 |
+
chunk_len = cur_text_end - prev_text_end
|
| 1509 |
+
|
| 1510 |
+
idx = torch.arange(1, summary_token_num + 1, device=device, dtype=torch.long,)
|
| 1511 |
+
|
| 1512 |
+
# 每一份的末尾(全局 position)
|
| 1513 |
+
slice_ends = prev_text_end + (idx * chunk_len) // summary_token_num - 1
|
| 1514 |
+
slice_ends = slice_ends.clamp(min=prev_text_end)
|
| 1515 |
+
|
| 1516 |
+
summary_pos = slice_ends.to(dtype=torch.long, device=device).unsqueeze(0)
|
| 1517 |
+
|
| 1518 |
+
else:
|
| 1519 |
+
raise ValueError('')
|
| 1520 |
+
|
| 1521 |
+
position_ids = torch.cat([text_pos, summary_pos], dim=1)
|
| 1522 |
+
else:
|
| 1523 |
+
# Normal decode: just the new text token with position = cur_chunk
|
| 1524 |
+
if position_ids is None:
|
| 1525 |
+
batch_size = input_ids.shape[0]
|
| 1526 |
+
if self.config.summary_chunk_position_ids_type == 'origin':
|
| 1527 |
+
position_ids = torch.full((batch_size, input_ids.shape[1]), past_key_values.get_true_token_num(), device=input_ids.device, dtype=torch.long)
|
| 1528 |
+
elif self.config.summary_chunk_position_ids_type == 'inner_chunk':
|
| 1529 |
+
position_ids = torch.full((batch_size, input_ids.shape[1]), cur_chunk, device=input_ids.device, dtype=torch.long)
|
| 1530 |
+
else:
|
| 1531 |
+
raise ValueError(f'Check config.summary_chunk_position_ids_type: {self.config.summary_chunk_position_ids_type}')
|
| 1532 |
+
return {
|
| 1533 |
+
"input_ids": input_ids,
|
| 1534 |
+
"attention_mask": self._build_summary_attention_mask_for_generation(
|
| 1535 |
+
input_ids=input_ids,
|
| 1536 |
+
past_key_values=past_key_values,
|
| 1537 |
+
attention_mask=attention_mask,
|
| 1538 |
+
),
|
| 1539 |
+
"position_ids": position_ids,
|
| 1540 |
+
"past_key_values": past_key_values,
|
| 1541 |
+
"cache_position": cache_position,
|
| 1542 |
+
"use_cache": kwargs.get("use_cache"),
|
| 1543 |
+
}
|
| 1544 |
+
|
| 1545 |
+
|
| 1546 |
+
class Qwen3ForSequenceClassification(GenericForSequenceClassification, Qwen3PreTrainedModel):
|
| 1547 |
+
pass
|
| 1548 |
+
|
| 1549 |
+
|
| 1550 |
+
class Qwen3ForTokenClassification(GenericForTokenClassification, Qwen3PreTrainedModel):
|
| 1551 |
+
pass
|
| 1552 |
+
|
| 1553 |
+
|
| 1554 |
+
class Qwen3ForQuestionAnswering(GenericForQuestionAnswering, Qwen3PreTrainedModel):
|
| 1555 |
+
base_model_prefix = "transformer" # For BC, where `transformer` was used instead of `model`
|
| 1556 |
+
|
| 1557 |
+
|
| 1558 |
+
__all__ = [
|
| 1559 |
+
"Qwen3ForCausalLM",
|
| 1560 |
+
"Qwen3ForQuestionAnswering",
|
| 1561 |
+
"Qwen3PreTrainedModel",
|
| 1562 |
+
"Qwen3Model",
|
| 1563 |
+
"Qwen3ForSequenceClassification",
|
| 1564 |
+
"Qwen3ForTokenClassification",
|
| 1565 |
+
"Qwen3RingBufferCache",
|
| 1566 |
+
"Qwen3SummaryAttention",
|
| 1567 |
+
"SummaryBatchContext",
|
| 1568 |
+
"build_summary_context",
|
| 1569 |
+
"build_summary_sliding_context",
|
| 1570 |
+
]
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python 3.12. Install Torch from the appropriate CUDA wheel index.
|
| 2 |
+
torch==2.6.0
|
| 3 |
+
triton==3.2.0
|
| 4 |
+
transformers==5.0.0
|
| 5 |
+
accelerate==1.9.0
|
| 6 |
+
safetensors==0.7.0
|
| 7 |
+
einops==0.8.1
|
| 8 |
+
nvidia-cutlass-dsl==4.3.5
|
| 9 |
+
cuda-python==12.9.0
|
| 10 |
+
apache-tvm-ffi==0.1.13.post3
|
| 11 |
+
torch-c-dlpack-ext==0.1.4
|
| 12 |
+
quack-kernels==0.2.4
|