A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 7, 06:30 UTC
[Kernel][Mamba] Optimize Mamba2 SSD prefill Triton kernels (#35397)
Signed-off-by: Tomer Asida <57313761+tomeras91@users.noreply.github.com>
tomeras91 committed
Mar 4, 2026 at 20:47 UTC
7faba503c403bc8c562888df3a841b6df104d042
6 files changed
+155
-369
vllm/model_executor/layers/mamba/ops/mamba_ssm.py
+3
-2
@@ -8,6 +8,7 @@ import torch
8
from packaging import version
9
10
from vllm import _custom_ops as ops
11
+from vllm.model_executor.layers.mamba.ops.triton_helpers import fast_exp
12
from vllm.triton_utils import HAS_TRITON, tl, triton
13
from vllm.v1.attention.backends.utils import PAD_SLOT_ID
14
@@ -215,7 +216,7 @@ def _selective_scan_update_kernel(
216
mask=(offs_m[:, None] < dim) & (offs_n[None, :] < dstate),
217
other=0.0,
218
).to(tl.float32)
218
- dA = tl.exp(A * dt[:, None])
219
+ dA = fast_exp(A * dt[:, None])
220
else:
221
dt = tl.load(dt_ptr).to(tl.float32)
222
if HAS_DT_BIAS:
@@ -223,7 +224,7 @@ def _selective_scan_update_kernel(
224
if DT_SOFTPLUS:
225
dt = softplus(dt)
226
A = tl.load(A_ptr).to(tl.float32)
226
- dA = tl.exp(A * dt) # scalar, not a matrix
227
+ dA = fast_exp(A * dt) # scalar, not a matrix
228
229
B = tl.load(B_ptrs, mask=offs_n < dstate, other=0.0).to(tl.float32)
230
C = tl.load(C_ptrs, mask=offs_n < dstate, other=0.0).to(tl.float32)
vllm/model_executor/layers/mamba/ops/ssd_chunk_scan.py
+73
-2
@@ -8,6 +8,7 @@
8
9
from packaging import version
10
11
+from vllm.model_executor.layers.mamba.ops.triton_helpers import fast_exp
12
from vllm.triton_utils import tl, triton
13
14
TRITON_22 = version.parse(triton.__version__) >= version.parse("2.2.0")
@@ -15,6 +16,76 @@ TRITON_22 = version.parse(triton.__version__) >= version.parse("2.2.0")
16
17
@triton.autotune(
18
configs=[
19
+ # =================================================================
20
+ # Higher warp count configs for better latency hiding
21
+ # More warps = more instructions in flight = better memory latency hiding
22
+ # =================================================================
23
+ triton.Config(
24
+ {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 32, "BLOCK_SIZE_K": 32},
25
+ num_stages=2,
26
+ num_warps=8,
27
+ ),
28
+ triton.Config(
29
+ {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 32},
30
+ num_stages=2,
31
+ num_warps=8,
32
+ ),
33
+ triton.Config(
34
+ {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 32, "BLOCK_SIZE_K": 32},
35
+ num_stages=2,
36
+ num_warps=8,
37
+ ),
38
+ # Smaller tiles with more stages for software pipelining
39
+ triton.Config(
40
+ {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 32, "BLOCK_SIZE_K": 32},
41
+ num_stages=3,
42
+ num_warps=4,
43
+ ),
44
+ triton.Config(
45
+ {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 32, "BLOCK_SIZE_K": 64},
46
+ num_stages=2,
47
+ num_warps=4,
48
+ ),
49
+ # =================================================================
50
+ # Low register pressure configs (num_stages=1) for large dstate
51
+ # =================================================================
52
+ triton.Config(
53
+ {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 64},
54
+ num_stages=1,
55
+ num_warps=4,
56
+ ),
57
+ triton.Config(
58
+ {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 32},
59
+ num_stages=1,
60
+ num_warps=4,
61
+ ),
62
+ triton.Config(
63
+ {"BLOCK_SIZE_M": 128, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 32},
64
+ num_stages=1,
65
+ num_warps=4,
66
+ ),
67
+ triton.Config(
68
+ {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 32},
69
+ num_stages=1,
70
+ num_warps=4,
71
+ ),
72
+ # num_stages=2 configs - moderate register pressure
73
+ triton.Config(
74
+ {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 64},
75
+ num_stages=2,
76
+ num_warps=4,
77
+ ),
78
+ triton.Config(
79
+ {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 32},
80
+ num_stages=2,
81
+ num_warps=4,
82
+ ),
83
+ triton.Config(
84
+ {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 32},
85
+ num_stages=2,
86
+ num_warps=4,
87
+ ),
88
+ # Original configs for larger dstate values
89
triton.Config(
90
{"BLOCK_SIZE_M": 128, "BLOCK_SIZE_N": 256, "BLOCK_SIZE_K": 64},
91
num_stages=3,
@@ -200,7 +271,7 @@ def _chunk_scan_fwd_kernel(
271
offs_m[:, None] * stride_C_seqlen + offs_k_dstate[None, :] * stride_C_dstate
272
)
273
203
- scale_m = tl.exp(dA_cs_m)
274
+ scale_m = fast_exp(dA_cs_m)
275
if BLOCK_SIZE_DSTATE <= 128:
276
C = tl.load(
277
C_ptrs,
@@ -285,7 +356,7 @@ def _chunk_scan_fwd_kernel(
356
)
357
# If there's seq_idx, we already set cb[i, j] = 0 for seq_idx[i] != seq_idx[j].
358
# So we don't need masking wrt seq_idx here.
288
- cb *= tl.exp(dA_cs_m[:, None] - dA_cs_k[None, :])
359
+ cb *= fast_exp(dA_cs_m[:, None] - dA_cs_k[None, :])
360
dt_k = tl.load(dt_ptrs, mask=offs_k < chunk_size - k, other=0.0).to(tl.float32)
361
cb *= dt_k
362
if IS_CAUSAL:
vllm/model_executor/layers/mamba/ops/ssd_chunk_state.py
+30
-319
@@ -8,6 +8,7 @@
8
9
import torch
10
11
+from vllm.model_executor.layers.mamba.ops.triton_helpers import fast_exp
12
from vllm.triton_utils import tl, triton
13
14
from .mamba_ssm import softplus
@@ -116,6 +117,34 @@ def _chunk_cumsum_fwd_kernel(
117
118
@triton.autotune(
119
configs=[
120
+ # Small headdim/dstate configs (hdim<=64, dstate<=128) - increased parallelism
121
+ triton.Config(
122
+ {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 32, "BLOCK_SIZE_K": 32},
123
+ num_stages=3,
124
+ num_warps=4,
125
+ ),
126
+ triton.Config(
127
+ {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 32},
128
+ num_stages=3,
129
+ num_warps=4,
130
+ ),
131
+ triton.Config(
132
+ {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 32, "BLOCK_SIZE_K": 32},
133
+ num_stages=3,
134
+ num_warps=4,
135
+ ),
136
+ # Low register pressure configs for large dstate (dstate=128)
137
+ triton.Config(
138
+ {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 64},
139
+ num_stages=2,
140
+ num_warps=4,
141
+ ),
142
+ triton.Config(
143
+ {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 64},
144
+ num_stages=2,
145
+ num_warps=4,
146
+ ),
147
+ # original configs for larger headdim/dstate values
148
triton.Config(
149
{"BLOCK_SIZE_M": 128, "BLOCK_SIZE_N": 256, "BLOCK_SIZE_K": 64},
150
num_stages=3,
@@ -251,7 +280,7 @@ def _chunk_state_fwd_kernel(
280
dt_k = tl.load(dt_ptrs, mask=offs_k < chunk_size_limit - k, other=0.0).to(
281
tl.float32
282
)
254
- scale = tl.exp(dA_cs_last - dA_cs_k) * dt_k
283
+ scale = fast_exp(dA_cs_last - dA_cs_k) * dt_k
284
b *= scale[:, None]
285
b = b.to(x_ptr.dtype.element_ty)
286
acc += tl.dot(x, b)
@@ -273,238 +302,6 @@ def _chunk_state_fwd_kernel(
302
tl.store(states_ptrs, states, mask=c_mask)
303
304
276
-@triton.autotune(
277
- configs=[
278
- triton.Config(
279
- {"BLOCK_SIZE_M": 128, "BLOCK_SIZE_N": 256, "BLOCK_SIZE_K": 64},
280
- num_stages=3,
281
- num_warps=8,
282
- ),
283
- triton.Config(
284
- {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 256, "BLOCK_SIZE_K": 32},
285
- num_stages=4,
286
- num_warps=4,
287
- ),
288
- triton.Config(
289
- {"BLOCK_SIZE_M": 128, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 32},
290
- num_stages=4,
291
- num_warps=4,
292
- ),
293
- triton.Config(
294
- {"BLOCK_SIZE_M": 128, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 32},
295
- num_stages=4,
296
- num_warps=4,
297
- ),
298
- triton.Config(
299
- {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 128, "BLOCK_SIZE_K": 32},
300
- num_stages=4,
301
- num_warps=4,
302
- ),
303
- triton.Config(
304
- {"BLOCK_SIZE_M": 128, "BLOCK_SIZE_N": 32, "BLOCK_SIZE_K": 32},
305
- num_stages=4,
306
- num_warps=4,
307
- ),
308
- triton.Config(
309
- {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 32, "BLOCK_SIZE_K": 32},
310
- num_stages=5,
311
- num_warps=2,
312
- ),
313
- triton.Config(
314
- {"BLOCK_SIZE_M": 32, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 32},
315
- num_stages=5,
316
- num_warps=2,
317
- ),
318
- triton.Config(
319
- {"BLOCK_SIZE_M": 64, "BLOCK_SIZE_N": 64, "BLOCK_SIZE_K": 32},
320
- num_stages=4,
321
- num_warps=2,
322
- ),
323
- ],
324
- key=["hdim", "dstate", "chunk_size"],
325
-)
326
-@triton.jit
327
-def _chunk_state_varlen_kernel(
328
- # Pointers to matrices
329
- x_ptr,
330
- b_ptr,
331
- dt_ptr,
332
- dA_cumsum_ptr,
333
- chunk_states_ptr,
334
- cu_seqlens_ptr,
335
- states_ptr,
336
- initstates_ptr,
337
- # Matrix dimensions
338
- hdim: tl.constexpr,
339
- dstate: tl.constexpr,
340
- chunk_size: tl.constexpr,
341
- nheads_ngroups_ratio: tl.constexpr,
342
- # Strides
343
- stride_x_seqlen: tl.int64,
344
- stride_x_head: tl.int64,
345
- stride_x_hdim: tl.constexpr,
346
- stride_b_seqlen: tl.int64,
347
- stride_b_head: tl.int64,
348
- stride_b_dstate: tl.constexpr,
349
- stride_dt_head: tl.int64,
350
- stride_dt_chunk: tl.int64,
351
- stride_dt_csize: tl.constexpr,
352
- stride_dA_cs_head: tl.int64,
353
- stride_dA_cs_chunk: tl.int64,
354
- stride_dA_cs_csize: tl.constexpr,
355
- stride_chunk_states_chunk: tl.int64,
356
- stride_chunk_states_head: tl.int64,
357
- stride_chunk_states_hdim: tl.int64,
358
- stride_chunk_states_dstate: tl.constexpr,
359
- stride_states_batch: tl.int64,
360
- stride_states_head: tl.int64,
361
- stride_states_hdim: tl.int64,
362
- stride_states_dstate: tl.constexpr,
363
- stride_init_states_batch: tl.int64,
364
- stride_init_states_head: tl.int64,
365
- stride_init_states_hdim: tl.int64,
366
- stride_init_states_dstate: tl.constexpr,
367
- # Meta-parameters
368
- BLOCK_SIZE_M: tl.constexpr,
369
- BLOCK_SIZE_N: tl.constexpr,
370
- BLOCK_SIZE_K: tl.constexpr,
371
- HAS_INITSTATES: tl.constexpr,
372
-):
373
- pid_b = tl.program_id(axis=1)
374
- pid_h = tl.program_id(axis=2)
375
- num_pid_n = tl.cdiv(dstate, BLOCK_SIZE_N)
376
- pid_m = tl.program_id(axis=0) // num_pid_n
377
- pid_n = tl.program_id(axis=0) % num_pid_n
378
- end_idx = tl.load(cu_seqlens_ptr + pid_b + 1)
379
- pid_c = (end_idx - 1) // chunk_size
380
- b_ptr += (
381
- pid_c * chunk_size * stride_b_seqlen
382
- + (pid_h // nheads_ngroups_ratio) * stride_b_head
383
- )
384
- x_ptr += pid_c * chunk_size * stride_x_seqlen + pid_h * stride_x_head
385
- dt_ptr += pid_c * stride_dt_chunk + pid_h * stride_dt_head
386
- dA_cumsum_ptr += pid_c * stride_dA_cs_chunk + pid_h * stride_dA_cs_head
387
- chunk_states_ptr += (
388
- pid_c * stride_chunk_states_chunk + pid_h * stride_chunk_states_head
389
- )
390
-
391
- if HAS_INITSTATES:
392
- # if there are init states provided, we differentiate between states (which
393
- # are boundary conditions at a chunk boundary) and initstates (which are boundary
394
- # conditions when a new example in a cont batch starts)
395
- initstates_ptr += pid_h * stride_init_states_head
396
-
397
- offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
398
- offs_n = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
399
- offs_k = tl.arange(0, BLOCK_SIZE_K)
400
- x_ptrs = x_ptr + (
401
- offs_m[:, None] * stride_x_hdim + offs_k[None, :] * stride_x_seqlen
402
- )
403
- b_ptrs = b_ptr + (
404
- offs_n[None, :] * stride_b_dstate + offs_k[:, None] * stride_b_seqlen
405
- )
406
- dt_ptrs = dt_ptr + offs_k * stride_dt_csize
407
- dA_cs_last = tl.load(
408
- dA_cumsum_ptr + (end_idx - pid_c * chunk_size - 1) * stride_dA_cs_csize
409
- ).to(tl.float32)
410
- dA_cumsum_ptrs = dA_cumsum_ptr + offs_k * stride_dA_cs_csize
411
-
412
- chunk_size_limit = end_idx - pid_c * chunk_size
413
- start_idx = tl.load(cu_seqlens_ptr + pid_b)
414
- start_idx_cur = tl.maximum(start_idx - pid_c * chunk_size, 0)
415
-
416
- acc = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
417
- for k in range(0, chunk_size_limit, BLOCK_SIZE_K):
418
- x = tl.load(
419
- x_ptrs,
420
- mask=(offs_m[:, None] < hdim)
421
- & (offs_k[None, :] < chunk_size_limit - k)
422
- & (offs_k[None, :] >= start_idx_cur - k),
423
- other=0.0,
424
- )
425
- b = tl.load(
426
- b_ptrs,
427
- mask=(offs_k[:, None] < chunk_size_limit - k)
428
- & (offs_n[None, :] < dstate)
429
- & (offs_k[:, None] >= start_idx_cur - k),
430
- other=0.0,
431
- ).to(tl.float32)
432
- dA_cs_k = tl.load(
433
- dA_cumsum_ptrs, mask=offs_k < chunk_size_limit - k, other=0.0
434
- ).to(tl.float32)
435
- dt_k = tl.load(dt_ptrs, mask=offs_k < chunk_size_limit - k, other=0.0).to(
436
- tl.float32
437
- )
438
- scale = tl.where(
439
- (offs_k >= start_idx_cur - k) & (offs_k < chunk_size_limit - k),
440
- tl.exp(dA_cs_last - dA_cs_k) * dt_k,
441
- 0.0,
442
- )
443
- b *= scale[:, None]
444
- b = b.to(x_ptr.dtype.element_ty)
445
- acc += tl.dot(x, b)
446
- x_ptrs += BLOCK_SIZE_K * stride_x_seqlen
447
- b_ptrs += BLOCK_SIZE_K * stride_b_seqlen
448
- dt_ptrs += BLOCK_SIZE_K * stride_dt_csize
449
- dA_cumsum_ptrs += BLOCK_SIZE_K * stride_dA_cs_csize
450
-
451
- # If the sequence starts after the last chunk idx, we don't need to add the contribution from the last chunk
452
- # If HAS_INITSTATES==True need to consider two possibilities
453
- # - if start_idx < pid_c * chunk_size, then we need to take the past_states_ptrs
454
- # - if state_idx >= pid * chunk_size, then we need to insert initstates
455
- if (
456
- (start_idx < pid_c * chunk_size) # first chunk
457
- or (HAS_INITSTATES)
458
- ):
459
- dA_cs_boundary = 0.0 # default
460
-
461
- if not HAS_INITSTATES:
462
- past_states_ptrs = chunk_states_ptr + (
463
- offs_m[:, None] * stride_chunk_states_hdim
464
- + offs_n[None, :] * stride_chunk_states_dstate
465
- )
466
- else:
467
- # - this seems repetitive, buts its to help the compiler
468
- if start_idx < pid_c * chunk_size:
469
- past_states_ptrs = chunk_states_ptr + (
470
- offs_m[:, None] * stride_chunk_states_hdim
471
- + offs_n[None, :] * stride_chunk_states_dstate
472
- )
473
- else:
474
- past_states_ptrs = initstates_ptr + (
475
- pid_b * stride_init_states_batch
476
- + offs_m[:, None] * stride_init_states_hdim
477
- + offs_n[None, :] * stride_init_states_dstate
478
- )
479
-
480
- # need to adjust the boundary
481
- if start_idx > pid_c * chunk_size:
482
- dA_cs_boundary = tl.load(
483
- dA_cumsum_ptr
484
- + (start_idx - pid_c * chunk_size - 1) * stride_dA_cs_csize
485
- ).to(tl.float32)
486
-
487
- past_states = tl.load(
488
- past_states_ptrs,
489
- mask=(offs_m[:, None] < hdim) & (offs_n[None, :] < dstate),
490
- other=0.0,
491
- ).to(tl.float32)
492
-
493
- scale = tl.exp(dA_cs_last - dA_cs_boundary)
494
- acc += past_states * scale
495
-
496
- states = acc.to(states_ptr.dtype.element_ty)
497
-
498
- states_ptr += pid_b * stride_states_batch + pid_h * stride_states_head
499
- offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
500
- offs_n = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
501
- states_ptrs = states_ptr + (
502
- offs_m[:, None] * stride_states_hdim + offs_n[None, :] * stride_states_dstate
503
- )
504
- c_mask = (offs_m[:, None] < hdim) & (offs_n[None, :] < dstate)
505
- tl.store(states_ptrs, states, mask=c_mask)
506
-
507
-
305
def _chunk_cumsum_fwd(
306
dt,
307
A,
@@ -612,89 +409,3 @@ def _chunk_state_fwd(
409
stride_dA_cs_csize=dA_cumsum.stride(2),
410
)
411
return states
615
-
616
-
617
-def chunk_state_varlen(
618
- B, x, dt, dA_cumsum, cu_seqlens, chunk_states, initial_states=None
619
-):
620
- total_seqlen, nheads, headdim = x.shape
621
- _, nchunks, chunk_size = dt.shape
622
- _, ngroups, dstate = B.shape
623
- batch = cu_seqlens.shape[0] - 1
624
- cu_seqlens = cu_seqlens.contiguous()
625
- assert nheads % ngroups == 0
626
- assert B.shape == (total_seqlen, ngroups, dstate)
627
- assert dt.shape == (nheads, nchunks, chunk_size)
628
- assert dA_cumsum.shape == dt.shape
629
- assert chunk_states.shape == (nchunks, nheads, headdim, dstate)
630
-
631
- if initial_states is not None:
632
- assert initial_states.shape == (batch, nheads, headdim, dstate)
633
-
634
- states = torch.empty(
635
- batch,
636
- nheads,
637
- headdim,
638
- dstate,
639
- dtype=chunk_states.dtype,
640
- device=chunk_states.device,
641
- )
642
-
643
- initial_states_strides = (
644
- (
645
- initial_states.stride(0),
646
- initial_states.stride(1),
647
- initial_states.stride(2),
648
- initial_states.stride(3),
649
- )
650
- if initial_states is not None
651
- else (0, 0, 0, 0)
652
- )
653
-
654
- grid = lambda META: (
655
- triton.cdiv(headdim, META["BLOCK_SIZE_M"])
656
- * triton.cdiv(dstate, META["BLOCK_SIZE_N"]),
657
- batch,
658
- nheads,
659
- )
660
- with torch.cuda.device(x.device.index):
661
- _chunk_state_varlen_kernel[grid](
662
- x_ptr=x,
663
- b_ptr=B,
664
- dt_ptr=dt,
665
- dA_cumsum_ptr=dA_cumsum,
666
- chunk_states_ptr=chunk_states,
667
- cu_seqlens_ptr=cu_seqlens,
668
- states_ptr=states,
669
- initstates_ptr=initial_states,
670
- hdim=headdim,
671
- dstate=dstate,
672
- chunk_size=chunk_size,
673
- nheads_ngroups_ratio=nheads // ngroups,
674
- stride_x_seqlen=x.stride(0),
675
- stride_x_head=x.stride(1),
676
- stride_x_hdim=x.stride(2),
677
- stride_b_seqlen=B.stride(0),
678
- stride_b_head=B.stride(1),
679
- stride_b_dstate=B.stride(2),
680
- stride_dt_head=dt.stride(0),
681
- stride_dt_chunk=dt.stride(1),
682
- stride_dt_csize=dt.stride(2),
683
- stride_dA_cs_head=dA_cumsum.stride(0),
684
- stride_dA_cs_chunk=dA_cumsum.stride(1),
685
- stride_dA_cs_csize=dA_cumsum.stride(2),
686
- stride_chunk_states_chunk=chunk_states.stride(0),
687
- stride_chunk_states_head=chunk_states.stride(1),
688
- stride_chunk_states_hdim=chunk_states.stride(2),
689
- stride_chunk_states_dstate=chunk_states.stride(3),
690
- stride_states_batch=states.stride(0),
691
- stride_states_head=states.stride(1),
692
- stride_states_hdim=states.stride(2),
693
- stride_states_dstate=states.stride(3),
694
- stride_init_states_batch=initial_states_strides[0],
695
- stride_init_states_head=initial_states_strides[1],
696
- stride_init_states_hdim=initial_states_strides[2],
697
- stride_init_states_dstate=initial_states_strides[3],
698
- HAS_INITSTATES=initial_states is not None,
699
- )
700
- return states
vllm/model_executor/layers/mamba/ops/ssd_combined.py
+3
-6
@@ -107,18 +107,15 @@ def _mamba_chunk_scan_combined_fwd(
107
108
# 3. Compute the inter-chunk SSM recurrence; produces correct SSM states at chunk boundaries
109
# (middle term of factorization of off-diag blocks; A terms)
110
- # - for handling chunked prefill, this requires i) initial_states and
111
- # ii) seq_idx to be all specified.
112
- # - When a new seq_idx is detected, we will stop passing the prev_state
113
- # and switch accordingly to the init_state corresponding to the new seq_idx.
110
+ # - parallelized across sequences using last_chunk_indices to derive
111
+ # per-sequence chunk ranges. Each sequence's state passing runs independently.
112
states = _state_passing_fwd(
113
rearrange(states, "... p n -> ... (p n)"),
114
dA_cumsum, # (nheads, nchunks, chunk_size)
117
- cu_chunk_seqlens,
115
+ last_chunk_indices,
116
initial_states=rearrange(initial_states, "... p n -> ... (p n)")
117
if initial_states is not None
118
else None, # (batch, nheads, headdim*dstate)
121
- seq_idx=seq_idx,
119
out_dtype=state_dtype if state_dtype is not None else C.dtype,
120
)
121
states = rearrange(states, "... (p n) -> ... p n", n=dstate)
vllm/model_executor/layers/mamba/ops/ssd_state_passing.py
+29
-40
@@ -8,6 +8,7 @@
8
9
import torch
10
11
+from vllm.model_executor.layers.mamba.ops.triton_helpers import fast_exp
12
from vllm.triton_utils import tl, triton
13
14
@@ -29,12 +30,9 @@ def _state_passing_fwd_kernel(
30
out_ptr,
31
dA_cs_ptr,
32
initstates_ptr,
32
- seq_idx_ptr,
33
- cu_chunk_seqlens_ptr,
33
+ last_chunk_indices_ptr,
34
# Matrix dimensions
35
dim: tl.constexpr,
36
- nchunks,
37
- seqlen,
36
chunk_size: tl.constexpr,
37
# Strides
38
stride_states_chunk: tl.int64,
@@ -49,55 +47,51 @@ def _state_passing_fwd_kernel(
47
stride_initstates_batch: tl.int64,
48
stride_initstates_head: tl.int64,
49
stride_initstates_dim: tl.constexpr,
52
- stride_seq_idx_chunk: tl.constexpr,
50
# Meta-parameters
51
HAS_INITSTATES: tl.constexpr,
52
BLOCK_SIZE: tl.constexpr,
53
):
57
- pid_h = tl.program_id(axis=1)
54
pid_m = tl.program_id(axis=0)
55
+ pid_b = tl.program_id(axis=1)
56
+ pid_h = tl.program_id(axis=2)
57
60
- states_ptr += pid_h * stride_states_head
61
- dA_cs_ptr += pid_h * stride_dA_cs_head + (chunk_size - 1) * stride_dA_cs_csize
62
- out_ptr += pid_h * stride_out_head
58
+ # Derive this sequence's chunk range from last_chunk_indices
59
+ chunk_end = tl.load(last_chunk_indices_ptr + pid_b) + 1
60
+ chunk_start = (
61
+ tl.load(last_chunk_indices_ptr + pid_b - 1, mask=pid_b > 0, other=-1) + 1
62
+ )
63
+
64
+ # Offset pointers to this sequence's first chunk
65
+ states_ptr += chunk_start * stride_states_chunk + pid_h * stride_states_head
66
+ dA_cs_ptr += (
67
+ pid_h * stride_dA_cs_head
68
+ + chunk_start * stride_dA_cs_chunk
69
+ + (chunk_size - 1) * stride_dA_cs_csize
70
+ )
71
+ out_ptr += chunk_start * stride_out_chunk + pid_h * stride_out_head
72
73
offs_m = pid_m * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
74
states_ptrs = states_ptr + offs_m * stride_states_dim
75
out_ptrs = out_ptr + offs_m * stride_out_dim
76
77
+ # Load initial state once — no per-chunk branching needed
78
if HAS_INITSTATES:
79
initstates_ptrs = (
80
initstates_ptr
81
+ + pid_b * stride_initstates_batch
82
+ pid_h * stride_initstates_head
83
+ offs_m * stride_initstates_dim
84
)
74
-
85
states = tl.load(initstates_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
86
else:
87
states = tl.zeros((BLOCK_SIZE,), dtype=tl.float32)
88
79
- prev_seq_idx = 0
80
- for c in range(nchunks):
89
+ # Loop over only this sequence's chunks — branchless
90
+ nchunks_this_seq = chunk_end - chunk_start
91
+ for _ in range(nchunks_this_seq):
92
new_states = tl.load(states_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
93
dA_cs = tl.load(dA_cs_ptr).to(tl.float32)
83
- seq_idx = tl.load(seq_idx_ptr + c * stride_seq_idx_chunk)
84
- # we have started a new sequence
85
- if prev_seq_idx != seq_idx:
86
- if HAS_INITSTATES:
87
- initstates_ptrs = (
88
- initstates_ptr
89
- + seq_idx * stride_initstates_batch
90
- + pid_h * stride_initstates_head
91
- + offs_m * stride_initstates_dim
92
- )
93
- states = tl.load(initstates_ptrs, mask=offs_m < dim, other=0.0).to(
94
- tl.float32
95
- )
96
- else:
97
- states = tl.zeros((BLOCK_SIZE,), dtype=tl.float32)
98
-
99
- prev_seq_idx = seq_idx
100
- states = tl.exp(dA_cs) * states + new_states
94
+ states = fast_exp(dA_cs) * states + new_states
95
tl.store(out_ptrs, states, mask=offs_m < dim)
96
97
states_ptrs += stride_states_chunk
@@ -108,15 +102,14 @@ def _state_passing_fwd_kernel(
102
def _state_passing_fwd(
103
states,
104
dA_cumsum,
111
- cu_chunk_seqlens,
112
- seq_idx,
105
+ last_chunk_indices,
106
initial_states=None,
107
out_dtype=None,
108
):
109
nchunks, nheads, dim = states.shape
110
chunk_size = dA_cumsum.shape[-1]
111
+ batch = last_chunk_indices.shape[0]
112
assert dA_cumsum.shape == (nheads, nchunks, chunk_size)
119
- seqlen = seq_idx.shape[-1]
113
out_dtype = states.dtype if out_dtype is None else out_dtype
114
out = torch.empty((nchunks, nheads, dim), device=states.device, dtype=out_dtype)
115
@@ -126,19 +119,16 @@ def _state_passing_fwd(
119
else (0, 0, 0)
120
)
121
129
- grid = lambda META: (triton.cdiv(dim, META["BLOCK_SIZE"]), nheads)
122
+ grid = lambda META: (triton.cdiv(dim, META["BLOCK_SIZE"]), batch, nheads)
123
with torch.cuda.device(states.device.index):
124
_state_passing_fwd_kernel[grid](
125
states_ptr=states,
126
out_ptr=out,
127
dA_cs_ptr=dA_cumsum,
128
initstates_ptr=initial_states,
136
- seq_idx_ptr=seq_idx,
137
- cu_chunk_seqlens_ptr=cu_chunk_seqlens,
129
+ last_chunk_indices_ptr=last_chunk_indices,
130
dim=dim,
139
- nchunks=nchunks,
140
- seqlen=seqlen if seq_idx is not None else 0,
141
- chunk_size=chunk_size if seq_idx is not None else 0,
131
+ chunk_size=chunk_size,
132
stride_states_chunk=states.stride(0),
133
stride_states_head=states.stride(1),
134
stride_states_dim=states.stride(2),
@@ -151,7 +141,6 @@ def _state_passing_fwd(
141
stride_initstates_batch=initial_states_strides[0],
142
stride_initstates_head=initial_states_strides[1],
143
stride_initstates_dim=initial_states_strides[2],
154
- stride_seq_idx_chunk=seq_idx.stride(0),
144
HAS_INITSTATES=initial_states is not None,
145
)
146
return out
vllm/model_executor/layers/mamba/ops/triton_helpers.py
new
+17
@@ -0,0 +1,17 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+from vllm.triton_utils import tl, triton
5
+
6
+
7
+@triton.jit
8
+def fast_exp(x):
9
+ """Faster alternative to tl.exp() using the hardware exp2 instruction.
10
+
11
+ tl.math.exp2 maps directly to a single ex2.approx.f32 PTX instruction,
12
+ while tl.exp goes through libdevice __nv_expf which adds function call
13
+ overhead and extra range checking.
14
+ """
15
+ # exp(x) = exp2(x * log2(e)), where log2(e) = 1/ln(2) = 1.4426950408889634
16
+ LOG2E = tl.constexpr(1.4426950408889634)
17
+ return tl.math.exp2(LOG2E * x)