main
py 704 lines 23.6 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import copy
4
5 import torch
6 import torch.nn.functional as F
7 from transformers.configuration_utils import PreTrainedConfig
8
9 from vllm.config import (
10 VllmConfig,
11 get_current_vllm_config,
12 )
13 from vllm.distributed import (
14 get_tensor_model_parallel_rank,
15 get_tensor_model_parallel_world_size,
16 )
17 from vllm.forward_context import get_forward_context
18 from vllm.model_executor.custom_op import PluggableLayer
19 from vllm.model_executor.layers.layernorm import RMSNorm
20 from vllm.model_executor.layers.linear import (
21 ColumnParallelLinear,
22 QKVParallelLinear,
23 RowParallelLinear,
24 )
25 from vllm.model_executor.layers.mamba.linear.base import LinearAttention
26 from vllm.model_executor.layers.mamba.linear.minimax_linear_attn import (
27 MiniMaxText01LinearAttention,
28 MiniMaxText01LinearKernel,
29 linear_attention_decode,
30 )
31 from vllm.model_executor.layers.rotary_embedding import get_rope
32 from vllm.third_party.flash_linear_attention.ops.layernorm_guard import (
33 RMSNormGated,
34 layernorm_fn,
35 )
36 from vllm.triton_utils import tl, triton
37 from vllm.v1.attention.backends.linear_attn import LinearAttentionMetadata
38
39
40 def _build_rope_parameters(config: PreTrainedConfig) -> dict | None:
41 rope_parameters = copy.deepcopy(getattr(config, "rope_parameters", None)) or {}
42 if "rope_theta" not in rope_parameters and hasattr(config, "rope_theta"):
43 rope_parameters["rope_theta"] = config.rope_theta
44 if "partial_rotary_factor" not in rope_parameters and hasattr(
45 config, "partial_rotary_factor"
46 ):
47 rope_parameters["partial_rotary_factor"] = config.partial_rotary_factor
48
49 rope_scaling = getattr(config, "rope_scaling", None)
50 if isinstance(rope_scaling, dict):
51 rope_scaling = copy.deepcopy(rope_scaling)
52 if "type" in rope_scaling and "rope_type" not in rope_scaling:
53 rope_scaling["rope_type"] = rope_scaling.pop("type")
54 rope_parameters.update(rope_scaling)
55
56 return rope_parameters or None
57
58
59 def clear_linear_attention_cache_for_new_sequences(
60 kv_cache: torch.Tensor,
61 state_indices_tensor: torch.Tensor,
62 attn_metadata: LinearAttentionMetadata,
63 ) -> None:
64 num_prefills = getattr(attn_metadata, "num_prefills", 0)
65 if num_prefills <= 0:
66 return
67
68 num_decodes = getattr(attn_metadata, "num_decodes", 0)
69 prefill_state_indices = getattr(attn_metadata, "state_indices_tensor_p", None)
70 for prefill_idx in range(num_prefills):
71 if num_decodes + prefill_idx + 1 >= len(attn_metadata.query_start_loc):
72 break
73 q_start = attn_metadata.query_start_loc[num_decodes + prefill_idx]
74 q_end = attn_metadata.query_start_loc[num_decodes + prefill_idx + 1]
75 query_len = q_end - q_start
76 context_len = attn_metadata.seq_lens[num_decodes + prefill_idx] - query_len
77 if context_len == 0:
78 if prefill_state_indices is not None:
79 block_to_clear = prefill_state_indices[prefill_idx]
80 else:
81 block_to_clear = state_indices_tensor[num_decodes + prefill_idx]
82 kv_cache[block_to_clear, ...] = 0
83
84
85 def linear_attention_prefill_and_mix(
86 q: torch.Tensor,
87 k: torch.Tensor,
88 v: torch.Tensor,
89 kv_cache: torch.Tensor,
90 state_indices_tensor: torch.Tensor,
91 attn_metadata: LinearAttentionMetadata,
92 slope_rate: torch.Tensor,
93 block_size: int,
94 decode_fn,
95 prefix_fn,
96 layer_idx: int | None = None,
97 ) -> torch.Tensor:
98 hidden = []
99 req_offset = getattr(attn_metadata, "num_decodes", 0)
100 prefill_state_indices = getattr(attn_metadata, "state_indices_tensor_p", None)
101 for _prefill_idx in range(getattr(attn_metadata, "num_prefills", 0)):
102 if req_offset + _prefill_idx + 1 >= len(attn_metadata.query_start_loc):
103 break
104 if prefill_state_indices is not None and _prefill_idx >= len(
105 prefill_state_indices
106 ):
107 break
108 if prefill_state_indices is None and _prefill_idx >= len(state_indices_tensor):
109 break
110 _start = attn_metadata.query_start_loc[req_offset + _prefill_idx]
111 _end = attn_metadata.query_start_loc[req_offset + _prefill_idx + 1]
112 if prefill_state_indices is not None:
113 slot_id = prefill_state_indices[_prefill_idx]
114 else:
115 slot_id = state_indices_tensor[req_offset + _prefill_idx]
116 qs = q[_start:_end].transpose(0, 1).contiguous()
117 ks = k[_start:_end].transpose(0, 1).contiguous()
118 vs = v[_start:_end].transpose(0, 1).contiguous()
119 slice_layer_cache = kv_cache[slot_id, ...]
120 out_slice = prefix_fn(
121 qs,
122 ks,
123 vs,
124 slice_layer_cache,
125 slope_rate,
126 block_size,
127 layer_idx=layer_idx,
128 )
129 hidden.append(out_slice.contiguous())
130
131 if attn_metadata.num_decode_tokens > 0:
132 hidden_decode = decode_fn(
133 q, k, v, kv_cache, state_indices_tensor, attn_metadata
134 )
135 hidden.insert(0, hidden_decode)
136
137 if not hidden:
138 return torch.empty((0, q.size(1) * q.size(2)), device=q.device, dtype=q.dtype)
139
140 hidden = torch.concat(hidden, dim=0).contiguous()
141 return hidden
142
143
144 @triton.jit
145 def _bailing_linear_attn_decode_spec_step_kernel(
146 q_ptr,
147 k_ptr,
148 v_ptr,
149 kv_cache_ptr,
150 slope_rate_ptr,
151 state_indices_ptr,
152 query_start_loc_ptr,
153 num_accepted_tokens_ptr,
154 output_ptr,
155 q_start: tl.constexpr,
156 D: tl.constexpr,
157 q_b_stride,
158 q_h_stride,
159 q_d_stride,
160 k_b_stride,
161 k_h_stride,
162 k_d_stride,
163 v_b_stride,
164 v_h_stride,
165 v_d_stride,
166 cache_b_stride,
167 cache_h_stride,
168 cache_d0_stride,
169 cache_d1_stride,
170 state_indices_b_stride,
171 state_indices_t_stride,
172 output_b_stride,
173 output_d_stride,
174 DRAFT_IDX: tl.constexpr,
175 STATE_WIDTH: tl.constexpr,
176 BLOCK_SIZE: tl.constexpr,
177 ):
178 req_id = tl.program_id(0)
179 head_id = tl.program_id(1)
180 block_id = tl.program_id(2)
181
182 req_start = tl.load(query_start_loc_ptr + req_id).to(tl.int64)
183 req_end = tl.load(query_start_loc_ptr + req_id + 1).to(tl.int64)
184 query_len = req_end - req_start
185 if query_len <= DRAFT_IDX:
186 return
187
188 dst_slot = tl.load(
189 state_indices_ptr
190 + req_id * state_indices_b_stride
191 + DRAFT_IDX * state_indices_t_stride
192 ).to(tl.int64)
193 if dst_slot == -1:
194 return
195
196 if DRAFT_IDX == 0:
197 accepted_offset = tl.load(num_accepted_tokens_ptr + req_id).to(tl.int64) - 1
198 accepted_offset = tl.maximum(accepted_offset, 0)
199 accepted_offset = tl.minimum(accepted_offset, STATE_WIDTH - 1)
200 src_slot = tl.load(
201 state_indices_ptr
202 + req_id * state_indices_b_stride
203 + accepted_offset * state_indices_t_stride
204 ).to(tl.int64)
205 else:
206 src_slot = tl.load(
207 state_indices_ptr
208 + req_id * state_indices_b_stride
209 + (DRAFT_IDX - 1) * state_indices_t_stride
210 ).to(tl.int64)
211 if src_slot == -1:
212 return
213
214 token_idx = req_start - q_start + DRAFT_IDX
215 qk_offsets = tl.arange(0, D)
216 v_offsets = tl.arange(0, BLOCK_SIZE) + block_id * BLOCK_SIZE
217 qk_mask = qk_offsets < D
218 v_mask = v_offsets < D
219 kv_mask = qk_mask[:, None] & v_mask[None, :]
220
221 q = tl.load(
222 q_ptr + token_idx * q_b_stride + head_id * q_h_stride + qk_offsets * q_d_stride,
223 mask=qk_mask,
224 other=0.0,
225 )
226 k = tl.load(
227 k_ptr + token_idx * k_b_stride + head_id * k_h_stride + qk_offsets * k_d_stride,
228 mask=qk_mask,
229 other=0.0,
230 )
231 v = tl.load(
232 v_ptr + token_idx * v_b_stride + head_id * v_h_stride + v_offsets * v_d_stride,
233 mask=v_mask,
234 other=0.0,
235 )
236
237 cache_offsets = (
238 qk_offsets[:, None] * cache_d0_stride + v_offsets[None, :] * cache_d1_stride
239 )
240 src_cache_ptr = (
241 kv_cache_ptr
242 + src_slot * cache_b_stride
243 + head_id * cache_h_stride
244 + cache_offsets
245 )
246 dst_cache_ptr = (
247 kv_cache_ptr
248 + dst_slot * cache_b_stride
249 + head_id * cache_h_stride
250 + cache_offsets
251 )
252
253 slope = tl.load(slope_rate_ptr + head_id)
254 decay = tl.exp(-slope)
255 kv_old = tl.load(src_cache_ptr, mask=kv_mask, other=0.0)
256 kv_new = k[:, None] * v[None, :] + decay * kv_old
257
258 output = tl.sum(q[:, None].to(tl.float32) * kv_new, axis=0)
259 tl.store(dst_cache_ptr, kv_new, mask=kv_mask)
260 tl.store(
261 output_ptr
262 + token_idx * output_b_stride
263 + (head_id * D + v_offsets) * output_d_stride,
264 output,
265 mask=v_mask,
266 )
267
268
269 def bailing_linear_attention_decode_spec(
270 q: torch.Tensor,
271 k: torch.Tensor,
272 v: torch.Tensor,
273 kv_cache: torch.Tensor,
274 slope_rate: torch.Tensor,
275 state_indices_tensor: torch.Tensor,
276 query_start_loc: torch.Tensor,
277 num_accepted_tokens: torch.Tensor,
278 q_start: int,
279 q_end: int | None,
280 slot_start: int,
281 slot_end: int | None,
282 block_size: int,
283 ) -> torch.Tensor:
284 q_decode = q[q_start:q_end]
285 k_decode = k[q_start:q_end]
286 v_decode = v[q_start:q_end]
287 hidden = torch.empty(
288 (q_decode.shape[0], q.shape[1] * q.shape[2]),
289 device=q.device,
290 dtype=q.dtype,
291 )
292 hidden.zero_()
293
294 state_indices_tensor = state_indices_tensor[slot_start:slot_end]
295 query_start_loc = query_start_loc.to(device=q.device)
296
297 batch_size = state_indices_tensor.shape[0]
298 num_heads = q_decode.shape[1]
299 head_dim = q_decode.shape[2]
300 assert k_decode.shape == (q_decode.shape[0], num_heads, head_dim)
301 assert v_decode.shape == (q_decode.shape[0], num_heads, head_dim)
302 state_width = state_indices_tensor.shape[1]
303
304 grid = (batch_size, num_heads, triton.cdiv(head_dim, block_size))
305 for draft_idx in range(state_width):
306 _bailing_linear_attn_decode_spec_step_kernel[grid](
307 q_decode,
308 k_decode,
309 v_decode,
310 kv_cache,
311 slope_rate,
312 state_indices_tensor,
313 query_start_loc,
314 num_accepted_tokens[:batch_size],
315 hidden,
316 q_start,
317 head_dim,
318 q_decode.stride(0),
319 q_decode.stride(1),
320 q_decode.stride(2),
321 k_decode.stride(0),
322 k_decode.stride(1),
323 k_decode.stride(2),
324 v_decode.stride(0),
325 v_decode.stride(1),
326 v_decode.stride(2),
327 kv_cache.stride(0),
328 kv_cache.stride(1),
329 kv_cache.stride(2),
330 kv_cache.stride(3),
331 state_indices_tensor.stride(0),
332 state_indices_tensor.stride(1),
333 hidden.stride(0),
334 hidden.stride(1),
335 DRAFT_IDX=draft_idx,
336 STATE_WIDTH=state_width,
337 BLOCK_SIZE=block_size,
338 )
339
340 return hidden
341
342
343 class BailingGroupRMSNormGate(RMSNormGated):
344 def __init__(
345 self,
346 hidden_size,
347 eps=1e-5,
348 group_size=None,
349 norm_before_gate=True,
350 device=None,
351 dtype=None,
352 ):
353 super().__init__(
354 hidden_size,
355 eps=eps,
356 group_size=group_size,
357 norm_before_gate=norm_before_gate,
358 device=device,
359 dtype=dtype,
360 activation="sigmoid",
361 )
362 # Add custom weight loader for TP sharding
363 self.weight.weight_loader = self._weight_loader
364
365 @staticmethod
366 def _weight_loader(param: torch.nn.Parameter, loaded_weight: torch.Tensor) -> None:
367 """Load weight with TP sharding."""
368 tp_size = get_tensor_model_parallel_world_size()
369 tp_rank = get_tensor_model_parallel_rank()
370 shard_size = loaded_weight.shape[0] // tp_size
371 shard = slice(tp_rank * shard_size, (tp_rank + 1) * shard_size)
372 param.data.copy_(loaded_weight[shard].contiguous())
373
374
375 # --8<-- [start:bailing_moe_linear_attention]
376 @PluggableLayer.register("bailing_moe_linear_attention")
377 class BailingMoELinearAttention(LinearAttention):
378 """Pluggable Bailing MoE Linear Attention layer which allows OOT backends
379 to add custom implementations.
380
381 This implements the linear attention mechanism from sglang, adapted for
382 vLLM's v1 engine with MambaBase interface support.
383 """
384
385 # --8<-- [end:bailing_moe_linear_attention]
386 def __init__(
387 self,
388 config: PreTrainedConfig,
389 vllm_config: VllmConfig,
390 prefix: str = "linear_attn",
391 ):
392 super().__init__(config, vllm_config, prefix)
393
394 self.scaling = self.head_dim**-0.5
395
396 self.tp_heads = self.num_heads // self.tp_size
397
398 self.max_position_embeddings = config.max_position_embeddings
399 self.rope_theta = getattr(config, "rope_theta", 600000)
400
401 self.tp_kv_heads = self.num_heads // self.tp_size
402 self.q_size_per_rank = self.head_dim * self.tp_heads
403 self.kv_size_per_rank = self.head_dim * self.tp_kv_heads
404
405 self.use_qk_norm = getattr(config, "use_qk_norm", False)
406 self.linear_backend = "minimax"
407 self.linear_scale = self.linear_backend == "minimax"
408 self.linear_rope = getattr(config, "linear_rope", True)
409 if hasattr(config, "use_linear_silu"):
410 self.linear_silu = config.use_linear_silu
411 elif hasattr(config, "linear_silu"):
412 self.linear_silu = config.linear_silu
413 else:
414 self.linear_silu = False
415
416 self.query_key_value = QKVParallelLinear(
417 self.hidden_size,
418 self.head_dim,
419 self.num_heads,
420 self.num_heads, # MHA: kv_heads = num_heads
421 bias=(config.use_bias or config.use_qkv_bias),
422 quant_config=self.quant_config,
423 prefix=f"{prefix}.query_key_value",
424 )
425
426 if self.use_qk_norm:
427 self.query_layernorm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
428 self.key_layernorm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
429
430 self.g_proj = ColumnParallelLinear(
431 self.hidden_size,
432 self.hidden_inner_size,
433 bias=False,
434 quant_config=self.quant_config,
435 prefix=f"{prefix}.g_proj",
436 )
437 self.dense = RowParallelLinear(
438 self.hidden_inner_size,
439 self.hidden_size,
440 bias=config.use_bias,
441 quant_config=self.quant_config,
442 prefix=f"{prefix}.dense",
443 reduce_results=True,
444 )
445
446 self.group_norm_size = getattr(config, "group_norm_size", 1)
447 self.rms_norm_eps = float(getattr(config, "rms_norm_eps", 1e-5))
448 assert self.tp_size <= self.group_norm_size, (
449 "tp_size must be <= group_norm_size for local rms norm"
450 )
451 assert self.group_norm_size % self.tp_size == 0, (
452 "group_norm_size must be divisible by tp_size"
453 )
454
455 # When group_norm_size == 1, group_size equals hidden_size // tp_size
456 self.g_norm = BailingGroupRMSNormGate(
457 hidden_size=self.hidden_inner_size // self.tp_size,
458 eps=self.rms_norm_eps,
459 group_size=(
460 self.hidden_inner_size // self.group_norm_size
461 if self.group_norm_size > 1
462 else self.hidden_inner_size // self.tp_size
463 ),
464 )
465
466 # use fp32 rotary embedding
467 rope_parameters = _build_rope_parameters(config)
468
469 self.rotary_emb = get_rope(
470 self.head_dim,
471 max_position=self.max_position_embeddings,
472 is_neox_style=True,
473 rope_parameters=rope_parameters or None,
474 )
475
476 # Build slope tensor for linear attention decay
477 slope_rate = MiniMaxText01LinearAttention._build_slope_tensor(self.num_heads)
478 if self.num_hidden_layers <= 1:
479 self.slope_rate = slope_rate * (1 + 1e-5)
480 else:
481 self.slope_rate = slope_rate * (
482 1 - self.layer_idx / (self.num_hidden_layers - 1) + 1e-5
483 )
484 self.tp_slope = self.slope_rate[
485 self.tp_rank * self.tp_heads : (self.tp_rank + 1) * self.tp_heads
486 ].contiguous()
487
488 # Register for compilation
489 compilation_config = get_current_vllm_config().compilation_config
490 if prefix in compilation_config.static_forward_context:
491 raise ValueError(f"Duplicate layer name: {prefix}")
492 compilation_config.static_forward_context[prefix] = self
493
494 def get_attn_backend(self):
495 from vllm.v1.attention.backends.linear_attn import (
496 BailingLinearAttentionBackend,
497 )
498
499 return BailingLinearAttentionBackend
500
501 @staticmethod
502 def weight_direct_load(param: torch.Tensor, loaded_weight: torch.Tensor) -> None:
503 """Load weight for linear attention layers.
504
505 For FP8 quantized parameters, we need to use the weight_loader if available,
506 as it handles special cases like tensor parallelism sharding.
507 """
508 # Check if param has a weight_loader (for vLLM ModelWeightParameter)
509 weight_loader = getattr(param, "weight_loader", None)
510 if weight_loader is not None:
511 # Use the weight_loader which handles TP sharding and quantization
512 weight_loader(param, loaded_weight)
513 else:
514 # Fall back to direct copy for standard tensors
515 assert param.size() == loaded_weight.size(), (
516 f"Shape mismatch: {param.shape} vs {loaded_weight.shape}"
517 )
518 param.data.copy_(loaded_weight)
519
520 def forward(
521 self,
522 hidden_states: torch.Tensor,
523 output: torch.Tensor,
524 positions: torch.Tensor,
525 ) -> None:
526 """Forward method called by torch.ops.vllm.linear_attention."""
527 torch.ops.vllm.linear_attention(
528 hidden_states,
529 output,
530 positions,
531 self.prefix,
532 )
533
534 def _forward(
535 self,
536 hidden_states: torch.Tensor,
537 output: torch.Tensor,
538 positions: torch.Tensor,
539 ) -> None:
540 """Actual forward implementation."""
541 forward_context = get_forward_context()
542 attn_metadata = forward_context.attn_metadata
543 if attn_metadata is not None:
544 assert isinstance(attn_metadata, dict)
545 attn_metadata = attn_metadata.get(self.prefix) # type: ignore
546 if attn_metadata is not None:
547 assert isinstance(attn_metadata, LinearAttentionMetadata)
548 num_actual_tokens = (
549 attn_metadata.num_prefill_tokens + attn_metadata.num_decode_tokens
550 )
551 else:
552 num_actual_tokens = hidden_states.shape[0]
553
554 # QKV projection
555 qkv, _ = self.query_key_value(hidden_states[:num_actual_tokens])
556
557 # use rotary_emb support fp32
558 qkv = qkv.to(torch.float32)
559 if self.linear_silu:
560 qkv = F.silu(qkv)
561
562 # Split q, k, v
563 q, k, v = torch.split(
564 qkv,
565 [self.q_size_per_rank, self.kv_size_per_rank, self.kv_size_per_rank],
566 dim=-1,
567 )
568
569 # Apply QK norm if needed
570 if self.use_qk_norm:
571 q = q.reshape(-1, self.tp_heads, self.head_dim)
572 k = k.reshape(-1, self.tp_kv_heads, self.head_dim)
573 q = layernorm_fn(
574 q,
575 self.query_layernorm.weight.data,
576 bias=None,
577 eps=self.rms_norm_eps,
578 is_rms_norm=True,
579 )
580 k = layernorm_fn(
581 k,
582 self.key_layernorm.weight.data,
583 bias=None,
584 eps=self.rms_norm_eps,
585 is_rms_norm=True,
586 )
587 q = q.reshape(-1, self.q_size_per_rank)
588 k = k.reshape(-1, self.kv_size_per_rank)
589
590 # Apply rotary embeddings
591 if self.linear_rope:
592 q, k = self.rotary_emb(positions[:num_actual_tokens], q, k)
593
594 # Reshape to [batch, heads, seq_len, head_dim]
595 q = q.view((qkv.shape[0], self.tp_heads, self.head_dim))
596 k = k.view((qkv.shape[0], self.tp_kv_heads, self.head_dim))
597 v = v.view((qkv.shape[0], self.tp_kv_heads, self.head_dim))
598
599 # Apply scaling if using minimax backend
600 if self.linear_scale:
601 q = q * self.scaling
602
603 # Get KV cache and state indices
604 if attn_metadata is not None:
605 kv_cache = self.kv_cache[0]
606 state_indices_tensor = attn_metadata.state_indices_tensor
607 clear_linear_attention_cache_for_new_sequences(
608 kv_cache, state_indices_tensor, attn_metadata
609 )
610
611 # Compute attention
612 decode_only = getattr(attn_metadata, "num_prefills", 0) == 0
613 if attn_metadata is None:
614 hidden = torch.empty(
615 (q.shape[0], q.shape[1] * q.shape[2]), device=q.device, dtype=q.dtype
616 )
617 else:
618 if not decode_only:
619 hidden = self._prefill_and_mix_infer(
620 q, k, v, kv_cache, state_indices_tensor, attn_metadata
621 )
622 else:
623 hidden = self._decode_infer(
624 q, k, v, kv_cache, state_indices_tensor, attn_metadata
625 )
626
627 # Apply group norm and gate (matching SGLang behavior)
628 gate, _ = self.g_proj(hidden_states[:num_actual_tokens])
629
630 if self.group_norm_size > 1:
631 hidden = self.g_norm(hidden, gate)
632 else:
633 hidden = self.g_norm(hidden)
634 hidden = F.sigmoid(gate) * hidden
635
636 hidden = hidden.to(hidden_states.dtype)
637
638 # Output projection
639 dense_out, _ = self.dense(hidden)
640 output[:num_actual_tokens] = dense_out
641
642 def _prefill_and_mix_infer(
643 self, q, k, v, kv_cache, state_indices_tensor, attn_metadata
644 ):
645 """Handle prefill (mixed with decode if any)."""
646 return linear_attention_prefill_and_mix(
647 q=q,
648 k=k,
649 v=v,
650 kv_cache=kv_cache,
651 state_indices_tensor=state_indices_tensor,
652 attn_metadata=attn_metadata,
653 slope_rate=self.tp_slope,
654 block_size=self.BLOCK,
655 decode_fn=self._decode_infer,
656 prefix_fn=MiniMaxText01LinearKernel.jit_linear_forward_prefix,
657 layer_idx=self.layer_idx,
658 )
659
660 def _decode_infer(self, q, k, v, kv_cache, state_indices_tensor, attn_metadata):
661 """Handle decode (single token per sequence)."""
662 decode_state_indices = getattr(attn_metadata, "state_indices_tensor_d", None)
663 num_accepted_tokens = getattr(attn_metadata, "num_accepted_tokens", None)
664 query_start_loc = getattr(attn_metadata, "query_start_loc_d", None)
665 if (
666 decode_state_indices is not None
667 and decode_state_indices.dim() > 1
668 and num_accepted_tokens is not None
669 and query_start_loc is not None
670 ):
671 return bailing_linear_attention_decode_spec(
672 q,
673 k,
674 v,
675 kv_cache,
676 self.tp_slope,
677 decode_state_indices,
678 query_start_loc,
679 num_accepted_tokens,
680 q_start=0,
681 q_end=attn_metadata.num_decode_tokens,
682 slot_start=0,
683 slot_end=attn_metadata.num_decodes,
684 block_size=32,
685 )
686 decode_state_indices = (
687 state_indices_tensor
688 if decode_state_indices is None
689 else decode_state_indices
690 )
691 hidden = linear_attention_decode(
692 q,
693 k,
694 v,
695 kv_cache,
696 self.tp_slope,
697 decode_state_indices,
698 q_start=0,
699 q_end=attn_metadata.num_decode_tokens,
700 slot_start=0,
701 slot_end=attn_metadata.num_decodes,
702 block_size=32,
703 )
704 return hidden