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.
@@ -0,0 +1,443 @@
1
+ """Triton kernels for FlashMap drafting in vLLM.
2
+
3
+ The FlashMap DiT normalizes with ``x * bf16(weight * rsqrt(mean(x^2) + eps))``
4
+ (the weight is ``1 + scale`` of the training code) and rotates interleaved pairs
5
+ ``(2i, 2i + 1)`` in float32 before rounding back to bf16. These kernels reproduce
6
+ that rounding so that vLLM drafts what the reference implementation drafts. The
7
+ ones the draft step calls are registered as custom ops so torch.compile keeps
8
+ them whole.
9
+ """
10
+
11
+ import torch
12
+ import triton
13
+ import triton.language as tl
14
+
15
+ __all__ = [
16
+ "block_self_attention",
17
+ "rms_norm",
18
+ "rms_norm_rope",
19
+ "rope_table",
20
+ "scatter_rows",
21
+ "seeded_normal",
22
+ "write_context_kv",
23
+ ]
24
+
25
+
26
+ def rope_table(max_positions: int, head_dim: int, base: float, device) -> torch.Tensor:
27
+ """``(max_positions, head_dim / 2, 2)`` float32 table of ``(cos, sin)`` of
28
+ ``position * base^(-2i / head_dim)``, as the training code computes them."""
29
+ inv_freq = 1.0 / (
30
+ base ** (torch.arange(0, head_dim, 2, device=device).float() / head_dim)
31
+ )
32
+ angles = torch.arange(max_positions, device=device).float()[:, None] * inv_freq
33
+ return torch.stack([angles.cos(), angles.sin()], dim=-1).contiguous()
34
+
35
+
36
+ @triton.jit
37
+ def _rms_norm_kernel(x_ptr, weight_ptr, out_ptr, n_cols, eps, BLOCK: tl.constexpr):
38
+ row = tl.program_id(0).to(tl.int64)
39
+ cols = tl.arange(0, BLOCK)
40
+ mask = cols < n_cols
41
+ x = tl.load(x_ptr + row * n_cols + cols, mask=mask, other=0.0).to(tl.float32)
42
+ rms = tl.rsqrt(tl.sum(x * x, 0) / n_cols + eps)
43
+ weight = tl.load(weight_ptr + cols, mask=mask, other=0.0).to(tl.float32)
44
+ scale = (weight * rms).to(tl.bfloat16).to(tl.float32)
45
+ tl.store(out_ptr + row * n_cols + cols, (x * scale).to(tl.bfloat16), mask=mask)
46
+
47
+
48
+ @torch.library.custom_op("flashmap_vllm::rms_norm", mutates_args=())
49
+ def rms_norm(x: torch.Tensor, weight: torch.Tensor, eps: float) -> torch.Tensor:
50
+ """FlashMap RMSNorm of a bf16 ``x (..., C)``, in one launch."""
51
+ assert x.dtype == torch.bfloat16
52
+ x = x.contiguous()
53
+ out = torch.empty_like(x)
54
+ n_cols = x.shape[-1]
55
+ if x.numel():
56
+ _rms_norm_kernel[(x.numel() // n_cols,)](
57
+ x, weight, out, n_cols, eps, BLOCK=triton.next_power_of_2(n_cols)
58
+ )
59
+ return out
60
+
61
+
62
+ @rms_norm.register_fake
63
+ def _(x, weight, eps):
64
+ return torch.empty_like(x)
65
+
66
+
67
+ @triton.jit
68
+ def _rms_norm_rope_kernel(
69
+ x_ptr,
70
+ weight_ptr,
71
+ cos_sin_ptr,
72
+ pos_ptr,
73
+ out_ptr,
74
+ eps,
75
+ H: tl.constexpr,
76
+ D: tl.constexpr,
77
+ ):
78
+ # One program per (token, head) of a contiguous (tokens, H, D) input.
79
+ token = tl.program_id(0) // H
80
+ head = tl.program_id(0) % H
81
+ HALF: tl.constexpr = D // 2
82
+ i = tl.arange(0, HALF)
83
+ row = x_ptr + (token * H + head) * D
84
+ even = tl.load(row + 2 * i).to(tl.float32)
85
+ odd = tl.load(row + 2 * i + 1).to(tl.float32)
86
+ rms = tl.rsqrt((tl.sum(even * even, 0) + tl.sum(odd * odd, 0)) / D + eps)
87
+ w_even = tl.load(weight_ptr + 2 * i).to(tl.float32)
88
+ w_odd = tl.load(weight_ptr + 2 * i + 1).to(tl.float32)
89
+ even = (even * (w_even * rms).to(tl.bfloat16).to(tl.float32)).to(tl.bfloat16)
90
+ odd = (odd * (w_odd * rms).to(tl.bfloat16).to(tl.float32)).to(tl.bfloat16)
91
+ even = even.to(tl.float32)
92
+ odd = odd.to(tl.float32)
93
+ pos = tl.load(pos_ptr + token).to(tl.int64)
94
+ cos = tl.load(cos_sin_ptr + pos * D + 2 * i)
95
+ sin = tl.load(cos_sin_ptr + pos * D + 2 * i + 1)
96
+ out = out_ptr + (token * H + head) * D
97
+ tl.store(out + 2 * i, (even * cos - odd * sin).to(tl.bfloat16))
98
+ tl.store(out + 2 * i + 1, (odd * cos + even * sin).to(tl.bfloat16))
99
+
100
+
101
+ @torch.library.custom_op("flashmap_vllm::rms_norm_rope", mutates_args=())
102
+ def rms_norm_rope(
103
+ x: torch.Tensor,
104
+ weight: torch.Tensor,
105
+ cos_sin: torch.Tensor,
106
+ positions: torch.Tensor,
107
+ eps: float,
108
+ ) -> torch.Tensor:
109
+ """RMSNorm then RoPE at ``positions (tokens,)`` of ``x (tokens, heads, head_dim)``
110
+ with the ``rope_table`` ``cos_sin``, in one launch."""
111
+ tokens, heads, head_dim = x.shape
112
+ assert cos_sin.dtype == torch.float32 and cos_sin.is_contiguous()
113
+ x, positions = x.contiguous(), positions.contiguous()
114
+ out = torch.empty_like(x)
115
+ if tokens:
116
+ _rms_norm_rope_kernel[(tokens * heads,)](
117
+ x, weight, cos_sin, positions, out, eps, H=heads, D=head_dim
118
+ )
119
+ return out
120
+
121
+
122
+ @rms_norm_rope.register_fake
123
+ def _(x, weight, cos_sin, positions, eps):
124
+ return torch.empty_like(x)
125
+
126
+
127
+ @triton.jit
128
+ def _block_self_attention_kernel(
129
+ qkv_ptr,
130
+ q_norm_ptr,
131
+ k_norm_ptr,
132
+ cos_sin_ptr,
133
+ pos_ptr,
134
+ out_ptr,
135
+ eps,
136
+ sm_scale,
137
+ T: tl.constexpr,
138
+ H: tl.constexpr,
139
+ D: tl.constexpr,
140
+ ):
141
+ # One program per (block, head): the block's T queries attend to its T keys.
142
+ b = tl.program_id(0) // H
143
+ h = tl.program_id(0) % H
144
+ HALF: tl.constexpr = D // 2
145
+ t = tl.arange(0, T)[:, None]
146
+ i = tl.arange(0, HALF)[None, :]
147
+ d = tl.arange(0, D)[None, :]
148
+ # qkv is (blocks, T, 3, H, D); RoPE pairs dims (2i, 2i + 1).
149
+ row = qkv_ptr + (b * T + t) * (3 * H * D) + h * D
150
+ pos = tl.load(pos_ptr + b * T + t).to(tl.int64)
151
+ cos = tl.load(cos_sin_ptr + pos * D + 2 * i)
152
+ sin = tl.load(cos_sin_ptr + pos * D + 2 * i + 1)
153
+
154
+ q_even = tl.load(row + 2 * i).to(tl.float32)
155
+ q_odd = tl.load(row + 2 * i + 1).to(tl.float32)
156
+ k_even = tl.load(row + H * D + 2 * i).to(tl.float32)
157
+ k_odd = tl.load(row + H * D + 2 * i + 1).to(tl.float32)
158
+
159
+ q_rms = tl.rsqrt((tl.sum(q_even * q_even, 1) + tl.sum(q_odd * q_odd, 1)) / D + eps)[
160
+ :, None
161
+ ]
162
+ k_rms = tl.rsqrt((tl.sum(k_even * k_even, 1) + tl.sum(k_odd * k_odd, 1)) / D + eps)[
163
+ :, None
164
+ ]
165
+ qw_even = tl.load(q_norm_ptr + 2 * i).to(tl.float32)
166
+ qw_odd = tl.load(q_norm_ptr + 2 * i + 1).to(tl.float32)
167
+ kw_even = tl.load(k_norm_ptr + 2 * i).to(tl.float32)
168
+ kw_odd = tl.load(k_norm_ptr + 2 * i + 1).to(tl.float32)
169
+ q_even = (q_even * (qw_even * q_rms).to(tl.bfloat16).to(tl.float32)).to(tl.bfloat16)
170
+ q_odd = (q_odd * (qw_odd * q_rms).to(tl.bfloat16).to(tl.float32)).to(tl.bfloat16)
171
+ k_even = (k_even * (kw_even * k_rms).to(tl.bfloat16).to(tl.float32)).to(tl.bfloat16)
172
+ k_odd = (k_odd * (kw_odd * k_rms).to(tl.bfloat16).to(tl.float32)).to(tl.bfloat16)
173
+ q_even = q_even.to(tl.float32)
174
+ q_odd = q_odd.to(tl.float32)
175
+ k_even = k_even.to(tl.float32)
176
+ k_odd = k_odd.to(tl.float32)
177
+
178
+ q_rot_even = (q_even * cos - q_odd * sin).to(tl.bfloat16)
179
+ q_rot_odd = (q_odd * cos + q_even * sin).to(tl.bfloat16)
180
+ k_rot_even = (k_even * cos - k_odd * sin).to(tl.bfloat16)
181
+ k_rot_odd = (k_odd * cos + k_even * sin).to(tl.bfloat16)
182
+
183
+ scores = tl.dot(q_rot_even, tl.trans(k_rot_even)) + tl.dot(
184
+ q_rot_odd, tl.trans(k_rot_odd)
185
+ )
186
+ scores = scores * sm_scale
187
+ probs = tl.exp(scores - tl.max(scores, 1)[:, None])
188
+ denom = tl.sum(probs, 1)[:, None]
189
+ values = tl.load(row + 2 * H * D + d)
190
+ out = tl.dot(probs.to(tl.bfloat16), values) / denom
191
+ tl.store(out_ptr + (b * T + t) * (H * D) + h * D + d, out.to(tl.bfloat16))
192
+
193
+
194
+ @torch.library.custom_op("flashmap_vllm::block_self_attention", mutates_args=())
195
+ def block_self_attention(
196
+ qkv: torch.Tensor,
197
+ q_norm: torch.Tensor,
198
+ k_norm: torch.Tensor,
199
+ cos_sin: torch.Tensor,
200
+ positions: torch.Tensor,
201
+ eps: float,
202
+ ) -> torch.Tensor:
203
+ """Unmasked self-attention within each draft block, from its projected
204
+ ``qkv (blocks, T, 3, heads, head_dim)``: q/k RMSNorms, RoPE at ``positions
205
+ (blocks * T,)``, softmax attention. Returns ``(blocks * T, heads * head_dim)``."""
206
+ blocks, block_size, _, heads, head_dim = qkv.shape
207
+ assert cos_sin.dtype == torch.float32 and cos_sin.is_contiguous()
208
+ qkv, positions = qkv.contiguous(), positions.contiguous()
209
+ out = torch.empty(
210
+ (blocks * block_size, heads * head_dim), dtype=qkv.dtype, device=qkv.device
211
+ )
212
+ if blocks:
213
+ _block_self_attention_kernel[(blocks * heads,)](
214
+ qkv,
215
+ q_norm,
216
+ k_norm,
217
+ cos_sin,
218
+ positions,
219
+ out,
220
+ eps,
221
+ head_dim**-0.5,
222
+ T=block_size,
223
+ H=heads,
224
+ D=head_dim,
225
+ )
226
+ return out
227
+
228
+
229
+ @block_self_attention.register_fake
230
+ def _(qkv, q_norm, k_norm, cos_sin, positions, eps):
231
+ blocks, block_size, _, heads, head_dim = qkv.shape
232
+ return qkv.new_empty((blocks * block_size, heads * head_dim))
233
+
234
+
235
+ @triton.jit
236
+ def _write_context_kv_kernel(
237
+ kv_ptr, # (tokens, L, 2, H, D) bf16, contiguous
238
+ pos_ptr, # (tokens,)
239
+ slot_ptr, # (tokens,) int64; negative = skip
240
+ k_norm_ptr, # (L, D)
241
+ cos_sin_ptr, # (max_positions, D / 2, 2) float32
242
+ cache_ptrs, # (L,) int64 data pointers of each layer's KV cache
243
+ scales_ptr, # (L, 2) float32 key and value scales of an fp8 cache
244
+ eps,
245
+ block_size,
246
+ stride_block,
247
+ stride_head,
248
+ stride_token,
249
+ v_offset,
250
+ L: tl.constexpr,
251
+ FP8: tl.constexpr,
252
+ H: tl.constexpr,
253
+ D: tl.constexpr,
254
+ ):
255
+ # One program per (token, layer); all heads at once.
256
+ token = tl.program_id(0).to(tl.int64)
257
+ layer = tl.program_id(1)
258
+ slot = tl.load(slot_ptr + token).to(tl.int64)
259
+ if slot < 0:
260
+ return
261
+ HALF: tl.constexpr = D // 2
262
+ h = tl.arange(0, H)[:, None]
263
+ i = tl.arange(0, HALF)[None, :]
264
+ d = tl.arange(0, D)[None, :]
265
+ k_row = kv_ptr + (token * L + layer) * (2 * H * D) + h * D
266
+ v_row = k_row + H * D
267
+ even = tl.load(k_row + 2 * i).to(tl.float32)
268
+ odd = tl.load(k_row + 2 * i + 1).to(tl.float32)
269
+ rms = tl.rsqrt((tl.sum(even * even, 1) + tl.sum(odd * odd, 1)) / D + eps)[:, None]
270
+ w_even = tl.load(k_norm_ptr + layer * D + 2 * i).to(tl.float32)
271
+ w_odd = tl.load(k_norm_ptr + layer * D + 2 * i + 1).to(tl.float32)
272
+ even = (even * (w_even * rms).to(tl.bfloat16).to(tl.float32)).to(tl.bfloat16)
273
+ odd = (odd * (w_odd * rms).to(tl.bfloat16).to(tl.float32)).to(tl.bfloat16)
274
+ even = even.to(tl.float32)
275
+ odd = odd.to(tl.float32)
276
+ pos = tl.load(pos_ptr + token).to(tl.int64)
277
+ cos = tl.load(cos_sin_ptr + pos * D + 2 * i)
278
+ sin = tl.load(cos_sin_ptr + pos * D + 2 * i + 1)
279
+
280
+ key_out = even * cos - odd * sin
281
+ key_odd_out = odd * cos + even * sin
282
+ values = tl.load(v_row + d)
283
+ if FP8:
284
+ # e4m3 with per-tensor scales, as vLLM's fp8 KV caches store them.
285
+ k_scale = tl.load(scales_ptr + layer * 2)
286
+ v_scale = tl.load(scales_ptr + layer * 2 + 1)
287
+ cache = tl.load(cache_ptrs + layer).to(tl.pointer_type(tl.float8e4nv))
288
+ key_out = (key_out / k_scale).to(tl.float8e4nv)
289
+ key_odd_out = (key_odd_out / k_scale).to(tl.float8e4nv)
290
+ values = (values.to(tl.float32) / v_scale).to(tl.float8e4nv)
291
+ else:
292
+ cache = tl.load(cache_ptrs + layer).to(tl.pointer_type(tl.bfloat16))
293
+ key_out = key_out.to(tl.bfloat16)
294
+ key_odd_out = key_odd_out.to(tl.bfloat16)
295
+ dst = cache + (slot // block_size) * stride_block + h * stride_head
296
+ dst += (slot % block_size) * stride_token
297
+ tl.store(dst + 2 * i, key_out)
298
+ tl.store(dst + 2 * i + 1, key_odd_out)
299
+ tl.store(dst + v_offset + d, values)
300
+
301
+
302
+ def write_context_kv(
303
+ kv: torch.Tensor,
304
+ positions: torch.Tensor,
305
+ slots: torch.Tensor,
306
+ k_norm: torch.Tensor,
307
+ cos_sin: torch.Tensor,
308
+ cache_ptrs: torch.Tensor,
309
+ cache_strides: tuple[int, int, int, int],
310
+ block_size: int,
311
+ eps: float,
312
+ scales: torch.Tensor | None = None,
313
+ ) -> None:
314
+ """Write every layer's context keys (k-normed and rotated) and values for
315
+ ``kv (tokens, layers, 2, heads, head_dim)`` into the paged KV caches at
316
+ ``slots`` (negative slots are skipped), in one launch.
317
+
318
+ ``cache_strides`` are ``(block, head, token, value offset)`` in elements of
319
+ the vLLM FlashAttention cache ``(blocks, heads, block_size, 2 * head_dim)``.
320
+ With ``scales (layers, 2)`` (each layer's key and value scale) the caches are
321
+ e4m3 and store ``key / k_scale`` and ``value / v_scale``.
322
+ """
323
+ tokens, layers, _, heads, head_dim = kv.shape
324
+ if not tokens:
325
+ return
326
+ stride_block, stride_head, stride_token, v_offset = cache_strides
327
+ _write_context_kv_kernel[(tokens, layers)](
328
+ kv.contiguous(),
329
+ positions.contiguous(),
330
+ slots.contiguous(),
331
+ k_norm,
332
+ cos_sin,
333
+ cache_ptrs,
334
+ scales if scales is not None else k_norm,
335
+ eps,
336
+ block_size,
337
+ stride_block,
338
+ stride_head,
339
+ stride_token,
340
+ v_offset,
341
+ L=layers,
342
+ FP8=scales is not None,
343
+ H=heads,
344
+ D=head_dim,
345
+ )
346
+
347
+
348
+ @triton.jit
349
+ def _seeded_normal_kernel(
350
+ seeds_ptr, # (max_num_reqs,) int64
351
+ rows_ptr, # (rows,) int32 request-state index per row; negative = unseeded
352
+ pos_ptr, # (rows,)
353
+ out_ptr, # (rows, n_cols) float32
354
+ n_cols,
355
+ scale,
356
+ BLOCK: tl.constexpr,
357
+ ):
358
+ row = tl.program_id(0).to(tl.int64)
359
+ cols = tl.program_id(1) * BLOCK + tl.arange(0, BLOCK)
360
+ state = tl.load(rows_ptr + row)
361
+ seed = tl.load(seeds_ptr + tl.maximum(state, 0)).to(tl.int64)
362
+ seed = tl.where(state >= 0, seed, 0)
363
+ # Philox keyed by the request's seed and the row's position: the noise of a
364
+ # seeded request is reproducible whatever else is batched with it.
365
+ key = seed * 1000003 + tl.load(pos_ptr + row).to(tl.int64)
366
+ z = tl.randn(key, cols)
367
+ tl.store(out_ptr + row * n_cols + cols, z * scale, mask=cols < n_cols)
368
+
369
+
370
+ def seeded_normal(
371
+ seeds: torch.Tensor,
372
+ rows: torch.Tensor,
373
+ positions: torch.Tensor,
374
+ n_cols: int,
375
+ scale: float = 1.0,
376
+ ) -> torch.Tensor:
377
+ """Standard normals ``(len(rows), n_cols)`` in float32, row ``i`` keyed by
378
+ ``seeds[rows[i]]`` (0 where ``rows[i] < 0``) and ``positions[i]``."""
379
+ out = torch.empty(rows.shape[0], n_cols, dtype=torch.float32, device=rows.device)
380
+ if rows.shape[0]:
381
+ block = 1024
382
+ _seeded_normal_kernel[(rows.shape[0], triton.cdiv(n_cols, block))](
383
+ seeds,
384
+ rows.contiguous(),
385
+ positions.contiguous(),
386
+ out,
387
+ n_cols,
388
+ scale,
389
+ BLOCK=block,
390
+ )
391
+ return out
392
+
393
+
394
+ @triton.jit
395
+ def _scatter_rows_kernel(
396
+ src_ptr, # (rows, n_cols)
397
+ req_ptr, # (rows,) first index into dst; negative = skip
398
+ col_ptr, # (rows,) second index into dst
399
+ ids_ptr, # (n_cols,) third index into dst
400
+ dst_ptr, # (A, B, C), contiguous
401
+ n_cols,
402
+ stride_a,
403
+ stride_b,
404
+ BLOCK: tl.constexpr,
405
+ ):
406
+ row = tl.program_id(0).to(tl.int64)
407
+ req = tl.load(req_ptr + row).to(tl.int64)
408
+ if req < 0:
409
+ return
410
+ col = tl.load(col_ptr + row).to(tl.int64)
411
+ j = tl.program_id(1) * BLOCK + tl.arange(0, BLOCK)
412
+ mask = j < n_cols
413
+ ids = tl.load(ids_ptr + j, mask=mask, other=0).to(tl.int64)
414
+ values = tl.load(src_ptr + row * n_cols + j, mask=mask)
415
+ dst = dst_ptr + req * stride_a + col * stride_b + ids
416
+ tl.store(dst, values.to(dst_ptr.dtype.element_ty), mask=mask)
417
+
418
+
419
+ def scatter_rows(
420
+ src: torch.Tensor,
421
+ req: torch.Tensor,
422
+ col: torch.Tensor,
423
+ ids: torch.Tensor,
424
+ dst: torch.Tensor,
425
+ ) -> None:
426
+ """``dst[req[i], col[i], ids] = src[i]`` for every row ``i`` with
427
+ ``req[i] >= 0``: draft-vocabulary logits into a target-vocabulary cache."""
428
+ assert dst.is_contiguous()
429
+ assert dst.dim() == 3 # noqa: PLR2004
430
+ rows, n_cols = src.shape
431
+ if rows:
432
+ block = 1024
433
+ _scatter_rows_kernel[(rows, triton.cdiv(n_cols, block))](
434
+ src.contiguous(),
435
+ req.contiguous(),
436
+ col.contiguous(),
437
+ ids.contiguous(),
438
+ dst,
439
+ n_cols,
440
+ dst.stride(0),
441
+ dst.stride(1),
442
+ BLOCK=block,
443
+ )