flashmap 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.
- flashmap/__init__.py +14 -0
- flashmap/configuration_flashmap.py +105 -0
- flashmap/modeling_flashmap.py +556 -0
- flashmap/sglang/__init__.py +78 -0
- flashmap/sglang/jump_bank.py +203 -0
- flashmap/sglang/kernels.py +457 -0
- flashmap/sglang/model.py +1076 -0
- flashmap/sglang/sampler.py +188 -0
- flashmap/sglang/tree.py +78 -0
- flashmap/sglang/tree_worker.py +396 -0
- flashmap/sglang/verify_attention.py +474 -0
- flashmap/vllm/__init__.py +268 -0
- flashmap/vllm/fold.py +128 -0
- flashmap/vllm/kernels.py +443 -0
- flashmap/vllm/model.py +881 -0
- flashmap/vllm/speculator.py +235 -0
- flashmap-0.1.0.dist-info/METADATA +152 -0
- flashmap-0.1.0.dist-info/RECORD +22 -0
- flashmap-0.1.0.dist-info/WHEEL +5 -0
- flashmap-0.1.0.dist-info/entry_points.txt +5 -0
- flashmap-0.1.0.dist-info/licenses/LICENSE +21 -0
- flashmap-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""Split-KV attention for speculative verification on sglang's Triton backend.
|
|
3
|
+
|
|
4
|
+
A verify step attends L query tokens per request (a draft chain or tree plus
|
|
5
|
+
its root) to the request's cached prefix and to each other. sglang's Triton
|
|
6
|
+
backend runs it as a prefill (extend_attention_fwd): one program per (request,
|
|
7
|
+
query head) walks the whole prefix serially, its 128-row query tile holding
|
|
8
|
+
only L = 17 real rows. At batch size 1 that is 32 programs on a 132-SM GPU, and
|
|
9
|
+
the time grows with the context; a tree's custom mask costs a further ~27%.
|
|
10
|
+
|
|
11
|
+
Here one program per (request, KV head, row block, split) holds all G query
|
|
12
|
+
heads that share the KV head (G * L rows, in blocks of up to 128 for wide
|
|
13
|
+
trees), so each prefix key and value is read once per KV head (and row block)
|
|
14
|
+
rather than once per query head, and the prefix is split across programs
|
|
15
|
+
(as flash-decoding does) when the batch alone cannot fill the GPU. One more
|
|
16
|
+
split per (request, KV head) attends the L tokens to each other, under the
|
|
17
|
+
tree's mask (sglang's layout: L rows of prefix + L columns per request, the
|
|
18
|
+
prefix all visible; or, with ``qlen_mask``, each request's L x L block alone,
|
|
19
|
+
EAGLE's QLEN_ONLY layout, which skips writing the prefix columns) or causally.
|
|
20
|
+
A second kernel merges the splits by their
|
|
21
|
+
log-sum-exp. Only the shapes are read on the host, so it is graph-capturable.
|
|
22
|
+
|
|
23
|
+
``SplitKVVerify`` takes extend_attention_fwd's arguments and returns False,
|
|
24
|
+
having done nothing, for a case it does not handle (the caller then runs
|
|
25
|
+
extend_attention_fwd); tree_worker.py installs it.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
|
|
30
|
+
import torch
|
|
31
|
+
import triton
|
|
32
|
+
import triton.language as tl
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@triton.jit
|
|
36
|
+
def _verify_partial_kernel(
|
|
37
|
+
q_ptr,
|
|
38
|
+
k_ext_ptr,
|
|
39
|
+
v_ext_ptr,
|
|
40
|
+
k_buf_ptr,
|
|
41
|
+
v_buf_ptr,
|
|
42
|
+
kv_indptr,
|
|
43
|
+
kv_indices,
|
|
44
|
+
mask_ptr,
|
|
45
|
+
mask_indptr,
|
|
46
|
+
part_out_ptr,
|
|
47
|
+
part_lse_ptr,
|
|
48
|
+
sm_scale,
|
|
49
|
+
k_scale,
|
|
50
|
+
v_scale,
|
|
51
|
+
stride_qt,
|
|
52
|
+
stride_qh,
|
|
53
|
+
stride_kt,
|
|
54
|
+
stride_kh,
|
|
55
|
+
stride_vt,
|
|
56
|
+
stride_vh,
|
|
57
|
+
stride_kbt,
|
|
58
|
+
stride_kbh,
|
|
59
|
+
stride_vbt,
|
|
60
|
+
stride_vbh,
|
|
61
|
+
H_KV: tl.constexpr,
|
|
62
|
+
G: tl.constexpr,
|
|
63
|
+
L: tl.constexpr,
|
|
64
|
+
S: tl.constexpr,
|
|
65
|
+
D: tl.constexpr,
|
|
66
|
+
DV: tl.constexpr,
|
|
67
|
+
BLOCK_D: tl.constexpr,
|
|
68
|
+
BLOCK_DV: tl.constexpr,
|
|
69
|
+
M: tl.constexpr,
|
|
70
|
+
RB: tl.constexpr,
|
|
71
|
+
BLOCK_L: tl.constexpr,
|
|
72
|
+
BLOCK_N: tl.constexpr,
|
|
73
|
+
MIN_CHUNK: tl.constexpr,
|
|
74
|
+
USE_MASK: tl.constexpr,
|
|
75
|
+
QLEN_MASK: tl.constexpr,
|
|
76
|
+
):
|
|
77
|
+
# Program (b, h * RB + rb, s): request b, KV head h, rows rb * M .. + M;
|
|
78
|
+
# splits s < S cover the prefix, split S the L verify tokens. Row r is
|
|
79
|
+
# query head h * G + r // L at token r % L of the request.
|
|
80
|
+
b = tl.program_id(0)
|
|
81
|
+
h = tl.program_id(1) // RB
|
|
82
|
+
rb = tl.program_id(1) % RB
|
|
83
|
+
s = tl.program_id(2)
|
|
84
|
+
rows = rb * M + tl.arange(0, M)
|
|
85
|
+
row_ok = rows < G * L
|
|
86
|
+
g = rows // L
|
|
87
|
+
t = rows % L
|
|
88
|
+
d = tl.arange(0, BLOCK_D)
|
|
89
|
+
dv = tl.arange(0, BLOCK_DV)
|
|
90
|
+
q = tl.load(
|
|
91
|
+
q_ptr
|
|
92
|
+
+ (b * L + t)[:, None] * stride_qt
|
|
93
|
+
+ (h * G + g)[:, None] * stride_qh
|
|
94
|
+
+ d[None, :],
|
|
95
|
+
mask=row_ok[:, None] & (d[None, :] < D),
|
|
96
|
+
other=0.0,
|
|
97
|
+
)
|
|
98
|
+
kv_start = tl.load(kv_indptr + b)
|
|
99
|
+
prefix = tl.load(kv_indptr + b + 1) - kv_start
|
|
100
|
+
m_i = tl.full([M], float("-inf"), dtype=tl.float32)
|
|
101
|
+
l_i = tl.zeros([M], dtype=tl.float32)
|
|
102
|
+
acc = tl.zeros([M, BLOCK_DV], dtype=tl.float32)
|
|
103
|
+
live = s >= 0 # a split that holds keys
|
|
104
|
+
if s < S:
|
|
105
|
+
chunk = tl.cdiv(tl.maximum(tl.cdiv(prefix, S), MIN_CHUNK), BLOCK_N) * BLOCK_N
|
|
106
|
+
lo = s * chunk
|
|
107
|
+
hi = tl.minimum(lo + chunk, prefix)
|
|
108
|
+
live = hi > lo
|
|
109
|
+
for start in range(lo, hi, BLOCK_N):
|
|
110
|
+
n = start + tl.arange(0, BLOCK_N)
|
|
111
|
+
n_ok = n < hi
|
|
112
|
+
loc = tl.load(kv_indices + kv_start + n, mask=n_ok, other=0)
|
|
113
|
+
k = tl.load(
|
|
114
|
+
k_buf_ptr + loc[None, :] * stride_kbt + h * stride_kbh + d[:, None],
|
|
115
|
+
mask=n_ok[None, :] & (d[:, None] < D),
|
|
116
|
+
other=0.0,
|
|
117
|
+
)
|
|
118
|
+
qk = tl.dot(q, k) * (sm_scale * k_scale)
|
|
119
|
+
qk = tl.where(n_ok[None, :], qk, float("-inf"))
|
|
120
|
+
v = tl.load(
|
|
121
|
+
v_buf_ptr + loc[:, None] * stride_vbt + h * stride_vbh + dv[None, :],
|
|
122
|
+
mask=n_ok[:, None] & (dv[None, :] < DV),
|
|
123
|
+
other=0.0,
|
|
124
|
+
)
|
|
125
|
+
m_new = tl.maximum(m_i, tl.max(qk, 1))
|
|
126
|
+
alpha = tl.exp(m_i - m_new)
|
|
127
|
+
p = tl.exp(qk - m_new[:, None])
|
|
128
|
+
l_i = l_i * alpha + tl.sum(p, 1)
|
|
129
|
+
acc = acc * alpha[:, None] + tl.dot(p.to(v.dtype), v)
|
|
130
|
+
m_i = m_new
|
|
131
|
+
acc = acc * v_scale
|
|
132
|
+
else:
|
|
133
|
+
# Names differ from the prefix branch's: Triton requires a name assigned
|
|
134
|
+
# in both branches of a runtime `if` to have one type.
|
|
135
|
+
j = tl.arange(0, BLOCK_L)
|
|
136
|
+
j_ok = j < L
|
|
137
|
+
k_ext = tl.load(
|
|
138
|
+
k_ext_ptr + (b * L + j)[None, :] * stride_kt + h * stride_kh + d[:, None],
|
|
139
|
+
mask=j_ok[None, :] & (d[:, None] < D),
|
|
140
|
+
other=0.0,
|
|
141
|
+
)
|
|
142
|
+
qk_ext = tl.dot(q, k_ext) * sm_scale
|
|
143
|
+
if USE_MASK:
|
|
144
|
+
if QLEN_MASK:
|
|
145
|
+
# Request b's L x L block, row t for verify token t.
|
|
146
|
+
offs = b * L * L + t[:, None] * L + j[None, :]
|
|
147
|
+
else:
|
|
148
|
+
# Row t of request b's mask: prefix + L entries, the last L for the verify tokens.
|
|
149
|
+
offs = (
|
|
150
|
+
tl.load(mask_indptr + b)
|
|
151
|
+
+ t[:, None] * (prefix + L)
|
|
152
|
+
+ prefix
|
|
153
|
+
+ j[None, :]
|
|
154
|
+
)
|
|
155
|
+
allowed = (
|
|
156
|
+
tl.load(mask_ptr + offs, mask=row_ok[:, None] & j_ok[None, :], other=0)
|
|
157
|
+
!= 0
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
allowed = j[None, :] <= t[:, None]
|
|
161
|
+
allowed = allowed & row_ok[:, None] & j_ok[None, :]
|
|
162
|
+
qk_ext = tl.where(allowed, qk_ext, float("-inf"))
|
|
163
|
+
v_ext = tl.load(
|
|
164
|
+
v_ext_ptr + (b * L + j)[:, None] * stride_vt + h * stride_vh + dv[None, :],
|
|
165
|
+
mask=j_ok[:, None] & (dv[None, :] < DV),
|
|
166
|
+
other=0.0,
|
|
167
|
+
)
|
|
168
|
+
m_i = tl.max(qk_ext, 1)
|
|
169
|
+
p_ext = tl.exp(qk_ext - tl.where(m_i == float("-inf"), 0.0, m_i)[:, None])
|
|
170
|
+
l_i = tl.sum(p_ext, 1)
|
|
171
|
+
acc = tl.dot(p_ext.to(v_ext.dtype), v_ext)
|
|
172
|
+
part = (b * H_KV + h) * (S + 1) + s
|
|
173
|
+
lse = tl.where(l_i > 0, m_i + tl.log(l_i), float("-inf"))
|
|
174
|
+
tl.store(part_lse_ptr + part * (RB * M) + rows, lse, mask=row_ok)
|
|
175
|
+
if live:
|
|
176
|
+
out = acc / tl.where(l_i > 0, l_i, 1.0)[:, None]
|
|
177
|
+
tl.store(
|
|
178
|
+
part_out_ptr + (part * (RB * M) + rows)[:, None] * BLOCK_DV + dv[None, :],
|
|
179
|
+
out,
|
|
180
|
+
mask=row_ok[:, None],
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@triton.jit
|
|
185
|
+
def _verify_merge_kernel(
|
|
186
|
+
part_out_ptr,
|
|
187
|
+
part_lse_ptr,
|
|
188
|
+
o_ptr,
|
|
189
|
+
stride_ot,
|
|
190
|
+
stride_oh,
|
|
191
|
+
H_KV: tl.constexpr,
|
|
192
|
+
G: tl.constexpr,
|
|
193
|
+
L: tl.constexpr,
|
|
194
|
+
S: tl.constexpr,
|
|
195
|
+
DV: tl.constexpr,
|
|
196
|
+
BLOCK_DV: tl.constexpr,
|
|
197
|
+
ROWS: tl.constexpr,
|
|
198
|
+
BLOCK_L: tl.constexpr,
|
|
199
|
+
):
|
|
200
|
+
# Program (b, query head): the splits' outputs for its L rows (of ROWS per
|
|
201
|
+
# partial), weighted by their share of the softmax mass.
|
|
202
|
+
b = tl.program_id(0)
|
|
203
|
+
head = tl.program_id(1)
|
|
204
|
+
h = head // G
|
|
205
|
+
t = tl.arange(0, BLOCK_L)
|
|
206
|
+
t_ok = t < L
|
|
207
|
+
rows = (head % G) * L + t
|
|
208
|
+
dv = tl.arange(0, BLOCK_DV)
|
|
209
|
+
m = tl.full([BLOCK_L], float("-inf"), dtype=tl.float32)
|
|
210
|
+
den = tl.zeros([BLOCK_L], dtype=tl.float32)
|
|
211
|
+
acc = tl.zeros([BLOCK_L, BLOCK_DV], dtype=tl.float32)
|
|
212
|
+
for s in tl.static_range(S + 1):
|
|
213
|
+
part = (b * H_KV + h) * (S + 1) + s
|
|
214
|
+
lse = tl.load(part_lse_ptr + part * ROWS + rows, mask=t_ok, other=float("-inf"))
|
|
215
|
+
live = lse > float("-inf")
|
|
216
|
+
out = tl.load(
|
|
217
|
+
part_out_ptr + (part * ROWS + rows)[:, None] * BLOCK_DV + dv[None, :],
|
|
218
|
+
mask=live[:, None],
|
|
219
|
+
other=0.0,
|
|
220
|
+
)
|
|
221
|
+
m_new = tl.maximum(m, lse)
|
|
222
|
+
base = tl.where(m_new == float("-inf"), 0.0, m_new)
|
|
223
|
+
alpha = tl.exp(m - base)
|
|
224
|
+
w = tl.exp(lse - base)
|
|
225
|
+
acc = acc * alpha[:, None] + out * w[:, None]
|
|
226
|
+
den = den * alpha + w
|
|
227
|
+
m = m_new
|
|
228
|
+
out = acc / tl.where(den > 0, den, 1.0)[:, None]
|
|
229
|
+
tl.store(
|
|
230
|
+
o_ptr + (b * L + t)[:, None] * stride_ot + head * stride_oh + dv[None, :],
|
|
231
|
+
out.to(o_ptr.dtype.element_ty),
|
|
232
|
+
mask=t_ok[:, None] & (dv[None, :] < DV),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def row_blocks(rows: int) -> tuple:
|
|
237
|
+
"""(rows per program, programs) covering ``rows`` query rows: blocks of 64
|
|
238
|
+
or 128, whichever pads less (ties to 128, fewer programs re-reading K/V)."""
|
|
239
|
+
best = None
|
|
240
|
+
for size in (128, 64):
|
|
241
|
+
size = min(size, max(16, triton.next_power_of_2(rows)))
|
|
242
|
+
blocks = triton.cdiv(rows, size)
|
|
243
|
+
if best is None or size * blocks < best[0] * best[1]:
|
|
244
|
+
best = (size, blocks)
|
|
245
|
+
return best
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _env_int(name: str, default: int) -> int:
|
|
249
|
+
return int(os.environ.get(name, default))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class SplitKVVerify:
|
|
253
|
+
"""extend_attention_fwd for a verify batch (L tokens per request), split
|
|
254
|
+
over the prefix. Scratch for the splits' partial outputs grows with the
|
|
255
|
+
batch, outside graph capture (a capture runs warm-up passes first); a
|
|
256
|
+
replaced buffer is kept, as captured graphs still point at it."""
|
|
257
|
+
|
|
258
|
+
MAX_SCRATCH_BYTES = 512 << 20
|
|
259
|
+
# Custom masks arrive as each request's L x L block (EAGLE's QLEN_ONLY) rather
|
|
260
|
+
# than sglang's full prefix + L rows. Whoever builds the masks sets this
|
|
261
|
+
# before graphs are captured; extend_attention_fwd cannot read that layout,
|
|
262
|
+
# so a batch this kernel cannot take then raises instead of falling back.
|
|
263
|
+
qlen_mask = False
|
|
264
|
+
|
|
265
|
+
def __init__(self):
|
|
266
|
+
# Splits per (request, KV head): enough programs for `waves` waves of
|
|
267
|
+
# the GPU's SMs, at most MAX; MIN_CHUNK keys at least per split, so
|
|
268
|
+
# short prefixes use fewer of them.
|
|
269
|
+
self.waves = _env_int("FLASHMAP_SPLITKV_WAVES", 2)
|
|
270
|
+
self.max_splits = _env_int("FLASHMAP_SPLITKV_MAX_SPLITS", 16)
|
|
271
|
+
self.min_chunk = _env_int("FLASHMAP_SPLITKV_MIN_CHUNK", 64)
|
|
272
|
+
self.block_n = _env_int("FLASHMAP_SPLITKV_BLOCK_N", 64)
|
|
273
|
+
self.num_warps = _env_int("FLASHMAP_SPLITKV_WARPS", 8)
|
|
274
|
+
self.part_out = None
|
|
275
|
+
self.part_lse = None
|
|
276
|
+
self._retired = []
|
|
277
|
+
self._sms = None
|
|
278
|
+
|
|
279
|
+
def splits(self, bs: int, h_kv: int, device) -> int:
|
|
280
|
+
if self._sms is None:
|
|
281
|
+
self._sms = torch.cuda.get_device_properties(device).multi_processor_count
|
|
282
|
+
want = max(1, self.waves * self._sms // (bs * h_kv))
|
|
283
|
+
return min(self.max_splits, 1 << (want.bit_length() - 1))
|
|
284
|
+
|
|
285
|
+
def _scratch(self, parts: int, m: int, block_dv: int, device) -> bool:
|
|
286
|
+
if (
|
|
287
|
+
self.part_out is not None
|
|
288
|
+
and self.part_out.shape[0] >= parts
|
|
289
|
+
and self.part_out.shape[1:] == (m, block_dv)
|
|
290
|
+
):
|
|
291
|
+
return True
|
|
292
|
+
if (
|
|
293
|
+
torch.cuda.is_current_stream_capturing()
|
|
294
|
+
or parts * m * block_dv * 4 > self.MAX_SCRATCH_BYTES
|
|
295
|
+
):
|
|
296
|
+
return False
|
|
297
|
+
if self.part_out is not None:
|
|
298
|
+
self._retired.append((self.part_out, self.part_lse))
|
|
299
|
+
self.part_out = torch.empty(
|
|
300
|
+
parts, m, block_dv, dtype=torch.float32, device=device
|
|
301
|
+
)
|
|
302
|
+
self.part_lse = torch.empty(parts, m, dtype=torch.float32, device=device)
|
|
303
|
+
return True
|
|
304
|
+
|
|
305
|
+
def __call__(
|
|
306
|
+
self,
|
|
307
|
+
q,
|
|
308
|
+
k,
|
|
309
|
+
v,
|
|
310
|
+
o,
|
|
311
|
+
k_buffer,
|
|
312
|
+
v_buffer,
|
|
313
|
+
qo_indptr,
|
|
314
|
+
kv_indptr,
|
|
315
|
+
kv_indices,
|
|
316
|
+
custom_mask,
|
|
317
|
+
is_causal,
|
|
318
|
+
mask_indptr,
|
|
319
|
+
max_len_extend,
|
|
320
|
+
k_scale=1.0,
|
|
321
|
+
v_scale=1.0,
|
|
322
|
+
sm_scale=None,
|
|
323
|
+
logit_cap=0.0,
|
|
324
|
+
sliding_window_size=-1,
|
|
325
|
+
sinks=None,
|
|
326
|
+
window_kv_offsets=None,
|
|
327
|
+
xai_temperature_len=-1,
|
|
328
|
+
max_bs=None,
|
|
329
|
+
**unused,
|
|
330
|
+
) -> bool:
|
|
331
|
+
del window_kv_offsets, max_bs, unused
|
|
332
|
+
handled = self._run(
|
|
333
|
+
q,
|
|
334
|
+
k,
|
|
335
|
+
v,
|
|
336
|
+
o,
|
|
337
|
+
k_buffer,
|
|
338
|
+
v_buffer,
|
|
339
|
+
kv_indptr,
|
|
340
|
+
kv_indices,
|
|
341
|
+
custom_mask,
|
|
342
|
+
is_causal,
|
|
343
|
+
mask_indptr,
|
|
344
|
+
qo_indptr,
|
|
345
|
+
max_len_extend,
|
|
346
|
+
k_scale,
|
|
347
|
+
v_scale,
|
|
348
|
+
sm_scale,
|
|
349
|
+
logit_cap,
|
|
350
|
+
sliding_window_size,
|
|
351
|
+
sinks,
|
|
352
|
+
xai_temperature_len,
|
|
353
|
+
)
|
|
354
|
+
if not handled and custom_mask is not None and self.qlen_mask:
|
|
355
|
+
raise RuntimeError(
|
|
356
|
+
"split-KV verify declined a batch whose mask only it can read (QLEN_ONLY layout)"
|
|
357
|
+
)
|
|
358
|
+
return handled
|
|
359
|
+
|
|
360
|
+
def _run(
|
|
361
|
+
self,
|
|
362
|
+
q,
|
|
363
|
+
k,
|
|
364
|
+
v,
|
|
365
|
+
o,
|
|
366
|
+
k_buffer,
|
|
367
|
+
v_buffer,
|
|
368
|
+
kv_indptr,
|
|
369
|
+
kv_indices,
|
|
370
|
+
custom_mask,
|
|
371
|
+
is_causal,
|
|
372
|
+
mask_indptr,
|
|
373
|
+
qo_indptr,
|
|
374
|
+
max_len_extend,
|
|
375
|
+
k_scale,
|
|
376
|
+
v_scale,
|
|
377
|
+
sm_scale,
|
|
378
|
+
logit_cap,
|
|
379
|
+
sliding_window_size,
|
|
380
|
+
sinks,
|
|
381
|
+
xai_temperature_len,
|
|
382
|
+
) -> bool:
|
|
383
|
+
if (logit_cap or 0) > 0 or sinks is not None or (sliding_window_size or -1) > 0:
|
|
384
|
+
return False
|
|
385
|
+
if (xai_temperature_len or -1) > 0 or (custom_mask is None and not is_causal):
|
|
386
|
+
return False
|
|
387
|
+
if not (
|
|
388
|
+
isinstance(k_scale, (int, float)) and isinstance(v_scale, (int, float))
|
|
389
|
+
):
|
|
390
|
+
return False
|
|
391
|
+
bs = qo_indptr.shape[0] - 1
|
|
392
|
+
length = int(max_len_extend)
|
|
393
|
+
h_q, d = q.shape[1], q.shape[2]
|
|
394
|
+
h_kv, dv = k_buffer.shape[1], v_buffer.shape[-1]
|
|
395
|
+
if (
|
|
396
|
+
bs < 1
|
|
397
|
+
or q.shape[0] != bs * length
|
|
398
|
+
or h_q % h_kv
|
|
399
|
+
or k_buffer.dtype != q.dtype
|
|
400
|
+
):
|
|
401
|
+
return False
|
|
402
|
+
group = h_q // h_kv
|
|
403
|
+
m, blocks = row_blocks(group * length)
|
|
404
|
+
k = k.view(-1, h_kv, d)
|
|
405
|
+
v = v.view(-1, h_kv, dv)
|
|
406
|
+
splits = self.splits(bs, h_kv, q.device)
|
|
407
|
+
block_dv = triton.next_power_of_2(dv)
|
|
408
|
+
if not self._scratch(bs * h_kv * (splits + 1), m * blocks, block_dv, q.device):
|
|
409
|
+
return False
|
|
410
|
+
block_l = max(16, triton.next_power_of_2(length))
|
|
411
|
+
sm_scale = sm_scale or d**-0.5
|
|
412
|
+
use_mask = custom_mask is not None
|
|
413
|
+
_verify_partial_kernel[(bs, h_kv * blocks, splits + 1)](
|
|
414
|
+
q,
|
|
415
|
+
k,
|
|
416
|
+
v,
|
|
417
|
+
k_buffer,
|
|
418
|
+
v_buffer,
|
|
419
|
+
kv_indptr,
|
|
420
|
+
kv_indices,
|
|
421
|
+
custom_mask if use_mask else q,
|
|
422
|
+
mask_indptr if use_mask else kv_indptr,
|
|
423
|
+
self.part_out,
|
|
424
|
+
self.part_lse,
|
|
425
|
+
sm_scale,
|
|
426
|
+
float(k_scale),
|
|
427
|
+
float(v_scale),
|
|
428
|
+
q.stride(0),
|
|
429
|
+
q.stride(1),
|
|
430
|
+
k.stride(0),
|
|
431
|
+
k.stride(1),
|
|
432
|
+
v.stride(0),
|
|
433
|
+
v.stride(1),
|
|
434
|
+
k_buffer.stride(0),
|
|
435
|
+
k_buffer.stride(1),
|
|
436
|
+
v_buffer.stride(0),
|
|
437
|
+
v_buffer.stride(1),
|
|
438
|
+
H_KV=h_kv,
|
|
439
|
+
G=group,
|
|
440
|
+
L=length,
|
|
441
|
+
S=splits,
|
|
442
|
+
D=d,
|
|
443
|
+
DV=dv,
|
|
444
|
+
BLOCK_D=triton.next_power_of_2(d),
|
|
445
|
+
BLOCK_DV=block_dv,
|
|
446
|
+
M=m,
|
|
447
|
+
RB=blocks,
|
|
448
|
+
BLOCK_L=block_l,
|
|
449
|
+
BLOCK_N=self.block_n,
|
|
450
|
+
MIN_CHUNK=self.min_chunk,
|
|
451
|
+
USE_MASK=use_mask,
|
|
452
|
+
QLEN_MASK=self.qlen_mask,
|
|
453
|
+
num_warps=self.num_warps,
|
|
454
|
+
num_stages=1,
|
|
455
|
+
)
|
|
456
|
+
o = o.view(-1, h_q, dv)
|
|
457
|
+
_verify_merge_kernel[(bs, h_q)](
|
|
458
|
+
self.part_out,
|
|
459
|
+
self.part_lse,
|
|
460
|
+
o,
|
|
461
|
+
o.stride(0),
|
|
462
|
+
o.stride(1),
|
|
463
|
+
H_KV=h_kv,
|
|
464
|
+
G=group,
|
|
465
|
+
L=length,
|
|
466
|
+
S=splits,
|
|
467
|
+
DV=dv,
|
|
468
|
+
BLOCK_DV=block_dv,
|
|
469
|
+
ROWS=m * blocks,
|
|
470
|
+
BLOCK_L=block_l,
|
|
471
|
+
num_warps=4,
|
|
472
|
+
num_stages=1,
|
|
473
|
+
)
|
|
474
|
+
return True
|