| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | from __future__ import annotations |
| 5 | |
| 6 | import torch |
| 7 | import torch.nn.functional as F |
| 8 | |
| 9 | from vllm._custom_ops import causal_conv1d_update_cpu_vec |
| 10 | from vllm.v1.attention.backends.utils import NULL_BLOCK_ID, PAD_SLOT_ID |
| 11 | |
| 12 | |
| 13 | def causal_conv1d_fn_cpu( |
| 14 | x: torch.Tensor, |
| 15 | weight: torch.Tensor, |
| 16 | bias: torch.Tensor | None, |
| 17 | conv_states: torch.Tensor, |
| 18 | query_start_loc: torch.Tensor, |
| 19 | cache_indices: torch.Tensor | None = None, |
| 20 | has_initial_state: torch.Tensor | None = None, |
| 21 | activation: str | None = "silu", |
| 22 | pad_slot_id: int = PAD_SLOT_ID, |
| 23 | **kwargs, |
| 24 | ) -> torch.Tensor: |
| 25 | """CPU implementation for causal_conv1d_fwd.""" |
| 26 | if isinstance(activation, bool) and activation: |
| 27 | activation = "silu" |
| 28 | elif isinstance(activation, bool): |
| 29 | activation = None |
| 30 | |
| 31 | original_x_dtype = x.dtype |
| 32 | x = x.to(conv_states.dtype) |
| 33 | |
| 34 | out = torch.empty_like(x) |
| 35 | state_len = weight.shape[1] - 1 |
| 36 | assert activation in {None, "silu", "swish"} |
| 37 | |
| 38 | seq_begin_end_idx = [ |
| 39 | (int(query_start_loc[idx].item()), int(query_start_loc[idx + 1].item())) |
| 40 | for idx in range(query_start_loc.shape[0] - 1) |
| 41 | ] |
| 42 | weight = weight.unsqueeze(1) |
| 43 | |
| 44 | for seq_idx, (bos, eos) in enumerate(seq_begin_end_idx): |
| 45 | if bos == eos: |
| 46 | continue |
| 47 | |
| 48 | slot = ( |
| 49 | int(cache_indices[seq_idx].item()) if cache_indices is not None else seq_idx |
| 50 | ) |
| 51 | |
| 52 | if slot == pad_slot_id: |
| 53 | continue |
| 54 | |
| 55 | seq_x = x[:, bos:eos].unsqueeze(0) |
| 56 | |
| 57 | if has_initial_state is not None and bool(has_initial_state[seq_idx].item()): |
| 58 | initial_state = conv_states[slot, :, :state_len].unsqueeze(0) |
| 59 | else: |
| 60 | initial_state = torch.zeros( |
| 61 | 1, |
| 62 | weight.shape[0], |
| 63 | state_len, |
| 64 | device=seq_x.device, |
| 65 | dtype=seq_x.dtype, |
| 66 | ) |
| 67 | |
| 68 | conv_input = torch.cat([initial_state, seq_x], dim=-1).to(weight.dtype) |
| 69 | seq_out = F.conv1d( |
| 70 | conv_input, |
| 71 | weight, |
| 72 | bias, |
| 73 | padding=0, |
| 74 | groups=weight.shape[0], |
| 75 | ) |
| 76 | seq_out = seq_out[..., -seq_x.shape[-1] :].to(dtype=x.dtype) |
| 77 | |
| 78 | if activation in ("silu", "swish"): |
| 79 | seq_out = F.silu(seq_out) |
| 80 | |
| 81 | out[:, bos:eos] = seq_out.squeeze(0) |
| 82 | conv_states[slot, :, :state_len].copy_(conv_input[..., -state_len:].squeeze(0)) |
| 83 | |
| 84 | return out.to(original_x_dtype) |
| 85 | |
| 86 | |
| 87 | def causal_conv1d_update_cpu( |
| 88 | x: torch.Tensor, |
| 89 | conv_state: torch.Tensor, |
| 90 | weight: torch.Tensor, |
| 91 | bias: torch.Tensor | None = None, |
| 92 | activation: bool | str | None = None, |
| 93 | conv_state_indices: torch.Tensor | None = None, |
| 94 | query_start_loc: torch.Tensor | None = None, |
| 95 | pad_slot_id: int | None = None, |
| 96 | **kwargs, |
| 97 | ) -> torch.Tensor: |
| 98 | """CPU implementation for causal_conv1d_update.""" |
| 99 | if isinstance(activation, bool): |
| 100 | activation = "silu" if activation else None |
| 101 | |
| 102 | if pad_slot_id is None: |
| 103 | pad_slot_id = kwargs.get("null_block_id", NULL_BLOCK_ID) |
| 104 | if pad_slot_id is None: |
| 105 | pad_slot_id = NULL_BLOCK_ID |
| 106 | |
| 107 | return causal_conv1d_update_cpu_vec( |
| 108 | x, |
| 109 | conv_state, |
| 110 | weight, |
| 111 | bias, |
| 112 | activation, |
| 113 | conv_state_indices, |
| 114 | query_start_loc, |
| 115 | pad_slot_id, |
| 116 | ) |
| 117 | |
| 118 | |
| 119 | def causal_conv1d_update_torch( |
| 120 | x: torch.Tensor, |
| 121 | conv_state: torch.Tensor, |
| 122 | weight: torch.Tensor, |
| 123 | bias: torch.Tensor | None = None, |
| 124 | activation: str | None = None, |
| 125 | ) -> torch.Tensor: |
| 126 | """Pure PyTorch fallback for causal_conv1d_update. |
| 127 | Currently used as a fallback for Arm (aarch64) to leverage |
| 128 | oneDNN/ACL F.conv1d kernels for batched decoding. |
| 129 | """ |
| 130 | assert activation in {None, "silu", "swish"} |
| 131 | |
| 132 | _, dim, seq_len = x.shape |
| 133 | state_len = conv_state.shape[-1] |
| 134 | |
| 135 | x_new = torch.cat([conv_state, x], dim=-1).to(weight.dtype) |
| 136 | conv_state.copy_(x_new[:, :, -state_len:]) |
| 137 | |
| 138 | out = F.conv1d( |
| 139 | x_new, |
| 140 | weight.unsqueeze(1), |
| 141 | bias, |
| 142 | padding=0, |
| 143 | groups=dim, |
| 144 | )[:, :, -seq_len:] |
| 145 | if activation in ("silu", "swish"): |
| 146 | out = F.silu(out) |
| 147 | return out |