main
py 857 lines 27.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Copyright (c) 2024, Tri Dao, Albert Gu.
5 # Adapted from https://github.com/state-spaces/mamba/blob/v2.2.4/mamba_ssm/ops/triton/selective_state_update.py
6
7 import functools
8 import json
9 import os
10 from contextlib import contextmanager
11 from typing import Any
12
13 import torch
14 from packaging import version
15
16 import vllm.envs as envs
17 from vllm import _custom_ops as ops
18 from vllm.logger import init_logger
19 from vllm.model_executor.layers.mamba.ops.triton_helpers import fast_exp
20 from vllm.platforms import current_platform
21 from vllm.triton_utils import HAS_TRITON, tl, triton
22 from vllm.utils.platform_utils import get_device_name_as_file_name
23 from vllm.v1.attention.backends.utils import NULL_BLOCK_ID
24
25 if current_platform.is_xpu():
26 from vllm._xpu_ops import xpu_ops
27
28 logger = init_logger(__name__)
29
30 TRITON3 = HAS_TRITON and (version.parse(triton.__version__) >= version.parse("3.0.0"))
31
32
33 # ---------------------------------------------------------------------------
34 # JSON config loading
35 # ---------------------------------------------------------------------------
36
37 _CONFIGS_DIR = os.path.join(
38 os.path.dirname(os.path.realpath(__file__)), "configs", "selective_state_update"
39 )
40
41
42 def get_ssm_config_file_name(
43 headdim: int, dstate: int, cache_dtype: str, device_name: str
44 ) -> str:
45 """Return the JSON filename for the given kernel shape.
46
47 Layout: ``configs/selective_state_update/
48 headdim=<H>,dstate=<D>,device_name=<dev>,cache_dtype=<dt>.json``.
49 """
50 return (
51 f"headdim={headdim},dstate={dstate},"
52 f"device_name={device_name},cache_dtype={cache_dtype}.json"
53 )
54
55
56 def get_ssm_device_name() -> str:
57 return get_device_name_as_file_name()
58
59
60 def _canonical_cache_dtype(cache_dtype: str) -> str:
61 """Canonical key for config lookup. bf16 and fp16 share the same tuned
62 configs because the kernel only sees bit width when accessing state."""
63 return "float16" if cache_dtype == "bfloat16" else cache_dtype
64
65
66 @functools.cache
67 def get_ssm_configs(
68 headdim: int, dstate: int, cache_dtype: str
69 ) -> dict[int, Any] | None:
70 """Return tuned (BLOCK_SIZE_M, num_warps) configs for *selective_state_update*
71 keyed by ``effective_batch = batch * nheads``, or ``None`` if no config
72 file is found for the (headdim, dstate, cache_dtype, device) combination.
73
74 They can be generated with:
75 benchmarks/kernels/benchmark_selective_state_update.py --save-configs
76 """
77 cache_dtype = _canonical_cache_dtype(cache_dtype)
78 device_name = get_ssm_device_name()
79 json_file_name = get_ssm_config_file_name(headdim, dstate, cache_dtype, device_name)
80
81 config_file_paths: list[str] = []
82
83 # User-supplied override
84 user_defined_config_folder = envs.VLLM_TUNED_CONFIG_FOLDER
85 if user_defined_config_folder is not None:
86 config_file_paths.append(
87 os.path.join(user_defined_config_folder, json_file_name)
88 )
89
90 # Bundled default
91 config_file_paths.append(os.path.join(_CONFIGS_DIR, json_file_name))
92
93 for path in config_file_paths:
94 if os.path.exists(path):
95 with open(path) as f:
96 logger.info_once(
97 "Using SSM config from %s for selective_state_update.",
98 path,
99 scope="global",
100 )
101 raw = json.load(f)
102 if isinstance(raw, dict):
103 # triton_version included in the config file only for reference
104 raw.pop("triton_version", None)
105 return {int(k): v for k, v in raw.items() if k.isdigit()}
106
107 logger.warning_once(
108 "Using default Mamba SSU config. Performance might be sub-optimal! "
109 "Config file not found at %s",
110 ", ".join(config_file_paths),
111 )
112 return None
113
114
115 def _get_default_ssm_launch_config(
116 dstate: int,
117 is_blackwell: bool,
118 ) -> tuple[int, int]:
119 """Hard-coded fallback heuristic used when no tuned config is available."""
120 BLOCK_SIZE_M, num_warps = 4, 8
121 if dstate <= 16:
122 BLOCK_SIZE_M, num_warps = 32, 4
123 elif dstate <= 32:
124 BLOCK_SIZE_M, num_warps = 16, 4
125 elif dstate <= 64:
126 BLOCK_SIZE_M, num_warps = 8, 4
127 else:
128 if is_blackwell:
129 BLOCK_SIZE_M, num_warps = 32, 8
130 elif dstate <= 128:
131 BLOCK_SIZE_M, num_warps = 4, 4
132 return BLOCK_SIZE_M, num_warps
133
134
135 @functools.cache
136 def _try_get_optimal_ssm_config_cached(
137 headdim: int,
138 dstate: int,
139 batch: int,
140 nheads: int,
141 cache_dtype: str,
142 is_blackwell: bool,
143 ) -> tuple[int, int]:
144 """Cached resolution. See :func:`try_get_optimal_ssm_config`."""
145 effective_batch = batch * nheads
146 configs = get_ssm_configs(headdim, dstate, cache_dtype)
147 if configs:
148 # Pick the closest effective_batch in the tuned grid (MoE strategy).
149 closest = min(configs.keys(), key=lambda x: abs(x - effective_batch))
150 cfg = configs[closest]
151 return cfg["BLOCK_SIZE_M"], cfg["num_warps"]
152
153 return _get_default_ssm_launch_config(dstate, is_blackwell)
154
155
156 # Override hook for benchmarks/tests, see `override_ssm_config`.
157 _ssm_config_override: tuple[int, int] | None = None
158
159
160 @contextmanager
161 def override_ssm_config(config: tuple[int, int]):
162 """Pin ``try_get_optimal_ssm_config`` to ``config`` for the duration of
163 the context. Used by the tuning benchmark to time specific configs."""
164 global _ssm_config_override
165 prev = _ssm_config_override
166 _ssm_config_override = config
167 try:
168 yield
169 finally:
170 _ssm_config_override = prev
171
172
173 def try_get_optimal_ssm_config(
174 headdim: int,
175 dstate: int,
176 batch: int,
177 nheads: int,
178 cache_dtype: str,
179 is_blackwell: bool,
180 ) -> tuple[int, int]:
181 """Return (BLOCK_SIZE_M, num_warps) for the given kernel shape.
182
183 Tuning is keyed on ``effective_batch = batch * nheads`` (the kernel grid
184 scales with the product), so configs transfer across (model, TP) combos
185 sharing ``(headdim, dstate, cache_dtype)``.
186 """
187 if _ssm_config_override is not None:
188 return _ssm_config_override
189 return _try_get_optimal_ssm_config_cached(
190 headdim, dstate, batch, nheads, cache_dtype, is_blackwell
191 )
192
193
194 if TRITON3:
195
196 @triton.jit
197 def softplus(dt):
198 dt = tl.where(dt <= 20.0, tl.math.log(tl.math.exp(dt) + 1), dt)
199 return dt
200 else:
201
202 @triton.jit
203 def softplus(dt):
204 dt = tl.where(dt <= 20.0, tl.math.log1p(tl.exp(dt)), dt)
205 return dt
206
207
208 @triton.jit
209 def convert_rs_fp16x2(x: tl.tensor, rand: tl.tensor) -> tl.tensor:
210 y = tl.inline_asm_elementwise(
211 asm="""{
212 cvt.rs.f16x2.f32 $0, $2, $1, $3;
213 }""",
214 constraints="=r,r,r,r,r",
215 args=(x, rand),
216 dtype=tl.float16,
217 is_pure=True,
218 pack=2,
219 )
220 return y
221
222
223 @triton.heuristics({"HAS_DT_BIAS": lambda args: args["dt_bias_ptr"] is not None})
224 @triton.heuristics({"HAS_D": lambda args: args["D_ptr"] is not None})
225 @triton.heuristics({"HAS_Z": lambda args: args["z_ptr"] is not None})
226 @triton.heuristics(
227 {
228 "HAS_STATE_BATCH_INDICES": lambda args: args["state_batch_indices_ptr"]
229 is not None
230 }
231 )
232 @triton.heuristics(
233 {"IS_SPEC_DECODING": lambda args: args["num_accepted_tokens_ptr"] is not None}
234 )
235 @triton.heuristics({"IS_VARLEN": lambda args: args["cu_seqlens_ptr"] is not None})
236 @triton.heuristics(
237 {"BLOCK_SIZE_DSTATE": lambda args: triton.next_power_of_2(args["dstate"])}
238 )
239 @triton.jit(do_not_specialize=["N"])
240 def _selective_scan_update_kernel(
241 # Pointers to matrices
242 state_ptr,
243 rand_seed_ptr,
244 x_ptr,
245 dt_ptr,
246 dt_bias_ptr,
247 A_ptr,
248 B_ptr,
249 C_ptr,
250 D_ptr,
251 z_ptr,
252 out_ptr,
253 state_batch_indices_ptr,
254 dst_state_batch_indices_ptr,
255 null_block_id,
256 num_accepted_tokens_ptr,
257 cu_seqlens_ptr,
258 # Matrix dimensions
259 N,
260 nheads,
261 dim,
262 dstate,
263 nheads_ngroups_ratio,
264 # Strides
265 stride_state_batch,
266 stride_state_head,
267 stride_state_dim,
268 stride_state_dstate,
269 stride_x_batch,
270 stride_x_head,
271 stride_x_dim,
272 stride_dt_batch,
273 stride_dt_head,
274 stride_dt_dim,
275 stride_dt_bias_head,
276 stride_dt_bias_dim,
277 stride_A_head,
278 stride_A_dim,
279 stride_A_dstate,
280 stride_B_batch,
281 stride_B_group,
282 stride_B_dstate,
283 stride_C_batch,
284 stride_C_group,
285 stride_C_dstate,
286 stride_D_head,
287 stride_D_dim,
288 stride_z_batch,
289 stride_z_head,
290 stride_z_dim,
291 stride_out_batch,
292 stride_out_head,
293 stride_out_dim,
294 stride_state_indices_batch,
295 stride_state_indices_T,
296 stride_dst_state_indices_batch,
297 stride_dst_state_indices_T,
298 # Meta-parameters
299 DT_SOFTPLUS: tl.constexpr,
300 TIE_HDIM: tl.constexpr,
301 BLOCK_SIZE_M: tl.constexpr,
302 HAS_DT_BIAS: tl.constexpr,
303 HAS_D: tl.constexpr,
304 HAS_Z: tl.constexpr,
305 HAS_STATE_BATCH_INDICES: tl.constexpr,
306 IS_SPEC_DECODING: tl.constexpr,
307 IS_VARLEN: tl.constexpr,
308 BLOCK_SIZE_DSTATE: tl.constexpr,
309 USE_RS_ROUNDING: tl.constexpr,
310 PHILOX_ROUNDS: tl.constexpr,
311 ):
312 pid_m = tl.program_id(axis=0)
313 pid_b = tl.program_id(axis=1)
314 pid_h = tl.program_id(axis=2)
315
316 if IS_VARLEN:
317 bos = tl.load(cu_seqlens_ptr + pid_b).to(tl.int64)
318 eos = tl.load(cu_seqlens_ptr + pid_b + 1).to(tl.int64)
319 seq_len = eos - bos
320
321 if seq_len == 0:
322 return
323 else:
324 bos = pid_b
325 seq_len = 1
326
327 state_ptr_base = state_ptr
328
329 # If HAS_STATE_BATCH_INDICES is true, then the ssm state's batch coordinate
330 # is taken from the state_batch_indices_ptr Otherwise, the state coordinate
331 # is the same as the batch id.
332 if HAS_STATE_BATCH_INDICES:
333 if IS_SPEC_DECODING:
334 num_accepted = tl.load(num_accepted_tokens_ptr + pid_b).to(tl.int64)
335 init_token_idx = tl.maximum(num_accepted - 1, 0)
336 else:
337 init_token_idx = 0
338
339 dst_state_batch_indices_ptr += pid_b * stride_dst_state_indices_batch
340 if not IS_SPEC_DECODING:
341 dst_state_batch_idx = tl.load(
342 dst_state_batch_indices_ptr
343 + init_token_idx * stride_dst_state_indices_T
344 ).to(tl.int64)
345 dst_state_ptr = state_ptr + (
346 dst_state_batch_idx * stride_state_batch + pid_h * stride_state_head
347 )
348
349 state_batch_indices_ptr += (
350 pid_b * stride_state_indices_batch + init_token_idx * stride_state_indices_T
351 )
352 state_batch_idx = tl.load(state_batch_indices_ptr).to(tl.int64)
353 state_ptr += state_batch_idx * stride_state_batch + pid_h * stride_state_head
354 else:
355 dst_state_ptr = (
356 state_ptr + pid_b * stride_state_batch + pid_h * stride_state_head
357 )
358 state_ptr += pid_b * stride_state_batch + pid_h * stride_state_head
359
360 x_ptr += bos * stride_x_batch + pid_h * stride_x_head
361 dt_ptr += bos * stride_dt_batch + pid_h * stride_dt_head
362 if HAS_DT_BIAS:
363 dt_bias_ptr += pid_h * stride_dt_bias_head
364 A_ptr += pid_h * stride_A_head
365 B_ptr += bos * stride_B_batch + (pid_h // nheads_ngroups_ratio) * stride_B_group
366 C_ptr += bos * stride_C_batch + (pid_h // nheads_ngroups_ratio) * stride_C_group
367 if HAS_Z:
368 z_ptr += bos * stride_z_batch + pid_h * stride_z_head
369 out_ptr += bos * stride_out_batch + pid_h * stride_out_head
370
371 offs_m = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
372 offs_n = tl.arange(0, BLOCK_SIZE_DSTATE)
373 state_ptrs = state_ptr + (
374 offs_m[:, None] * stride_state_dim + offs_n[None, :] * stride_state_dstate
375 )
376 if not IS_SPEC_DECODING:
377 dst_state_ptrs = dst_state_ptr + (
378 offs_m[:, None] * stride_state_dim + offs_n[None, :] * stride_state_dstate
379 )
380
381 mask = (offs_m[:, None] < dim) & (offs_n[None, :] < dstate)
382 if HAS_STATE_BATCH_INDICES:
383 mask &= state_batch_idx != null_block_id
384 state = tl.load(state_ptrs, mask=mask, other=0.0).to(tl.float32)
385
386 if HAS_DT_BIAS:
387 dt_bias_ptrs = dt_bias_ptr + offs_m * stride_dt_bias_dim
388 if HAS_D:
389 D_ptr += pid_h * stride_D_head
390 D_ptrs = D_ptr + offs_m * stride_D_dim
391 A_ptrs = A_ptr + offs_m[:, None] * stride_A_dim + offs_n[None, :] * stride_A_dstate
392
393 for i_t in range(seq_len):
394 x_ptrs = x_ptr + offs_m * stride_x_dim
395 dt_ptrs = dt_ptr + offs_m * stride_dt_dim
396 B_ptrs = B_ptr + offs_n * stride_B_dstate
397 C_ptrs = C_ptr + offs_n * stride_C_dstate
398 if HAS_Z:
399 z_ptrs = z_ptr + offs_m * stride_z_dim
400 out_ptrs = out_ptr + offs_m * stride_out_dim
401
402 x = tl.load(x_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
403 if not TIE_HDIM:
404 dt = tl.load(dt_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
405 if HAS_DT_BIAS:
406 dt += tl.load(dt_bias_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
407 if DT_SOFTPLUS:
408 dt = softplus(dt)
409 A = tl.load(
410 A_ptrs,
411 mask=(offs_m[:, None] < dim) & (offs_n[None, :] < dstate),
412 other=0.0,
413 ).to(tl.float32)
414 dA = fast_exp(A * dt[:, None])
415 else:
416 dt = tl.load(dt_ptr).to(tl.float32)
417 if HAS_DT_BIAS:
418 dt += tl.load(dt_bias_ptr).to(tl.float32)
419 if DT_SOFTPLUS:
420 dt = softplus(dt)
421 A = tl.load(A_ptr).to(tl.float32)
422 dA = fast_exp(A * dt) # scalar, not a matrix
423
424 B = tl.load(B_ptrs, mask=offs_n < dstate, other=0.0).to(tl.float32)
425 C = tl.load(C_ptrs, mask=offs_n < dstate, other=0.0).to(tl.float32)
426 if HAS_D:
427 D = tl.load(D_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
428 if HAS_Z:
429 z = tl.load(z_ptrs, mask=offs_m < dim, other=0.0).to(tl.float32)
430
431 dB = B[None, :] * dt[:, None] if not TIE_HDIM else B * dt
432 state = state * dA + dB * x[:, None]
433
434 if IS_SPEC_DECODING:
435 dst_idx_ptr = dst_state_batch_indices_ptr + i_t * stride_dst_state_indices_T
436 token_dst_idx = tl.load(dst_idx_ptr).to(tl.int64)
437 if token_dst_idx != null_block_id:
438 token_dst_ptrs = (
439 state_ptr_base
440 + token_dst_idx * stride_state_batch
441 + pid_h * stride_state_head
442 + offs_m[:, None] * stride_state_dim
443 + offs_n[None, :] * stride_state_dstate
444 )
445 tl.store(
446 token_dst_ptrs, state.to(token_dst_ptrs.dtype.element_ty), mask=mask
447 )
448
449 out = tl.sum(state * C[None, :], axis=1)
450 if HAS_D:
451 out += x * D
452 if HAS_Z:
453 out *= z * tl.sigmoid(z)
454 tl.store(out_ptrs, out, mask=offs_m < dim)
455
456 x_ptr += stride_x_batch
457 dt_ptr += stride_dt_batch
458 B_ptr += stride_B_batch
459 C_ptr += stride_C_batch
460 out_ptr += stride_out_batch
461 if HAS_Z:
462 z_ptr += stride_z_batch
463
464 if not IS_SPEC_DECODING:
465 if USE_RS_ROUNDING:
466 # Load random seed
467 rand_seed = tl.load(rand_seed_ptr)
468 # Generate random offsets for each element in state
469 if HAS_STATE_BATCH_INDICES:
470 rand_offsets = (
471 state_batch_idx * stride_state_batch + pid_h * stride_state_head
472 )
473 else:
474 rand_offsets = pid_b * stride_state_batch + pid_h * stride_state_head
475 rand_offsets += (
476 offs_m[:, None] * stride_state_dim
477 + offs_n[None, :] * stride_state_dstate
478 )
479 # Generate random 32-bits for each element in state
480 if PHILOX_ROUNDS > 0:
481 rand = tl.randint(rand_seed, rand_offsets, PHILOX_ROUNDS)
482 else:
483 rand = tl.randint(rand_seed, rand_offsets)
484 # Convert state to fp16 with RS rounding
485 state = convert_rs_fp16x2(state, rand)
486 tl.static_assert(state.dtype == tl.float16, "state must be fp16")
487 tl.static_assert(
488 dst_state_ptrs.dtype.element_ty == tl.float16,
489 "dst_state_ptrs must be fp16",
490 )
491 else:
492 state = state.to(dst_state_ptrs.dtype.element_ty)
493 tl.store(dst_state_ptrs, state, mask=mask)
494
495
496 def selective_state_update(
497 state,
498 x,
499 dt,
500 A,
501 B,
502 C,
503 D,
504 dt_bias,
505 z=None,
506 dt_softplus=False,
507 state_batch_indices=None,
508 dst_state_batch_indices=None,
509 null_block_id=NULL_BLOCK_ID,
510 out=None,
511 num_accepted_tokens=None,
512 cu_seqlens=None,
513 is_blackwell=False,
514 enable_stochastic_rounding=False,
515 cache_philox_rounds=0,
516 ):
517 """Argument:
518 state: (batch, dim, dstate) or (batch, nheads, dim, dstate)
519 x: (batch, dim) or (batch, nheads, dim)
520 dt: (batch, dim) or (batch, nheads, dim)
521 A: (dim, dstate) or (nheads, dim, dstate)
522 B: (batch, dstate) or (batch, ngroups, dstate)
523 C: (batch, dstate) or (batch, ngroups, dstate)
524 D: (dim,) or (nheads, dim)
525 z: (batch, dim) or (batch, nheads, dim)
526 dt_bias: (dim,) or (nheads, dim)
527 null_block_id: int
528 if state_batch_indices is passed, lets the kernel identify
529 padded entries that will not be processed,
530 for example: state_batch_indices = [null_block_id, 1, 20,
531 null_block_id] in this case, the kernel will not process
532 entries at indices 0 and 3
533 out: Preallocated ssm output tensor. Assume same shape as x.
534 In-place updated.
535 num_accepted_tokens: (batch,)
536 number of accepted tokens from previous verification step,
537 tells the kernel which initial state to use
538 cu_seqlens: (batch,)
539 length per sequence, for variable length in speculative decoding cases
540 """
541 if state.dim() == 3:
542 state = state.unsqueeze(1)
543 if x.dim() == 2:
544 x = x.unsqueeze(1)
545 if dt.dim() == 2:
546 dt = dt.unsqueeze(1)
547 if A.dim() == 2:
548 A = A.unsqueeze(0)
549 if B.dim() == 2:
550 B = B.unsqueeze(1)
551 if C.dim() == 2:
552 C = C.unsqueeze(1)
553 if D.dim() == 1:
554 D = D.unsqueeze(0)
555 if z is not None and z.dim() == 2:
556 z = z.unsqueeze(1)
557 if dt_bias.dim() == 1:
558 dt_bias = dt_bias.unsqueeze(0)
559 if out.dim() == 2:
560 out = out.unsqueeze(1)
561 if state_batch_indices is not None and state_batch_indices.dim() == 1:
562 state_batch_indices = state_batch_indices.unsqueeze(1)
563 if dst_state_batch_indices is not None and dst_state_batch_indices.dim() == 1:
564 dst_state_batch_indices = dst_state_batch_indices.unsqueeze(1)
565 if num_accepted_tokens is not None:
566 assert state_batch_indices is not None and state_batch_indices.dim() == 2
567 assert dst_state_batch_indices is None or dst_state_batch_indices.dim() == 2
568
569 _, nheads, dim, dstate = state.shape
570 batch = x.shape[0]
571 if cu_seqlens is not None:
572 N = len(cu_seqlens) - 1
573 # Only used to verify the shape of
574 # state_batch_indices and dst_state_batch_indices
575 max_seqlen = (
576 state_batch_indices.size(-1) if state_batch_indices is not None else 1
577 )
578 else:
579 N = batch
580 max_seqlen = 1
581
582 assert x.shape == (batch, nheads, dim)
583 assert dt.shape == x.shape
584 assert A.shape == (nheads, dim, dstate)
585 ngroups = B.shape[1]
586 assert nheads % ngroups == 0, "nheads must be divisible by ngroups"
587 assert B.shape == (batch, ngroups, dstate)
588 assert C.shape == B.shape
589 assert D.shape == (nheads, dim)
590 if z is not None:
591 assert z.shape == x.shape
592 assert dt_bias.shape == (nheads, dim)
593 if state_batch_indices is not None:
594 assert state_batch_indices.shape[0] >= N
595 assert state_batch_indices.shape[1] >= max_seqlen
596 if dst_state_batch_indices is not None:
597 assert dst_state_batch_indices.shape[0] >= N
598 assert dst_state_batch_indices.shape[1] >= max_seqlen
599 else:
600 # revert to the default behavior of in-place state updates
601 dst_state_batch_indices = state_batch_indices
602 assert out.shape == x.shape
603 if num_accepted_tokens is not None:
604 assert num_accepted_tokens.shape == (N,)
605
606 grid = lambda META: (triton.cdiv(dim, META["BLOCK_SIZE_M"]), N, nheads)
607 z_strides = (z.stride(0), z.stride(1), z.stride(2)) if z is not None else (0, 0, 0)
608 state_batch_indices_strides = (
609 (state_batch_indices.stride(0), state_batch_indices.stride(1))
610 if state_batch_indices is not None
611 else (0, 0)
612 )
613 dst_state_batch_indices_strides = (
614 (dst_state_batch_indices.stride(0), dst_state_batch_indices.stride(1))
615 if dst_state_batch_indices is not None
616 else (0, 0)
617 )
618 # We don't want autotune since it will overwrite the state.
619 # Load from JSON config if available, otherwise fall back to heuristic.
620 cache_dtype = str(state.dtype).removeprefix("torch.")
621 BLOCK_SIZE_M, num_warps = try_get_optimal_ssm_config(
622 dim, dstate, N, nheads, cache_dtype, is_blackwell
623 )
624
625 tie_hdim = (
626 A.stride(-1) == 0
627 and A.stride(-2) == 0
628 and dt.stride(-1) == 0
629 and dt_bias.stride(-1) == 0
630 )
631 rand_seed = (
632 torch.randint(0, 2**32, (1,), device=state.device)
633 if enable_stochastic_rounding
634 else None
635 )
636
637 with torch.accelerator.device_index(x.device.index):
638 _selective_scan_update_kernel[grid](
639 state,
640 rand_seed,
641 x,
642 dt,
643 dt_bias,
644 A,
645 B,
646 C,
647 D,
648 z,
649 out,
650 state_batch_indices,
651 dst_state_batch_indices,
652 null_block_id,
653 num_accepted_tokens,
654 cu_seqlens,
655 N,
656 nheads,
657 dim,
658 dstate,
659 nheads // ngroups,
660 state.stride(0),
661 state.stride(1),
662 state.stride(2),
663 state.stride(3),
664 x.stride(0),
665 x.stride(1),
666 x.stride(2),
667 dt.stride(0),
668 dt.stride(1),
669 dt.stride(2),
670 dt_bias.stride(0),
671 dt_bias.stride(1),
672 A.stride(0),
673 A.stride(1),
674 A.stride(2),
675 B.stride(0),
676 B.stride(1),
677 B.stride(2),
678 C.stride(0),
679 C.stride(1),
680 C.stride(2),
681 D.stride(0),
682 D.stride(1),
683 z_strides[0],
684 z_strides[1],
685 z_strides[2],
686 out.stride(0),
687 out.stride(1),
688 out.stride(2),
689 state_batch_indices_strides[0],
690 state_batch_indices_strides[1],
691 dst_state_batch_indices_strides[0],
692 dst_state_batch_indices_strides[1],
693 dt_softplus,
694 tie_hdim,
695 BLOCK_SIZE_M,
696 num_warps=num_warps,
697 USE_RS_ROUNDING=enable_stochastic_rounding,
698 PHILOX_ROUNDS=cache_philox_rounds,
699 )
700
701
702 def selective_scan_fn(
703 u,
704 ssm_states,
705 delta,
706 A,
707 B,
708 C,
709 D=None,
710 z=None,
711 delta_bias=None,
712 delta_softplus=False,
713 query_start_loc=None,
714 cache_indices=None,
715 has_initial_state=None,
716 null_block_id=NULL_BLOCK_ID,
717 block_size=1024,
718 block_idx_first_scheduled_token=None,
719 block_idx_last_scheduled_token=None,
720 initial_state_idx=None,
721 cu_chunk_seqlen=None,
722 last_chunk_indices=None,
723 ) -> torch.Tensor:
724 """u: (dim, total_length) for varlen or (batch, dim, seqlen)
725 applies changes in place.
726 ssm_states: (batch, dim, dstate) or (batch, nheads, dim, dstate)
727 applies changes in place.
728 delta: (dim, total_length) for varlen or (batch, dim, seqlen)
729 A: (dim, dstate)
730 B: (ngroups, dstate, total_length) for varlen or
731 (batch,ngroups,dstate,seqlen)
732 C: (ngroups, dstate, total_length) for varlen or
733 (batch,ngroups,dstate,seqlen)
734 D: (dim,)
735 z: (dim, total_length) for varlen or (batch, dim, seqlen)
736 dt_bias: (dim,) or (dim)
737 query_start_loc: (batch + 1) int32
738 The cumulative sequence lengths of the sequences in
739 the batch, used to index into sequence. prepended with 0.
740 for example: query_start_loc = torch.Tensor([0,10,16,17]),
741 x.shape=(dim,17)
742 cache_indices: (batch) int32
743 A tensor with each cell is a correspondent
744 input and output ssm_state indices
745 - Without APC: (batch,) - single state index per batch item
746 - With APC: (batch, max_positions) - cache block indices for read/write
747 Each non-zero value indicates a cache block to load from and/or write to.
748 has_initial_state: (batch) bool
749 A tensor populated with ones and zeros,
750 indicate if the ssm_state at the corresponding index should be
751 used as initial state. Not providing argument assumes
752 there's no initial state
753 null_block_id: int
754 if cache_indices is passed, lets the kernel identify padding entries
755 that will not be processed,
756 for example: cache_indices = [null_block_id, 1 ,20 ,null_block_id]
757 in this case, the kernel will not process entries at indices 0 and 3
758 block_size: int
759 The block size to align the cached states to
760 block_idx_first_scheduled_token: (batch,), dtype int32
761 The pointer into cache_indices, where the first
762 cache block to be filled is located.
763 block_idx_last_scheduled_token: (batch,), dtype int32
764 The pointer into cache_indices, where the last cache block
765 to be filled is located.
766 initial_state_idx: (batch,), dtype int32
767 The pointer into cache_indices, where the cache block
768 containing the initial state is located.
769
770 Returns
771 -------
772 output: (dim, total_length) for varlen or (batch, dim, seqlen)
773 supports inplace replacement
774
775 """
776 if u.stride(-1) != 1:
777 u = u.contiguous()
778 if delta.stride(-1) != 1:
779 delta = delta.contiguous()
780 if D is not None:
781 D = D.contiguous()
782 if B.stride(-1) != 1:
783 B = B.contiguous()
784 if C.stride(-1) != 1:
785 C = C.contiguous()
786 if z is not None and z.stride(-1) != 1:
787 z = z.contiguous()
788 if B.dim() == 3 and query_start_loc is None:
789 B = B.unsqueeze(1)
790 if B.dim() == 2 and query_start_loc is not None:
791 B = B.unsqueeze(0)
792 if C.dim() == 3 and query_start_loc is None:
793 C = C.unsqueeze(1)
794 if C.dim() == 2 and query_start_loc is not None:
795 C = C.unsqueeze(0)
796
797 if current_platform.is_xpu():
798 xpu_ops.selective_scan_fwd(
799 u,
800 delta,
801 A,
802 B,
803 C,
804 D,
805 z,
806 delta_bias,
807 delta_softplus,
808 query_start_loc,
809 cache_indices,
810 has_initial_state,
811 ssm_states,
812 null_block_id,
813 block_size,
814 block_idx_first_scheduled_token,
815 block_idx_last_scheduled_token,
816 initial_state_idx,
817 cu_chunk_seqlen,
818 last_chunk_indices,
819 )
820 else:
821 ops.selective_scan_fwd(
822 u,
823 delta,
824 A,
825 B,
826 C,
827 D,
828 z,
829 delta_bias,
830 delta_softplus,
831 query_start_loc,
832 cache_indices,
833 has_initial_state,
834 ssm_states,
835 null_block_id,
836 block_size,
837 block_idx_first_scheduled_token,
838 block_idx_last_scheduled_token,
839 initial_state_idx,
840 cu_chunk_seqlen,
841 last_chunk_indices,
842 )
843
844 if z is None:
845 return delta # output written inplace to delta
846 else:
847 return z # output written inplace to z
848
849
850 from vllm.platforms import current_platform # noqa: E402
851
852 if current_platform.is_cpu():
853 from vllm.model_executor.layers.mamba.ops.cpu.mamba_ssm import (
854 selective_state_update as selective_state_update_cpu,
855 )
856
857 selective_state_update = selective_state_update_cpu # type: ignore