[Refactor] Remove dead kernel code (#56845)
Signed-off-by: yewentao256 <zhyanwentao@126.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Wentao Ye committed
Sep 15, 2026 at 16:17 UTC
18f8aa04656de0a37eb5c7e9efc8ebb595ac73cf
8 files changed
-1895
vllm/model_executor/layers/mamba/ops/replayssm_config.py
-15
@@ -8,7 +8,6 @@ auto-detected (Blackwell vs not) so call sites need not thread it through.
8
"""
9
10
import functools
11
-from contextlib import contextmanager
11
12
from vllm.platforms import current_platform
13
from vllm.triton_utils import triton
@@ -26,20 +25,6 @@ def _is_blackwell() -> bool:
25
_overrides: dict[str, tuple] = {}
26
27
29
-@contextmanager
30
-def override_replayssm_config(kernel: str, config: tuple):
31
- """Pin ``kernel``'s launch config for the duration of the context."""
32
- prev = _overrides.get(kernel)
33
- _overrides[kernel] = config
34
- try:
35
- yield
36
- finally:
37
- if prev is None:
38
- _overrides.pop(kernel, None)
39
- else:
40
- _overrides[kernel] = prev
41
-
42
-
28
def _dstate_tile(dstate: int, tile: int) -> int:
29
return max(16, min(tile, triton.next_power_of_2(dstate)))
30
vllm/models/glm5next/amd/ops/kpool_compress.py
-44
@@ -65,50 +65,6 @@ def _hadamard128(x):
65
return x * 0.08838834764831845 # 1/sqrt(128)
66
67
68
-# Map pool ids to physical cache slots.
69
-
70
-
71
-def compute_pooled_write_locs(
72
- page_table_64: torch.Tensor,
73
- pool_ids: torch.Tensor,
74
- pool_size: int,
75
-) -> torch.Tensor:
76
- """Map logical pooled-K ids to physical flat cache slots.
77
-
78
- ``pool_size`` consecutive tokens share one pool slot that lives at the
79
- *first* token page of each page-group. ``page_table_64`` maps token pages
80
- to physical block ids; we gather the block id of each pool's page-group
81
- and add the in-block pool offset.
82
- """
83
- assert page_table_64.ndim == 1
84
- pool_ids = pool_ids.to(torch.int64)
85
- block_size = 64
86
- pool_page_group = torch.div(pool_ids, block_size, rounding_mode="floor")
87
- token_page_row = pool_page_group * pool_size
88
- packed_page = page_table_64.index_select(0, token_page_row.to(torch.int64))
89
- return packed_page.to(torch.int64) * block_size + torch.remainder(
90
- pool_ids, block_size
91
- )
92
-
93
-
94
-def build_pooled_page_table(
95
- page_table: torch.Tensor,
96
- pool_size: int,
97
-) -> torch.Tensor:
98
- """Build a pool-granular page table by taking every ``pool_size``-th
99
- token-page column (one pool maps to ``pool_size`` token pages).
100
-
101
- Uses gather (not strided slicing) so the result is always a fresh
102
- row-major tensor — some downstream kernels require stride(-1) == 1.
103
- """
104
- block_size = page_table.shape[-1]
105
- assert block_size % pool_size == 0, (
106
- f"pool_size ({pool_size}) must divide page columns ({block_size})"
107
- )
108
- idx = torch.arange(0, block_size, pool_size, device=page_table.device)
109
- return page_table[..., idx].contiguous()
110
-
111
-
68
# Fused pool compression and cache write.
69
70
vllm/models/inkling/amd/ops/lamport.py
deleted
-766
@@ -1,766 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-"""Deadlock-free fused RS + short-conv + AG + residual + RMSNorm.
4
-
5
-The public integration surface is ``LamportRSConv.rs_sconv_ag_add_norm``.
6
-
7
-Liveness
8
---------
9
-Large grids are deliberately split at the two communication dependencies:
10
-
11
- 1. ``_publish_input_kernel`` only publishes rank partials.
12
- 2. ``_reduce_insert_kernel`` waits, reduces, and inserts the local shard.
13
- 3. ``_sconv_publish_kernel`` only computes and publishes the local result.
14
- 4. ``_gather_norm_kernel`` waits, gathers, and normalizes.
15
-
16
-Without PDL, CUDA stream order completes a producer before its consumer. With
17
-PDL, every producer CTA posts all peer stores before triggering its dependent,
18
-and the consumer executes ``gdc_wait`` before polling. A consumer therefore
19
-waits only for stores from a producer that is already running (or complete) on
20
-another GPU. It never waits for another block in its own grid. Consequently
21
-spinning consumers cannot occupy resources needed by any producer, and the
22
-wait-for graph has no cycle. The proof is independent of grid size, block
23
-dispatch order, and occupancy.
24
-
25
-For one token, the first three phases use eight independent channel slices to
26
-expose enough CTA parallelism for decode latency. Each slice has exclusive
27
-ownership of its cache and Lamport columns; the gather/RMSNorm phase retains
28
-one CTA per token so no cross-CTA reduction or completion counter is needed.
29
-
30
-Immediate buffer reuse (including replay of a captured CUDA graph) is also
31
-safe. Rank R cannot republish input for call n+1 until its gather for n has
32
-finished; that gather waited for owner O's output, which O publishes only
33
-after consuming R's input for n. Likewise, R cannot republish output for n+1
34
-until its reduction for n+1 has observed destination D's input; D publishes
35
-that input only after its gather consumed R's output for n. Thus every prior
36
-read happens-before a same-slot rewrite. Three generations reduce incidental
37
-coupling for ordinary launches, but correctness does not depend on rotation.
38
-
39
-The payload itself is the Lamport flag. A 32-bit store publishes two bf16s
40
-atomically; 0x80008000 (two negative zeroes) denotes an empty pair. Real
41
-negative zeroes are changed to positive zero before publication. Consumers
42
-use volatile 32-bit loads and restore the sentinel after consuming a slot.
43
-"""
44
-
45
-from __future__ import annotations
46
-
47
-import os
48
-
49
-import torch
50
-
51
-from vllm.distributed import get_tp_group
52
-from vllm.distributed.parallel_state import in_the_same_node_as
53
-from vllm.logger import init_logger
54
-from vllm.triton_utils import tl, triton
55
-
56
-logger = init_logger(__name__)
57
-
58
-_MAX_TOKENS = 16384
59
-_EMPTY_PAIR = tl.constexpr(0x80008000)
60
-
61
-
62
-@triton.jit
63
-def _pack_bf16_pairs(values):
64
- """Pack bf16 values into atomic u32 pairs and reserve negative zero."""
65
- lo, hi = tl.split(values.reshape([values.shape[0] // 2, 2]))
66
- lo = lo.to(tl.uint16, bitcast=True)
67
- hi = hi.to(tl.uint16, bitcast=True)
68
- lo = tl.where(lo == 0x8000, 0, lo).to(tl.uint32)
69
- hi = tl.where(hi == 0x8000, 0, hi).to(tl.uint32)
70
- return lo | (hi << 16)
71
-
72
-
73
-@triton.jit
74
-def _unpack_bf16_pairs(values):
75
- lo = (values & 0xFFFF).to(tl.uint16).to(tl.bfloat16, bitcast=True)
76
- hi = (values >> 16).to(tl.uint16).to(tl.bfloat16, bitcast=True)
77
- return tl.interleave(lo, hi)
78
-
79
-
80
-@triton.jit
81
-def _wait_pairs(ptr, offsets, mask):
82
- values = tl.load(ptr + offsets, mask=mask, other=0, volatile=True)
83
- while tl.max(tl.where(mask & (values == _EMPTY_PAIR), 1, 0)) != 0:
84
- values = tl.load(ptr + offsets, mask=mask, other=0, volatile=True)
85
- return values
86
-
87
-
88
-@triton.jit
89
-def _publish_input_kernel(
90
- stage_ptr,
91
- peer_ptrs,
92
- peer_offset_u32,
93
- stride_stage_t,
94
- C: tl.constexpr,
95
- CS: tl.constexpr,
96
- CS_P2: tl.constexpr,
97
- SPLITS: tl.constexpr,
98
- RANK: tl.constexpr,
99
- WORLD: tl.constexpr,
100
- USE_PDL: tl.constexpr,
101
- launch_pdl: tl.constexpr,
102
-):
103
- """Publish this rank's full partial row into every shard owner's slots."""
104
- token = tl.program_id(0).to(tl.int64)
105
- split = tl.program_id(1).to(tl.int64)
106
- CSS: tl.constexpr = CS // SPLITS
107
- pair = tl.arange(0, CS_P2 // 2)
108
- pair_mask = pair < CSS // 2
109
- elem = tl.arange(0, CS_P2)
110
- elem_mask = elem < CSS
111
- ptrs = peer_ptrs.to(tl.pointer_type(tl.uint64))
112
- # Address generation is independent of the preceding stream kernel. The
113
- # acquire remains before stage/peer loads, which may consume its writes.
114
- if USE_PDL:
115
- tl.extra.cuda.gdc_wait()
116
-
117
- for owner in tl.static_range(WORLD):
118
- values = tl.load(
119
- stage_ptr + token * stride_stage_t + owner * CS + split * CSS + elem,
120
- mask=elem_mask,
121
- other=0.0,
122
- )
123
- packed = _pack_bf16_pairs(values)
124
- base = tl.load(ptrs + owner).to(tl.pointer_type(tl.uint32))
125
- dst = (token * WORLD + RANK) * (CS // 2) + split * (CSS // 2) + pair
126
- tl.store(base + peer_offset_u32 + dst, packed, mask=pair_mask)
127
- if USE_PDL:
128
- tl.extra.cuda.gdc_launch_dependents()
129
-
130
-
131
-@triton.jit
132
-def _reduce_insert_kernel(
133
- input_peer_ptrs,
134
- input_peer_offset_u32,
135
- cache_ptr,
136
- slot_ptr,
137
- stride_cache_block,
138
- stride_cache_head,
139
- stride_cache_token,
140
- stride_cache_dim,
141
- block_size,
142
- C: tl.constexpr,
143
- CS: tl.constexpr,
144
- CS_P2: tl.constexpr,
145
- SPLITS: tl.constexpr,
146
- HEAD_SIZE: tl.constexpr,
147
- CACHE_OFFSET: tl.constexpr,
148
- RANK: tl.constexpr,
149
- WORLD: tl.constexpr,
150
- USE_PDL: tl.constexpr,
151
- launch_pdl: tl.constexpr,
152
-):
153
- """Consume all partials for this rank and insert the reduced cache row."""
154
- token = tl.program_id(0).to(tl.int64)
155
- split = tl.program_id(1).to(tl.int64)
156
- CSS: tl.constexpr = CS // SPLITS
157
- source = tl.arange(0, WORLD)
158
- pair = tl.arange(0, CS_P2 // 2)
159
- pair_mask = pair < CSS // 2
160
- offsets = (
161
- (token * WORLD + source)[:, None] * (CS // 2)
162
- + split * (CSS // 2)
163
- + pair[None, :]
164
- )
165
- mask = tl.full([WORLD], True, tl.int1)[:, None] & pair_mask[None, :]
166
- input_ptrs = input_peer_ptrs.to(tl.pointer_type(tl.uint64))
167
- input_u32 = tl.load(input_ptrs + RANK).to(tl.pointer_type(tl.uint32))
168
- input_u32 += input_peer_offset_u32
169
- # Slot metadata and the cache destination do not depend on input publish.
170
- slot = tl.load(slot_ptr + token)
171
- valid = slot >= 0
172
- channel = tl.arange(0, CS_P2)
173
- channel_mask = channel < CSS
174
- global_channel = split * CSS + channel
175
- head = tl.minimum(global_channel // HEAD_SIZE, CS // HEAD_SIZE - 1)
176
- dim = CACHE_OFFSET + global_channel % HEAD_SIZE
177
- safe_slot = tl.maximum(slot, 0).to(tl.int64)
178
- dst = (
179
- cache_ptr
180
- + (safe_slot // block_size) * stride_cache_block
181
- + head * stride_cache_head
182
- + (safe_slot % block_size) * stride_cache_token
183
- + dim * stride_cache_dim
184
- )
185
- if USE_PDL:
186
- tl.extra.cuda.gdc_wait()
187
- packed = _wait_pairs(input_u32, offsets, mask)
188
-
189
- lo = (packed & 0xFFFF).to(tl.uint16).to(tl.bfloat16, bitcast=True)
190
- hi = (packed >> 16).to(tl.uint16).to(tl.bfloat16, bitcast=True)
191
- reduced = tl.interleave(
192
- tl.sum(lo.to(tl.float32), axis=0).to(tl.bfloat16),
193
- tl.sum(hi.to(tl.float32), axis=0).to(tl.bfloat16),
194
- )
195
- tl.store(
196
- input_u32 + offsets,
197
- tl.full([WORLD, CS_P2 // 2], _EMPTY_PAIR, tl.uint32),
198
- mask=mask,
199
- )
200
-
201
- tl.store(dst, reduced, mask=valid & channel_mask)
202
- if USE_PDL:
203
- tl.extra.cuda.gdc_launch_dependents()
204
-
205
-
206
-@triton.jit
207
-def _sconv_publish_kernel(
208
- peer_ptrs,
209
- peer_offset_u32,
210
- residual_ptr,
211
- weight_ptr,
212
- cache_ptr,
213
- position_ptr,
214
- sequence_ptr,
215
- slot_ptr,
216
- block_table_ptr,
217
- stride_residual_t,
218
- stride_cache_block,
219
- stride_cache_head,
220
- stride_cache_token,
221
- stride_cache_dim,
222
- stride_block_table_r,
223
- max_blocks,
224
- block_size,
225
- C: tl.constexpr,
226
- CS: tl.constexpr,
227
- CS_P2: tl.constexpr,
228
- SPLITS: tl.constexpr,
229
- HEAD_SIZE: tl.constexpr,
230
- CACHE_OFFSET: tl.constexpr,
231
- RANK: tl.constexpr,
232
- WORLD: tl.constexpr,
233
- WINDOW: tl.constexpr,
234
- USE_PDL: tl.constexpr,
235
- launch_pdl: tl.constexpr,
236
-):
237
- """Compute this rank's shard, then publish it to every rank."""
238
- tl.static_assert(WINDOW == 4)
239
- token = tl.program_id(0).to(tl.int64)
240
- split = tl.program_id(1).to(tl.int64)
241
- CSS: tl.constexpr = CS // SPLITS
242
- channel = tl.arange(0, CS_P2)
243
- channel_mask = channel < CSS
244
- global_channel = split * CSS + channel
245
- head = tl.minimum(global_channel // HEAD_SIZE, CS // HEAD_SIZE - 1)
246
- dim = CACHE_OFFSET + global_channel % HEAD_SIZE
247
- slot = tl.load(slot_ptr + token)
248
- valid = slot >= 0
249
- position = tl.load(position_ptr + token)
250
- sequence = tl.load(sequence_ptr + token)
251
- safe_slot = tl.maximum(slot, 0).to(tl.int64)
252
- own_ptr = (
253
- cache_ptr
254
- + (safe_slot // block_size) * stride_cache_block
255
- + head * stride_cache_head
256
- + (safe_slot % block_size) * stride_cache_token
257
- + dim * stride_cache_dim
258
- )
259
- # These loads were made visible before reduce/insert could signal us and
260
- # are independent of its cache store. Hoisting them gives PDL useful work
261
- # to overlap while retaining the acquire before every cache read.
262
- residual = tl.load(
263
- residual_ptr + token * stride_residual_t + RANK * CS + global_channel,
264
- mask=channel_mask,
265
- other=0.0,
266
- )
267
- weight0 = tl.load(
268
- weight_ptr + global_channel * WINDOW,
269
- mask=channel_mask,
270
- other=0.0,
271
- ).to(tl.float32)
272
- weight1 = tl.load(
273
- weight_ptr + global_channel * WINDOW + 1,
274
- mask=channel_mask,
275
- other=0.0,
276
- ).to(tl.float32)
277
- weight2 = tl.load(
278
- weight_ptr + global_channel * WINDOW + 2,
279
- mask=channel_mask,
280
- other=0.0,
281
- ).to(tl.float32)
282
- weight3 = tl.load(
283
- weight_ptr + global_channel * WINDOW + 3,
284
- mask=channel_mask,
285
- other=0.0,
286
- ).to(tl.float32)
287
- ptrs = peer_ptrs.to(tl.pointer_type(tl.uint64))
288
- if USE_PDL:
289
- tl.extra.cuda.gdc_wait()
290
- current = tl.load(own_ptr, mask=valid & channel_mask, other=0.0)
291
-
292
- conv = tl.zeros([CS_P2], tl.float32)
293
- for tap_idx in tl.static_range(WINDOW):
294
- source_position = position - (WINDOW - 1) + tap_idx
295
- take = valid & (source_position >= 0)
296
- if tap_idx == WINDOW - 1:
297
- value = tl.where(take, current.to(tl.float32), 0.0)
298
- else:
299
- safe_position = tl.maximum(source_position, 0)
300
- logical_block = tl.minimum(safe_position // block_size, max_blocks - 1)
301
- physical_block = tl.load(
302
- block_table_ptr + sequence * stride_block_table_r + logical_block,
303
- mask=take,
304
- other=0,
305
- ).to(tl.int64)
306
- source_ptr = (
307
- cache_ptr
308
- + physical_block * stride_cache_block
309
- + head * stride_cache_head
310
- + (safe_position % block_size) * stride_cache_token
311
- + dim * stride_cache_dim
312
- )
313
- cached = tl.load(source_ptr, mask=take & channel_mask, other=0.0)
314
- value = tl.where(take, cached.to(tl.float32), 0.0)
315
- if tap_idx == 0:
316
- weight = weight0
317
- elif tap_idx == 1:
318
- weight = weight1
319
- elif tap_idx == 2:
320
- weight = weight2
321
- else:
322
- weight = weight3
323
- conv += value * weight
324
-
325
- # Preserve both bf16 rounding points of the original sublayer.
326
- short_conv_with_skip = (conv + current.to(tl.float32)).to(tl.bfloat16)
327
- output = (residual.to(tl.float32) + short_conv_with_skip.to(tl.float32)).to(
328
- tl.bfloat16
329
- )
330
-
331
- pair = tl.arange(0, CS_P2 // 2)
332
- pair_mask = pair < CSS // 2
333
- packed = _pack_bf16_pairs(output)
334
- row_offset = token * (C // 2) + RANK * (CS // 2) + split * (CSS // 2) + pair
335
-
336
- for destination in tl.static_range(WORLD):
337
- base = tl.load(ptrs + destination).to(tl.pointer_type(tl.uint32))
338
- tl.store(base + peer_offset_u32 + row_offset, packed, mask=pair_mask)
339
- if USE_PDL:
340
- tl.extra.cuda.gdc_launch_dependents()
341
-
342
-
343
-@triton.jit
344
-def _gather_norm_kernel(
345
- output_peer_ptrs,
346
- output_peer_offset_u32,
347
- norm_weight_ptr,
348
- normed_ptr,
349
- residual_out_ptr,
350
- eps,
351
- stride_output_t,
352
- C: tl.constexpr,
353
- C_P2: tl.constexpr,
354
- RANK: tl.constexpr,
355
- HAS_NORM: tl.constexpr,
356
- USE_PDL: tl.constexpr,
357
- launch_pdl: tl.constexpr,
358
-):
359
- """Consume a complete row; one CTA owns all outputs for one token."""
360
- token = tl.program_id(0).to(tl.int64)
361
- pair = tl.arange(0, C_P2 // 2)
362
- pair_mask = pair < C // 2
363
- output_ptrs = output_peer_ptrs.to(tl.pointer_type(tl.uint64))
364
- output_u32 = tl.load(output_ptrs + RANK).to(tl.pointer_type(tl.uint32))
365
- output_u32 += output_peer_offset_u32
366
- offsets = token * (C // 2) + pair
367
- channel = tl.arange(0, C_P2)
368
- channel_mask = channel < C
369
- # Gamma is independent of the preceding sconv publication. Keep the
370
- # acquire immediately before polling the Lamport output slots.
371
- if HAS_NORM:
372
- weight = tl.load(norm_weight_ptr + channel, mask=channel_mask, other=0.0)
373
- if USE_PDL:
374
- tl.extra.cuda.gdc_wait()
375
- packed = _wait_pairs(output_u32, offsets, pair_mask)
376
- row = _unpack_bf16_pairs(packed)
377
- tl.store(
378
- residual_out_ptr + token * stride_output_t + channel, row, mask=channel_mask
379
- )
380
- if HAS_NORM:
381
- row_f32 = tl.where(channel_mask, row.to(tl.float32), 0.0)
382
- inv_rms = tl.rsqrt(tl.sum(row_f32 * row_f32, axis=0) / C + eps)
383
- tl.store(
384
- normed_ptr + token * stride_output_t + channel,
385
- (row_f32 * inv_rms * weight.to(tl.float32)).to(tl.bfloat16),
386
- mask=channel_mask,
387
- )
388
- tl.store(
389
- output_u32 + offsets,
390
- tl.full([C_P2 // 2], _EMPTY_PAIR, tl.uint32),
391
- mask=pair_mask,
392
- )
393
- if USE_PDL:
394
- tl.extra.cuda.gdc_launch_dependents()
395
-
396
-
397
-@triton.jit
398
-def _validate_lamport_init_kernel(
399
- input_peer_ptrs,
400
- output_peer_ptrs,
401
- bad_ptr,
402
- num_pairs,
403
- RANK: tl.constexpr,
404
- BLOCK: tl.constexpr,
405
-):
406
- """Validate both complete local allocations through their fabric pointers."""
407
- offsets = tl.program_id(0).to(tl.int64) * BLOCK + tl.arange(0, BLOCK)
408
- mask = offsets < num_pairs
409
- ptrs_in = input_peer_ptrs.to(tl.pointer_type(tl.uint64))
410
- ptrs_out = output_peer_ptrs.to(tl.pointer_type(tl.uint64))
411
- local_in = tl.load(ptrs_in + RANK).to(tl.pointer_type(tl.uint32))
412
- local_out = tl.load(ptrs_out + RANK).to(tl.pointer_type(tl.uint32))
413
- value_in = tl.load(local_in + offsets, mask=mask, other=_EMPTY_PAIR)
414
- value_out = tl.load(local_out + offsets, mask=mask, other=_EMPTY_PAIR)
415
- bad = tl.max(
416
- tl.where(mask & ((value_in != _EMPTY_PAIR) | (value_out != _EMPTY_PAIR)), 1, 0)
417
- )
418
- if bad != 0:
419
- tl.atomic_max(bad_ptr, 1)
420
-
421
-
422
-class LamportRSConv:
423
- """Persistent symmetric buffers for one TP group."""
424
-
425
- def _initialize_lamport_buffers(self) -> None:
426
- """Arm every slot and collectively verify the exact sentinel bits.
427
-
428
- Do not replace this with a zero-fill: Lamport readiness distinguishes
429
- the bf16 bit pattern for -0.0 (0x8000) from every published payload.
430
- The validation is deliberately collective so one rank cannot enter the
431
- first polling kernel while another rank still has an unarmed buffer.
432
- """
433
- if not self.buf_in.is_contiguous() or not self.buf_out.is_contiguous():
434
- raise RuntimeError("Lamport symmetric buffers must be contiguous")
435
- if self.buf_in.numel() % 2 or self.buf_out.numel() % 2:
436
- raise RuntimeError("Lamport buffers must contain whole uint32 pairs")
437
-
438
- # Fill through int16 so each bf16 lane receives the exact -0.0 bits.
439
- # This covers all three generations and all max-token slots, including
440
- # slots that a smaller first invocation does not touch.
441
- self.buf_in.view(torch.int16).fill_(-0x8000)
442
- self.buf_out.view(torch.int16).fill_(-0x8000)
443
- torch.accelerator.synchronize(self.device)
444
- self.tp.barrier()
445
-
446
- # Scan the complete local allocations. Since every rank performs the
447
- # scan and participates in MAX, success proves every symmetric backing
448
- # allocation was armed before any rank is allowed to use generation 0.
449
- bad = torch.logical_or(
450
- self.buf_in.view(torch.int16).ne(-0x8000).any(),
451
- self.buf_out.view(torch.int16).ne(-0x8000).any(),
452
- ).to(dtype=torch.float32)
453
- bad = self.tp.all_reduce(bad)
454
- if int(bad.item()) != 0:
455
- raise RuntimeError("Lamport sentinel initialization failed on a TP rank")
456
- self.tp.barrier()
457
-
458
- def _initialize_mnnvl_buffers(self) -> None:
459
- """Initialize and validate FlashInfer fabric-mapped Lamport storage."""
460
- self._mnnvl_input_handle.lamport_initialize(self.rank, torch.bfloat16)
461
- self._mnnvl_output_handle.lamport_initialize(self.rank, torch.bfloat16)
462
- torch.accelerator.synchronize(self.device)
463
- self.tp.barrier()
464
-
465
- bad = torch.zeros((), dtype=torch.int32, device=self.device)
466
- num_pairs = self.num_buffers * self.max_tokens * self.hidden_size // 2
467
- _validate_lamport_init_kernel[(triton.cdiv(num_pairs, 256),)](
468
- self.input_peer_ptrs,
469
- self.output_peer_ptrs,
470
- bad,
471
- num_pairs,
472
- RANK=self.rank,
473
- BLOCK=256,
474
- num_warps=4,
475
- )
476
- bad = self.tp.all_reduce(bad.to(torch.float32))
477
- if int(bad.item()) != 0:
478
- raise RuntimeError("MNNVL Lamport sentinel initialization failed")
479
- self.tp.barrier()
480
-
481
- def __init__(
482
- self, hidden_size: int, window_size: int, max_tokens: int = _MAX_TOKENS
483
- ) -> None:
484
- import torch.distributed._symmetric_memory as symm_mem
485
-
486
- tp = get_tp_group()
487
- self.tp = tp
488
- self.group = tp.device_group
489
- self.world_size = tp.world_size
490
- self.rank = tp.rank_in_group
491
- self.device = torch.device(tp.device)
492
- if self.world_size not in (2, 4, 8):
493
- raise ValueError(f"TP world size must be 2, 4, or 8, got {self.world_size}")
494
- if hidden_size % (2 * self.world_size) != 0:
495
- raise ValueError("hidden size must produce an even shard on every rank")
496
- if window_size != 4:
497
- raise ValueError(f"short-conv window size must be 4, got {window_size}")
498
- if max_tokens < 1 or max_tokens > _MAX_TOKENS:
499
- raise ValueError(f"max_tokens must be in [1, {_MAX_TOKENS}]")
500
-
501
- is_cross_node = not all(in_the_same_node_as(tp.cpu_group))
502
- self.hidden_size = hidden_size
503
- self.window_size = window_size
504
- self.max_tokens = max_tokens
505
- self.shard_size = hidden_size // self.world_size
506
- # Three generations follow FlashInfer's Lamport layout. A generation
507
- # is reused only after two intervening collective calls.
508
- self.num_buffers = 3
509
- self.input_generation_bytes = max_tokens * hidden_size * 2
510
- self.output_generation_bytes = max_tokens * hidden_size * 2
511
- self.use_pdl = torch.cuda.get_device_capability(self.device)[0] >= 9
512
- if is_cross_node:
513
- try:
514
- from flashinfer.comm.mnnvl import (
515
- McastGPUBuffer,
516
- TorchDistBackend,
517
- is_mnnvl_fabric_supported,
518
- )
519
- except ImportError as error:
520
- raise RuntimeError(
521
- "cross-node TP requires FlashInfer MNNVL support"
522
- ) from error
523
-
524
- local_supported = int(
525
- is_mnnvl_fabric_supported(torch.accelerator.current_device_index())
526
- )
527
- unsupported = torch.tensor(
528
- 1 - local_supported, dtype=torch.float32, device=self.device
529
- )
530
- unsupported = tp.all_reduce(unsupported)
531
- if int(unsupported.item()) != 0:
532
- raise RuntimeError("cross-node TP is supported only on MNNVL fabric")
533
-
534
- comm_backend = TorchDistBackend(self.group)
535
- allocation_bytes = self.num_buffers * max_tokens * hidden_size * 2
536
- self._mnnvl_input_handle = McastGPUBuffer(
537
- allocation_bytes,
538
- self.world_size,
539
- self.rank,
540
- self.device,
541
- comm_backend,
542
- )
543
- self._mnnvl_output_handle = McastGPUBuffer(
544
- allocation_bytes,
545
- self.world_size,
546
- self.rank,
547
- self.device,
548
- comm_backend,
549
- )
550
- self.input_peer_ptrs = self._mnnvl_input_handle.get_buffer_ptrs_dev()
551
- self.output_peer_ptrs = self._mnnvl_output_handle.get_buffer_ptrs_dev()
552
- self._initialize_mnnvl_buffers()
553
- logger.info("using FlashInfer fabric-mapped MNNVL Lamport buffers")
554
- else:
555
- self.buf_in = symm_mem.empty(
556
- self.num_buffers,
557
- max_tokens,
558
- self.world_size,
559
- self.shard_size,
560
- dtype=torch.bfloat16,
561
- device=self.device,
562
- )
563
- self.buf_out = symm_mem.empty(
564
- self.num_buffers,
565
- max_tokens,
566
- hidden_size,
567
- dtype=torch.bfloat16,
568
- device=self.device,
569
- )
570
- group_name = self.group.group_name
571
- input_handle = symm_mem.rendezvous(self.buf_in, group_name)
572
- output_handle = symm_mem.rendezvous(self.buf_out, group_name)
573
- self.input_peer_ptrs = input_handle.buffer_ptrs_dev
574
- self.output_peer_ptrs = output_handle.buffer_ptrs_dev
575
- self._input_handle = input_handle
576
- self._output_handle = output_handle
577
- self._initialize_lamport_buffers()
578
- self.generation = 0
579
-
580
- def usable(self, num_tokens: int) -> bool:
581
- return 0 < num_tokens <= self.max_tokens
582
-
583
- def rs_sconv_ag_add_norm(
584
- self,
585
- input_tensor: torch.Tensor,
586
- residual: torch.Tensor,
587
- conv_weight: torch.Tensor,
588
- norm_weight: torch.Tensor | None,
589
- eps: float,
590
- cache: torch.Tensor,
591
- positions: torch.Tensor,
592
- block_table: torch.Tensor,
593
- seq_idx: torch.Tensor,
594
- slot_mapping: torch.Tensor,
595
- off_s: int,
596
- ws: int,
597
- block_size: int,
598
- ) -> tuple[torch.Tensor | None, torch.Tensor]:
599
- """Return ``(normed | None, new_residual)``, both shaped ``[T, 6144]``."""
600
- tokens, hidden_size = residual.shape
601
- if not self.usable(tokens):
602
- raise ValueError(f"num_tokens must be in [1, {self.max_tokens}]")
603
- if hidden_size != self.hidden_size or residual.dtype != torch.bfloat16:
604
- raise ValueError("residual must be bf16 [T, 6144]")
605
- if (
606
- input_tensor.shape != residual.shape
607
- or input_tensor.dtype != torch.bfloat16
608
- or input_tensor.stride(1) != 1
609
- ):
610
- raise ValueError("input_tensor must be channel-contiguous bf16 [T, 6144]")
611
- shard_size = hidden_size // self.world_size
612
- if conv_weight.shape != (shard_size, self.window_size):
613
- raise ValueError(
614
- f"conv_weight must have shape [{shard_size}, {self.window_size}]"
615
- )
616
- if (
617
- conv_weight.dtype != torch.bfloat16
618
- or conv_weight.stride(0) != self.window_size
619
- ):
620
- raise ValueError("conv_weight must be contiguous bf16")
621
- if norm_weight is not None and (
622
- norm_weight.shape != (hidden_size,) or norm_weight.dtype != torch.bfloat16
623
- ):
624
- raise ValueError("norm_weight must be bf16 [6144] or None")
625
- if cache.dtype != torch.bfloat16 or cache.ndim != 4:
626
- raise ValueError("cache must be a 4-D bf16 tensor")
627
- if shard_size % ws != 0 or cache.shape[1] != shard_size // ws:
628
- raise ValueError("cache head layout is inconsistent with ws")
629
- if off_s < 0 or off_s + ws > cache.shape[3]:
630
- raise ValueError("cache channel offset is out of bounds")
631
-
632
- index = self.generation
633
- input_offset = index * self.input_generation_bytes // 4
634
- output_offset = index * self.output_generation_bytes // 4
635
- normed = torch.empty_like(residual) if norm_weight is not None else None
636
- residual_out = torch.empty_like(residual)
637
- phase_splits = 8 if tokens == 1 and shard_size % 8 == 0 else 1
638
- phase_tile_p2 = triton.next_power_of_2(shard_size // phase_splits)
639
- phase_grid = (tokens, phase_splits)
640
- # Wide CTAs win before the grid saturates; smaller CTAs reduce register
641
- # pressure once high-throughput batches provide enough parallelism.
642
- if 128 <= tokens <= 2048:
643
- phase_warps = 16
644
- elif tokens > 2048:
645
- phase_warps = 8
646
- else:
647
- phase_warps = 4
648
- gather_warps = 4 if tokens >= 256 else 8
649
- _publish_input_kernel[phase_grid](
650
- input_tensor,
651
- self.input_peer_ptrs,
652
- input_offset,
653
- input_tensor.stride(0),
654
- C=hidden_size,
655
- CS=shard_size,
656
- CS_P2=phase_tile_p2,
657
- SPLITS=phase_splits,
658
- RANK=self.rank,
659
- WORLD=self.world_size,
660
- USE_PDL=self.use_pdl,
661
- launch_pdl=self.use_pdl,
662
- num_warps=phase_warps,
663
- )
664
- _reduce_insert_kernel[phase_grid](
665
- self.input_peer_ptrs,
666
- input_offset,
667
- cache,
668
- slot_mapping,
669
- cache.stride(0),
670
- cache.stride(1),
671
- cache.stride(2),
672
- cache.stride(3),
673
- block_size,
674
- C=hidden_size,
675
- CS=shard_size,
676
- CS_P2=phase_tile_p2,
677
- SPLITS=phase_splits,
678
- HEAD_SIZE=ws,
679
- CACHE_OFFSET=off_s,
680
- RANK=self.rank,
681
- WORLD=self.world_size,
682
- USE_PDL=self.use_pdl,
683
- launch_pdl=self.use_pdl,
684
- num_warps=phase_warps,
685
- )
686
- _sconv_publish_kernel[phase_grid](
687
- self.output_peer_ptrs,
688
- output_offset,
689
- residual,
690
- conv_weight,
691
- cache,
692
- positions,
693
- seq_idx,
694
- slot_mapping,
695
- block_table,
696
- residual.stride(0),
697
- cache.stride(0),
698
- cache.stride(1),
699
- cache.stride(2),
700
- cache.stride(3),
701
- block_table.stride(0),
702
- block_table.shape[1],
703
- block_size,
704
- C=hidden_size,
705
- CS=shard_size,
706
- CS_P2=phase_tile_p2,
707
- SPLITS=phase_splits,
708
- HEAD_SIZE=ws,
709
- CACHE_OFFSET=off_s,
710
- RANK=self.rank,
711
- WORLD=self.world_size,
712
- WINDOW=self.window_size,
713
- USE_PDL=self.use_pdl,
714
- launch_pdl=self.use_pdl,
715
- num_warps=phase_warps,
716
- )
717
- _gather_norm_kernel[(tokens,)](
718
- self.output_peer_ptrs,
719
- output_offset,
720
- norm_weight if norm_weight is not None else residual,
721
- normed if normed is not None else residual_out,
722
- residual_out,
723
- eps,
724
- residual.stride(0),
725
- C=hidden_size,
726
- C_P2=triton.next_power_of_2(hidden_size),
727
- RANK=self.rank,
728
- HAS_NORM=norm_weight is not None,
729
- USE_PDL=self.use_pdl,
730
- launch_pdl=self.use_pdl,
731
- num_warps=gather_warps,
732
- )
733
- self.generation = (index + 1) % self.num_buffers
734
- return normed, residual_out
735
-
736
-
737
-_STATE: LamportRSConv | None = None
738
-_STATE_FAILED = False
739
-
740
-
741
-def initialize_lamport_rs_conv(
742
- hidden_size: int, window_size: int, max_num_batched_tokens: int
743
-) -> None:
744
- """Collectively initialize the TP-group state during model construction."""
745
- global _STATE, _STATE_FAILED
746
- if _STATE is not None:
747
- if _STATE.hidden_size != hidden_size or _STATE.window_size != window_size:
748
- raise RuntimeError("all Lamport users must share hidden and window sizes")
749
- return
750
- if _STATE_FAILED or os.environ.get("LAMPORT_RS_SCONV", "1") == "0":
751
- return
752
- try:
753
- max_tokens = min(_MAX_TOKENS, max_num_batched_tokens)
754
- _STATE = LamportRSConv(hidden_size, window_size, max_tokens=max_tokens)
755
- except Exception:
756
- _STATE_FAILED = True
757
- logger.exception("fused collective unavailable; use the NCCL fallback")
758
-
759
-
760
-def get_lamport_rs_conv(hidden_size: int, window_size: int) -> LamportRSConv | None:
761
- """Return the state initialized with the model, or ``None`` for fallback."""
762
- if _STATE is not None and (
763
- _STATE.hidden_size != hidden_size or _STATE.window_size != window_size
764
- ):
765
- raise RuntimeError("all Lamport users must share hidden and window sizes")
766
- return _STATE
vllm/models/inkling/amd/ops/mm_towers.py
deleted
-190
@@ -1,190 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-"""Fused CUDA kernels for the Inkling vision/audio towers.
4
-
5
-Both kernels keep the reference paths' fp32 accumulation and per-op bf16
6
-rounding points (native ``rms_norm`` / ``F.gelu``); outputs are frequently
7
-bit-identical and otherwise differ by 1-2 bf16 ulps from reduction-order
8
-(real-checkpoint-weight cosine vs reference > 0.9999998).
9
-"""
10
-
11
-from __future__ import annotations
12
-
13
-import torch
14
-
15
-from vllm.triton_utils import tl, tldevice, triton
16
-
17
-from .norm import _get_num_warps_from_block_size
18
-
19
-
20
-@triton.jit
21
-def _dmel_embed_sum_norm_kernel(
22
- idx_ptr, # [T, NB] int32 dMel bin indices (values in [0, VOCAB))
23
- w_ptr, # [NB * VOCAB, D] bf16 embedding table
24
- norm_w_ptr, # [D] (unused if HAS_NORM=False)
25
- out_ptr, # [T, D] bf16
26
- eps,
27
- D,
28
- stride_idx_t,
29
- NB: tl.constexpr,
30
- VOCAB: tl.constexpr,
31
- D_P2: tl.constexpr,
32
- HAS_NORM: tl.constexpr,
33
-):
34
- t = tl.program_id(0).to(tl.int64)
35
- offs = tl.arange(0, D_P2)
36
- mask = offs < D
37
- # One embedding row per mel bin (bin b uses table rows [b*VOCAB, (b+1)*VOCAB)),
38
- # summed in fp32 (matches torch's fp32-accumulated bf16 .sum()).
39
- acc = tl.zeros([D_P2], dtype=tl.float32)
40
- for b in tl.static_range(NB):
41
- v = tl.load(idx_ptr + t * stride_idx_t + b)
42
- row = (b * VOCAB + v).to(tl.int64)
43
- acc += tl.load(w_ptr + row * D + offs, mask=mask, other=0.0).to(tl.float32)
44
- h = acc.to(tl.bfloat16)
45
- if HAS_NORM:
46
- # Match ir.ops.rms_norm: fp32 variance/normalize, then a single-rounded
47
- # bf16 multiply with the bf16 weight.
48
- x32 = h.to(tl.float32)
49
- var = tl.sum(tl.where(mask, x32 * x32, 0.0), axis=0) / D
50
- xn = (x32 * tl.math.rsqrt(var + eps)).to(tl.bfloat16)
51
- w = tl.load(norm_w_ptr + offs, mask=mask, other=0.0)
52
- h = xn * w
53
- tl.store(out_ptr + t * D + offs, h, mask=mask)
54
-
55
-
56
-def dmel_embed_sum_norm(
57
- dmel_idx: torch.Tensor, # [T, NB] int32
58
- weight: torch.Tensor, # [NB * VOCAB, D] bf16
59
- norm_weight: torch.Tensor | None,
60
- eps: float,
61
-) -> torch.Tensor:
62
- """``rmsnorm(sum_b weight[b * VOCAB + idx[:, b]])`` in one launch (no
63
- [T, NB, D] intermediate)."""
64
- T, nb = dmel_idx.shape
65
- D = weight.shape[1]
66
- assert weight.shape[0] % nb == 0
67
- vocab = weight.shape[0] // nb
68
- out = torch.empty((T, D), dtype=weight.dtype, device=weight.device)
69
- if T == 0:
70
- return out
71
- d_p2 = triton.next_power_of_2(D)
72
- _dmel_embed_sum_norm_kernel[(T,)](
73
- dmel_idx,
74
- weight,
75
- norm_weight if norm_weight is not None else weight,
76
- out,
77
- eps,
78
- D,
79
- dmel_idx.stride(0),
80
- NB=nb,
81
- VOCAB=vocab,
82
- D_P2=d_p2,
83
- HAS_NORM=norm_weight is not None,
84
- # Swept on GB200: 8 warps beats the block-size heuristic's 16 by ~4%
85
- # at large T (the 80-row gather chain is latency- not lane-bound).
86
- num_warps=8,
87
- )
88
- return out
89
-
90
-
91
-@triton.jit
92
-def _rmsnorm_gelu_kernel(
93
- x_ptr, # [R, D] bf16
94
- w_ptr, # [D]
95
- out_ptr, # [R, D] bf16 (or the folded layout when FOLD)
96
- eps,
97
- R,
98
- D,
99
- D_P2: tl.constexpr,
100
- BLOCK_M: tl.constexpr, # rows per block (>1 for small D)
101
- HAS_GELU: tl.constexpr,
102
- FOLD: tl.constexpr,
103
- # fold geometry: input rows index [N, T, H, W]; the store scatters each
104
- # row to (out_row, slot) of fold_timespace_to_depth's output layout.
105
- FT: tl.constexpr,
106
- FH: tl.constexpr,
107
- FW: tl.constexpr,
108
- TF: tl.constexpr,
109
- HF: tl.constexpr,
110
-):
111
- pid = tl.program_id(0).to(tl.int64)
112
- rows = pid * BLOCK_M + tl.arange(0, BLOCK_M)
113
- rmask = rows < R
114
- offs = tl.arange(0, D_P2)
115
- mask = rmask[:, None] & (offs < D)[None, :]
116
- x32 = tl.load(x_ptr + rows[:, None] * D + offs[None, :], mask=mask, other=0.0).to(
117
- tl.float32
118
- )
119
- var = tl.sum(x32 * x32, axis=1) / D
120
- xn = (x32 * tl.math.rsqrt(var + eps)[:, None]).to(tl.bfloat16)
121
- w = tl.load(w_ptr + offs, mask=offs < D, other=0.0)
122
- h = xn * w[None, :] # bf16 multiply, matching ir.ops.rms_norm
123
- if HAS_GELU:
124
- # Exact (erf) GELU on the bf16-rounded norm output, fp32 math,
125
- # matching F.gelu's opmath on a bf16 tensor.
126
- g32 = h.to(tl.float32)
127
- h = (0.5 * g32 * (1.0 + tldevice.erf(g32 * 0.7071067811865476))).to(tl.bfloat16)
128
- if FOLD:
129
- # Store directly into the next layer's folded layout (a pure
130
- # permutation — replaces the separate fold copy pass).
131
- t = (rows // (FH * FW)) % FT
132
- hh = (rows // FW) % FH
133
- ww = rows % FW
134
- n = rows // (FT * FH * FW)
135
- slot = ((t % TF) * HF + hh % HF) * HF + ww % HF
136
- out_row = ((n * (FT // TF) + t // TF) * (FH // HF) + hh // HF) * (
137
- FW // HF
138
- ) + ww // HF
139
- base = (out_row * (TF * HF * HF) + slot) * D
140
- tl.store(out_ptr + base[:, None] + offs[None, :], h, mask=mask)
141
- else:
142
- tl.store(out_ptr + rows[:, None] * D + offs[None, :], h, mask=mask)
143
-
144
-
145
-def rmsnorm_gelu(
146
- x: torch.Tensor, # [..., D] bf16 contiguous
147
- weight: torch.Tensor,
148
- eps: float,
149
- gelu: bool = True,
150
- fold: tuple[int, int] | None = None, # (t_fold, hw_fold) of the NEXT fold
151
-) -> torch.Tensor:
152
- """Fused ``gelu(rmsnorm(x))`` (or plain rmsnorm); multiple rows per block
153
- when D is small. With ``fold``, x must be [N, T, H, W, D] and the output
154
- comes back as ``fold_timespace_to_depth(result, *fold)``."""
155
- D = x.shape[-1]
156
- flat = x.reshape(-1, D)
157
- assert flat.stride(1) == 1 and flat.stride(0) == D
158
- R = flat.shape[0]
159
- if fold is None:
160
- out = torch.empty_like(flat)
161
- ft = fh = fw = tf = hf = 1
162
- out_shape = x.shape
163
- else:
164
- tf, hf = fold
165
- N, ft, fh, fw, _ = x.shape
166
- out_shape = (N, ft // tf, fh // hf, fw // hf, tf * hf * hf * D)
167
- out = torch.empty(out_shape, dtype=x.dtype, device=x.device)
168
- if R == 0:
169
- return out.reshape(out_shape)
170
- d_p2 = triton.next_power_of_2(D)
171
- block_m = max(1, 4096 // d_p2)
172
- _rmsnorm_gelu_kernel[(triton.cdiv(R, block_m),)](
173
- flat,
174
- weight,
175
- out,
176
- eps,
177
- R,
178
- D,
179
- D_P2=d_p2,
180
- BLOCK_M=block_m,
181
- HAS_GELU=gelu,
182
- FOLD=fold is not None,
183
- FT=ft,
184
- FH=fh,
185
- FW=fw,
186
- TF=tf,
187
- HF=hf,
188
- num_warps=_get_num_warps_from_block_size(d_p2 * block_m),
189
- )
190
- return out.reshape(out_shape)
vllm/models/kimi_k3/nvidia/ops/cute_dsl/latent_moe_tail/primitives.py
-31
@@ -587,34 +587,3 @@ def warp_sum_specialized(
587
mask_and_clamp=31,
588
)
589
return value
590
-
591
-
592
-@cute.jit
593
-def block_sum_specialized(
594
- value: Float32,
595
- warp_sums: cute.Tensor,
596
- tidx: Int32,
597
- warps: cutlass.Constexpr[int],
598
- last_warp_lanes: cutlass.Constexpr[int],
599
- last_warp_mask: cutlass.Constexpr[int],
600
-) -> Float32:
601
- """Upstream-equivalent FP32 block reduction."""
602
-
603
- lane = cute.arch.lane_idx()
604
- warp_idx = cute.arch.warp_idx()
605
- value = warp_sum_specialized(
606
- value, warp_idx, lane, warps, last_warp_lanes, last_warp_mask
607
- )
608
- if lane == 0:
609
- warp_sums[warp_idx] = value
610
- cute.arch.barrier()
611
-
612
- block_sum = Float32(0.0)
613
- if warp_idx == 0:
614
- if lane < Int32(warps):
615
- block_sum = warp_sums[lane]
616
- block_sum = cute.arch.warp_reduction_sum(block_sum)
617
- if lane == 0:
618
- warp_sums[0] = block_sum
619
- cute.arch.barrier()
620
- return warp_sums[0]
vllm/v1/attention/ops/triton_fp8_mqa_logits.py
deleted
-262
@@ -1,262 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-"""Temporary gfx942 fallback for AITER's fp8_mqa_logits kernel.
4
-
5
-This module vendors AITER's Triton fp8_mqa_logits kernel with the gfx942
6
-tile-size workaround from ROCm/aiter#3257. It is used only while vLLM's
7
-pinned AITER version lacks that fix.
8
-
9
-TODO: Remove this vendored copy once vLLM pins an AITER version that includes
10
-ROCm/aiter#3257 bugfix for gfx942.
11
-"""
12
-
13
-import torch
14
-
15
-from vllm.triton_utils import tl, triton
16
-
17
-# gfx942 (MI300X) has 64 KiB of LDS per CU. We accept the default
18
-# (BLOCK_KV=128, num_stages=2) tile only when *both* of these hold:
19
-#
20
-# 1. Occupancy gate. With waves_per_eu=2 and num_warps=4 we target two
21
-# workgroups co-resident on a CU -> per-WG LDS budget = 32 KiB. Triton
22
-# keeps Q in registers (loop-invariant) and the fp32 scores accumulator
23
-# in VGPRs (heavy VALU), so only the double-buffered KV tile is
24
-# expected to live in LDS. A 0.9 safety factor leaves headroom for any
25
-# LDS overhead the compiler may add.
26
-#
27
-# 2. Hardware ceiling. Defensive upper bound that also counts Q and
28
-# scores against the 64 KiB CU limit, in case a Triton version (older
29
-# or future) decides to spill them to LDS. False positives here only
30
-# shrink the tile; false negatives are JIT-aborts, so we lean
31
-# conservative.
32
-_GFX942_CU_LDS_BYTES = 64 * 1024
33
-_GFX942_PER_WG_LDS_BUDGET_BYTES = _GFX942_CU_LDS_BYTES * 9 // 20 # ~28.8 KiB
34
-
35
-
36
-def _gfx942_default_tile_fits_lds(num_heads: int, head_size: int) -> bool:
37
- """Return True iff (BLOCK_KV=128, num_stages=2) fits in MI300X LDS."""
38
- BLOCK_KV = 128
39
- NUM_STAGES = 2
40
- kv_bytes = head_size * BLOCK_KV * NUM_STAGES
41
- scores_bytes = num_heads * BLOCK_KV * 4
42
- q_bytes = num_heads * head_size
43
- fits_occupancy = kv_bytes < _GFX942_PER_WG_LDS_BUDGET_BYTES
44
- fits_hardware = q_bytes + kv_bytes + scores_bytes <= _GFX942_CU_LDS_BYTES
45
- return fits_occupancy and fits_hardware
46
-
47
-
48
-@triton.jit
49
-def _fp8_mqa_logits_kernel(
50
- Q_ptr, # fp8e4m3 [seq_len, H, D]
51
- KV_ptr, # fp8e4m3 [seq_len_kv, D]
52
- kv_scales_ptr, # fp32 [seq_len_kv]
53
- weights_ptr, # fp32 [seq_len, H]
54
- cu_start_ptr, # int32 [seq_len]
55
- cu_end_ptr, # int32 [seq_len]
56
- logits_ptr, # fp32 [seq_len, seq_len_kv]
57
- seq_len,
58
- seq_len_kv,
59
- NUM_HEADS: tl.constexpr,
60
- HEAD_SIZE: tl.constexpr,
61
- # strides
62
- stride_q_s: tl.int64,
63
- stride_q_h: tl.constexpr,
64
- stride_q_d: tl.constexpr,
65
- stride_kv_s: tl.int64,
66
- stride_kv_d: tl.constexpr,
67
- stride_w_s: tl.int64,
68
- stride_w_h: tl.constexpr,
69
- stride_logits_s: tl.int64,
70
- stride_logits_k: tl.int64,
71
- # block sizes
72
- BLOCK_KV: tl.constexpr,
73
-):
74
- row_id = tl.program_id(0)
75
- # go from larger to smaller in terms of work
76
- # to reduce the tail effect
77
- row_id = tl.num_programs(0) - row_id - 1
78
- tl.assume(row_id >= 0)
79
- tl.assume(stride_q_s > 0)
80
- tl.assume(stride_q_h > 0)
81
- tl.assume(stride_q_d > 0)
82
- tl.assume(stride_kv_s > 0)
83
- tl.assume(stride_kv_d > 0)
84
- tl.assume(stride_w_s > 0)
85
- tl.assume(stride_w_h > 0)
86
-
87
- logits_row_ptrs = logits_ptr + row_id * stride_logits_s
88
-
89
- h_inds = tl.arange(0, NUM_HEADS)[:, None]
90
- d_inds = tl.arange(0, HEAD_SIZE)
91
-
92
- # load Q[BLOCK_Q, NUM_HEADS, HEAD_SIZE]
93
- q_ptrs = (
94
- Q_ptr + row_id * stride_q_s + h_inds * stride_q_h + d_inds[None, :] * stride_q_d
95
- )
96
-
97
- q_block = tl.load(q_ptrs, cache_modifier=".cg")
98
- w_ptrs = weights_ptr + row_id * stride_w_s + h_inds * stride_w_h
99
- w_block = tl.load(w_ptrs, cache_modifier=".cg").to(tl.float32)
100
-
101
- # Load start/end for each row in this block
102
- start_ind = tl.load(cu_start_ptr + row_id)
103
- end_ind = tl.load(cu_end_ptr + row_id)
104
-
105
- start_ind = tl.maximum(start_ind, 0)
106
- end_ind = tl.minimum(end_ind, seq_len_kv)
107
- shifted_end = end_ind - start_ind
108
- shifted_unmasked_end = shifted_end // BLOCK_KV * BLOCK_KV
109
-
110
- kv_col_offsets = tl.arange(0, BLOCK_KV) + start_ind
111
- kv_ptrs = (
112
- KV_ptr + kv_col_offsets[None, :] * stride_kv_s + d_inds[:, None] * stride_kv_d
113
- )
114
-
115
- kv_scales_ptrs = kv_scales_ptr + kv_col_offsets
116
-
117
- logits_ptrs = logits_row_ptrs + kv_col_offsets * stride_logits_k
118
-
119
- # Loop over KV tiles
120
- for _ in tl.range(0, shifted_unmasked_end, BLOCK_KV):
121
- kv_block = tl.load(kv_ptrs)
122
- kv_scales = tl.load(kv_scales_ptrs)
123
-
124
- # [NUM_HEADS, BLOCK_KV] = [NUM_HEADS, HEAD_SIZE] x [HEAD_SIZE, BLOCK_KV]
125
- scores = tl.dot(q_block, kv_block, input_precision="ieee")
126
- # Multiply by kv_scales (broadcast along rows)
127
- scores = scores * kv_scales[None, :]
128
- # ReLU
129
- scores = tl.maximum(scores, 0.0)
130
- scores = scores * w_block
131
- # [NUM_HEADS, BLOCK_KV] -> [BLOCK_KV, ]
132
- scores = tl.sum(scores, axis=0)
133
- tl.store(logits_ptrs, scores)
134
-
135
- kv_ptrs += BLOCK_KV * stride_kv_s
136
- kv_scales_ptrs += BLOCK_KV
137
- logits_ptrs += BLOCK_KV * stride_logits_k
138
- kv_col_offsets += BLOCK_KV
139
-
140
- # masked load
141
- kv_col_mask = kv_col_offsets < end_ind
142
- kv_block = tl.load(kv_ptrs, mask=kv_col_mask[None, :], other=0.0)
143
- kv_scales = tl.load(kv_scales_ptrs, mask=kv_col_mask, other=0.0)
144
-
145
- # [NUM_HEADS, BLOCK_KV] = [NUM_HEADS, HEAD_SIZE] x [HEAD_SIZE, BLOCK_KV]
146
- scores = tl.dot(q_block, kv_block, input_precision="ieee")
147
- # Multiply by kv_scales (broadcast along rows)
148
- scores = scores * kv_scales[None, :]
149
- # ReLU
150
- scores = tl.maximum(scores, 0.0)
151
- scores = scores * w_block
152
- # [NUM_HEADS, BLOCK_KV] -> [BLOCK_KV, ]
153
- scores = tl.sum(scores, axis=0)
154
- # masked store
155
- in_window = (kv_col_offsets >= start_ind) & (kv_col_offsets < end_ind)
156
- tl.store(logits_ptrs, scores, mask=in_window)
157
-
158
-
159
-def fp8_mqa_logits_gfx942(
160
- q: torch.Tensor,
161
- k_fp8: torch.Tensor,
162
- kv_scales: torch.Tensor,
163
- weights: torch.Tensor,
164
- cu_starts: torch.Tensor,
165
- cu_ends: torch.Tensor,
166
-) -> torch.Tensor:
167
- """Compute FP8 MQA logits on MI300X (gfx942) using the vendored kernel.
168
-
169
- Drop-in replacement for ``aiter.ops.triton.attention.fp8_mqa_logits.
170
- fp8_mqa_logits`` on MI300X. Selects ``(BLOCK_KV, num_stages)`` based on
171
- whether the default tile fits within the 64 KiB LDS budget of a gfx942
172
- CU (see module docstring).
173
-
174
- Args:
175
- q: Query tensor of shape ``[M, H, D]``, FP8 dtype.
176
- k_fp8: Key tensor of shape ``[N, D]``, FP8 dtype.
177
- kv_scales: K scales of shape ``[N]`` (or ``[N, 1]`` -- viewed as
178
- ``[N]``), float32.
179
- weights: Per-head weights of shape ``[M, H]``, float32.
180
- cu_starts: Start indices (inclusive) of shape ``[M]``, int32.
181
- cu_ends: End indices (exclusive) of shape ``[M]``, int32.
182
-
183
- Returns:
184
- Logits of shape ``[M, N]``, float32 -- positions outside
185
- ``[cu_starts[i], cu_ends[i])`` for row ``i`` are pre-filled with
186
- ``-inf`` so the caller can run a top-k without masking.
187
- """
188
- seq_len, num_heads, head_size = q.shape
189
- seq_len_kv = k_fp8.shape[0]
190
- assert num_heads & (num_heads - 1) == 0, (
191
- f"num_heads must be a power of two (got {num_heads})"
192
- )
193
- assert head_size & (head_size - 1) == 0, (
194
- f"head_size must be a power of two (got {head_size})"
195
- )
196
-
197
- # The kernel walks ``kv_scales`` as a 1-D contiguous array of size N
198
- # (it indexes by ``kv_scales_ptr + kv_col_offsets``). The vLLM caller
199
- # passes a ``[N, 4]`` uint8 view-cast-to-float32 which lands as
200
- # ``[N, 1]`` contiguous -- byte-identical to ``[N]`` -- but flatten
201
- # explicitly to keep the kernel's pointer arithmetic intent clear.
202
- kv_scales_1d = kv_scales.reshape(-1)
203
-
204
- # Initialise with -inf so positions outside [cu_starts, cu_ends) read
205
- # as ``-inf`` after the masked store path -- this matches AITER's
206
- # ``fp8_mqa_logits`` semantics and is what the top-k consumer expects.
207
- logits = torch.full(
208
- (seq_len, seq_len_kv),
209
- fill_value=-float("inf"),
210
- dtype=torch.float32,
211
- device=q.device,
212
- )
213
-
214
- if _gfx942_default_tile_fits_lds(num_heads, head_size):
215
- block_kv = 128
216
- num_stages = 2
217
- else:
218
- # DSv4 sparse indexer (NUM_HEADS=64, HEAD_SIZE=128) lands here:
219
- # default tile spills past gfx942's 64 KiB LDS budget. (64, 1)
220
- # needs ~33 KiB and clears the per-WG budget with margin.
221
- block_kv = 64
222
- num_stages = 1
223
-
224
- # heuristic for MFMA instruction shape, identical to AITER's choice
225
- matrix_instr_nonkdim = 32
226
- if seq_len <= 1024:
227
- matrix_instr_nonkdim = 16
228
-
229
- stride_q_s, stride_q_h, stride_q_d = q.stride()
230
- stride_kv_s, stride_kv_d = k_fp8.stride()
231
- stride_w_s, stride_w_h = weights.stride()
232
- stride_logits_s, stride_logits_k = logits.stride()
233
-
234
- _fp8_mqa_logits_kernel[(seq_len,)](
235
- Q_ptr=q,
236
- KV_ptr=k_fp8,
237
- kv_scales_ptr=kv_scales_1d,
238
- weights_ptr=weights,
239
- cu_start_ptr=cu_starts,
240
- cu_end_ptr=cu_ends,
241
- logits_ptr=logits,
242
- seq_len=seq_len,
243
- seq_len_kv=seq_len_kv,
244
- NUM_HEADS=num_heads,
245
- HEAD_SIZE=head_size,
246
- stride_q_s=stride_q_s,
247
- stride_q_h=stride_q_h,
248
- stride_q_d=stride_q_d,
249
- stride_kv_s=stride_kv_s,
250
- stride_kv_d=stride_kv_d,
251
- stride_w_s=stride_w_s,
252
- stride_w_h=stride_w_h,
253
- stride_logits_s=stride_logits_s,
254
- stride_logits_k=stride_logits_k,
255
- BLOCK_KV=block_kv,
256
- num_warps=4,
257
- num_stages=num_stages,
258
- waves_per_eu=2,
259
- matrix_instr_nonkdim=matrix_instr_nonkdim,
260
- )
261
-
262
- return logits
vllm/v1/attention/ops/turboquant_soa/triton_turboquant_decode_v2.py
-572
@@ -21,15 +21,11 @@ Stage 2 is reused unchanged from triton_decode_attention.py.
21
"""
22
23
import math
24
-from typing import Any
24
25
import torch
26
27
from vllm.platforms import current_platform
28
from vllm.triton_utils import tl, triton
30
-from vllm.v1.attention.ops.triton_decode_attention import _fwd_kernel_stage2
31
-
32
-from .triton_turboquant_decode import _use_fp8_e4b15
29
30
# ROCm prefers num_stages=1 in attention-like kernels to reduce shared-memory
31
# pressure (mirrors the pattern used in triton_decode_attention.py and
@@ -66,387 +62,6 @@ def build_pair_lut(centroids: torch.Tensor) -> torch.Tensor:
62
return lut.reshape(N * N, 2).contiguous()
63
64
69
-# ---------------------------------------------------------------------------
70
-# Stage 1 v2: Grouped-Q + pair LUT + tl.dot + exp2
71
-# ---------------------------------------------------------------------------
72
-
73
-
74
-@triton.jit
75
-def _tq_decode_stage1_v2(
76
- Q_rot_ptr,
77
- KV_cache_ptr,
78
- KV_cache_u16_ptr, # uint16 view — Opt#3 SoA loads
79
- Block_table_ptr,
80
- Seq_lens_ptr,
81
- Centroids_ptr,
82
- Pair_lut_ptr,
83
- Mid_o_ptr,
84
- stride_qb,
85
- stride_qh,
86
- stride_cache_block, # bytes per block (bs*H*slot_aligned)
87
- stride_bt_b,
88
- stride_mid_b,
89
- stride_mid_h,
90
- stride_mid_s,
91
- NUM_Q_HEADS: tl.constexpr,
92
- NUM_KV_HEADS: tl.constexpr,
93
- HEAD_DIM: tl.constexpr,
94
- BLOCK_SIZE: tl.constexpr,
95
- PADDED_SLOT: tl.constexpr,
96
- MAX_NUM_BLOCKS: tl.constexpr,
97
- NUM_KV_SPLITS: tl.constexpr,
98
- KV_GROUP_SIZE: tl.constexpr,
99
- MSE_BITS: tl.constexpr,
100
- MSE_BYTES: tl.constexpr,
101
- VQB: tl.constexpr,
102
- VAL_DATA_BYTES: tl.constexpr,
103
- # Opt#3 SoA layout
104
- KEY_DATA_BYTES: tl.constexpr,
105
- META_REGION_OFFSET: tl.constexpr,
106
- NUM_SOA_FIELDS: tl.constexpr,
107
- SOA_K_NORM: tl.constexpr,
108
- SOA_V_SCALE: tl.constexpr,
109
- SOA_V_ZERO: tl.constexpr,
110
- N_CENTROIDS: tl.constexpr,
111
- ATTN_SCALE: tl.constexpr,
112
- BLOCK_D: tl.constexpr,
113
- TILE_SIZE: tl.constexpr,
114
- BLOCK_M: tl.constexpr,
115
- KEY_FP8: tl.constexpr,
116
- NORM_CORRECTION: tl.constexpr = 0,
117
- FP8_E4B15: tl.constexpr = 0,
118
- USE_PAIR_LUT: tl.constexpr = 0,
119
- USE_BF16_DOT: tl.constexpr = 0,
120
-):
121
- # Grid: (B, Hk, NUM_KV_SPLITS) — one program per KV head group
122
- bid = tl.program_id(0)
123
- kv_hid = tl.program_id(1)
124
- sid = tl.program_id(2)
125
-
126
- seq_len = tl.load(Seq_lens_ptr + bid)
127
- split_len = tl.cdiv(seq_len, NUM_KV_SPLITS)
128
- split_start = split_len * sid
129
- split_end = tl.minimum(split_start + split_len, seq_len)
130
-
131
- if split_start >= split_end:
132
- return
133
-
134
- # Dimension and tile offsets
135
- d_offs = tl.arange(0, BLOCK_D)
136
- d_mask = d_offs < HEAD_DIM
137
- m_offs = tl.arange(0, BLOCK_M)
138
- t_offs = tl.arange(0, TILE_SIZE)
139
-
140
- # Q head indices for this KV group: [kv_hid * GS, kv_hid * GS + GS)
141
- q_head_offs = kv_hid * KV_GROUP_SIZE + m_offs
142
- q_mask = m_offs < KV_GROUP_SIZE
143
-
144
- # Load Q tile: [BLOCK_M, BLOCK_D] float32
145
- q_addrs = bid * stride_qb + q_head_offs[:, None] * stride_qh + d_offs[None, :]
146
- Q = tl.load(
147
- Q_rot_ptr + q_addrs,
148
- mask=q_mask[:, None] & d_mask[None, :],
149
- other=0.0,
150
- )
151
-
152
- # Pre-warm centroids in L1
153
- if not KEY_FP8:
154
- _c_warm = tl.load(Centroids_ptr + tl.arange(0, N_CENTROIDS)) # noqa: F841
155
-
156
- # Loop-invariant bit-extraction constants for MSE keys
157
- if not KEY_FP8:
158
- if MSE_BITS == 4:
159
- pass # 4-bit byte extraction is computed inline in the tile loop
160
- else:
161
- # Generic bit extraction
162
- mse_bit_off = d_offs * MSE_BITS
163
- mse_byte_idx = mse_bit_off // 8
164
- mse_bit_shift = mse_bit_off % 8
165
- mse_mask_val = (1 << MSE_BITS) - 1
166
-
167
- # Loop-invariant bit-extraction for 3-bit values
168
- if VQB == 3:
169
- val_bit_off = d_offs * 3
170
- val_byte_idx = val_bit_off // 8
171
- val_bit_shift = val_bit_off % 8
172
-
173
- # exp2 pre-scaling
174
- RCP_LN2: tl.constexpr = 1.4426950408889634
175
- LN2: tl.constexpr = 0.6931471805599453
176
- QK_SCALE = ATTN_SCALE * RCP_LN2
177
-
178
- # Online softmax accumulators: [BLOCK_M] for max, [BLOCK_M] for L
179
- M = tl.full([BLOCK_M], float("-inf"), dtype=tl.float32)
180
- L = tl.zeros([BLOCK_M], dtype=tl.float32)
181
-
182
- acc = tl.zeros([BLOCK_M, BLOCK_D], dtype=tl.float32)
183
-
184
- bt_base = bid * stride_bt_b
185
-
186
- # ================================================================
187
- # TILED LOOP: TILE_SIZE KV tokens per iteration
188
- # ================================================================
189
- for start_n in range(split_start, split_end, TILE_SIZE):
190
- kv_offs = start_n + t_offs
191
- kv_mask_1d = kv_offs < split_end
192
-
193
- page_idx = kv_offs // BLOCK_SIZE
194
- page_off = kv_offs % BLOCK_SIZE
195
- block_nums = tl.load(
196
- Block_table_ptr + bt_base + page_idx,
197
- mask=kv_mask_1d,
198
- other=0,
199
- ).to(tl.int64)
200
-
201
- # Opt#3 SoA addressing: data region then per-block SoA metadata.
202
- slot_within_block = page_off.to(tl.int64)
203
- block_base = block_nums * stride_cache_block
204
- DATA_BYTES_PER_SLOT: tl.constexpr = KEY_DATA_BYTES + VAL_DATA_BYTES
205
- data_bases = (
206
- block_base
207
- + slot_within_block * (NUM_KV_HEADS * DATA_BYTES_PER_SLOT)
208
- + tl.cast(kv_hid, tl.int64) * DATA_BYTES_PER_SLOT
209
- )
210
- head_meta_u16_base = (block_base + META_REGION_OFFSET) // 2 + tl.cast(
211
- kv_hid, tl.int64
212
- ) * (NUM_SOA_FIELDS * BLOCK_SIZE)
213
- knorm_u16_addrs = (
214
- head_meta_u16_base + SOA_K_NORM * BLOCK_SIZE + slot_within_block
215
- )
216
- vscale_u16_addrs = (
217
- head_meta_u16_base + SOA_V_SCALE * BLOCK_SIZE + slot_within_block
218
- )
219
- vzero_u16_addrs = (
220
- head_meta_u16_base + SOA_V_ZERO * BLOCK_SIZE + slot_within_block
221
- )
222
-
223
- # ============================================================
224
- # KEY DEQUANT → K_T: [BLOCK_D, TILE_SIZE] for tl.dot(Q, K_T)
225
- # ============================================================
226
- if KEY_FP8:
227
- k_addrs = data_bases[:, None] + d_offs[None, :]
228
- k_raw = tl.load(
229
- KV_cache_ptr + k_addrs,
230
- mask=kv_mask_1d[:, None] & d_mask[None, :],
231
- other=0,
232
- )
233
- if FP8_E4B15:
234
- K_f = k_raw.to(tl.float8e4b15, bitcast=True).to(tl.float32)
235
- else:
236
- K_f = k_raw.to(tl.float8e4nv, bitcast=True).to(tl.float32)
237
- # K_f: [TILE_SIZE, BLOCK_D] → transpose for tl.dot
238
- K_T = tl.trans(K_f)
239
- # S: [BLOCK_M, TILE_SIZE] (bf16 dot on ROCm for better dynamic range)
240
- if USE_BF16_DOT:
241
- S = QK_SCALE * tl.dot(Q.to(tl.bfloat16), K_T.to(tl.bfloat16))
242
- else:
243
- S = QK_SCALE * tl.dot(Q.to(tl.float16), K_T.to(tl.float16))
244
- else:
245
- # --- MSE dequantization ---
246
- if MSE_BITS == 4 and USE_PAIR_LUT:
247
- # OPT#1 (load-halving): FLUTE §3.2 pair LUT, but with each
248
- # packed byte loaded EXACTLY ONCE instead of twice (the
249
- # previous code built byte_addrs from d_offs // 2 which has
250
- # the pattern [0,0,1,1,2,2,...], duplicating every byte
251
- # load). We load the packed bytes as a [TILE_SIZE, HALF_D]
252
- # tile, decode both nibbles, then issue a single 3-D gather
253
- # against pair_lut which returns (T[lo], T[hi]) in slots
254
- # 0/1. Reshape [TILE_SIZE, HALF_D, 2] -> [TILE_SIZE, BLOCK_D]
255
- # interleaves the pairs into the expected dim order
256
- # (d=2k -> T[lo_k], d=2k+1 -> T[hi_k]).
257
- HALF_D: tl.constexpr = BLOCK_D // 2
258
- half_offs = tl.arange(0, HALF_D)
259
- # Each byte k covers dims (2k, 2k+1); it is in-range iff
260
- # 2k < HEAD_DIM. HEAD_DIM is always even in our layouts,
261
- # so this is equivalent to k < HEAD_DIM // 2.
262
- byte_mask = (half_offs * 2) < HEAD_DIM
263
-
264
- byte_addrs = data_bases[:, None] + half_offs[None, :]
265
- byte_raw = tl.load(
266
- KV_cache_ptr + byte_addrs,
267
- mask=kv_mask_1d[:, None] & byte_mask[None, :],
268
- other=0,
269
- ).to(tl.int32)
270
-
271
- lo_idx = byte_raw & 0xF # [TILE_SIZE, HALF_D]
272
- hi_idx = (byte_raw >> 4) & 0xF # [TILE_SIZE, HALF_D]
273
- pair_key = lo_idx * N_CENTROIDS + hi_idx # [TILE_SIZE, HALF_D]
274
-
275
- # Single 3-D gather: pair_lut[pair_key*2 + {0,1}]
276
- # -> [TILE_SIZE, HALF_D, 2] = (T[lo_k], T[hi_k]) per element.
277
- pair_slot = tl.arange(0, 2)
278
- c_pair = tl.load(
279
- Pair_lut_ptr + pair_key[:, :, None] * 2 + pair_slot[None, None, :],
280
- mask=(kv_mask_1d[:, None, None] & byte_mask[None, :, None]),
281
- other=0.0,
282
- )
283
- # Row-major reshape interleaves pairs:
284
- # out[t, 2k] = c_pair[t, k, 0] = T[lo_k]
285
- # out[t, 2k+1] = c_pair[t, k, 1] = T[hi_k]
286
- c_vals = tl.reshape(c_pair, [TILE_SIZE, BLOCK_D])
287
- elif MSE_BITS == 4:
288
- # 4-bit without pair LUT: exploit byte alignment
289
- half_idx = d_offs // 2
290
- nibble_shift = (d_offs % 2) * 4
291
- mse_addrs = data_bases[:, None] + half_idx[None, :]
292
- mse_raw = tl.load(
293
- KV_cache_ptr + mse_addrs,
294
- mask=kv_mask_1d[:, None] & d_mask[None, :],
295
- other=0,
296
- ).to(tl.int32)
297
- mse_idx = (mse_raw >> nibble_shift[None, :]) & 0xF
298
- c_vals = tl.load(
299
- Centroids_ptr + mse_idx,
300
- mask=kv_mask_1d[:, None] & d_mask[None, :],
301
- other=0.0,
302
- )
303
- else:
304
- # Generic 3-bit path: two byte loads → 16-bit → shift/mask
305
- mse_addrs0 = data_bases[:, None] + mse_byte_idx[None, :]
306
- mse_raw0 = tl.load(
307
- KV_cache_ptr + mse_addrs0,
308
- mask=kv_mask_1d[:, None] & d_mask[None, :],
309
- other=0,
310
- ).to(tl.int32)
311
- mse_raw1 = tl.load(
312
- KV_cache_ptr + mse_addrs0 + 1,
313
- mask=kv_mask_1d[:, None] & d_mask[None, :],
314
- other=0,
315
- ).to(tl.int32)
316
- raw16 = mse_raw0 | (mse_raw1 << 8)
317
- mse_idx = (raw16 >> mse_bit_shift[None, :]) & mse_mask_val
318
- c_vals = tl.load(
319
- Centroids_ptr + mse_idx,
320
- mask=kv_mask_1d[:, None] & d_mask[None, :],
321
- other=0.0,
322
- )
323
-
324
- # Opt#1: norm-correction is pre-folded into the stored per-token
325
- # scalar at store time, so no per-tile sum+sqrt+divide here. The
326
- # stored vec_norm carries ||k||/||c_vec||; multiply by c_vals
327
- # below gives the original c_vec/||c_vec|| * ||k||.
328
-
329
- # Opt#3: K-norms from per-block SoA region. Single u16 load per
330
- # token; contiguous across tokens within a block.
331
- norm_u16 = tl.load(
332
- KV_cache_u16_ptr + knorm_u16_addrs, mask=kv_mask_1d, other=0
333
- )
334
- vec_norms = norm_u16.to(tl.float16, bitcast=True).to(tl.float32)
335
-
336
- # Reconstruct K: K_recon[t, d] = norm[t] * centroid[t, d]
337
- K_recon = c_vals * vec_norms[:, None]
338
- # K_T: [BLOCK_D, TILE_SIZE]
339
- K_T = tl.trans(K_recon)
340
-
341
- # S: [BLOCK_M, TILE_SIZE] via tensor core MMA
342
- if USE_BF16_DOT:
343
- S = QK_SCALE * tl.dot(Q.to(tl.bfloat16), K_T.to(tl.bfloat16))
344
- else:
345
- S = QK_SCALE * tl.dot(Q.to(tl.float16), K_T.to(tl.float16))
346
-
347
- # Mask out-of-range positions
348
- S = tl.where(kv_mask_1d[None, :], S, float("-inf"))
349
-
350
- # ============================================================
351
- # ONLINE SOFTMAX with exp2
352
- # ============================================================
353
- m_j = tl.maximum(M, tl.max(S, axis=1))
354
- m_j = tl.where(m_j > float("-inf"), m_j, 0.0)
355
- P = tl.math.exp2(S - m_j[:, None])
356
- l_j = tl.sum(P, axis=1)
357
- alpha = tl.math.exp2(M - m_j)
358
-
359
- acc = acc * alpha[:, None]
360
- L = L * alpha + l_j
361
- M = m_j
362
-
363
- # ============================================================
364
- # VALUE DEQUANT → V: [TILE_SIZE, BLOCK_D] for tl.dot(P, V)
365
- # Opt#3: V data lives at data_base + KEY_DATA_BYTES; scale/zero
366
- # move to the SoA metadata region (one u16 load each per token).
367
- # ============================================================
368
- val_bases = data_bases + KEY_DATA_BYTES
369
-
370
- if VQB == 3:
371
- val_addrs0 = val_bases[:, None] + val_byte_idx[None, :]
372
- val_raw0 = tl.load(
373
- KV_cache_ptr + val_addrs0,
374
- mask=kv_mask_1d[:, None] & d_mask[None, :],
375
- other=0,
376
- ).to(tl.int32)
377
- val_raw1 = tl.load(
378
- KV_cache_ptr + val_addrs0 + 1,
379
- mask=kv_mask_1d[:, None] & d_mask[None, :],
380
- other=0,
381
- ).to(tl.int32)
382
- raw16 = val_raw0 | (val_raw1 << 8)
383
- v_idx = ((raw16 >> val_bit_shift[None, :]) & 0x7).to(tl.float32)
384
- else: # VQB == 4
385
- # OPT#3 (load-halving, value path): same pattern as OPT#1 for
386
- # keys -- load each byte exactly once as [TILE_SIZE, HALF_D],
387
- # decode both nibbles, then tl.interleave to [TILE_SIZE, BLOCK_D]
388
- # with pattern [v_lo0, v_hi0, v_lo1, v_hi1, ...].
389
- V_HALF_D: tl.constexpr = BLOCK_D // 2
390
- v_half_offs = tl.arange(0, V_HALF_D)
391
- v_byte_mask = (v_half_offs * 2) < HEAD_DIM
392
-
393
- val_addrs = val_bases[:, None] + v_half_offs[None, :]
394
- val_byte = tl.load(
395
- KV_cache_ptr + val_addrs,
396
- mask=kv_mask_1d[:, None] & v_byte_mask[None, :],
397
- other=0,
398
- ).to(tl.int32)
399
- v_lo = (val_byte & 0xF).to(tl.float32) # [T, HALF_D]
400
- v_hi = ((val_byte >> 4) & 0xF).to(tl.float32) # [T, HALF_D]
401
- v_idx = tl.interleave(v_lo, v_hi) # [T, BLOCK_D]
402
-
403
- scale_u16 = tl.load(
404
- KV_cache_u16_ptr + vscale_u16_addrs, mask=kv_mask_1d, other=0
405
- )
406
- zero_u16 = tl.load(KV_cache_u16_ptr + vzero_u16_addrs, mask=kv_mask_1d, other=0)
407
- v_scales = scale_u16.to(tl.float16, bitcast=True).to(tl.float32)
408
- v_zeros = zero_u16.to(tl.float16, bitcast=True).to(tl.float32)
409
- V = v_idx * v_scales[:, None] + v_zeros[:, None]
410
-
411
- # P·V accumulation via tensor core MMA
412
- # P: [BLOCK_M, TILE_SIZE], V: [TILE_SIZE, BLOCK_D]
413
- if USE_BF16_DOT:
414
- acc += tl.dot(P.to(tl.bfloat16), V.to(tl.bfloat16))
415
- else:
416
- acc += tl.dot(P.to(tl.float16), V.to(tl.float16))
417
-
418
- # ================================================================
419
- # EPILOGUE: Store per-Q-head partial results for stage2
420
- # ================================================================
421
- # acc: [BLOCK_M, BLOCK_D], L: [BLOCK_M], M: [BLOCK_M]
422
- # Stage2 expects per-Q-head: mid_o[B, Hq, split, D+1]
423
- safe_L = tl.where(L > 0.0, L, 1.0)
424
- one_over_L = 1.0 / safe_L
425
- acc = acc * one_over_L[:, None]
426
-
427
- # Convert M from log2 space back to natural log for stage2 compatibility
428
- lse = M * LN2 + tl.log(safe_L)
429
-
430
- # Store each Q head's result using 2D scatter
431
- # q_head_offs: [BLOCK_M] = kv_hid * KV_GROUP_SIZE + m_offs
432
- out_addrs = (
433
- bid * stride_mid_b
434
- + q_head_offs[:, None] * stride_mid_h
435
- + sid * stride_mid_s
436
- + d_offs[None, :]
437
- )
438
- tl.store(
439
- Mid_o_ptr + out_addrs,
440
- acc,
441
- mask=q_mask[:, None] & d_mask[None, :],
442
- )
443
- # Store LSE per Q head
444
- lse_addrs = (
445
- bid * stride_mid_b + q_head_offs * stride_mid_h + sid * stride_mid_s + HEAD_DIM
446
- )
447
- tl.store(Mid_o_ptr + lse_addrs, lse, mask=q_mask)
448
-
449
-
65
# ---------------------------------------------------------------------------
66
# Launcher v2
67
# ---------------------------------------------------------------------------
@@ -480,190 +95,3 @@ def _get_pair_lut(centroids: torch.Tensor) -> torch.Tensor:
95
shows up on a profile, cache by a hash-of-values fingerprint instead.
96
"""
97
return build_pair_lut(centroids)
483
-
484
-
485
-def triton_turboquant_decode_attention_v2(
486
- query: torch.Tensor,
487
- kv_cache: torch.Tensor,
488
- block_table: torch.Tensor,
489
- seq_lens: torch.Tensor,
490
- Pi: torch.Tensor,
491
- centroids: torch.Tensor,
492
- scale: float,
493
- mse_bits: int,
494
- key_packed_size: int,
495
- value_quant_bits: int,
496
- value_packed_size: int,
497
- max_seq_len: int = 0, # unused; kept for backward compatibility
498
- key_fp8: bool = False,
499
- norm_correction: bool = False,
500
- PiT: torch.Tensor | None = None,
501
- # Pre-allocated buffers (optional, required for CUDA-graph stability).
502
- mid_o_buf: torch.Tensor | None = None,
503
- output_buf: torch.Tensor | None = None,
504
- lse_buf: torch.Tensor | None = None,
505
- buf_holder: Any = None,
506
- # Fixed split count — MUST be a compile-time constant across iterations
507
- # for CUDA-graph capture/replay to work. Mirrors the v1 launcher contract.
508
- max_num_kv_splits: int = 32,
509
-) -> torch.Tensor:
510
- """Launch optimized TQ decode attention (v2 stage1 + shared stage2).
511
-
512
- Follows the same buffer-reuse + fixed-grid contract as the v1 launcher
513
- (triton_turboquant_decode_attention) so the backend can capture a CUDA
514
- graph across both versions.
515
- """
516
- B, Hq, D = query.shape
517
- Hk = kv_cache.shape[2]
518
- block_size = kv_cache.shape[1]
519
- padded_slot = kv_cache.shape[3]
520
- max_num_blocks = block_table.shape[1]
521
- n_centroids = centroids.shape[0]
522
- kv_group_size = Hq // Hk
523
- device = query.device
524
- del max_seq_len # no longer used: splits is fixed via max_num_kv_splits
525
-
526
- cfg = _get_layout(D, mse_bits, value_quant_bits, key_packed_size)
527
-
528
- # Opt#3 SoA layout constants (match store-side computation).
529
- key_data_bytes = D if key_fp8 else cfg["mse_bytes"]
530
- data_bytes_per_slot = key_data_bytes + cfg["val_data_bytes"]
531
- meta_region_offset = block_size * Hk * data_bytes_per_slot
532
- num_soa_fields = 2 if key_fp8 else 3
533
- soa_k_norm = 0
534
- soa_v_scale = 0 if key_fp8 else 1
535
- soa_v_zero = 1 if key_fp8 else 2
536
- kv_cache_u16 = kv_cache.view(torch.uint16)
537
-
538
- # Compute q_rot = q @ Pi.T
539
- if key_fp8:
540
- q_rot = query.float().contiguous()
541
- else:
542
- q_float = query.float()
543
- if PiT is None:
544
- PiT = Pi.T.contiguous()
545
- q_rot = (q_float @ PiT).contiguous()
546
-
547
- # BLOCK_M: pad KV_GROUP_SIZE to power of 2, minimum 16 for tensor cores
548
- BLOCK_M = max(16, triton.next_power_of_2(kv_group_size))
549
-
550
- # TILE_SIZE (BLOCK_KV): tokens per inner-loop iteration
551
- TILE_SIZE = 16
552
-
553
- # Fixed split count — must be constant across calls for cudagraph replay.
554
- NUM_KV_SPLITS = max_num_kv_splits
555
-
556
- # --- mid_o buffer reuse (same pattern as v1 launcher) ---
557
- if (
558
- mid_o_buf is not None
559
- and mid_o_buf.shape[0] >= B
560
- and mid_o_buf.shape[2] >= NUM_KV_SPLITS
561
- ):
562
- mid_o = mid_o_buf[:B, :Hq, :NUM_KV_SPLITS, :]
563
- else:
564
- mid_o = torch.empty(
565
- B,
566
- Hq,
567
- NUM_KV_SPLITS,
568
- D + 1,
569
- dtype=torch.float32,
570
- device=device,
571
- )
572
- if buf_holder is not None:
573
- buf_holder._tq_mid_o_buf = mid_o
574
-
575
- fp8_e4b15 = _use_fp8_e4b15(device.index or 0)
576
-
577
- # Build pair LUT for 4-bit MSE (FLUTE §3.2)
578
- use_pair_lut = mse_bits == 4 and not key_fp8
579
- pair_lut = _get_pair_lut(centroids) if use_pair_lut else centroids
580
-
581
- # Platform-dependent pipelining depth: ROCm prefers num_stages=1.
582
- stage1_num_stages = 1 if _is_hip else 2
583
-
584
- # --- Stage 1: v2 kernel ---
585
- # Grid over KV heads (not Q heads) — each program handles all Q heads
586
- grid = (B, Hk, NUM_KV_SPLITS)
587
- _tq_decode_stage1_v2[grid](
588
- q_rot,
589
- kv_cache,
590
- kv_cache_u16,
591
- block_table,
592
- seq_lens,
593
- centroids,
594
- pair_lut,
595
- mid_o,
596
- q_rot.stride(0),
597
- q_rot.stride(1),
598
- kv_cache.stride(0),
599
- block_table.stride(0),
600
- mid_o.stride(0),
601
- mid_o.stride(1),
602
- mid_o.stride(2),
603
- NUM_Q_HEADS=Hq,
604
- NUM_KV_HEADS=Hk,
605
- HEAD_DIM=D,
606
- BLOCK_SIZE=block_size,
607
- PADDED_SLOT=padded_slot,
608
- MAX_NUM_BLOCKS=max_num_blocks,
609
- NUM_KV_SPLITS=NUM_KV_SPLITS,
610
- KV_GROUP_SIZE=kv_group_size,
611
- MSE_BITS=mse_bits,
612
- MSE_BYTES=cfg["mse_bytes"],
613
- VQB=value_quant_bits,
614
- VAL_DATA_BYTES=cfg["val_data_bytes"],
615
- KEY_DATA_BYTES=key_data_bytes,
616
- META_REGION_OFFSET=meta_region_offset,
617
- NUM_SOA_FIELDS=num_soa_fields,
618
- SOA_K_NORM=soa_k_norm,
619
- SOA_V_SCALE=soa_v_scale,
620
- SOA_V_ZERO=soa_v_zero,
621
- N_CENTROIDS=n_centroids,
622
- ATTN_SCALE=scale,
623
- BLOCK_D=cfg["BLOCK_D"],
624
- TILE_SIZE=TILE_SIZE,
625
- BLOCK_M=BLOCK_M,
626
- KEY_FP8=1 if key_fp8 else 0,
627
- NORM_CORRECTION=1 if norm_correction else 0,
628
- FP8_E4B15=fp8_e4b15,
629
- USE_PAIR_LUT=1 if use_pair_lut else 0,
630
- USE_BF16_DOT=1 if _is_hip else 0,
631
- num_warps=4,
632
- num_stages=stage1_num_stages,
633
- )
634
-
635
- # --- output / lse buffer reuse (same pattern as v1 launcher) ---
636
- if output_buf is not None and output_buf.shape[0] >= B:
637
- output = output_buf[:B, :Hq, :D]
638
- else:
639
- output = torch.empty(B, Hq, D, dtype=torch.float32, device=device)
640
- if buf_holder is not None:
641
- buf_holder._tq_output_buf = output
642
- if lse_buf is not None and lse_buf.shape[0] >= B:
643
- lse = lse_buf[:B, :Hq]
644
- else:
645
- lse = torch.empty(B, Hq, dtype=torch.float32, device=device)
646
- if buf_holder is not None:
647
- buf_holder._tq_lse_buf = lse
648
-
649
- # --- Stage 2: reduce across KV splits (unchanged) ---
650
- grid2 = (B, Hq)
651
- _fwd_kernel_stage2[grid2](
652
- mid_o,
653
- output,
654
- lse,
655
- seq_lens,
656
- mid_o.stride(0),
657
- mid_o.stride(1),
658
- mid_o.stride(2),
659
- output.stride(0),
660
- output.stride(1),
661
- lse.stride(0),
662
- NUM_KV_SPLITS=NUM_KV_SPLITS,
663
- BLOCK_DV=cfg["BLOCK_D"],
664
- Lv=D,
665
- num_warps=4,
666
- num_stages=2,
667
- )
668
-
669
- return output.to(query.dtype)
vllm/v1/attention/ops/turboquant_soa/triton_turboquant_unified_attention.py
-15
@@ -876,21 +876,6 @@ def _get_pair_lut(centroids: torch.Tensor) -> torch.Tensor:
876
return build_pair_lut(centroids)
877
878
879
-# PiT in Q's dtype cached per (PiT-id, dtype) to avoid a host cast on every
880
-# launcher call. Keyed by the fp32 PiT's data_ptr so ownership/lifetime is
881
-# tied to the caller's tensor.
882
-_pit_qdtype_cache: dict = {}
883
-
884
-
885
-def _get_pit_in_query_dtype(PiT: torch.Tensor, qdtype: torch.dtype) -> torch.Tensor:
886
- key = (PiT.data_ptr(), qdtype)
887
- cached = _pit_qdtype_cache.get(key)
888
- if cached is None:
889
- cached = PiT.to(qdtype).contiguous()
890
- _pit_qdtype_cache[key] = cached
891
- return cached
892
-
893
-
879
def triton_turboquant_unified_attention(
880
query: torch.Tensor, # [num_tokens, Hq, D] - fp16/bf16
881
kv_cache: torch.Tensor, # [num_blocks, block_size, Hk, padded_slot] uint8