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 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))