flash-msa 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flash_msa/__init__.py +14 -0
- flash_msa/_flash_attn_compat.py +76 -0
- flash_msa/csrc/reverse_index_cuda.cu +211 -0
- flash_msa/flash_msa.py +214 -0
- flash_msa/msa_backward_cutedsl.py +1114 -0
- flash_msa/msa_forward_cutedsl.py +74 -0
- flash_msa/msa_select_cutedsl.py +885 -0
- flash_msa/reverse_index_cuda.py +169 -0
- flash_msa/selected_forward_cutedsl.py +612 -0
- flash_msa/tests/__init__.py +1 -0
- flash_msa/tests/testing_model.py +197 -0
- flash_msa/tests/testing_model_warmup.py +132 -0
- flash_msa/warmup/__init__.py +1 -0
- flash_msa/warmup/flash_msa_warmup.py +106 -0
- flash_msa/warmup/msa_backward_cutedsl_warmup.py +185 -0
- flash_msa/warmup/msa_forward_cutedsl_warmup.py +67 -0
- flash_msa-0.1.0.dist-info/METADATA +104 -0
- flash_msa-0.1.0.dist-info/RECORD +21 -0
- flash_msa-0.1.0.dist-info/WHEEL +5 -0
- flash_msa-0.1.0.dist-info/licenses/LICENSE +21 -0
- flash_msa-0.1.0.dist-info/top_level.txt +1 -0
flash_msa/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Public API for Flash-MSA."""
|
|
2
|
+
|
|
3
|
+
from flash_msa.flash_msa import sparse_attention as flash_msa_func
|
|
4
|
+
from flash_msa.warmup.flash_msa_warmup import sparse_attention_warmup
|
|
5
|
+
|
|
6
|
+
flash_msa_func_warmup = sparse_attention_warmup
|
|
7
|
+
flash_msa_warmup_func = sparse_attention_warmup
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"flash_msa_func",
|
|
11
|
+
"flash_msa_func_warmup",
|
|
12
|
+
"flash_msa_warmup_func",
|
|
13
|
+
"sparse_attention_warmup",
|
|
14
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
""" Patch to call the right varlen flash func depending on if FA2,3, or 4 was installed"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def flash_attn_varlen_forward(
|
|
9
|
+
*,
|
|
10
|
+
q: torch.Tensor,
|
|
11
|
+
k: torch.Tensor,
|
|
12
|
+
v: torch.Tensor,
|
|
13
|
+
cu_seqlens_q: torch.Tensor,
|
|
14
|
+
cu_seqlens_k: torch.Tensor,
|
|
15
|
+
max_seqlen_q: int,
|
|
16
|
+
max_seqlen_k: int,
|
|
17
|
+
softmax_scale: float,
|
|
18
|
+
causal: bool,
|
|
19
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
20
|
+
"""Return ``(out, lse)`` for either FlashAttention 2 or 3."""
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from flash_attn.flash_attn_interface import _flash_attn_varlen_forward
|
|
24
|
+
|
|
25
|
+
out, lse, *_ = _flash_attn_varlen_forward(
|
|
26
|
+
q=q,
|
|
27
|
+
k=k,
|
|
28
|
+
v=v,
|
|
29
|
+
cu_seqlens_q=cu_seqlens_q,
|
|
30
|
+
cu_seqlens_k=cu_seqlens_k,
|
|
31
|
+
max_seqlen_q=max_seqlen_q,
|
|
32
|
+
max_seqlen_k=max_seqlen_k,
|
|
33
|
+
softmax_scale=softmax_scale,
|
|
34
|
+
causal=causal,
|
|
35
|
+
dropout_p=0.0,
|
|
36
|
+
)
|
|
37
|
+
return out, lse
|
|
38
|
+
except ModuleNotFoundError as exc:
|
|
39
|
+
if exc.name not in {"flash_attn", "flash_attn.flash_attn_interface"}:
|
|
40
|
+
raise
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
from flash_attn_interface import flash_attn_varlen_func
|
|
44
|
+
|
|
45
|
+
out, lse = flash_attn_varlen_func(
|
|
46
|
+
q,
|
|
47
|
+
k,
|
|
48
|
+
v,
|
|
49
|
+
cu_seqlens_q,
|
|
50
|
+
cu_seqlens_k,
|
|
51
|
+
max_seqlen_q,
|
|
52
|
+
max_seqlen_k,
|
|
53
|
+
softmax_scale=softmax_scale,
|
|
54
|
+
causal=causal,
|
|
55
|
+
return_attn_probs=True,
|
|
56
|
+
)
|
|
57
|
+
return out, lse
|
|
58
|
+
except ModuleNotFoundError as exc:
|
|
59
|
+
if exc.name != "flash_attn_interface":
|
|
60
|
+
raise
|
|
61
|
+
|
|
62
|
+
from flash_attn.cute.interface import flash_attn_varlen_func
|
|
63
|
+
|
|
64
|
+
out, lse = flash_attn_varlen_func(
|
|
65
|
+
q,
|
|
66
|
+
k,
|
|
67
|
+
v,
|
|
68
|
+
cu_seqlens_q=cu_seqlens_q,
|
|
69
|
+
cu_seqlens_k=cu_seqlens_k,
|
|
70
|
+
max_seqlen_q=max_seqlen_q,
|
|
71
|
+
max_seqlen_k=max_seqlen_k,
|
|
72
|
+
softmax_scale=softmax_scale,
|
|
73
|
+
causal=causal,
|
|
74
|
+
return_lse=True,
|
|
75
|
+
)
|
|
76
|
+
return out, lse
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#include <torch/extension.h>
|
|
2
|
+
#include <ATen/cuda/CUDAContext.h>
|
|
3
|
+
#include <c10/cuda/CUDAException.h>
|
|
4
|
+
#include <cuda.h>
|
|
5
|
+
#include <cuda_runtime.h>
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <cstdint>
|
|
9
|
+
|
|
10
|
+
#define CHECK_CUDA(x) TORCH_CHECK((x).is_cuda(), #x " must be CUDA")
|
|
11
|
+
#define CHECK_CONTIGUOUS(x) TORCH_CHECK((x).is_contiguous(), #x " must be contiguous")
|
|
12
|
+
#define CHECK_INT32(x) TORCH_CHECK((x).scalar_type() == at::kInt, #x " must be int32")
|
|
13
|
+
#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x); CHECK_INT32(x)
|
|
14
|
+
|
|
15
|
+
namespace {
|
|
16
|
+
|
|
17
|
+
constexpr int kThreads = 256;
|
|
18
|
+
|
|
19
|
+
__global__ void count_edges_kernel(
|
|
20
|
+
int const* __restrict__ block_indices,
|
|
21
|
+
int* __restrict__ counts,
|
|
22
|
+
int B,
|
|
23
|
+
int Hp,
|
|
24
|
+
int S,
|
|
25
|
+
int Kb,
|
|
26
|
+
int NB)
|
|
27
|
+
{
|
|
28
|
+
int64_t E = (int64_t)B * Hp * S * Kb;
|
|
29
|
+
int64_t stride = (int64_t)blockDim.x * gridDim.x;
|
|
30
|
+
for (int64_t e = (int64_t)blockIdx.x * blockDim.x + threadIdx.x; e < E; e += stride) {
|
|
31
|
+
int slot = (int)(e % Kb);
|
|
32
|
+
int64_t tmp = e / Kb;
|
|
33
|
+
int q = (int)(tmp % S);
|
|
34
|
+
tmp /= S;
|
|
35
|
+
int p = (int)(tmp % Hp);
|
|
36
|
+
int b = (int)(tmp / Hp);
|
|
37
|
+
|
|
38
|
+
int key_block = block_indices[((int64_t)(b * Hp + p) * S + q) * Kb + slot];
|
|
39
|
+
if ((unsigned)key_block >= (unsigned)NB) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
int bucket = (b * Hp + p) * NB + key_block;
|
|
43
|
+
atomicAdd(counts + bucket, 1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
__global__ void scan_fill_meta_kernel(
|
|
48
|
+
int const* __restrict__ counts,
|
|
49
|
+
int* __restrict__ bucket_offsets,
|
|
50
|
+
int* __restrict__ task_meta,
|
|
51
|
+
int B,
|
|
52
|
+
int Hp,
|
|
53
|
+
int NB,
|
|
54
|
+
int query_chunk,
|
|
55
|
+
int padded_tasks)
|
|
56
|
+
{
|
|
57
|
+
if (blockIdx.x != 0 || threadIdx.x != 0) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
int task = 0;
|
|
62
|
+
int buckets = B * Hp * NB;
|
|
63
|
+
for (int bucket = 0; bucket < buckets; ++bucket) {
|
|
64
|
+
bucket_offsets[bucket] = task;
|
|
65
|
+
int count = counts[bucket];
|
|
66
|
+
int chunks = (count + query_chunk - 1) / query_chunk;
|
|
67
|
+
int key_block = bucket % NB;
|
|
68
|
+
int tmp = bucket / NB;
|
|
69
|
+
int proxy_head = tmp % Hp;
|
|
70
|
+
int batch = tmp / Hp;
|
|
71
|
+
|
|
72
|
+
for (int c = 0; c < chunks && task < padded_tasks; ++c) {
|
|
73
|
+
int valid = count - c * query_chunk;
|
|
74
|
+
valid = valid > query_chunk ? query_chunk : valid;
|
|
75
|
+
int64_t row = (int64_t)task * 4;
|
|
76
|
+
task_meta[row + 0] = batch;
|
|
77
|
+
task_meta[row + 1] = proxy_head;
|
|
78
|
+
task_meta[row + 2] = key_block;
|
|
79
|
+
task_meta[row + 3] = valid;
|
|
80
|
+
++task;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
__global__ void scatter_edges_kernel(
|
|
86
|
+
int const* __restrict__ block_indices,
|
|
87
|
+
int const* __restrict__ bucket_offsets,
|
|
88
|
+
int* __restrict__ write_counts,
|
|
89
|
+
int* __restrict__ task_qids,
|
|
90
|
+
int B,
|
|
91
|
+
int Hp,
|
|
92
|
+
int S,
|
|
93
|
+
int Kb,
|
|
94
|
+
int NB,
|
|
95
|
+
int query_chunk,
|
|
96
|
+
int padded_tasks)
|
|
97
|
+
{
|
|
98
|
+
int64_t E = (int64_t)B * Hp * S * Kb;
|
|
99
|
+
int64_t stride = (int64_t)blockDim.x * gridDim.x;
|
|
100
|
+
for (int64_t e = (int64_t)blockIdx.x * blockDim.x + threadIdx.x; e < E; e += stride) {
|
|
101
|
+
int slot = (int)(e % Kb);
|
|
102
|
+
int64_t tmp = e / Kb;
|
|
103
|
+
int q = (int)(tmp % S);
|
|
104
|
+
tmp /= S;
|
|
105
|
+
int p = (int)(tmp % Hp);
|
|
106
|
+
int b = (int)(tmp / Hp);
|
|
107
|
+
|
|
108
|
+
int key_block = block_indices[((int64_t)(b * Hp + p) * S + q) * Kb + slot];
|
|
109
|
+
if ((unsigned)key_block >= (unsigned)NB) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
int bucket = (b * Hp + p) * NB + key_block;
|
|
114
|
+
int local = atomicAdd(write_counts + bucket, 1);
|
|
115
|
+
int task = bucket_offsets[bucket] + local / query_chunk;
|
|
116
|
+
if (task >= padded_tasks) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
int lane = local - (local / query_chunk) * query_chunk;
|
|
120
|
+
task_qids[(int64_t)task * query_chunk + lane] = q;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
} // namespace
|
|
125
|
+
|
|
126
|
+
void run_build_reverse_index(
|
|
127
|
+
torch::Tensor block_indices,
|
|
128
|
+
torch::Tensor counts,
|
|
129
|
+
torch::Tensor write_counts,
|
|
130
|
+
torch::Tensor bucket_offsets,
|
|
131
|
+
torch::Tensor task_meta,
|
|
132
|
+
torch::Tensor task_qids,
|
|
133
|
+
int64_t block_size,
|
|
134
|
+
int64_t query_chunk)
|
|
135
|
+
{
|
|
136
|
+
CHECK_INPUT(block_indices);
|
|
137
|
+
CHECK_INPUT(counts);
|
|
138
|
+
CHECK_INPUT(write_counts);
|
|
139
|
+
CHECK_INPUT(bucket_offsets);
|
|
140
|
+
CHECK_INPUT(task_meta);
|
|
141
|
+
CHECK_INPUT(task_qids);
|
|
142
|
+
|
|
143
|
+
TORCH_CHECK(block_indices.dim() == 4, "block_indices must have shape [B, Hp, S, Kb]");
|
|
144
|
+
TORCH_CHECK(task_meta.dim() == 2 && task_meta.size(1) == 4, "task_meta must have shape [T, 4]");
|
|
145
|
+
TORCH_CHECK(task_qids.dim() == 2 && task_qids.size(1) == query_chunk, "task_qids must have shape [T, query_chunk]");
|
|
146
|
+
TORCH_CHECK(block_size > 0 && query_chunk > 0, "block_size and query_chunk must be positive");
|
|
147
|
+
|
|
148
|
+
int B = (int)block_indices.size(0);
|
|
149
|
+
int Hp = (int)block_indices.size(1);
|
|
150
|
+
int S = (int)block_indices.size(2);
|
|
151
|
+
int Kb = (int)block_indices.size(3);
|
|
152
|
+
int NB = S / (int)block_size;
|
|
153
|
+
int buckets = B * Hp * NB;
|
|
154
|
+
int padded_tasks = (int)task_meta.size(0);
|
|
155
|
+
|
|
156
|
+
TORCH_CHECK(S % block_size == 0, "S must be divisible by block_size");
|
|
157
|
+
TORCH_CHECK(counts.numel() == buckets, "counts has wrong size");
|
|
158
|
+
TORCH_CHECK(write_counts.numel() == buckets, "write_counts has wrong size");
|
|
159
|
+
TORCH_CHECK(bucket_offsets.numel() == buckets, "bucket_offsets has wrong size");
|
|
160
|
+
TORCH_CHECK(task_qids.size(0) == padded_tasks, "task_meta/task_qids row mismatch");
|
|
161
|
+
|
|
162
|
+
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
|
|
163
|
+
|
|
164
|
+
C10_CUDA_CHECK(cudaMemsetAsync(counts.data_ptr<int>(), 0, counts.numel() * sizeof(int), stream));
|
|
165
|
+
C10_CUDA_CHECK(cudaMemsetAsync(write_counts.data_ptr<int>(), 0, write_counts.numel() * sizeof(int), stream));
|
|
166
|
+
C10_CUDA_CHECK(cudaMemsetAsync(task_meta.data_ptr<int>(), 0, task_meta.numel() * sizeof(int), stream));
|
|
167
|
+
C10_CUDA_CHECK(cudaMemsetAsync(task_qids.data_ptr<int>(), 0xff, task_qids.numel() * sizeof(int), stream));
|
|
168
|
+
|
|
169
|
+
int64_t edges = (int64_t)B * Hp * S * Kb;
|
|
170
|
+
int blocks = (int)std::min<int64_t>((edges + kThreads - 1) / kThreads, 65535);
|
|
171
|
+
blocks = std::max(blocks, 1);
|
|
172
|
+
|
|
173
|
+
count_edges_kernel<<<blocks, kThreads, 0, stream>>>(
|
|
174
|
+
block_indices.data_ptr<int>(),
|
|
175
|
+
counts.data_ptr<int>(),
|
|
176
|
+
B,
|
|
177
|
+
Hp,
|
|
178
|
+
S,
|
|
179
|
+
Kb,
|
|
180
|
+
NB);
|
|
181
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
182
|
+
|
|
183
|
+
scan_fill_meta_kernel<<<1, 1, 0, stream>>>(
|
|
184
|
+
counts.data_ptr<int>(),
|
|
185
|
+
bucket_offsets.data_ptr<int>(),
|
|
186
|
+
task_meta.data_ptr<int>(),
|
|
187
|
+
B,
|
|
188
|
+
Hp,
|
|
189
|
+
NB,
|
|
190
|
+
(int)query_chunk,
|
|
191
|
+
padded_tasks);
|
|
192
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
193
|
+
|
|
194
|
+
scatter_edges_kernel<<<blocks, kThreads, 0, stream>>>(
|
|
195
|
+
block_indices.data_ptr<int>(),
|
|
196
|
+
bucket_offsets.data_ptr<int>(),
|
|
197
|
+
write_counts.data_ptr<int>(),
|
|
198
|
+
task_qids.data_ptr<int>(),
|
|
199
|
+
B,
|
|
200
|
+
Hp,
|
|
201
|
+
S,
|
|
202
|
+
Kb,
|
|
203
|
+
NB,
|
|
204
|
+
(int)query_chunk,
|
|
205
|
+
padded_tasks);
|
|
206
|
+
C10_CUDA_KERNEL_LAUNCH_CHECK();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
|
210
|
+
m.def("run_build_reverse_index", &run_build_reverse_index, "Build MSA reverse index on CUDA");
|
|
211
|
+
}
|
flash_msa/flash_msa.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Block-sparse top-k GQA attention training entrypoint.
|
|
2
|
+
|
|
3
|
+
This module owns the Python boundary for the native fused MSA kernels.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
from flash_msa.msa_select_cutedsl import compute_proxy_lse, select_blocks
|
|
9
|
+
from flash_msa.msa_backward_cutedsl import run_fused_backward
|
|
10
|
+
from flash_msa.msa_forward_cutedsl import run_main_forward
|
|
11
|
+
|
|
12
|
+
BLOCK_SIZE = 128
|
|
13
|
+
NATIVE_MMA_ROWS_PER_TASK = 128
|
|
14
|
+
|
|
15
|
+
def _validate_inputs(
|
|
16
|
+
q_proxy: torch.Tensor,
|
|
17
|
+
k_proxy: torch.Tensor,
|
|
18
|
+
q: torch.Tensor,
|
|
19
|
+
k: torch.Tensor,
|
|
20
|
+
v: torch.Tensor,
|
|
21
|
+
top_k: int,
|
|
22
|
+
) -> tuple[int, int]:
|
|
23
|
+
if q_proxy.ndim != 4 or k_proxy.ndim != 4 or q.ndim != 4 or k.ndim != 4 or v.ndim != 4:
|
|
24
|
+
raise ValueError("all attention tensors must have shape (B, H, S, D)")
|
|
25
|
+
|
|
26
|
+
b, n_proxy_heads, s, head_dim = q_proxy.shape
|
|
27
|
+
bk, n_proxy_kv_heads, sk, dk = k_proxy.shape
|
|
28
|
+
bq, n_heads, sq, dq = q.shape
|
|
29
|
+
bm, n_kv_heads, sm, dm = k.shape
|
|
30
|
+
bv, n_v_heads, sv, dv = v.shape
|
|
31
|
+
|
|
32
|
+
if not (
|
|
33
|
+
b == bk == bq == bm == bv
|
|
34
|
+
and s == sk == sq == sm == sv
|
|
35
|
+
and head_dim == dk == dq == dm == dv
|
|
36
|
+
and n_kv_heads == n_v_heads
|
|
37
|
+
):
|
|
38
|
+
raise ValueError("q_proxy, k_proxy, q, k, and v must agree on batch, sequence, head dimension, and KV head count")
|
|
39
|
+
|
|
40
|
+
if s % BLOCK_SIZE != 0:
|
|
41
|
+
raise ValueError(f"sequence length must be divisible by {BLOCK_SIZE}, got {s}")
|
|
42
|
+
if top_k % BLOCK_SIZE != 0:
|
|
43
|
+
raise ValueError(f"top_k must be divisible by {BLOCK_SIZE}, got {top_k}")
|
|
44
|
+
if n_proxy_heads % n_proxy_kv_heads != 0:
|
|
45
|
+
raise ValueError("n_proxy_heads must be divisible by n_proxy_kv_heads")
|
|
46
|
+
if n_heads % n_kv_heads != 0:
|
|
47
|
+
raise ValueError("n_heads must be divisible by n_kv_heads")
|
|
48
|
+
if n_heads % n_proxy_heads != 0:
|
|
49
|
+
raise ValueError("n_heads must be divisible by n_proxy_heads")
|
|
50
|
+
if q_proxy.shape[-1] != 128:
|
|
51
|
+
raise NotImplementedError("Headdim must be 128")
|
|
52
|
+
|
|
53
|
+
num_blocks = s // BLOCK_SIZE
|
|
54
|
+
top_k_blocks = int(top_k) // BLOCK_SIZE
|
|
55
|
+
if not 1 <= top_k_blocks <= num_blocks:
|
|
56
|
+
raise ValueError(f"top_k selects {top_k_blocks} blocks, but sequence has {num_blocks} blocks")
|
|
57
|
+
if top_k_blocks > 32:
|
|
58
|
+
raise NotImplementedError("No more than 32 blocks / topk=4096 supported")
|
|
59
|
+
|
|
60
|
+
return num_blocks, top_k_blocks
|
|
61
|
+
|
|
62
|
+
def _run_fused_selected_edge_backward(
|
|
63
|
+
q_proxy: torch.Tensor,
|
|
64
|
+
k_proxy: torch.Tensor,
|
|
65
|
+
q: torch.Tensor,
|
|
66
|
+
k: torch.Tensor,
|
|
67
|
+
v: torch.Tensor,
|
|
68
|
+
block_indices: torch.Tensor,
|
|
69
|
+
lse_main: torch.Tensor,
|
|
70
|
+
o_main: torch.Tensor,
|
|
71
|
+
grad_o_main: torch.Tensor,
|
|
72
|
+
grad_kl: torch.Tensor | None,
|
|
73
|
+
*,
|
|
74
|
+
scale: float,
|
|
75
|
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
76
|
+
"""Run the native reverse-index fused selected-edge backward."""
|
|
77
|
+
|
|
78
|
+
bsz, n_heads, seq_len, head_dim = q.shape
|
|
79
|
+
n_proxy_heads = q_proxy.shape[1]
|
|
80
|
+
n_kv_heads = k.shape[1]
|
|
81
|
+
|
|
82
|
+
_main_per_proxy = int(n_heads) // int(n_proxy_heads)
|
|
83
|
+
query_chunk = NATIVE_MMA_ROWS_PER_TASK // _main_per_proxy
|
|
84
|
+
|
|
85
|
+
if NATIVE_MMA_ROWS_PER_TASK % _main_per_proxy != 0:
|
|
86
|
+
raise ValueError("Main q heads / proxy q heads ratio must divide NATIVE_MMA_ROWS_PER_TASK evenly, edit NATIVE_MMA_ROWS_PER_TASK to fix")
|
|
87
|
+
|
|
88
|
+
lse_proxy = compute_proxy_lse(
|
|
89
|
+
q_proxy,
|
|
90
|
+
k_proxy,
|
|
91
|
+
block_indices,
|
|
92
|
+
scale=float(scale),
|
|
93
|
+
)
|
|
94
|
+
from flash_msa.reverse_index_cuda import build_reverse_index_cuda
|
|
95
|
+
|
|
96
|
+
task_meta, task_qids = build_reverse_index_cuda(
|
|
97
|
+
block_indices,
|
|
98
|
+
query_chunk=query_chunk,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
delta_main = (o_main.float() * grad_o_main.float()).sum(dim=-1)
|
|
102
|
+
grad_kl_scale_value = 0.0
|
|
103
|
+
if grad_kl is not None:
|
|
104
|
+
grad_kl_scale_value = float(grad_kl.detach().float().item()) / float(
|
|
105
|
+
bsz * n_proxy_heads * seq_len
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return run_fused_backward(
|
|
109
|
+
q_proxy,
|
|
110
|
+
k_proxy,
|
|
111
|
+
q,
|
|
112
|
+
k,
|
|
113
|
+
v,
|
|
114
|
+
grad_o_main,
|
|
115
|
+
lse_main,
|
|
116
|
+
lse_proxy,
|
|
117
|
+
delta_main,
|
|
118
|
+
task_meta,
|
|
119
|
+
task_qids,
|
|
120
|
+
scale=float(scale),
|
|
121
|
+
grad_kl_scale=grad_kl_scale_value,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class _SparseAttentionFunction(torch.autograd.Function):
|
|
126
|
+
@staticmethod
|
|
127
|
+
def forward(
|
|
128
|
+
ctx,
|
|
129
|
+
q_proxy: torch.Tensor,
|
|
130
|
+
k_proxy: torch.Tensor,
|
|
131
|
+
q: torch.Tensor,
|
|
132
|
+
k: torch.Tensor,
|
|
133
|
+
v: torch.Tensor,
|
|
134
|
+
top_k: int,
|
|
135
|
+
scale: float,
|
|
136
|
+
):
|
|
137
|
+
b, n_proxy_heads, s, head_dim = q_proxy.shape
|
|
138
|
+
n_heads = q.shape[1]
|
|
139
|
+
n_kv_heads = k.shape[1]
|
|
140
|
+
num_blocks, top_k_blocks = _validate_inputs(q_proxy, k_proxy, q, k, v, int(top_k))
|
|
141
|
+
block_indices = select_blocks(
|
|
142
|
+
q_proxy,
|
|
143
|
+
k_proxy,
|
|
144
|
+
scale=float(scale),
|
|
145
|
+
num_blocks=num_blocks,
|
|
146
|
+
top_k_blocks=top_k_blocks,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
o_main, lse_main, kl_loss = run_main_forward(
|
|
150
|
+
q,
|
|
151
|
+
k,
|
|
152
|
+
v,
|
|
153
|
+
block_indices,
|
|
154
|
+
scale=float(scale),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
out = o_main.transpose(1, 2).reshape(b, s, -1)
|
|
158
|
+
|
|
159
|
+
save_tensors = (
|
|
160
|
+
q_proxy,
|
|
161
|
+
k_proxy,
|
|
162
|
+
q,
|
|
163
|
+
k,
|
|
164
|
+
v,
|
|
165
|
+
block_indices,
|
|
166
|
+
lse_main,
|
|
167
|
+
o_main,
|
|
168
|
+
)
|
|
169
|
+
ctx.save_for_backward(*save_tensors)
|
|
170
|
+
ctx.scale = float(scale)
|
|
171
|
+
return out, kl_loss
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def backward(ctx, grad_out: torch.Tensor | None, grad_kl: torch.Tensor | None):
|
|
175
|
+
q_proxy, k_proxy, q, k, v, block_indices, lse_main, o_main = ctx.saved_tensors
|
|
176
|
+
|
|
177
|
+
if grad_out is None:
|
|
178
|
+
grad_o_main = torch.zeros_like(o_main)
|
|
179
|
+
else:
|
|
180
|
+
b, s, _ = grad_out.shape
|
|
181
|
+
grad_o_main = (
|
|
182
|
+
grad_out.reshape(b, s, q.shape[1], q.shape[3]).transpose(1, 2).contiguous()
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
dq_proxy, dk_proxy, dq, dk, dv = _run_fused_selected_edge_backward(
|
|
186
|
+
q_proxy,
|
|
187
|
+
k_proxy,
|
|
188
|
+
q,
|
|
189
|
+
k,
|
|
190
|
+
v,
|
|
191
|
+
block_indices,
|
|
192
|
+
lse_main,
|
|
193
|
+
o_main,
|
|
194
|
+
grad_o_main,
|
|
195
|
+
grad_kl,
|
|
196
|
+
scale=ctx.scale,
|
|
197
|
+
)
|
|
198
|
+
return dq_proxy, dk_proxy, dq, dk, dv, None, None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def sparse_attention(
|
|
202
|
+
q_proxy: torch.Tensor,
|
|
203
|
+
k_proxy: torch.Tensor,
|
|
204
|
+
q: torch.Tensor,
|
|
205
|
+
k: torch.Tensor,
|
|
206
|
+
v: torch.Tensor,
|
|
207
|
+
top_k: int,
|
|
208
|
+
scale: float,
|
|
209
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
210
|
+
"""
|
|
211
|
+
Return (attn_out, kl_loss placeholder)
|
|
212
|
+
Saves Block indices and main attn LSE into HBM for the backward
|
|
213
|
+
"""
|
|
214
|
+
return _SparseAttentionFunction.apply(q_proxy, k_proxy, q, k, v, int(top_k), float(scale))
|