| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """Attention layer with FlashInfer.""" |
| 4 | |
| 5 | from dataclasses import dataclass, replace |
| 6 | from enum import Enum |
| 7 | from functools import partial |
| 8 | from typing import ClassVar |
| 9 | |
| 10 | import numpy as np |
| 11 | import torch |
| 12 | from flashinfer import ( |
| 13 | BatchAttentionWithAttentionSinkWrapper, |
| 14 | BatchDecodeWithPagedKVCacheWrapper, |
| 15 | BatchPrefillWithPagedKVCacheWrapper, |
| 16 | BatchPrefillWithRaggedKVCacheWrapper, |
| 17 | MultiLevelCascadeAttentionWrapper, |
| 18 | get_seq_lens, |
| 19 | ) |
| 20 | from flashinfer.decode import fast_decode_plan, trtllm_batch_decode_with_kv_cache |
| 21 | from flashinfer.prefill import trtllm_batch_context_with_kv_cache |
| 22 | from flashinfer.utils import FP4Tensor |
| 23 | from typing_extensions import override |
| 24 | |
| 25 | from vllm import _custom_ops as custom_ops |
| 26 | from vllm import envs |
| 27 | from vllm.config import ( |
| 28 | CUDAGraphMode, |
| 29 | VllmConfig, |
| 30 | get_current_vllm_config_or_none, |
| 31 | ) |
| 32 | from vllm.config.cache import CacheDType |
| 33 | from vllm.distributed.parallel_state import ( |
| 34 | get_dcp_group, |
| 35 | get_dcp_world_size_and_rank, |
| 36 | ) |
| 37 | from vllm.logger import init_logger |
| 38 | from vllm.model_executor.layers.quantization.utils.quant_utils import ( |
| 39 | QuantKey, |
| 40 | kFp8StaticTensorSym, |
| 41 | kNvfp4Dynamic, |
| 42 | ) |
| 43 | from vllm.platforms import current_platform |
| 44 | from vllm.platforms.interface import DeviceCapability |
| 45 | from vllm.triton_utils import tl, triton |
| 46 | from vllm.utils.flashinfer import ( |
| 47 | can_use_trtllm_attention, |
| 48 | flashinfer_xqa_batch_decode_with_kv_cache, |
| 49 | force_use_trtllm_attention, |
| 50 | pin_host_range_buf, |
| 51 | supports_trtllm_attention, |
| 52 | use_trtllm_attention, |
| 53 | ) |
| 54 | from vllm.utils.gpu_sync_debug import gpu_sync_allowed |
| 55 | from vllm.utils.math_utils import cdiv |
| 56 | from vllm.utils.torch_utils import ( |
| 57 | PIN_MEMORY, |
| 58 | canonicalize_singleton_dim_strides, |
| 59 | get_dtype_size, |
| 60 | is_quantized_kv_cache, |
| 61 | is_strictly_contiguous, |
| 62 | nvfp4_kv_cache_full_dim, |
| 63 | nvfp4_split_data_scale, |
| 64 | ) |
| 65 | from vllm.v1.attention.backend import ( |
| 66 | AttentionBackend, |
| 67 | AttentionCGSupport, |
| 68 | AttentionImpl, |
| 69 | AttentionMetadataBuilder, |
| 70 | AttentionType, |
| 71 | CommonAttentionMetadata, |
| 72 | MultipleOf, |
| 73 | max_decode_query_len, |
| 74 | ) |
| 75 | from vllm.v1.attention.backends.utils import ( |
| 76 | get_dcp_local_seq_lens, |
| 77 | get_flashinfer_layout_string, |
| 78 | get_num_attention_heads_from_layers, |
| 79 | get_per_layer_parameters, |
| 80 | infer_global_hyperparameters, |
| 81 | log2_lse_to_ln, |
| 82 | split_decodes_and_prefills, |
| 83 | ) |
| 84 | from vllm.v1.attention.ops.dcp import ( |
| 85 | cp_lse_ag_out_rs, |
| 86 | dcp_a2a_lse_reduce, |
| 87 | ) |
| 88 | from vllm.v1.attention.ops.merge_attn_states import merge_attn_states |
| 89 | from vllm.v1.kv_cache_interface import ( |
| 90 | AttentionSpec, |
| 91 | KVCacheLayout, |
| 92 | KVCacheSpec, |
| 93 | KVQuantMode, |
| 94 | iter_layer_specs, |
| 95 | ) |
| 96 | from vllm.v1.utils import CpuGpuBuffer |
| 97 | |
| 98 | FLASHINFER_WORKSPACE_BUFFER_SIZE_BATCH_INVARIANT = 2048 * 1024 * 1024 |
| 99 | FLASHINFER_PREFILL_WORKSPACE_BYTES_PER_ELEM = 16 |
| 100 | |
| 101 | FP8_DTYPE = current_platform.fp8_dtype() |
| 102 | FP4_DTYPE = torch.uint8 |
| 103 | |
| 104 | logger = init_logger(__name__) |
| 105 | |
| 106 | trtllm_workspace_buffer = None |
| 107 | |
| 108 | |
| 109 | def _get_trtllm_workspace_buffer(): |
| 110 | global trtllm_workspace_buffer |
| 111 | if trtllm_workspace_buffer is None: |
| 112 | trtllm_workspace_buffer = torch.zeros( |
| 113 | envs.VLLM_FLASHINFER_WORKSPACE_BUFFER_SIZE, dtype=torch.uint8, device="cuda" |
| 114 | ) |
| 115 | return trtllm_workspace_buffer |
| 116 | |
| 117 | |
| 118 | def _pack_draft_block_bool_mask( |
| 119 | bool_mask: torch.Tensor, num_packed: int |
| 120 | ) -> torch.Tensor: |
| 121 | """Pack a boolean draft mask into XQA's uint16 layout.""" |
| 122 | num_rows = bool_mask.shape[0] |
| 123 | bits = 1 << torch.arange(32, device=bool_mask.device, dtype=torch.int64) |
| 124 | mask_u32 = ( |
| 125 | (bool_mask.view(num_rows, num_packed, 32).to(torch.int64) * bits) |
| 126 | .sum(dim=-1) |
| 127 | .to(torch.uint32) |
| 128 | ) |
| 129 | return mask_u32.view(torch.uint16).reshape(num_rows, num_packed * 2) |
| 130 | |
| 131 | |
| 132 | def _make_xqa_draft_block_mask( |
| 133 | q_len: int, causal: bool, device: torch.device |
| 134 | ) -> torch.Tensor: |
| 135 | """Build a packed XQA draft mask for a uniform request.""" |
| 136 | num_packed = (q_len + 31) // 32 |
| 137 | padded = num_packed * 32 |
| 138 | q_idx = torch.arange(q_len, device=device).unsqueeze(1) |
| 139 | kv_idx = torch.arange(padded, device=device).unsqueeze(0) |
| 140 | bool_mask = kv_idx <= q_idx if causal else (kv_idx < q_len).expand(q_len, padded) |
| 141 | return _pack_draft_block_bool_mask(bool_mask, num_packed) |
| 142 | |
| 143 | |
| 144 | def _make_xqa_ragged_draft_block_mask( |
| 145 | q_lens: list[int], max_q_len: int, causal: bool, device: torch.device |
| 146 | ) -> torch.Tensor: |
| 147 | """Build a packed XQA draft mask for a ragged batch.""" |
| 148 | num_packed = (max_q_len + 31) // 32 |
| 149 | padded = num_packed * 32 |
| 150 | q_lens_t = torch.tensor(q_lens, device=device) |
| 151 | row_lens = torch.repeat_interleave(q_lens_t, q_lens_t) |
| 152 | request_starts = torch.cumsum(q_lens_t, dim=0) - q_lens_t |
| 153 | row_starts = torch.repeat_interleave(request_starts, q_lens_t) |
| 154 | q_idx = torch.arange(sum(q_lens), device=device) - row_starts |
| 155 | kv_idx = torch.arange(padded, device=device).unsqueeze(0) |
| 156 | bool_mask = kv_idx < row_lens.unsqueeze(1) |
| 157 | if causal: |
| 158 | bool_mask &= kv_idx <= q_idx.unsqueeze(1) |
| 159 | return _pack_draft_block_bool_mask(bool_mask, num_packed) |
| 160 | |
| 161 | |
| 162 | @triton.jit |
| 163 | def _trtllm_prefill_attn_kvfp8_dequant( |
| 164 | kv_cache_ptr, |
| 165 | block_tables_prefill_ptr, |
| 166 | block_table_stride, |
| 167 | mock_kv_cache_ptr, |
| 168 | k_scale_ptr, |
| 169 | v_scale_ptr, |
| 170 | src_stride_page, |
| 171 | src_stride_kv, |
| 172 | src_stride_head, |
| 173 | src_stride_block, |
| 174 | src_stride_head_size, |
| 175 | DST_K_CACHE_STRIDE: tl.constexpr, |
| 176 | DST_KV_CACHE_STRIDE: tl.constexpr, |
| 177 | HEAD_STRIDE: tl.constexpr, |
| 178 | HEAD_SIZE: tl.constexpr, |
| 179 | NUM_KV_HEADS: tl.constexpr, |
| 180 | ): |
| 181 | batch_idx = tl.program_id(0).to(tl.int64) |
| 182 | mock_block_table_idx = tl.program_id(1).to(tl.int64) |
| 183 | orig_page_num = tl.load( |
| 184 | block_tables_prefill_ptr + batch_idx * block_table_stride + mock_block_table_idx |
| 185 | ).to(tl.int64) |
| 186 | if orig_page_num <= 0: |
| 187 | return |
| 188 | dequant_dtype = mock_kv_cache_ptr.dtype.element_ty |
| 189 | |
| 190 | k_scale_val = tl.load(k_scale_ptr) |
| 191 | v_scale_val = tl.load(v_scale_ptr) |
| 192 | |
| 193 | mock_page_idx = batch_idx * block_table_stride + mock_block_table_idx + 1 |
| 194 | logical_offsets = tl.arange(0, HEAD_STRIDE) |
| 195 | block_offsets = logical_offsets // HEAD_SIZE |
| 196 | head_size_offsets = logical_offsets % HEAD_SIZE |
| 197 | |
| 198 | for h in range(NUM_KV_HEADS): |
| 199 | h_off = tl.cast(h, tl.int64) |
| 200 | |
| 201 | # Read K from source (supports non-contiguous page/kv/head strides) |
| 202 | src_k = ( |
| 203 | orig_page_num * src_stride_page |
| 204 | + h_off * src_stride_head |
| 205 | + block_offsets * src_stride_block |
| 206 | + head_size_offsets * src_stride_head_size |
| 207 | ) |
| 208 | fp8_k = tl.load(kv_cache_ptr + src_k) |
| 209 | dequant_k = (fp8_k.to(tl.float32) * k_scale_val).to(dequant_dtype) |
| 210 | |
| 211 | # Write K to contiguous mock cache |
| 212 | dst_k = mock_page_idx * DST_KV_CACHE_STRIDE + h * HEAD_STRIDE + logical_offsets |
| 213 | tl.store(mock_kv_cache_ptr + dst_k, dequant_k) |
| 214 | |
| 215 | # Read V from source (offset by src_stride_kv for the V half) |
| 216 | src_v = ( |
| 217 | orig_page_num * src_stride_page |
| 218 | + src_stride_kv |
| 219 | + h_off * src_stride_head |
| 220 | + block_offsets * src_stride_block |
| 221 | + head_size_offsets * src_stride_head_size |
| 222 | ) |
| 223 | fp8_v = tl.load(kv_cache_ptr + src_v) |
| 224 | dequant_v = (fp8_v.to(tl.float32) * v_scale_val).to(dequant_dtype) |
| 225 | |
| 226 | # Write V to contiguous mock cache |
| 227 | dst_v = ( |
| 228 | mock_page_idx * DST_KV_CACHE_STRIDE |
| 229 | + DST_K_CACHE_STRIDE |
| 230 | + h * HEAD_STRIDE |
| 231 | + logical_offsets |
| 232 | ) |
| 233 | tl.store(mock_kv_cache_ptr + dst_v, dequant_v) |
| 234 | |
| 235 | |
| 236 | def trtllm_prefill_attn_kvfp8_dequant( |
| 237 | kv_cache: torch.Tensor, |
| 238 | block_tables_prefill: torch.Tensor, |
| 239 | k_scale: torch.Tensor, |
| 240 | v_scale: torch.Tensor, |
| 241 | dequant_dtype: torch.dtype, |
| 242 | ) -> tuple[torch.Tensor, torch.Tensor]: |
| 243 | batch_size, num_of_page_per_token = block_tables_prefill.shape |
| 244 | s = kv_cache.shape |
| 245 | assert s[1] == 2 |
| 246 | assert dequant_dtype in (torch.bfloat16, torch.float16) |
| 247 | |
| 248 | # Logical source layout is (B, 2, H, N, D). The tensor may be a |
| 249 | # non-contiguous view, so the Triton kernel indexes it with actual strides. |
| 250 | strides = kv_cache.stride() |
| 251 | num_kv_heads, block_size, head_size = s[2], s[3], s[4] |
| 252 | head_stride = block_size * head_size |
| 253 | k_cache_stride = num_kv_heads * head_stride |
| 254 | kv_cache_stride = k_cache_stride * s[1] |
| 255 | |
| 256 | new_s = (batch_size * num_of_page_per_token + 1, s[1], s[2], s[3], s[4]) |
| 257 | # mock kv cache contains just the pages needed by this prefill |
| 258 | mock_kv_cache = torch.empty(new_s, dtype=dequant_dtype, device=kv_cache.device) |
| 259 | # we simply sequentially index the pages needed by this prefill |
| 260 | mock_block_table = torch.arange( |
| 261 | start=1, |
| 262 | end=batch_size * num_of_page_per_token + 1, |
| 263 | dtype=torch.int32, |
| 264 | device=block_tables_prefill.device, |
| 265 | ).reshape(batch_size, num_of_page_per_token) |
| 266 | grid = (batch_size, num_of_page_per_token) |
| 267 | _trtllm_prefill_attn_kvfp8_dequant[grid]( |
| 268 | kv_cache, |
| 269 | block_tables_prefill, |
| 270 | num_of_page_per_token, |
| 271 | mock_kv_cache, |
| 272 | k_scale, |
| 273 | v_scale, |
| 274 | strides[0], |
| 275 | strides[1], |
| 276 | strides[2], |
| 277 | strides[3], |
| 278 | strides[4], |
| 279 | k_cache_stride, |
| 280 | kv_cache_stride, |
| 281 | head_stride, |
| 282 | head_size, |
| 283 | num_kv_heads, |
| 284 | ) |
| 285 | return mock_kv_cache, mock_block_table |
| 286 | |
| 287 | |
| 288 | class BatchDCPPrefillWrapper: |
| 289 | def __init__( |
| 290 | self, |
| 291 | kv_layout: str, |
| 292 | workspace_buffer: torch.Tensor | None = None, |
| 293 | dcp_a2a: bool = False, |
| 294 | ): |
| 295 | if dcp_a2a: |
| 296 | self._dcp_combine = partial(dcp_a2a_lse_reduce, is_lse_base_on_e=False) |
| 297 | else: |
| 298 | self._dcp_combine = partial(cp_lse_ag_out_rs, is_lse_base_on_e=False) |
| 299 | self._context = BatchPrefillWithPagedKVCacheWrapper(workspace_buffer, kv_layout) |
| 300 | self._new_tokens = BatchPrefillWithRaggedKVCacheWrapper(workspace_buffer) |
| 301 | |
| 302 | def plan( |
| 303 | self, |
| 304 | qo_indptr_cpu: torch.Tensor, |
| 305 | paged_kv_indptr_cpu: torch.Tensor, |
| 306 | paged_kv_indices: torch.Tensor, |
| 307 | paged_kv_last_page_len_cpu: torch.Tensor, |
| 308 | kv_lens_cpu: torch.Tensor, |
| 309 | page_size: int, |
| 310 | num_qo_heads: int, |
| 311 | dcp_world_size: int, |
| 312 | num_kv_heads: int, |
| 313 | head_dim: int, |
| 314 | sm_scale: float, |
| 315 | window_left: int, |
| 316 | logits_soft_cap: float | None, |
| 317 | q_data_type: torch.dtype, |
| 318 | kv_cache_dtype: torch.dtype, |
| 319 | prefill_fixed_split_size: int, |
| 320 | disable_split_kv: bool, |
| 321 | ): |
| 322 | """Plan the prefill operation with given parameters.""" |
| 323 | self._context.plan( |
| 324 | qo_indptr=qo_indptr_cpu, |
| 325 | paged_kv_indptr=paged_kv_indptr_cpu, |
| 326 | paged_kv_indices=paged_kv_indices, |
| 327 | paged_kv_last_page_len=paged_kv_last_page_len_cpu, |
| 328 | seq_lens=kv_lens_cpu, |
| 329 | num_qo_heads=num_qo_heads * dcp_world_size, |
| 330 | num_kv_heads=num_kv_heads, |
| 331 | head_dim_qk=head_dim, |
| 332 | page_size=page_size, |
| 333 | causal=False, # This is context run |
| 334 | sm_scale=sm_scale, |
| 335 | window_left=window_left, |
| 336 | logits_soft_cap=logits_soft_cap, |
| 337 | q_data_type=q_data_type, |
| 338 | kv_data_type=kv_cache_dtype, |
| 339 | fixed_split_size=prefill_fixed_split_size, |
| 340 | disable_split_kv=disable_split_kv, |
| 341 | ) |
| 342 | self._new_tokens.plan( |
| 343 | qo_indptr=qo_indptr_cpu, |
| 344 | kv_indptr=qo_indptr_cpu, |
| 345 | num_qo_heads=num_qo_heads, |
| 346 | num_kv_heads=num_kv_heads, |
| 347 | head_dim_qk=head_dim, |
| 348 | head_dim_vo=head_dim, |
| 349 | causal=True, # This is newtokens run |
| 350 | sm_scale=sm_scale, |
| 351 | window_left=window_left, |
| 352 | logits_soft_cap=logits_soft_cap, |
| 353 | q_data_type=q_data_type, |
| 354 | ) |
| 355 | |
| 356 | def run( |
| 357 | self, |
| 358 | layer: torch.nn.Module, |
| 359 | prefill_query: torch.Tensor, |
| 360 | kv_cache_tuple: tuple[torch.Tensor, torch.Tensor], |
| 361 | key: torch.Tensor, |
| 362 | value: torch.Tensor, |
| 363 | out: torch.Tensor, |
| 364 | ): |
| 365 | prefill_query_across_dcp = get_dcp_group().all_gather( |
| 366 | prefill_query.contiguous(), dim=1 |
| 367 | ) |
| 368 | output_context_tmp, lse_context_tmp = self._context.run( |
| 369 | prefill_query_across_dcp, |
| 370 | kv_cache_tuple, |
| 371 | k_scale=layer._k_scale_float, |
| 372 | v_scale=layer._v_scale_float, |
| 373 | return_lse=True, |
| 374 | ) |
| 375 | output_context, lse_context = self._dcp_combine( |
| 376 | output_context_tmp, |
| 377 | lse_context_tmp, |
| 378 | get_dcp_group(), |
| 379 | return_lse=True, |
| 380 | ) |
| 381 | lse_context = log2_lse_to_ln(lse_context.transpose(0, 1).contiguous()) |
| 382 | |
| 383 | output_query, lse_query = self._new_tokens.run( |
| 384 | prefill_query, |
| 385 | key, |
| 386 | value, |
| 387 | return_lse=True, |
| 388 | ) |
| 389 | lse_query = log2_lse_to_ln(lse_query.transpose(0, 1).contiguous()) |
| 390 | |
| 391 | merge_attn_states( |
| 392 | out, |
| 393 | output_context, |
| 394 | lse_context, |
| 395 | output_query, |
| 396 | lse_query, |
| 397 | ) |
| 398 | return out |
| 399 | |
| 400 | |
| 401 | class FlashInferBackend(AttentionBackend): |
| 402 | @classmethod |
| 403 | def customize_spec(cls, spec: "AttentionSpec") -> "AttentionSpec": |
| 404 | """NVFP4 stores K and V as separate per-head slots of packed fp4 data |
| 405 | plus fp8 block scales.""" |
| 406 | if spec.state_content_bytes is not None or not spec.kv_quant_mode.is_nvfp4: |
| 407 | return spec |
| 408 | hs_k = nvfp4_kv_cache_full_dim(spec.head_size) |
| 409 | hs_v = nvfp4_kv_cache_full_dim(spec.head_size_v) |
| 410 | assert hs_k == hs_v, "nvfp4 with asymmetric K/V head sizes not yet supported" |
| 411 | return replace( |
| 412 | spec, |
| 413 | num_head_slots=2 * spec.num_kv_heads, |
| 414 | state_content_bytes=hs_k * get_dtype_size(spec.dtype), |
| 415 | ) |
| 416 | |
| 417 | supported_dtypes: ClassVar[list[torch.dtype]] = [torch.float16, torch.bfloat16] |
| 418 | supported_kv_cache_dtypes: ClassVar[list[CacheDType]] = [ |
| 419 | "auto", |
| 420 | "float16", |
| 421 | "bfloat16", |
| 422 | "fp8", |
| 423 | "fp8_e4m3", |
| 424 | "fp8_e5m2", |
| 425 | "nvfp4", |
| 426 | "nvfp4_4over6", |
| 427 | ] |
| 428 | |
| 429 | @staticmethod |
| 430 | def get_supported_kernel_block_sizes(kv_cache_spec=None) -> list[int | MultipleOf]: |
| 431 | # Page sizes >= 128 only run on the trtllm-gen dynamic kernel (GQA/MQA |
| 432 | # on Blackwell); advertise them only when usable so selection never |
| 433 | # picks a large kernel block we cannot serve. |
| 434 | use_large_pages = False |
| 435 | vllm_config = get_current_vllm_config_or_none() |
| 436 | if vllm_config is not None and vllm_config.model_config is not None: |
| 437 | pc = vllm_config.parallel_config |
| 438 | mc = vllm_config.model_config |
| 439 | num_qo_heads = mc.get_num_attention_heads(pc) |
| 440 | num_kv_heads = mc.get_num_kv_heads(pc) |
| 441 | use_large_pages = ( |
| 442 | num_kv_heads > 0 |
| 443 | and num_qo_heads // num_kv_heads > 1 |
| 444 | and current_platform.is_device_capability_family(100) |
| 445 | and can_use_trtllm_attention(num_qo_heads, num_kv_heads) |
| 446 | ) |
| 447 | if not use_large_pages: |
| 448 | return [16, 32, 64] |
| 449 | return [16, 32, 64, 128, 256, 512, 1024] |
| 450 | |
| 451 | @staticmethod |
| 452 | def get_name() -> str: |
| 453 | return "FLASHINFER" |
| 454 | |
| 455 | @classmethod |
| 456 | def supports_non_causal(cls) -> bool: |
| 457 | return True |
| 458 | |
| 459 | @classmethod |
| 460 | def supports_device_cpu_query_lens_mismatch(cls) -> bool: |
| 461 | # Only trtllm-gen decode (SM100 family) reads query offsets from the |
| 462 | # device tensor; XQA and wrapper decode plan from qo_indptr_cpu, and |
| 463 | # under DCP spec queries are routed to the prefill path. |
| 464 | if not current_platform.is_device_capability_family(100): |
| 465 | return False |
| 466 | vllm_config = get_current_vllm_config_or_none() |
| 467 | if vllm_config is None or vllm_config.model_config is None: |
| 468 | return True |
| 469 | pc = vllm_config.parallel_config |
| 470 | mc = vllm_config.model_config |
| 471 | return pc.decode_context_parallel_size == 1 and can_use_trtllm_attention( |
| 472 | mc.get_num_attention_heads(pc), mc.get_num_kv_heads(pc), is_prefill=False |
| 473 | ) |
| 474 | |
| 475 | @classmethod |
| 476 | def supports_sliding_window(cls) -> bool: |
| 477 | return True |
| 478 | |
| 479 | @staticmethod |
| 480 | def get_impl_cls() -> type["FlashInferImpl"]: |
| 481 | return FlashInferImpl |
| 482 | |
| 483 | @staticmethod |
| 484 | def get_builder_cls() -> type["FlashInferMetadataBuilder"]: |
| 485 | return FlashInferMetadataBuilder |
| 486 | |
| 487 | @staticmethod |
| 488 | def get_dtype_for_flashinfer(kv_cache_dtype: str) -> torch.dtype: |
| 489 | if kv_cache_dtype in ("fp8", "fp8_e4m3"): |
| 490 | return torch.float8_e4m3fn |
| 491 | elif kv_cache_dtype == "fp8_e5m2": |
| 492 | return torch.float8_e5m2 |
| 493 | elif kv_cache_dtype.startswith("nvfp4"): |
| 494 | return torch.uint8 |
| 495 | else: |
| 496 | raise ValueError(f"Unrecognized dtype: {kv_cache_dtype}") |
| 497 | |
| 498 | @classmethod |
| 499 | def supports_kv_cache_dtype(cls, kv_cache_dtype: CacheDType | None) -> bool: |
| 500 | if kv_cache_dtype is not None and kv_cache_dtype.startswith("nvfp4"): |
| 501 | return ( |
| 502 | current_platform.is_device_capability_family(100) |
| 503 | and supports_trtllm_attention(is_prefill=True) |
| 504 | and supports_trtllm_attention(is_prefill=False) |
| 505 | ) |
| 506 | return super().supports_kv_cache_dtype(kv_cache_dtype) |
| 507 | |
| 508 | @classmethod |
| 509 | def get_supported_head_sizes(cls) -> list[int]: |
| 510 | # https://github.com/flashinfer-ai/flashinfer/blob/3d55c71a62052c590c130897d3a3db49b14fcc34/include/flashinfer/utils.cuh#L157 |
| 511 | return [64, 128, 256, 512] |
| 512 | |
| 513 | @classmethod |
| 514 | def supports_compute_capability(cls, capability: DeviceCapability) -> bool: |
| 515 | # FlashInfer supports SM75+, but is currently broken on SM75 (Turing): |
| 516 | # https://github.com/flashinfer-ai/flashinfer/issues/3620 (fix: |
| 517 | # https://github.com/flashinfer-ai/flashinfer/pull/3621). Temporarily |
| 518 | # raise the floor to SM80 so it is not auto-selected on SM75 until |
| 519 | # that fix lands; revert to DeviceCapability(7, 5) once it does. |
| 520 | return capability >= DeviceCapability(8, 0) and capability <= DeviceCapability( |
| 521 | 12, 1 |
| 522 | ) |
| 523 | |
| 524 | @classmethod |
| 525 | def supports_sink(cls) -> bool: |
| 526 | """Whether FlashInfer can serve attention sinks on this platform.""" |
| 527 | from vllm.utils.flashinfer import ( |
| 528 | force_use_trtllm_attention, |
| 529 | ) |
| 530 | |
| 531 | if force_use_trtllm_attention() is False: |
| 532 | return False |
| 533 | |
| 534 | if current_platform.is_device_capability( |
| 535 | 90 |
| 536 | ) or current_platform.is_device_capability_family(120): |
| 537 | return supports_trtllm_attention(is_prefill=False) |
| 538 | |
| 539 | if not current_platform.is_device_capability_family(100): |
| 540 | return False |
| 541 | |
| 542 | return supports_trtllm_attention( |
| 543 | is_prefill=False |
| 544 | ) and supports_trtllm_attention(is_prefill=True) |
| 545 | |
| 546 | @classmethod |
| 547 | def supported_kv_cache_layouts(cls) -> tuple[KVCacheLayout, ...] | None: |
| 548 | capability = current_platform.get_device_capability() |
| 549 | if capability is not None and capability.major == 10: |
| 550 | # The trtllm-gen kernels consume head-major block interiors; the L/B |
| 551 | # nesting outside the block is immaterial to them. |
| 552 | return (KVCacheLayout.LBHNC, KVCacheLayout.BLHNC) |
| 553 | return super().supported_kv_cache_layouts() |
| 554 | |
| 555 | forward_includes_kv_cache_update: bool = False |
| 556 | |
| 557 | |
| 558 | @dataclass |
| 559 | class FIPrefill: |
| 560 | """Metadata for the native FlashInfer prefill pathway (non-TRTLLM).""" |
| 561 | |
| 562 | wrapper: BatchPrefillWithPagedKVCacheWrapper | BatchDCPPrefillWrapper |
| 563 | |
| 564 | |
| 565 | @dataclass |
| 566 | class FIDecode: |
| 567 | """Metadata for the native FlashInfer decode pathway (non-TRTLLM).""" |
| 568 | |
| 569 | wrapper: BatchDecodeWithPagedKVCacheWrapper |
| 570 | |
| 571 | |
| 572 | class FlashInferDecodeKernel(Enum): |
| 573 | """Decode kernels selected inside the FlashInfer backend.""" |
| 574 | |
| 575 | XQA = "xqa" |
| 576 | TRTLLM_GEN = "trtllm-gen" |
| 577 | |
| 578 | |
| 579 | def _is_xqa_head_dim_supported(head_dim: int) -> bool: |
| 580 | return 16 <= head_dim <= 256 and head_dim % 16 == 0 |
| 581 | |
| 582 | |
| 583 | @dataclass |
| 584 | class TRTLLMPrefill: |
| 585 | """Metadata for the TRTLLM prefill pathway.""" |
| 586 | |
| 587 | block_tables: torch.Tensor |
| 588 | """ |
| 589 | The slice of the block table tensor corresponding *only* to prefill requests. |
| 590 | Shape: [num_prefills, max_num_blocks_per_seq] |
| 591 | """ |
| 592 | |
| 593 | seq_lens: torch.Tensor |
| 594 | """ |
| 595 | The slice of the sequence lengths tensor corresponding *only* to prefill requests. |
| 596 | Shape: [num_prefills] |
| 597 | """ |
| 598 | |
| 599 | cum_seq_lens_q: torch.Tensor |
| 600 | cum_seq_lens_kv: torch.Tensor |
| 601 | |
| 602 | max_q_len: int |
| 603 | """ |
| 604 | The maximum query length *among prefill requests*. |
| 605 | """ |
| 606 | |
| 607 | max_seq_len: int |
| 608 | """The maximum sequence length for KV Cache.""" |
| 609 | |
| 610 | |
| 611 | @dataclass |
| 612 | class FlashInferTrtllmAPIDecode: |
| 613 | """Metadata for XQA and trtllm-gen decode.""" |
| 614 | |
| 615 | kernel: FlashInferDecodeKernel |
| 616 | |
| 617 | block_tables: torch.Tensor |
| 618 | """ |
| 619 | The slice of the block table tensor corresponding *only* to decode requests. |
| 620 | Shape: [num_decodes, max_num_blocks_per_seq] |
| 621 | """ |
| 622 | |
| 623 | seq_lens: torch.Tensor |
| 624 | """ |
| 625 | The slice of the sequence lengths tensor corresponding *only* to decode requests. |
| 626 | Shape: [num_decodes] |
| 627 | """ |
| 628 | |
| 629 | max_seq_len: int |
| 630 | """The maximum sequence length for KV Cache.""" |
| 631 | |
| 632 | q_len_per_req: int = 1 |
| 633 | """Query tokens per request for uniform speculative decode.""" |
| 634 | |
| 635 | q_cu_seq_lens: torch.Tensor | None = None |
| 636 | """Cumulative query lengths for ragged XQA decode.""" |
| 637 | |
| 638 | mask: torch.Tensor | None = None |
| 639 | """Packed XQA draft mask.""" |
| 640 | |
| 641 | dcp_query_start_loc: torch.Tensor | None = None |
| 642 | """Decode query boundaries for masking empty DCP shards during reduction.""" |
| 643 | |
| 644 | |
| 645 | @dataclass |
| 646 | class FlashInferMetadata: |
| 647 | kv_cache_layout: KVCacheLayout |
| 648 | num_actual_tokens: int |
| 649 | """Total number of tokens in the batch (excluding padding).""" |
| 650 | |
| 651 | slot_mapping: torch.Tensor |
| 652 | """Tensor for writing K/V to the cache. Shape: [num_actual_tokens]""" |
| 653 | |
| 654 | # The data types of the query for prefill and decode. |
| 655 | # On SM90, these two data types may be different. |
| 656 | q_data_type_prefill: torch.dtype |
| 657 | q_data_type_decode: torch.dtype |
| 658 | |
| 659 | num_decodes: int |
| 660 | num_decode_tokens: int |
| 661 | num_prefills: int |
| 662 | num_prefill_tokens: int |
| 663 | causal: bool |
| 664 | |
| 665 | prefill: FIPrefill | TRTLLMPrefill | None |
| 666 | """ |
| 667 | Holds the metadata for the prefill portion of the batch. |
| 668 | Will be `None` if `num_prefill_tokens == 0`. |
| 669 | """ |
| 670 | |
| 671 | decode: FIDecode | FlashInferTrtllmAPIDecode | None |
| 672 | """ |
| 673 | Holds the metadata for the decode portion of the batch. |
| 674 | Will be `None` if `num_decode_tokens == 0`. |
| 675 | """ |
| 676 | |
| 677 | # --- Special Case: Cascade Attention --- |
| 678 | |
| 679 | use_cascade: bool |
| 680 | """ |
| 681 | If True, the entire batch is a cascade attention call, and the |
| 682 | `prefill` and `decode` fields will both be None. |
| 683 | """ |
| 684 | |
| 685 | cascade_wrapper: MultiLevelCascadeAttentionWrapper | None |
| 686 | |
| 687 | |
| 688 | class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]): |
| 689 | kv_cache_spec: AttentionSpec |
| 690 | reorder_batch_threshold: int = 1 |
| 691 | |
| 692 | def __init__( |
| 693 | self, |
| 694 | kv_cache_spec: AttentionSpec, |
| 695 | layer_names: list[str], |
| 696 | vllm_config: VllmConfig, |
| 697 | device: torch.device, |
| 698 | ): |
| 699 | super().__init__(kv_cache_spec, layer_names, vllm_config, device) |
| 700 | self.cache_config = vllm_config.cache_config |
| 701 | self.model_config = vllm_config.model_config |
| 702 | self.attention_config = vllm_config.attention_config |
| 703 | self._num_speculative_tokens = vllm_config.num_speculative_tokens |
| 704 | self._workspace_buffer = None |
| 705 | self._prefill_wrapper: ( |
| 706 | BatchPrefillWithPagedKVCacheWrapper | BatchDCPPrefillWrapper | None |
| 707 | ) = None # Wrapper for prefill/append |
| 708 | self._noncausal_prefill_wrapper: BatchPrefillWithPagedKVCacheWrapper | None = ( |
| 709 | None # Wrapper for non-causal prefill (DFlash) |
| 710 | ) |
| 711 | self._decode_wrapper = None # Wrapper for decode (general shape) |
| 712 | |
| 713 | if envs.VLLM_BATCH_INVARIANT: |
| 714 | self.decode_fixed_split_size = 2048 |
| 715 | self.prefill_fixed_split_size = 4096 |
| 716 | self.disable_split_kv = True |
| 717 | else: |
| 718 | self.decode_fixed_split_size = -1 |
| 719 | self.prefill_fixed_split_size = -1 |
| 720 | self.disable_split_kv = False |
| 721 | |
| 722 | self.compilation_config = vllm_config.compilation_config |
| 723 | self.max_num_batched_tokens = ( |
| 724 | vllm_config.scheduler_config.max_num_batched_tokens |
| 725 | ) |
| 726 | max_num_pages_per_req = cdiv( |
| 727 | self.model_config.max_model_len, self.kv_cache_spec.block_size |
| 728 | ) |
| 729 | max_num_reqs = vllm_config.scheduler_config.max_num_seqs |
| 730 | self.max_num_reqs = max_num_reqs |
| 731 | max_num_pages = max_num_reqs * max_num_pages_per_req |
| 732 | # Persistent uniform masks keep stable addresses for CUDA graphs. |
| 733 | self._decode_mask_cache: dict[tuple[int, bool], torch.Tensor] = {} |
| 734 | speculative_config = vllm_config.speculative_config |
| 735 | num_spec_tokens = ( |
| 736 | speculative_config.num_speculative_tokens |
| 737 | if speculative_config is not None |
| 738 | else 0 |
| 739 | ) |
| 740 | self.enable_cuda_graph = ( |
| 741 | self.compilation_config.cudagraph_mode.decode_mode() == CUDAGraphMode.FULL |
| 742 | ) |
| 743 | if self.enable_cuda_graph: |
| 744 | # For full cudagraph capture, one `decode_wrapper` for each batch |
| 745 | # size is needed for FlashInfer. |
| 746 | self._decode_wrappers_cudagraph: dict[ |
| 747 | int, BatchDecodeWithPagedKVCacheWrapper |
| 748 | ] = {} |
| 749 | self._decode_cudagraph_max_bs = (1 + num_spec_tokens) * max_num_reqs |
| 750 | if self.compilation_config.max_cudagraph_capture_size is not None: |
| 751 | self._decode_cudagraph_max_bs = min( |
| 752 | self._decode_cudagraph_max_bs, |
| 753 | self.compilation_config.max_cudagraph_capture_size, |
| 754 | ) |
| 755 | self.dcp_world_size, self.dcp_rank = get_dcp_world_size_and_rank( |
| 756 | kv_cache_spec.dcp_sharded |
| 757 | ) |
| 758 | self.dcp_kv_cache_interleave_size = ( |
| 759 | vllm_config.parallel_config.cp_kv_cache_interleave_size |
| 760 | ) |
| 761 | self.use_dcp = self.dcp_world_size > 1 |
| 762 | self.dcp_a2a = ( |
| 763 | self.use_dcp and vllm_config.parallel_config.dcp_comm_backend == "a2a" |
| 764 | ) |
| 765 | |
| 766 | # Compatible with models with non-uniform per-layer head counts. |
| 767 | self.num_qo_heads = get_num_attention_heads_from_layers( |
| 768 | vllm_config, layer_names |
| 769 | ) or self.model_config.get_num_attention_heads(self.vllm_config.parallel_config) |
| 770 | |
| 771 | self.num_kv_heads = self.kv_cache_spec.num_kv_heads |
| 772 | self.head_dim = self.kv_cache_spec.head_size |
| 773 | self.page_size = self.kv_cache_spec.block_size |
| 774 | |
| 775 | if self.kv_cache_spec.kv_quant_mode != KVQuantMode.NONE: |
| 776 | self.cache_dtype = self.cache_config.cache_dtype |
| 777 | # Cannot use self.kv_cache_spec.dtype here because kv_cache_spec |
| 778 | # storage dtype may not be the same as the op dtype (uint8 vs fp8_e4m3) |
| 779 | self.is_kvcache_nvfp4 = self.cache_dtype.startswith("nvfp4") |
| 780 | if self.is_kvcache_nvfp4: |
| 781 | if ( |
| 782 | force_use_trtllm_attention() is False |
| 783 | or not supports_trtllm_attention(is_prefill=True) |
| 784 | or not supports_trtllm_attention(is_prefill=False) |
| 785 | ): |
| 786 | raise ValueError( |
| 787 | f"--kv-cache-dtype {self.cache_dtype} requires the " |
| 788 | "SM100 trtllm-gen " |
| 789 | "FlashInfer path." |
| 790 | ) |
| 791 | # The scale search only affects the store kernel. FlashInfer |
| 792 | # reads both variants using the same NVFP4 layout. |
| 793 | self.kv_cache_dtype = "nvfp4" |
| 794 | else: |
| 795 | self.kv_cache_dtype = FlashInferBackend.get_dtype_for_flashinfer( |
| 796 | self.cache_dtype |
| 797 | ) |
| 798 | else: |
| 799 | self.cache_dtype = "auto" |
| 800 | self.is_kvcache_nvfp4 = False |
| 801 | assert self.kv_cache_spec.dtype == self.model_config.dtype |
| 802 | self.kv_cache_dtype = self.kv_cache_spec.dtype |
| 803 | |
| 804 | # Compute per-phase Q dtype. On SM90 (XQA decode), the prefill and |
| 805 | # decode phases require different Q dtypes when the KV cache is FP8 |
| 806 | # (FP8-Q for the FI native prefill, BF16/FP16-Q for XQA decode), |
| 807 | # so both values must be tracked independently. |
| 808 | self.q_data_type_prefill = self.get_q_data_type(is_prefill=True) |
| 809 | self.q_data_type_decode = self.get_q_data_type(is_prefill=False) |
| 810 | |
| 811 | # Prefer TRTLLM/XQA for decoding whenever supported. The decode kernel |
| 812 | # must be selected statically for FULL cudagraph capture. |
| 813 | can_use_xqa_or_trtllm_gen_decode = can_use_trtllm_attention( |
| 814 | self.num_qo_heads, self.num_kv_heads, is_prefill=False |
| 815 | ) |
| 816 | # Page sizes >= 128 require the trtllm-gen GQA/MQA path (guaranteed by |
| 817 | # get_supported_kernel_block_sizes). |
| 818 | assert self.page_size <= 64 or ( |
| 819 | current_platform.is_device_capability_family(100) |
| 820 | and can_use_xqa_or_trtllm_gen_decode |
| 821 | and self.num_qo_heads // self.num_kv_heads > 1 |
| 822 | ), f"Unexpected FlashInfer page size {self.page_size} without trtllm-gen GQA" |
| 823 | self.use_trtllm_decode_attention = can_use_xqa_or_trtllm_gen_decode |
| 824 | self.flashinfer_trtllm_api_decode_kernel: FlashInferDecodeKernel | None = ( |
| 825 | self._get_flashinfer_trtllm_api_decode_kernel() |
| 826 | if can_use_xqa_or_trtllm_gen_decode |
| 827 | else None |
| 828 | ) |
| 829 | # The dedicated FlashInfer XQA API accepts head dimensions in |
| 830 | # [16, 256] that are divisible by 16. Some hybrid-attention models |
| 831 | # (for example Gemma 4) use XQA-compatible sliding-attention groups |
| 832 | # alongside global-attention groups with head_dim=512. Resolve the |
| 833 | # backend per KV-cache group so the eligible groups still use XQA and |
| 834 | # the wider groups fall back to native FlashInfer decode. |
| 835 | if ( |
| 836 | self.flashinfer_trtllm_api_decode_kernel == FlashInferDecodeKernel.XQA |
| 837 | and not _is_xqa_head_dim_supported(self.head_dim) |
| 838 | ): |
| 839 | logger.warning_once( |
| 840 | "FlashInfer XQA decode does not support head_dim=%d; " |
| 841 | "reverting this KV-cache group to native FlashInfer decode.", |
| 842 | self.head_dim, |
| 843 | ) |
| 844 | self.use_trtllm_decode_attention = False |
| 845 | self.flashinfer_trtllm_api_decode_kernel = None |
| 846 | if ( |
| 847 | self.use_dcp |
| 848 | and self.flashinfer_trtllm_api_decode_kernel == FlashInferDecodeKernel.XQA |
| 849 | ): |
| 850 | logger.warning_once( |
| 851 | "FlashInfer XQA decode does not support returning LSE and " |
| 852 | "therefore does not support DCP, reverting to native FlashInfer " |
| 853 | "decode." |
| 854 | ) |
| 855 | self.use_trtllm_decode_attention = False |
| 856 | self.flashinfer_trtllm_api_decode_kernel = None |
| 857 | self.use_xqa = ( |
| 858 | self.flashinfer_trtllm_api_decode_kernel == FlashInferDecodeKernel.XQA |
| 859 | ) |
| 860 | self.use_trtllm_gen_varlen_decode = self._uses_trtllm_gen_varlen_decode( |
| 861 | vllm_config, self.flashinfer_trtllm_api_decode_kernel, self.use_dcp |
| 862 | ) |
| 863 | # Fused draft steps advance seq_lens in place and keep block tables fixed, |
| 864 | # so one trtllm-gen decode build serves every step (not DCP-local seq_lens). |
| 865 | self.supports_draft_decode_metadata_update = ( |
| 866 | self.flashinfer_trtllm_api_decode_kernel |
| 867 | == FlashInferDecodeKernel.TRTLLM_GEN |
| 868 | and not self.use_dcp |
| 869 | ) |
| 870 | self._init_reorder_batch_threshold( |
| 871 | 1, |
| 872 | supports_spec_as_decode=( |
| 873 | self.flashinfer_trtllm_api_decode_kernel is not None |
| 874 | ), |
| 875 | # trtllm-gen decode receives no cp_rank/global-seq-len information, |
| 876 | # so its end-aligned causal mask is wrong for q_len > 1 over the |
| 877 | # DCP-interleaved local KV shard (spec token i misses up to |
| 878 | # (dcp_world_size - 1) * (q_len - 1 - i) KV entries, including its |
| 879 | # own). Keep the threshold at 1 under DCP so spec queries take the |
| 880 | # DCP-aware prefill path, until the kernel is CP-aware (compare |
| 881 | # flash_attn_varlen_func's cp_world_size/cp_rank/cp_tot_seqused_k). |
| 882 | supports_dcp_with_varlen=False, |
| 883 | ) |
| 884 | |
| 885 | self._cascade_wrapper = None # Wrapper for cascade attention |
| 886 | |
| 887 | # Global hyperparameters shared by all attention layers |
| 888 | # TODO: discard this for trtllm-gen backend |
| 889 | per_layer_parameters = get_per_layer_parameters( |
| 890 | vllm_config, layer_names, FlashInferImpl |
| 891 | ) |
| 892 | self.global_hyperparameters = infer_global_hyperparameters(per_layer_parameters) |
| 893 | self.sm_scale = self.global_hyperparameters.sm_scale |
| 894 | self.window_left = self.global_hyperparameters.window_left |
| 895 | self.logits_soft_cap = self.global_hyperparameters.logits_soft_cap |
| 896 | self.has_sinks = self.global_hyperparameters.has_sinks |
| 897 | if self.has_sinks and not FlashInferBackend.supports_sink(): |
| 898 | raise NotImplementedError( |
| 899 | "FlashInfer backend currently does not support attention " |
| 900 | "sinks, please use trtllm on blackwell or flash attention on " |
| 901 | "earlier GPUs." |
| 902 | ) |
| 903 | capability = current_platform.get_device_capability() |
| 904 | arch = f"sm{capability.major}{capability.minor}" if capability else "unknown" |
| 905 | decode_backend = ( |
| 906 | self.flashinfer_trtllm_api_decode_kernel.value |
| 907 | if self.flashinfer_trtllm_api_decode_kernel is not None |
| 908 | else "flashinfer-native" |
| 909 | ) |
| 910 | logger.info_once( |
| 911 | "FlashInfer resolved query dtypes: prefill=%s, decode=%s, " |
| 912 | "decode_backend=%s, kv_cache_dtype=%s, arch=%s", |
| 913 | self.q_data_type_prefill, |
| 914 | self.q_data_type_decode, |
| 915 | decode_backend, |
| 916 | self.kv_cache_dtype, |
| 917 | arch, |
| 918 | ) |
| 919 | # Preparing persistent buffers |
| 920 | self.paged_kv_indptr = CpuGpuBuffer( |
| 921 | max_num_reqs + 1, dtype=torch.int32, device=self.device, pin_memory=False |
| 922 | ) |
| 923 | self.paged_kv_indices = torch.zeros( |
| 924 | max_num_pages, dtype=torch.int32, device=self.device |
| 925 | ) |
| 926 | self.paged_kv_last_page_len = CpuGpuBuffer( |
| 927 | max_num_reqs, dtype=torch.int32, device=self.device, pin_memory=False |
| 928 | ) |
| 929 | |
| 930 | @property |
| 931 | def kv_cache_layout(self) -> KVCacheLayout: |
| 932 | return self.cache_config.get_resolved_kv_cache_layout() |
| 933 | |
| 934 | # Keep SM90 prefill/decode Q dtype selection in one place. |
| 935 | def get_q_data_type(self, is_prefill: bool) -> torch.dtype: |
| 936 | # The user sets --attention-config.disable_flashinfer_q_quantization |
| 937 | # to 1 explicitly, use model dtype for query. |
| 938 | if self.vllm_config.attention_config.disable_flashinfer_q_quantization: |
| 939 | return self.model_config.dtype |
| 940 | |
| 941 | # self.cache_dtype is resolved per KV-cache group: it is "auto" when |
| 942 | # this group is unquantized (e.g. --kv-cache-dtype-skip-layers), even |
| 943 | # if cache_config requests a quantized dtype globally. |
| 944 | cache_dtype = self.cache_dtype |
| 945 | |
| 946 | # On SM90/SM12x, XQA decode requires BF16/FP16-Q even with FP8 KV cache. |
| 947 | # FI native prefill on SM90 still uses FP8-Q in that case; SM12x prefill |
| 948 | # is fa2-only and keeps the model dtype (handled below). |
| 949 | if ( |
| 950 | ( |
| 951 | current_platform.is_device_capability(90) |
| 952 | or current_platform.is_device_capability_family(120) |
| 953 | ) |
| 954 | and not is_prefill |
| 955 | and force_use_trtllm_attention() is not False |
| 956 | and cache_dtype.startswith("fp8") |
| 957 | ): |
| 958 | return self.model_config.dtype |
| 959 | |
| 960 | # Otherwise, match Q dtype to the KV cache dtype. |
| 961 | if cache_dtype.startswith("fp8"): |
| 962 | # FP8-Q requires an fp8 tensor-core attention path. SM90 uses |
| 963 | # native FA3 for prefill; XQA decode on SM90/SM12x is handled |
| 964 | # above with model-dtype Q, while SM100 uses trtllm-gen. |
| 965 | # Architectures with only fa2 (e.g. SM89, SM120) cannot |
| 966 | # consume FP8 queries, so keep the model dtype for Q there. |
| 967 | if current_platform.is_device_capability( |
| 968 | 90 |
| 969 | ) or current_platform.is_device_capability_family(100): |
| 970 | return FlashInferBackend.get_dtype_for_flashinfer(cache_dtype) |
| 971 | return self.model_config.dtype |
| 972 | if cache_dtype.startswith("nvfp4"): |
| 973 | return FlashInferBackend.get_dtype_for_flashinfer("fp8_e4m3") |
| 974 | return self.kv_cache_spec.dtype |
| 975 | |
| 976 | @override # type: ignore[misc] |
| 977 | @classmethod |
| 978 | def get_cudagraph_support( |
| 979 | cls: type["FlashInferMetadataBuilder"], |
| 980 | vllm_config: VllmConfig, |
| 981 | kv_cache_spec: KVCacheSpec, |
| 982 | ) -> AttentionCGSupport: |
| 983 | """Get the cudagraph support level for FlashInfer attention.""" |
| 984 | # XQA lacks LSE for DCP; DCP also cannot graph variable-length trtllm-gen. |
| 985 | if vllm_config.parallel_config.decode_context_parallel_size > 1: |
| 986 | return AttentionCGSupport.UNIFORM_SINGLE_TOKEN_DECODE |
| 987 | |
| 988 | kv_specs = iter_layer_specs(kv_cache_spec) |
| 989 | num_qo_heads = vllm_config.model_config.get_num_attention_heads( |
| 990 | vllm_config.parallel_config |
| 991 | ) |
| 992 | is_xqa_arch = current_platform.is_device_capability( |
| 993 | 90 |
| 994 | ) or current_platform.is_device_capability_family(120) |
| 995 | has_uniform_batch_support: bool = len(kv_specs) > 0 |
| 996 | for spec in kv_specs: |
| 997 | if not isinstance(spec, AttentionSpec): |
| 998 | # FlashInfer only applies to attention, so we don't consider other types |
| 999 | # of KV spec (e.g. Mamba) here. This is mostly for type checking. |
| 1000 | continue |
| 1001 | if not can_use_trtllm_attention( |
| 1002 | num_qo_heads=num_qo_heads, |
| 1003 | num_kv_heads=spec.num_kv_heads, |
| 1004 | is_prefill=False, |
| 1005 | ) or (is_xqa_arch and not _is_xqa_head_dim_supported(spec.head_size)): |
| 1006 | has_uniform_batch_support = False |
| 1007 | break |
| 1008 | |
| 1009 | use_non_causal = vllm_config.attention_config.use_non_causal |
| 1010 | if has_uniform_batch_support and (not use_non_causal or is_xqa_arch): |
| 1011 | return AttentionCGSupport.UNIFORM_BATCH |
| 1012 | else: |
| 1013 | return AttentionCGSupport.UNIFORM_SINGLE_TOKEN_DECODE |
| 1014 | |
| 1015 | @override # type: ignore[misc] |
| 1016 | @classmethod |
| 1017 | def get_varlen_cudagraph_max_query_len( |
| 1018 | cls: type["FlashInferMetadataBuilder"], |
| 1019 | vllm_config: VllmConfig, |
| 1020 | kv_cache_spec: KVCacheSpec, |
| 1021 | ) -> int | None: |
| 1022 | # The uniform gate already covers DCP, head geometry, the XQA head-dim |
| 1023 | # limit and causality, and yields None for subclasses that force NEVER. |
| 1024 | if ( |
| 1025 | cls.get_cudagraph_support(vllm_config, kv_cache_spec) |
| 1026 | != AttentionCGSupport.UNIFORM_BATCH |
| 1027 | ): |
| 1028 | return None |
| 1029 | # The kernel selector asserts off the trtllm-capable architectures, so |
| 1030 | # call it only past the gate, as get_cudagraph_support does. |
| 1031 | if not cls._uses_trtllm_gen_varlen_decode( |
| 1032 | vllm_config, |
| 1033 | cls._get_flashinfer_trtllm_api_decode_kernel(), |
| 1034 | use_dcp=vllm_config.parallel_config.decode_context_parallel_size > 1, |
| 1035 | ): |
| 1036 | return None |
| 1037 | # Longer requests are split off as prefills. |
| 1038 | return max_decode_query_len(vllm_config) |
| 1039 | |
| 1040 | def _get_workspace_buffer(self): |
| 1041 | if self._workspace_buffer is None: |
| 1042 | buffer_size = envs.VLLM_FLASHINFER_WORKSPACE_BUFFER_SIZE |
| 1043 | if envs.VLLM_BATCH_INVARIANT: |
| 1044 | buffer_size = FLASHINFER_WORKSPACE_BUFFER_SIZE_BATCH_INVARIANT |
| 1045 | else: |
| 1046 | # FlashInfer prefill temp buffers (batch_prefill_tmp_v, ...) |
| 1047 | # scale with the prefill chunk and query-head footprint, NOT |
| 1048 | # context length. The fixed ~394 MiB default is too small for |
| 1049 | # wide-head models at the default 8192-token chunk on some |
| 1050 | # archs (e.g. sm_120), where FlashInfer hard-errors instead of |
| 1051 | # growing. Size to the batch's head footprint; never shrink |
| 1052 | # below the configured default. |
| 1053 | est = ( |
| 1054 | self.max_num_batched_tokens |
| 1055 | * self.num_qo_heads |
| 1056 | * self.head_dim |
| 1057 | * FLASHINFER_PREFILL_WORKSPACE_BYTES_PER_ELEM |
| 1058 | ) |
| 1059 | buffer_size = max(buffer_size, est) |
| 1060 | self._workspace_buffer = torch.zeros( |
| 1061 | buffer_size, dtype=torch.uint8, device=self.device |
| 1062 | ) |
| 1063 | return self._workspace_buffer |
| 1064 | |
| 1065 | def set_workspace_buffer(self, workspace_buffer: torch.Tensor): |
| 1066 | self._workspace_buffer = workspace_buffer |
| 1067 | |
| 1068 | @staticmethod |
| 1069 | def _uses_trtllm_gen_varlen_decode( |
| 1070 | vllm_config: VllmConfig, |
| 1071 | decode_kernel: FlashInferDecodeKernel | None, |
| 1072 | use_dcp: bool, |
| 1073 | ) -> bool: |
| 1074 | """Whether decode reads per-request query lengths from the device |
| 1075 | qo_indptr. Adaptive verification trims drafts on device, and only |
| 1076 | trtllm-gen supports that (the selector already rejects the other |
| 1077 | configurations).""" |
| 1078 | speculative_config = vllm_config.speculative_config |
| 1079 | return ( |
| 1080 | speculative_config is not None |
| 1081 | and speculative_config.enable_adaptive_verification |
| 1082 | and decode_kernel == FlashInferDecodeKernel.TRTLLM_GEN |
| 1083 | and not use_dcp |
| 1084 | ) |
| 1085 | |
| 1086 | @staticmethod |
| 1087 | def _get_flashinfer_trtllm_api_decode_kernel() -> FlashInferDecodeKernel: |
| 1088 | if current_platform.is_device_capability( |
| 1089 | 90 |
| 1090 | ) or current_platform.is_device_capability_family(120): |
| 1091 | return FlashInferDecodeKernel.XQA |
| 1092 | assert current_platform.is_device_capability_family(100) |
| 1093 | return FlashInferDecodeKernel.TRTLLM_GEN |
| 1094 | |
| 1095 | def _compute_decode_query_lens( |
| 1096 | self, |
| 1097 | qo_indptr: torch.Tensor, |
| 1098 | qo_indptr_cpu: torch.Tensor, |
| 1099 | num_decodes: int, |
| 1100 | num_decode_tokens: int, |
| 1101 | ) -> tuple[int, torch.Tensor | None, list[int] | None]: |
| 1102 | """Return the query width, ragged offsets, and effective query lengths.""" |
| 1103 | if num_decodes == 0 or num_decode_tokens == 0: |
| 1104 | return 1, None, None |
| 1105 | |
| 1106 | if not self.use_xqa: |
| 1107 | decode_q_lens = ( |
| 1108 | qo_indptr_cpu[1 : num_decodes + 1] - qo_indptr_cpu[:num_decodes] |
| 1109 | ) |
| 1110 | q_len_per_req = num_decode_tokens // num_decodes |
| 1111 | if num_decode_tokens % num_decodes == 0 and bool( |
| 1112 | (decode_q_lens == q_len_per_req).all().item() |
| 1113 | ): |
| 1114 | return q_len_per_req, None, None |
| 1115 | |
| 1116 | # CUDA-graph padding can leave zero-length requests after a |
| 1117 | # uniform speculative decode batch. Keep the packed query and |
| 1118 | # describe it through the trtllm-gen varlen API. |
| 1119 | max_q_len = int(decode_q_lens.max().item()) |
| 1120 | if max_q_len > 1: |
| 1121 | return max_q_len, qo_indptr[: num_decodes + 1], None |
| 1122 | return 1, None, None |
| 1123 | |
| 1124 | decode_q_lens = qo_indptr_cpu[1 : num_decodes + 1] - qo_indptr_cpu[:num_decodes] |
| 1125 | nonzero = decode_q_lens[decode_q_lens > 0] |
| 1126 | q_len_per_req = int(nonzero.max().item()) if nonzero.numel() > 0 else 1 |
| 1127 | uniform = nonzero.numel() <= 1 or bool((nonzero == nonzero[0]).all().item()) |
| 1128 | if uniform and num_decode_tokens == num_decodes * q_len_per_req: |
| 1129 | return q_len_per_req, None, None |
| 1130 | |
| 1131 | if q_len_per_req <= 1: |
| 1132 | return 1, None, None |
| 1133 | |
| 1134 | q_lens = decode_q_lens.tolist() |
| 1135 | return ( |
| 1136 | q_len_per_req, |
| 1137 | qo_indptr[: num_decodes + 1], |
| 1138 | q_lens, |
| 1139 | ) |
| 1140 | |
| 1141 | def _get_decode_mask( |
| 1142 | self, |
| 1143 | q_len_per_req: int, |
| 1144 | ragged_q_lens: list[int] | None, |
| 1145 | num_decodes: int, |
| 1146 | causal: bool, |
| 1147 | ) -> torch.Tensor | None: |
| 1148 | """Return the packed XQA draft mask for speculative decode.""" |
| 1149 | if q_len_per_req <= 1: |
| 1150 | return None |
| 1151 | |
| 1152 | if ragged_q_lens is None: |
| 1153 | key = (q_len_per_req, causal) |
| 1154 | buf = self._decode_mask_cache.get(key) |
| 1155 | if buf is None: |
| 1156 | per_req = _make_xqa_draft_block_mask(q_len_per_req, causal, self.device) |
| 1157 | buf = ( |
| 1158 | per_req.unsqueeze(0).expand(self.max_num_reqs, -1, -1).contiguous() |
| 1159 | ) |
| 1160 | self._decode_mask_cache[key] = buf |
| 1161 | return buf[:num_decodes] |
| 1162 | |
| 1163 | return _make_xqa_ragged_draft_block_mask( |
| 1164 | ragged_q_lens, q_len_per_req, causal, self.device |
| 1165 | ) |
| 1166 | |
| 1167 | def _get_prefill_wrapper( |
| 1168 | self, |
| 1169 | causal: bool = True, |
| 1170 | ) -> BatchPrefillWithPagedKVCacheWrapper | BatchDCPPrefillWrapper: |
| 1171 | if not causal: |
| 1172 | if self.use_dcp: |
| 1173 | raise NotImplementedError( |
| 1174 | "FlashInfer non-causal prefill is not supported with DCP yet." |
| 1175 | ) |
| 1176 | if self.is_kvcache_nvfp4: |
| 1177 | raise NotImplementedError( |
| 1178 | "FlashInfer non-causal attention is not supported with " |
| 1179 | "NVFP4 KV cache." |
| 1180 | ) |
| 1181 | if self._noncausal_prefill_wrapper is None: |
| 1182 | if self.has_sinks and ( |
| 1183 | current_platform.is_device_capability(90) |
| 1184 | or current_platform.is_device_capability_family(120) |
| 1185 | ): |
| 1186 | self._noncausal_prefill_wrapper = ( |
| 1187 | BatchAttentionWithAttentionSinkWrapper( |
| 1188 | self._get_workspace_buffer(), |
| 1189 | get_flashinfer_layout_string(self.kv_cache_layout), |
| 1190 | backend="auto", |
| 1191 | q_data_type=self.q_data_type_prefill, |
| 1192 | kv_data_type=self.kv_cache_dtype, |
| 1193 | head_dim_qk=self.head_dim, |
| 1194 | head_dim_vo=self.head_dim, |
| 1195 | window_left=self.window_left, |
| 1196 | ) |
| 1197 | ) |
| 1198 | else: |
| 1199 | self._noncausal_prefill_wrapper = ( |
| 1200 | BatchPrefillWithPagedKVCacheWrapper( |
| 1201 | self._get_workspace_buffer(), |
| 1202 | get_flashinfer_layout_string(self.kv_cache_layout), |
| 1203 | backend="auto", |
| 1204 | ) |
| 1205 | ) |
| 1206 | return self._noncausal_prefill_wrapper |
| 1207 | |
| 1208 | if self._prefill_wrapper is None: |
| 1209 | if self.use_dcp: |
| 1210 | self._prefill_wrapper = BatchDCPPrefillWrapper( |
| 1211 | kv_layout=get_flashinfer_layout_string(self.kv_cache_layout), |
| 1212 | workspace_buffer=self._get_workspace_buffer(), |
| 1213 | dcp_a2a=self.dcp_a2a, |
| 1214 | ) |
| 1215 | else: |
| 1216 | if self.has_sinks and ( |
| 1217 | current_platform.is_device_capability(90) |
| 1218 | or current_platform.is_device_capability_family(120) |
| 1219 | ): |
| 1220 | assert not self.is_kvcache_nvfp4 |
| 1221 | self._prefill_wrapper = BatchAttentionWithAttentionSinkWrapper( |
| 1222 | self._get_workspace_buffer(), |
| 1223 | get_flashinfer_layout_string(self.kv_cache_layout), |
| 1224 | backend="auto", |
| 1225 | q_data_type=self.q_data_type_prefill, |
| 1226 | kv_data_type=self.kv_cache_dtype, |
| 1227 | head_dim_qk=self.head_dim, |
| 1228 | head_dim_vo=self.head_dim, |
| 1229 | window_left=self.window_left, |
| 1230 | ) |
| 1231 | else: |
| 1232 | # NVFP4 KV cache requires the trtllm-gen backend inside |
| 1233 | # the wrapper; fa2/fa3 do not support nvfp4. |
| 1234 | backend = "trtllm-gen" if self.is_kvcache_nvfp4 else "auto" |
| 1235 | self._prefill_wrapper = BatchPrefillWithPagedKVCacheWrapper( |
| 1236 | self._get_workspace_buffer(), |
| 1237 | get_flashinfer_layout_string(self.kv_cache_layout), |
| 1238 | backend=backend, |
| 1239 | ) |
| 1240 | assert self._prefill_wrapper is not None |
| 1241 | return self._prefill_wrapper |
| 1242 | |
| 1243 | def _get_decode_wrapper(self, batch_size: int, use_cudagraph: bool = False): |
| 1244 | if use_cudagraph: |
| 1245 | decode_wrapper = self._decode_wrappers_cudagraph.get(batch_size, None) |
| 1246 | else: |
| 1247 | decode_wrapper = self._decode_wrapper |
| 1248 | |
| 1249 | if decode_wrapper is None: |
| 1250 | if use_cudagraph: |
| 1251 | paged_kv_indptr = self.paged_kv_indptr.gpu[: batch_size + 1] |
| 1252 | paged_kv_indices = self.paged_kv_indices |
| 1253 | paged_kv_last_page_len = self.paged_kv_last_page_len.gpu[:batch_size] |
| 1254 | else: |
| 1255 | paged_kv_indptr = None |
| 1256 | paged_kv_indices = None |
| 1257 | paged_kv_last_page_len = None |
| 1258 | # NVFP4 KV cache requires the trtllm-gen backend inside |
| 1259 | # the wrapper; fa2/fa3 do not support nvfp4. |
| 1260 | backend = "trtllm-gen" if self.is_kvcache_nvfp4 else "auto" |
| 1261 | decode_wrapper = BatchDecodeWithPagedKVCacheWrapper( |
| 1262 | self._get_workspace_buffer(), |
| 1263 | get_flashinfer_layout_string(self.kv_cache_layout), |
| 1264 | use_cuda_graph=use_cudagraph, |
| 1265 | paged_kv_indptr_buffer=paged_kv_indptr, |
| 1266 | paged_kv_indices_buffer=paged_kv_indices, |
| 1267 | paged_kv_last_page_len_buffer=paged_kv_last_page_len, |
| 1268 | # Tensor cores are enabled by default because the perf would be |
| 1269 | # at least as good as cuda cores for all attention ops in latest |
| 1270 | # gpus. |
| 1271 | use_tensor_cores=True, |
| 1272 | backend=backend, |
| 1273 | ) |
| 1274 | |
| 1275 | # save the decode wrapper |
| 1276 | if use_cudagraph: |
| 1277 | self._decode_wrappers_cudagraph[batch_size] = decode_wrapper |
| 1278 | else: |
| 1279 | self._decode_wrapper = decode_wrapper |
| 1280 | |
| 1281 | return decode_wrapper |
| 1282 | |
| 1283 | def _get_cascade_wrapper(self): |
| 1284 | if self._cascade_wrapper is None: |
| 1285 | self._cascade_wrapper = MultiLevelCascadeAttentionWrapper( |
| 1286 | 2, |
| 1287 | self._get_workspace_buffer(), |
| 1288 | get_flashinfer_layout_string(self.kv_cache_layout), |
| 1289 | ) |
| 1290 | return self._cascade_wrapper |
| 1291 | |
| 1292 | def _compute_flashinfer_kv_metadata( |
| 1293 | self, |
| 1294 | num_blocks_np: np.ndarray, |
| 1295 | seq_lens_np: np.ndarray, |
| 1296 | block_table_tensor: torch.Tensor, |
| 1297 | num_reqs: int, |
| 1298 | page_size: int, |
| 1299 | ) -> torch.Tensor: |
| 1300 | """Compute paged_kv_indptr, paged_kv_indices and paged_kv_last_page_len. |
| 1301 | |
| 1302 | Results are stored in self.paged_kv_indptr, |
| 1303 | self.paged_kv_indices, self.paged_kv_last_page_len buffers. |
| 1304 | |
| 1305 | Returns paged_kv_indices, a GPU tensor with shape [num_actual_pages]. |
| 1306 | """ |
| 1307 | # write self.paged_kv_indptr_cpu inplace (0-index is always 0) |
| 1308 | np.cumsum( |
| 1309 | num_blocks_np, dtype=np.int32, out=self.paged_kv_indptr.np[1 : num_reqs + 1] |
| 1310 | ) |
| 1311 | paged_kv_indptr = self.paged_kv_indptr.copy_to_gpu(num_reqs + 1) |
| 1312 | |
| 1313 | # write self.paged_kv_indices inplace |
| 1314 | num_actual_pages = self.paged_kv_indptr.np[num_reqs] |
| 1315 | paged_kv_indices = self.paged_kv_indices[:num_actual_pages] |
| 1316 | _copy_page_indices_kernel[(num_reqs,)]( |
| 1317 | paged_kv_indices, |
| 1318 | block_table_tensor, |
| 1319 | block_table_tensor.stride(0), |
| 1320 | paged_kv_indptr, |
| 1321 | BLOCK_SIZE=1024, |
| 1322 | ) |
| 1323 | |
| 1324 | # write self.paged_kv_last_page_len_cpu inplace |
| 1325 | paged_kv_last_page_len_np = seq_lens_np % page_size |
| 1326 | self.paged_kv_last_page_len.np[:num_reqs] = np.where( |
| 1327 | (paged_kv_last_page_len_np == 0) & (seq_lens_np != 0), |
| 1328 | page_size, |
| 1329 | paged_kv_last_page_len_np, |
| 1330 | ) |
| 1331 | self.paged_kv_last_page_len.copy_to_gpu(num_reqs) |
| 1332 | return paged_kv_indices |
| 1333 | |
| 1334 | def build( |
| 1335 | self, |
| 1336 | common_prefix_len: int, |
| 1337 | common_attn_metadata: CommonAttentionMetadata, |
| 1338 | fast_build: bool = False, |
| 1339 | ) -> FlashInferMetadata: |
| 1340 | num_reqs = common_attn_metadata.num_reqs |
| 1341 | num_actual_tokens = common_attn_metadata.num_actual_tokens |
| 1342 | causal = common_attn_metadata.causal |
| 1343 | route_decode = causal or self.use_xqa |
| 1344 | if route_decode: |
| 1345 | num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = ( |
| 1346 | split_decodes_and_prefills( |
| 1347 | common_attn_metadata, |
| 1348 | decode_threshold=self.reorder_batch_threshold, |
| 1349 | require_uniform=not ( |
| 1350 | self.use_xqa or self.use_trtllm_gen_varlen_decode |
| 1351 | ), |
| 1352 | ) |
| 1353 | ) |
| 1354 | else: |
| 1355 | num_decodes = 0 |
| 1356 | num_prefills = num_reqs |
| 1357 | num_decode_tokens = 0 |
| 1358 | num_prefill_tokens = num_actual_tokens |
| 1359 | |
| 1360 | page_size = self.page_size |
| 1361 | max_seq_len = common_attn_metadata.max_seq_len |
| 1362 | seq_lens = common_attn_metadata.seq_lens |
| 1363 | block_table_tensor = common_attn_metadata.block_table_tensor |
| 1364 | qo_indptr = common_attn_metadata.query_start_loc |
| 1365 | qo_indptr_cpu = common_attn_metadata.query_start_loc_cpu |
| 1366 | |
| 1367 | # Step 1: Decide which dispatch modes to use: |
| 1368 | # - Cascade attention (distinct mode) |
| 1369 | # - Prefill (FI native or TRTLLM) |
| 1370 | # - Decode (FI native, XQA, or trtllm-gen) |
| 1371 | use_cascade = common_prefix_len > 0 |
| 1372 | uses_spec_reorder = self.reorder_batch_threshold > 1 |
| 1373 | # Page sizes >= 128 must use trtllm-gen; force it for prefill too. |
| 1374 | prefill_force_trtllm = ( |
| 1375 | True if page_size >= 128 else self.attention_config.use_trtllm_attention |
| 1376 | ) |
| 1377 | prefill_use_trtllm = causal and use_trtllm_attention( |
| 1378 | self.num_qo_heads, |
| 1379 | self.num_kv_heads, |
| 1380 | num_prefill_tokens, |
| 1381 | max_seq_len, |
| 1382 | self.dcp_world_size, |
| 1383 | self.cache_dtype, |
| 1384 | self.q_data_type_prefill, |
| 1385 | is_prefill=True, |
| 1386 | force_use_trtllm=prefill_force_trtllm, |
| 1387 | has_sinks=self.has_sinks, |
| 1388 | has_spec=uses_spec_reorder, |
| 1389 | ) |
| 1390 | decode_with_flashinfer_trtllm_api = self.use_trtllm_decode_attention and ( |
| 1391 | causal or self.use_xqa |
| 1392 | ) |
| 1393 | |
| 1394 | if not causal and self.use_dcp: |
| 1395 | raise NotImplementedError( |
| 1396 | "FlashInfer non-causal prefill is not supported with DCP yet." |
| 1397 | ) |
| 1398 | if not causal and self.use_trtllm_decode_attention: |
| 1399 | logger.warning_once( |
| 1400 | "Using FlashInfer for draft model non-causal attention; TRTLLM " |
| 1401 | "can still be used for target model causal attention." |
| 1402 | ) |
| 1403 | all_uses_trtllm = causal and ( |
| 1404 | (num_prefills == 0 or prefill_use_trtllm) |
| 1405 | and (num_decodes == 0 or decode_with_flashinfer_trtllm_api) |
| 1406 | ) |
| 1407 | |
| 1408 | if not all_uses_trtllm: |
| 1409 | sinks_would_be_dropped = self.use_dcp or use_cascade |
| 1410 | if self.has_sinks and sinks_would_be_dropped: |
| 1411 | raise NotImplementedError( |
| 1412 | "FlashInfer BatchDCPPrefillWrapper and " |
| 1413 | "MultiLevelCascadeAttentionWrapper do not support attention sinks. " |
| 1414 | "Please use TRTLLM on Blackwell or FlashAttention on earlier GPUs." |
| 1415 | ) |
| 1416 | |
| 1417 | if not self.global_hyperparameters.has_same_window_lefts: |
| 1418 | raise ValueError( |
| 1419 | "Window left is not the same for all layers. " |
| 1420 | "One potential fix is to set disable_sliding_window=True" |
| 1421 | ) |
| 1422 | |
| 1423 | assert self.global_hyperparameters.has_same_all_params, ( |
| 1424 | "FlashInfer backend currently only supports models in which " |
| 1425 | "all layers share the same values for the following " |
| 1426 | "hyperparameters: `window_left`, `logits_soft_cap`, " |
| 1427 | "`sm_scale`." |
| 1428 | ) |
| 1429 | |
| 1430 | # Step 2: Initialize the output metadata |
| 1431 | # Leave prefill/decode/cascade_wrapper empty, to be populated |
| 1432 | # case by case depending on the batch contents and backend selection. |
| 1433 | attn_metadata = FlashInferMetadata( |
| 1434 | kv_cache_layout=self.kv_cache_layout, |
| 1435 | num_actual_tokens=num_actual_tokens, |
| 1436 | slot_mapping=common_attn_metadata.slot_mapping, |
| 1437 | q_data_type_prefill=self.q_data_type_prefill, |
| 1438 | q_data_type_decode=self.q_data_type_decode, |
| 1439 | num_decodes=num_decodes, |
| 1440 | num_decode_tokens=num_decode_tokens, |
| 1441 | num_prefills=num_prefills, |
| 1442 | num_prefill_tokens=num_prefill_tokens, |
| 1443 | causal=causal, |
| 1444 | use_cascade=use_cascade, |
| 1445 | prefill=None, |
| 1446 | decode=None, |
| 1447 | cascade_wrapper=None, |
| 1448 | ) |
| 1449 | |
| 1450 | # Guard access to seq_lens_cpu, which may not always be needed |
| 1451 | # and can be expensive to retrieve in async mode. |
| 1452 | # When all attention (both prefill and decode) uses TRTLLM, |
| 1453 | # seq_lens_cpu is not needed since TRTLLM paths use GPU tensors |
| 1454 | # (block_tables, seq_lens) directly. |
| 1455 | needs_seq_lens_cpu = self.use_dcp or use_cascade or not all_uses_trtllm |
| 1456 | if needs_seq_lens_cpu: |
| 1457 | if ( |
| 1458 | self._num_speculative_tokens == 0 |
| 1459 | and common_attn_metadata.seq_lens_cpu_upper_bound is not None |
| 1460 | ): |
| 1461 | # No speculative token accounting, so the upper bound is exact. |
| 1462 | seq_lens_cpu = common_attn_metadata.seq_lens_cpu_upper_bound |
| 1463 | else: |
| 1464 | with gpu_sync_allowed(): |
| 1465 | seq_lens_cpu = common_attn_metadata.seq_lens.cpu() |
| 1466 | else: |
| 1467 | seq_lens_cpu = None |
| 1468 | |
| 1469 | # Adjust seq_lens_cpu for DCP |
| 1470 | if self.use_dcp: |
| 1471 | assert seq_lens_cpu is not None |
| 1472 | if num_prefills > 0: |
| 1473 | # Other attention groups may reuse the same common metadata. |
| 1474 | seq_lens_cpu = seq_lens_cpu.clone() |
| 1475 | qo_indptr_prefill_cpu = ( |
| 1476 | qo_indptr_cpu[num_decodes:] - qo_indptr_cpu[num_decodes] |
| 1477 | ) |
| 1478 | query_lens_prefill_cpu = ( |
| 1479 | qo_indptr_prefill_cpu[1:] - qo_indptr_prefill_cpu[:-1] |
| 1480 | ) |
| 1481 | seq_lens_cpu[num_decodes:] = ( |
| 1482 | seq_lens_cpu[num_decodes:] - query_lens_prefill_cpu |
| 1483 | ) |
| 1484 | |
| 1485 | seq_lens_cpu = get_dcp_local_seq_lens( |
| 1486 | seq_lens_cpu, |
| 1487 | self.dcp_world_size, |
| 1488 | self.dcp_rank, |
| 1489 | self.dcp_kv_cache_interleave_size, |
| 1490 | ) |
| 1491 | |
| 1492 | # Native paged attention consumes rank-local lengths (context only |
| 1493 | # for DCP prefills), so derive its page counts after the conversion. |
| 1494 | if seq_lens_cpu is not None: |
| 1495 | seq_lens_np = seq_lens_cpu.numpy() |
| 1496 | num_blocks_np = (seq_lens_np + (page_size - 1)) // page_size |
| 1497 | else: |
| 1498 | seq_lens_np = None |
| 1499 | num_blocks_np = None |
| 1500 | |
| 1501 | # Adjust num_block_np for cascade attention |
| 1502 | if use_cascade: |
| 1503 | assert num_blocks_np is not None |
| 1504 | assert common_prefix_len % page_size == 0 |
| 1505 | num_common_kv_blocks = common_prefix_len // page_size |
| 1506 | num_blocks_np -= num_common_kv_blocks |
| 1507 | |
| 1508 | # Compute paged_kv_indices if necessary |
| 1509 | # paged_kv_indices is only needed for FlashInfer native paths; |
| 1510 | # XQA/trtllm-gen paths use block_tables directly on GPU. |
| 1511 | needs_native_paged_prefill = num_prefills > 0 and not prefill_use_trtllm |
| 1512 | needs_native_paged_decode = ( |
| 1513 | num_decodes > 0 and not decode_with_flashinfer_trtllm_api |
| 1514 | ) |
| 1515 | needs_paged_kv_indices = ( |
| 1516 | use_cascade or needs_native_paged_prefill or needs_native_paged_decode |
| 1517 | ) |
| 1518 | if needs_paged_kv_indices: |
| 1519 | assert num_blocks_np is not None |
| 1520 | assert seq_lens_np is not None |
| 1521 | paged_kv_indices = self._compute_flashinfer_kv_metadata( |
| 1522 | num_blocks_np, |
| 1523 | seq_lens_np, |
| 1524 | block_table_tensor, |
| 1525 | num_reqs, |
| 1526 | page_size, |
| 1527 | ) |
| 1528 | else: |
| 1529 | paged_kv_indices = None |
| 1530 | |
| 1531 | # Early-out for cascade attention |
| 1532 | if use_cascade: |
| 1533 | assert num_blocks_np is not None |
| 1534 | # Grab the blocks of the shared prefix from the first request. |
| 1535 | num_common_kv_blocks = common_prefix_len // page_size |
| 1536 | |
| 1537 | # Create CPU versions directly for cascade (no GPU versions needed) |
| 1538 | shared_qo_indptr_cpu = torch.tensor( |
| 1539 | [0, num_actual_tokens], dtype=torch.int32, device="cpu" |
| 1540 | ) |
| 1541 | shared_kv_page_indptr_cpu = torch.tensor( |
| 1542 | [0, num_common_kv_blocks], dtype=torch.int32, device="cpu" |
| 1543 | ) |
| 1544 | shared_kv_page_indices_cpu = block_table_tensor[0, :num_common_kv_blocks] |
| 1545 | shared_kv_last_page_len_cpu = torch.tensor( |
| 1546 | [page_size], dtype=torch.int32, device="cpu" |
| 1547 | ) |
| 1548 | |
| 1549 | # Remove the blocks of the shared prefix from all requests. |
| 1550 | block_table_tensor = block_table_tensor[:, num_common_kv_blocks:] |
| 1551 | num_blocks_np -= num_common_kv_blocks |
| 1552 | |
| 1553 | assert paged_kv_indices is not None |
| 1554 | paged_kv_indptr_cpu = self.paged_kv_indptr.cpu[: 1 + num_reqs] |
| 1555 | paged_kv_last_page_len_cpu = self.paged_kv_last_page_len.cpu[:num_reqs] |
| 1556 | |
| 1557 | attn_metadata.cascade_wrapper = self._get_cascade_wrapper() |
| 1558 | # Cascade attention must use the same q dtype for prefill and decode |
| 1559 | # because it does not support FP8 kv-cache or FP8 query yet. |
| 1560 | assert self.q_data_type_prefill == self.q_data_type_decode |
| 1561 | attn_metadata.cascade_wrapper.plan( |
| 1562 | qo_indptr_arr=[shared_qo_indptr_cpu, qo_indptr_cpu], |
| 1563 | paged_kv_indptr_arr=[shared_kv_page_indptr_cpu, paged_kv_indptr_cpu], |
| 1564 | paged_kv_indices_arr=[shared_kv_page_indices_cpu, paged_kv_indices], |
| 1565 | paged_kv_last_page_len=[ |
| 1566 | shared_kv_last_page_len_cpu, |
| 1567 | paged_kv_last_page_len_cpu, |
| 1568 | ], |
| 1569 | num_qo_heads=self.num_qo_heads, |
| 1570 | num_kv_heads=self.num_kv_heads, |
| 1571 | head_dim=self.head_dim, |
| 1572 | page_size=self.page_size, |
| 1573 | causal=True, |
| 1574 | sm_scale=self.sm_scale, |
| 1575 | window_left=self.window_left, |
| 1576 | logits_soft_cap=self.logits_soft_cap, |
| 1577 | q_data_type=self.q_data_type_prefill, |
| 1578 | kv_data_type=self.kv_cache_dtype, |
| 1579 | ) |
| 1580 | return attn_metadata |
| 1581 | |
| 1582 | # Step 3: Handle prefill and decode pathways case by case |
| 1583 | ## PREFILL PATHWAY |
| 1584 | if num_prefills > 0: |
| 1585 | # Slices for shared prefill metadata |
| 1586 | prefill_start = num_decodes |
| 1587 | qo_indptr_prefill_cpu = ( |
| 1588 | qo_indptr_cpu[prefill_start:] - qo_indptr_cpu[prefill_start] |
| 1589 | ) |
| 1590 | assert qo_indptr_prefill_cpu.shape[0] == num_prefills + 1 |
| 1591 | |
| 1592 | if prefill_use_trtllm: |
| 1593 | # TRTLLM prefill has no cross-rank combine for DCP-sharded KV; |
| 1594 | # use_trtllm_attention never selects it when DCP is enabled. |
| 1595 | assert not self.use_dcp |
| 1596 | # Create GPU versions |
| 1597 | qo_indptr_prefill_gpu = ( |
| 1598 | qo_indptr[prefill_start:] - qo_indptr[prefill_start] |
| 1599 | ) |
| 1600 | # Compute cum_seq_lens_kv on GPU to avoid CPU sync. |
| 1601 | # This is the cumulative sum of the number of KV cache |
| 1602 | # blocks per prefill request. |
| 1603 | prefill_seq_lens = seq_lens[prefill_start:] |
| 1604 | num_blocks_per_req = (prefill_seq_lens + page_size - 1) // page_size |
| 1605 | paged_kv_indptr_prefill_gpu = self.paged_kv_indptr.gpu[ |
| 1606 | prefill_start : num_reqs + 1 |
| 1607 | ] |
| 1608 | # Assign to slice to avoid cpu sync. |
| 1609 | paged_kv_indptr_prefill_gpu[:1] = 0 |
| 1610 | torch.cumsum( |
| 1611 | num_blocks_per_req, |
| 1612 | dim=0, |
| 1613 | out=paged_kv_indptr_prefill_gpu[1:], |
| 1614 | ) |
| 1615 | # Compute max_q_len for prefill requests |
| 1616 | query_lens_prefill_cpu = ( |
| 1617 | qo_indptr_prefill_cpu[1:] - qo_indptr_prefill_cpu[:-1] |
| 1618 | ) |
| 1619 | max_q_len_prefill = int(query_lens_prefill_cpu.max().item()) |
| 1620 | prefill_block_tables = block_table_tensor[prefill_start:] |
| 1621 | if ( |
| 1622 | self.q_data_type_prefill != FP8_DTYPE |
| 1623 | and self.cache_dtype.startswith("fp8") |
| 1624 | ): |
| 1625 | seq_lens_cpu_upper_bound = ( |
| 1626 | common_attn_metadata.seq_lens_cpu_upper_bound |
| 1627 | ) |
| 1628 | max_prefill_seq_len = max_seq_len |
| 1629 | if seq_lens_cpu_upper_bound is not None: |
| 1630 | max_prefill_seq_len = int( |
| 1631 | seq_lens_cpu_upper_bound[prefill_start:num_reqs] |
| 1632 | .max() |
| 1633 | .item() |
| 1634 | ) |
| 1635 | # Dequantization allocates one page per table entry and |
| 1636 | # indexes rows by their logical width. |
| 1637 | prefill_block_tables = canonicalize_singleton_dim_strides( |
| 1638 | prefill_block_tables[ |
| 1639 | :, : cdiv(max_prefill_seq_len, page_size) |
| 1640 | ].contiguous() |
| 1641 | ) |
| 1642 | attn_metadata.prefill = TRTLLMPrefill( |
| 1643 | block_tables=prefill_block_tables, |
| 1644 | seq_lens=prefill_seq_lens, |
| 1645 | cum_seq_lens_q=qo_indptr_prefill_gpu, |
| 1646 | cum_seq_lens_kv=paged_kv_indptr_prefill_gpu, |
| 1647 | max_q_len=max_q_len_prefill, |
| 1648 | max_seq_len=max_seq_len, |
| 1649 | ) |
| 1650 | else: |
| 1651 | prefill_wrapper = self._get_prefill_wrapper(causal=attn_metadata.causal) |
| 1652 | # Slicing CPU buffers that are only needed for FI native prefills |
| 1653 | paged_kv_last_page_len_prefill_cpu = self.paged_kv_last_page_len.cpu[ |
| 1654 | prefill_start:num_reqs |
| 1655 | ] |
| 1656 | assert paged_kv_last_page_len_prefill_cpu.shape[0] == num_prefills |
| 1657 | paged_kv_indptr_prefill_cpu = self.paged_kv_indptr.cpu[ |
| 1658 | prefill_start : num_reqs + 1 |
| 1659 | ] |
| 1660 | assert paged_kv_indptr_prefill_cpu.shape[0] == num_prefills + 1 |
| 1661 | # plan() copies these to the GPU with non_blocking=True; |
| 1662 | # stage them in pinned memory so the copies stay async |
| 1663 | # (the reused buffers themselves are intentionally not pinned). |
| 1664 | if PIN_MEMORY: |
| 1665 | qo_indptr_prefill_cpu = qo_indptr_prefill_cpu.pin_memory() |
| 1666 | paged_kv_indptr_prefill_cpu = ( |
| 1667 | paged_kv_indptr_prefill_cpu.pin_memory() |
| 1668 | ) |
| 1669 | paged_kv_last_page_len_prefill_cpu = ( |
| 1670 | paged_kv_last_page_len_prefill_cpu.pin_memory() |
| 1671 | ) |
| 1672 | # plan() also copies kv_lens to the GPU with non_blocking=True, |
| 1673 | # deriving them via get_seq_lens() into unpinned memory when |
| 1674 | # seq_lens is not passed; pass an explicit pinned copy of the |
| 1675 | # same values so that copy stays async too. |
| 1676 | kv_lens_prefill_cpu = get_seq_lens( |
| 1677 | paged_kv_indptr_prefill_cpu, |
| 1678 | paged_kv_last_page_len_prefill_cpu, |
| 1679 | page_size, |
| 1680 | ) |
| 1681 | if PIN_MEMORY: |
| 1682 | kv_lens_prefill_cpu = kv_lens_prefill_cpu.pin_memory() |
| 1683 | if self.use_dcp: |
| 1684 | assert isinstance(prefill_wrapper, BatchDCPPrefillWrapper) |
| 1685 | prefill_wrapper.plan( |
| 1686 | qo_indptr_cpu=qo_indptr_prefill_cpu, |
| 1687 | paged_kv_indptr_cpu=paged_kv_indptr_prefill_cpu, |
| 1688 | paged_kv_indices=paged_kv_indices, |
| 1689 | paged_kv_last_page_len_cpu=paged_kv_last_page_len_prefill_cpu, |
| 1690 | kv_lens_cpu=kv_lens_prefill_cpu, |
| 1691 | page_size=self.page_size, |
| 1692 | num_qo_heads=self.num_qo_heads, |
| 1693 | dcp_world_size=self.dcp_world_size, |
| 1694 | num_kv_heads=self.num_kv_heads, |
| 1695 | head_dim=self.head_dim, |
| 1696 | sm_scale=self.sm_scale, |
| 1697 | window_left=self.window_left, |
| 1698 | logits_soft_cap=self.logits_soft_cap, |
| 1699 | q_data_type=self.q_data_type_prefill, |
| 1700 | kv_cache_dtype=self.kv_cache_dtype, |
| 1701 | prefill_fixed_split_size=self.prefill_fixed_split_size, |
| 1702 | disable_split_kv=self.disable_split_kv, |
| 1703 | ) |
| 1704 | else: |
| 1705 | assert isinstance( |
| 1706 | prefill_wrapper, |
| 1707 | BatchPrefillWithPagedKVCacheWrapper, |
| 1708 | ) |
| 1709 | # NVFP4 trtllm kernel only supports FP8 output; |
| 1710 | # use FP8 o_data_type so the wrapper matches the |
| 1711 | # FP8 output buffer allocated in forward(). |
| 1712 | o_dtype = ( |
| 1713 | FP8_DTYPE if self.is_kvcache_nvfp4 else self.model_config.dtype |
| 1714 | ) |
| 1715 | prefill_wrapper.plan( |
| 1716 | qo_indptr=qo_indptr_prefill_cpu, |
| 1717 | paged_kv_indptr=paged_kv_indptr_prefill_cpu, |
| 1718 | paged_kv_indices=paged_kv_indices, |
| 1719 | paged_kv_last_page_len=paged_kv_last_page_len_prefill_cpu, |
| 1720 | seq_lens=kv_lens_prefill_cpu, |
| 1721 | num_qo_heads=self.num_qo_heads, |
| 1722 | num_kv_heads=self.num_kv_heads, |
| 1723 | head_dim_qk=self.head_dim, |
| 1724 | page_size=self.page_size, |
| 1725 | causal=attn_metadata.causal, |
| 1726 | sm_scale=self.sm_scale, |
| 1727 | window_left=self.window_left, |
| 1728 | logits_soft_cap=self.logits_soft_cap, |
| 1729 | q_data_type=self.q_data_type_prefill, |
| 1730 | kv_data_type=self.kv_cache_dtype, |
| 1731 | o_data_type=o_dtype, |
| 1732 | fixed_split_size=self.prefill_fixed_split_size, |
| 1733 | disable_split_kv=self.disable_split_kv, |
| 1734 | ) |
| 1735 | attn_metadata.prefill = FIPrefill(wrapper=prefill_wrapper) |
| 1736 | |
| 1737 | ## DECODE PATHWAY |
| 1738 | if num_decodes > 0: |
| 1739 | if decode_with_flashinfer_trtllm_api: |
| 1740 | assert self.flashinfer_trtllm_api_decode_kernel is not None |
| 1741 | seq_lens_decode = seq_lens[:num_decodes] |
| 1742 | if self.use_dcp: |
| 1743 | assert common_attn_metadata.dcp_local_seq_lens is not None |
| 1744 | seq_lens_decode = common_attn_metadata.dcp_local_seq_lens[ |
| 1745 | :num_decodes |
| 1746 | ] |
| 1747 | q_len_per_req, q_cu_seq_lens, ragged_q_lens = ( |
| 1748 | self._compute_decode_query_lens( |
| 1749 | qo_indptr, qo_indptr_cpu, num_decodes, num_decode_tokens |
| 1750 | ) |
| 1751 | ) |
| 1752 | decode_mask = None |
| 1753 | if self.use_xqa: |
| 1754 | decode_mask = self._get_decode_mask( |
| 1755 | q_len_per_req, |
| 1756 | ragged_q_lens, |
| 1757 | num_decodes, |
| 1758 | bool(causal), |
| 1759 | ) |
| 1760 | elif self.use_trtllm_gen_varlen_decode: |
| 1761 | # CPU placeholders may underestimate query lengths; |
| 1762 | # use a safe upper bound. |
| 1763 | q_len_per_req = min( |
| 1764 | common_attn_metadata.max_query_len, self.reorder_batch_threshold |
| 1765 | ) |
| 1766 | # Always pass GPU offsets so a graph captured with uniform |
| 1767 | # query lengths can replay batches with variable query lengths. |
| 1768 | q_cu_seq_lens = qo_indptr[: num_decodes + 1] |
| 1769 | attn_metadata.decode = FlashInferTrtllmAPIDecode( |
| 1770 | kernel=self.flashinfer_trtllm_api_decode_kernel, |
| 1771 | block_tables=block_table_tensor[:num_decodes], |
| 1772 | seq_lens=seq_lens_decode, |
| 1773 | max_seq_len=max_seq_len, |
| 1774 | q_len_per_req=q_len_per_req, |
| 1775 | q_cu_seq_lens=q_cu_seq_lens, |
| 1776 | mask=decode_mask, |
| 1777 | dcp_query_start_loc=( |
| 1778 | qo_indptr[: num_decodes + 1] if self.use_dcp else None |
| 1779 | ), |
| 1780 | ) |
| 1781 | else: |
| 1782 | assert seq_lens_cpu is not None |
| 1783 | pure_decode = num_prefills == 0 |
| 1784 | use_cudagraph = ( |
| 1785 | self.enable_cuda_graph |
| 1786 | and pure_decode |
| 1787 | and num_decode_tokens <= self._decode_cudagraph_max_bs |
| 1788 | ) |
| 1789 | num_input_tokens = num_decode_tokens |
| 1790 | |
| 1791 | decode_wrapper = self._get_decode_wrapper( |
| 1792 | num_input_tokens, use_cudagraph |
| 1793 | ) |
| 1794 | # Use the persistent buffer with padding length, |
| 1795 | # instead of the same address but chunked version |
| 1796 | # in atten_metadata when using cudagraph. |
| 1797 | # NVFP4 trtllm kernel only supports FP8 output; |
| 1798 | # use FP8 o_data_type so the wrapper matches the |
| 1799 | # FP8 output buffer allocated in forward(). |
| 1800 | o_dtype = ( |
| 1801 | FP8_DTYPE if self.is_kvcache_nvfp4 else self.model_config.dtype |
| 1802 | ) |
| 1803 | paged_kv_indptr_cpu = self.paged_kv_indptr.cpu[: num_input_tokens + 1] |
| 1804 | paged_kv_last_page_len_cpu = self.paged_kv_last_page_len.cpu[ |
| 1805 | :num_input_tokens |
| 1806 | ] |
| 1807 | # plan() copies these to the GPU with non_blocking=True; |
| 1808 | # stage them in pinned memory so the copies stay async |
| 1809 | # (the reused buffers themselves are intentionally not pinned). |
| 1810 | if PIN_MEMORY: |
| 1811 | paged_kv_indptr_cpu = paged_kv_indptr_cpu.pin_memory() |
| 1812 | paged_kv_last_page_len_cpu = paged_kv_last_page_len_cpu.pin_memory() |
| 1813 | # plan() also copies kv_lens to the GPU with non_blocking=True |
| 1814 | # (trtllm-gen/cute-dsl backends), deriving them via |
| 1815 | # get_seq_lens() into unpinned memory when seq_lens is not |
| 1816 | # passed; pass an explicit pinned copy of the same values |
| 1817 | # so that copy stays async too. |
| 1818 | kv_lens_decode_cpu = get_seq_lens( |
| 1819 | paged_kv_indptr_cpu, paged_kv_last_page_len_cpu, page_size |
| 1820 | ) |
| 1821 | if PIN_MEMORY: |
| 1822 | kv_lens_decode_cpu = kv_lens_decode_cpu.pin_memory() |
| 1823 | fast_plan_decode( |
| 1824 | decode_wrapper, |
| 1825 | indptr_cpu=paged_kv_indptr_cpu, |
| 1826 | indices=paged_kv_indices, |
| 1827 | last_page_len_cpu=paged_kv_last_page_len_cpu, |
| 1828 | seq_lens_cpu=kv_lens_decode_cpu, |
| 1829 | num_qo_heads=self.num_qo_heads * self.dcp_world_size, |
| 1830 | num_kv_heads=self.num_kv_heads, |
| 1831 | head_dim=self.head_dim, |
| 1832 | page_size=self.page_size, |
| 1833 | # Disable flashinfer's pos encoding and use vllm's rope. |
| 1834 | pos_encoding_mode="NONE", |
| 1835 | sm_scale=self.sm_scale, |
| 1836 | window_left=self.window_left, |
| 1837 | logits_soft_cap=self.logits_soft_cap, |
| 1838 | q_data_type=self.q_data_type_decode, |
| 1839 | kv_data_type=self.kv_cache_dtype, |
| 1840 | o_data_type=o_dtype, |
| 1841 | fixed_split_size=self.decode_fixed_split_size, |
| 1842 | disable_split_kv=self.disable_split_kv, |
| 1843 | ) |
| 1844 | attn_metadata.decode = FIDecode(wrapper=decode_wrapper) |
| 1845 | return attn_metadata |
| 1846 | |
| 1847 | def update_draft_decode_metadata(self, _metadata: FlashInferMetadata) -> None: |
| 1848 | pass |
| 1849 | |
| 1850 | def use_cascade_attention(self, *args, **kwargs) -> bool: |
| 1851 | if self.kv_cache_spec.dtype != self.vllm_config.model_config.dtype: |
| 1852 | # TODO: The cascade wrapper currently does not support setting |
| 1853 | # kv cache dtype to something different from query dtype. |
| 1854 | return False |
| 1855 | # TODO: Cascade attention doesn't work, disable it for now |
| 1856 | # return use_cascade_attention(*args, **kwargs) |
| 1857 | return False |
| 1858 | |
| 1859 | |
| 1860 | class FlashInferImpl(AttentionImpl): |
| 1861 | can_return_lse_for_decode: bool = True |
| 1862 | supports_dcp: bool = True |
| 1863 | |
| 1864 | def __init__( |
| 1865 | self, |
| 1866 | num_heads: int, |
| 1867 | head_size: int, |
| 1868 | scale: float, |
| 1869 | num_kv_heads: int, |
| 1870 | alibi_slopes: list[float] | None, |
| 1871 | sliding_window: int | None, |
| 1872 | kv_cache_dtype: str, |
| 1873 | logits_soft_cap: float | None = None, |
| 1874 | attn_type: AttentionType = AttentionType.DECODER, |
| 1875 | kv_sharing_target_layer_name: int | None = None, |
| 1876 | sinks: torch.Tensor | None = None, |
| 1877 | ) -> None: |
| 1878 | self.num_heads = num_heads |
| 1879 | self.head_size = head_size |
| 1880 | self.scale = float(scale) |
| 1881 | self.num_kv_heads = num_kv_heads |
| 1882 | if alibi_slopes is not None: |
| 1883 | alibi_slopes = torch.tensor(alibi_slopes, dtype=torch.float32) |
| 1884 | self.alibi_slopes = alibi_slopes |
| 1885 | if sliding_window is None: |
| 1886 | self.sliding_window = (-1, -1) |
| 1887 | else: |
| 1888 | self.sliding_window = (sliding_window - 1, 0) |
| 1889 | self.window_left = ( |
| 1890 | self.sliding_window[0] if self.sliding_window is not None else -1 |
| 1891 | ) |
| 1892 | self.cache_dtype = kv_cache_dtype |
| 1893 | self.is_kvcache_nvfp4 = kv_cache_dtype.startswith("nvfp4") |
| 1894 | self.kv_cache_dtype = "nvfp4" if self.is_kvcache_nvfp4 else kv_cache_dtype |
| 1895 | self.fp4_data_dim = head_size // 2 if self.is_kvcache_nvfp4 else 0 |
| 1896 | self.logits_soft_cap = logits_soft_cap |
| 1897 | self.kv_sharing_target_layer_name = kv_sharing_target_layer_name |
| 1898 | |
| 1899 | self.num_queries_per_kv = self.num_heads // self.num_kv_heads |
| 1900 | |
| 1901 | if attn_type != AttentionType.DECODER: |
| 1902 | raise NotImplementedError( |
| 1903 | "Encoder self-attention and " |
| 1904 | "encoder/decoder cross-attention " |
| 1905 | "are not implemented for " |
| 1906 | "FlashInferImpl" |
| 1907 | ) |
| 1908 | |
| 1909 | self.sinks: torch.Tensor | None = None |
| 1910 | # Keep the source so RL weight updates can refresh the runtime tensor. |
| 1911 | self._sinks_source = sinks |
| 1912 | if sinks is not None: |
| 1913 | if sinks.shape[0] != num_heads: |
| 1914 | raise ValueError( |
| 1915 | "Sinks must have the same number of heads as the number of " |
| 1916 | f"heads in the layer. Expected {num_heads}, but got " |
| 1917 | f"{sinks.shape[0]}." |
| 1918 | ) |
| 1919 | self.sinks = sinks |
| 1920 | |
| 1921 | self.supports_xqa_or_trtllm_gen_decode = can_use_trtllm_attention( |
| 1922 | num_heads, num_kv_heads, is_prefill=False |
| 1923 | ) |
| 1924 | vllm_config = get_current_vllm_config_or_none() |
| 1925 | # Query pre-quantization needs a single dtype for the whole query tensor. |
| 1926 | # SM90 XQA needs BF16/FP16-Q for decode and FP8 for prefill, |
| 1927 | # so only enable this for SM100 trtllm-gen where both use FP8-Q. |
| 1928 | self.supports_quant_query_input = ( |
| 1929 | self.supports_xqa_or_trtllm_gen_decode |
| 1930 | and is_quantized_kv_cache(self.kv_cache_dtype) |
| 1931 | and current_platform.is_device_capability_family(100) |
| 1932 | and vllm_config is not None |
| 1933 | and not vllm_config.attention_config.disable_flashinfer_q_quantization |
| 1934 | ) |
| 1935 | self.bmm1_scale: float | None = None |
| 1936 | self.bmm2_scale: float | None = None |
| 1937 | self.o_sf_scale: float | None = None |
| 1938 | |
| 1939 | # Pre-allocated FP8 output buffer for NVFP4 without fused output quant. |
| 1940 | if self.is_kvcache_nvfp4 and vllm_config is not None: |
| 1941 | max_num_tokens = vllm_config.scheduler_config.max_num_batched_tokens |
| 1942 | self._nvfp4_fp8_out = torch.empty( |
| 1943 | (max_num_tokens, num_heads, head_size), |
| 1944 | dtype=FP8_DTYPE, |
| 1945 | device="cuda", |
| 1946 | ) |
| 1947 | else: |
| 1948 | self._nvfp4_fp8_out = None |
| 1949 | |
| 1950 | dcp_a2a = ( |
| 1951 | vllm_config is not None |
| 1952 | and vllm_config.parallel_config.decode_context_parallel_size > 1 |
| 1953 | and vllm_config.parallel_config.dcp_comm_backend == "a2a" |
| 1954 | ) |
| 1955 | if dcp_a2a: |
| 1956 | self.dcp_combine = partial(dcp_a2a_lse_reduce, is_lse_base_on_e=False) |
| 1957 | else: |
| 1958 | self.dcp_combine = partial(cp_lse_ag_out_rs, is_lse_base_on_e=False) |
| 1959 | |
| 1960 | def fused_output_quant_supported(self, quant_key: QuantKey): |
| 1961 | if quant_key == kNvfp4Dynamic and self.is_kvcache_nvfp4: |
| 1962 | logger.warning_once( |
| 1963 | "fuse_attn_quant: not registering the NVFP4-output attention " |
| 1964 | "fusion with an NVFP4 KV cache (no such trtllm-gen kernel). " |
| 1965 | "Layers whose o_proj takes NVFP4 input keep the unfused path " |
| 1966 | "(FP8 -> BF16 -> o_proj quant); FP8-output fusion is unaffected." |
| 1967 | ) |
| 1968 | return False |
| 1969 | # XQA does not support FP8/NVFP4 output, so require trtllm-gen |
| 1970 | # (SM100+) here. Without that we cannot fuse the output quant. |
| 1971 | return ( |
| 1972 | self.supports_xqa_or_trtllm_gen_decode |
| 1973 | and is_quantized_kv_cache(self.kv_cache_dtype) |
| 1974 | and current_platform.is_device_capability_family(100) |
| 1975 | and quant_key in (kFp8StaticTensorSym, kNvfp4Dynamic) |
| 1976 | ) |
| 1977 | |
| 1978 | # FlashInfer requires attention sinks to be float32 |
| 1979 | def process_weights_after_loading(self, act_dtype: torch.dtype): |
| 1980 | source_sinks = self._sinks_source |
| 1981 | if source_sinks is None: |
| 1982 | return |
| 1983 | if source_sinks.dtype == torch.float32: |
| 1984 | self.sinks = source_sinks |
| 1985 | elif self.sinks is None or self.sinks.dtype != torch.float32: |
| 1986 | self.sinks = source_sinks.to(torch.float32) |
| 1987 | else: |
| 1988 | self.sinks.copy_(source_sinks) |
| 1989 | |
| 1990 | def get_xqa_bmm1_scale(self, layer: torch.nn.Module, q_data_type: torch.dtype): |
| 1991 | bmm1_scale = self.scale |
| 1992 | if is_quantized_kv_cache(self.kv_cache_dtype): |
| 1993 | if q_data_type in (torch.float8_e4m3fn, torch.float8_e5m2): |
| 1994 | bmm1_scale *= layer._q_scale_float |
| 1995 | bmm1_scale *= layer._k_scale_float |
| 1996 | return bmm1_scale |
| 1997 | |
| 1998 | # SM90 may need FP8-Q for native prefill and BF16/FP16-Q for XQA decode, |
| 1999 | # so quantize only the slice whose target dtype differs. |
| 2000 | def maybe_quant_query( |
| 2001 | self, |
| 2002 | query: torch.Tensor, |
| 2003 | q_data_type: torch.dtype, |
| 2004 | scale: torch.Tensor, |
| 2005 | ) -> torch.Tensor: |
| 2006 | if query.dtype != q_data_type: |
| 2007 | assert query.dtype in [torch.float16, torch.bfloat16] |
| 2008 | assert q_data_type in [torch.float8_e4m3fn, torch.float8_e5m2] |
| 2009 | assert query.dim() == 3 |
| 2010 | num_tokens = query.shape[0] |
| 2011 | num_heads = query.shape[1] |
| 2012 | head_size = query.shape[2] |
| 2013 | assert query.stride(2) == 1 and query.stride(1) == head_size |
| 2014 | query_quantized, _ = custom_ops.scaled_fp8_quant( |
| 2015 | query.view(num_tokens, num_heads * head_size), scale=scale |
| 2016 | ) |
| 2017 | return query_quantized.view(num_tokens, num_heads, head_size) |
| 2018 | |
| 2019 | return query |
| 2020 | |
| 2021 | def forward( |
| 2022 | self, |
| 2023 | layer: torch.nn.Module, |
| 2024 | query: torch.Tensor, |
| 2025 | key: torch.Tensor | None, |
| 2026 | value: torch.Tensor | None, |
| 2027 | kv_cache: torch.Tensor, |
| 2028 | attn_metadata: FlashInferMetadata, |
| 2029 | output: torch.Tensor, |
| 2030 | output_scale: torch.Tensor | None = None, |
| 2031 | output_block_scale: torch.Tensor | None = None, |
| 2032 | ) -> torch.Tensor: |
| 2033 | """Forward pass with FlashInfer. |
| 2034 | |
| 2035 | Args: |
| 2036 | layer: The attention layer, providing the q/k/v quantization scales. |
| 2037 | query: shape = [num_tokens, num_heads, head_size] |
| 2038 | key: shape = [num_tokens, num_kv_heads, head_size], or None for a |
| 2039 | KV-sharing decoder layer. |
| 2040 | value: shape = [num_tokens, num_kv_heads, head_size], or None for a |
| 2041 | KV-sharing decoder layer. |
| 2042 | kv_cache: [num_blocks, num_kv_heads, block_size, 2*head_size] |
| 2043 | attn_metadata: Metadata for attention. |
| 2044 | output: Tensor that the attention result is written into. |
| 2045 | output_scale: Scale for fused output quantization. Enables the |
| 2046 | attention+quantization fusion path when provided. |
| 2047 | output_block_scale: Block scale for fused output quantization, |
| 2048 | required for nvfp4 output and rejected for fp8 output. |
| 2049 | |
| 2050 | Returns: |
| 2051 | shape = [num_tokens, num_heads * head_size] |
| 2052 | |
| 2053 | """ |
| 2054 | if attn_metadata is None: |
| 2055 | # Profiling run. |
| 2056 | return output.fill_(0) |
| 2057 | |
| 2058 | if self.bmm1_scale is None: |
| 2059 | self.bmm1_scale = self.scale |
| 2060 | if is_quantized_kv_cache(self.kv_cache_dtype): |
| 2061 | self.bmm1_scale *= layer._q_scale_float * layer._k_scale_float |
| 2062 | |
| 2063 | if self.bmm2_scale is None: |
| 2064 | self.bmm2_scale = 1.0 |
| 2065 | if is_quantized_kv_cache(self.kv_cache_dtype): |
| 2066 | self.bmm2_scale *= layer._v_scale_float |
| 2067 | |
| 2068 | prefill_use_trtllm = isinstance(attn_metadata.prefill, TRTLLMPrefill) |
| 2069 | decode_kernel = ( |
| 2070 | attn_metadata.decode.kernel |
| 2071 | if isinstance(attn_metadata.decode, FlashInferTrtllmAPIDecode) |
| 2072 | else None |
| 2073 | ) |
| 2074 | decode_with_xqa = decode_kernel == FlashInferDecodeKernel.XQA |
| 2075 | decode_with_trtllm_gen = decode_kernel == FlashInferDecodeKernel.TRTLLM_GEN |
| 2076 | decode_with_flashinfer_trtllm_api = decode_with_xqa or decode_with_trtllm_gen |
| 2077 | |
| 2078 | # The attn+quant fusion happens when output_scale is provided. |
| 2079 | if output_scale is None: |
| 2080 | assert output_block_scale is None, ( |
| 2081 | "output_block_scale is not supported when fusion has not happened" |
| 2082 | ) |
| 2083 | else: |
| 2084 | assert attn_metadata.q_data_type_prefill == FP8_DTYPE, ( |
| 2085 | "Query must be FP8 when attn+quant fusion happened for prefill." |
| 2086 | ) |
| 2087 | assert attn_metadata.q_data_type_decode == FP8_DTYPE, ( |
| 2088 | "Query must be FP8 when attn+quant fusion happened for decode." |
| 2089 | ) |
| 2090 | assert (attn_metadata.num_prefills == 0 or prefill_use_trtllm) and ( |
| 2091 | attn_metadata.num_decodes == 0 or decode_with_trtllm_gen |
| 2092 | ), "Output quant fusion requires TRTLLM prefill/trtllm-gen decode" |
| 2093 | |
| 2094 | if output.dtype == FP8_DTYPE: |
| 2095 | assert output_block_scale is None, ( |
| 2096 | "output_block_scale should not be provided for fp8 output" |
| 2097 | ) |
| 2098 | elif output.dtype == FP4_DTYPE: |
| 2099 | assert output_block_scale is not None, ( |
| 2100 | "output_block_scale is required for nvfp4 output" |
| 2101 | ) |
| 2102 | else: |
| 2103 | raise ValueError(f"Unsupported output dtype: {output.dtype}") |
| 2104 | |
| 2105 | # TRTLLM attn kernel requires to scale to pass as a host scalar, |
| 2106 | # store the o scale as a host scalar in warmup run with cuda graph |
| 2107 | # not enabled |
| 2108 | if layer._o_scale_float is None: |
| 2109 | layer._o_scale_float = output_scale.cpu().item() |
| 2110 | if output.dtype == FP8_DTYPE: |
| 2111 | self.bmm2_scale = self.bmm2_scale / layer._o_scale_float |
| 2112 | elif output.dtype == FP4_DTYPE: |
| 2113 | self.o_sf_scale = layer._o_scale_float |
| 2114 | |
| 2115 | # IMPORTANT! |
| 2116 | # NOTE(woosuk): With piece-wise CUDA graphs, this method is executed in |
| 2117 | # eager-mode PyTorch. Thus, we need to be careful about any CPU overhead |
| 2118 | # in this method. For example, `view` and `slice` (or `[:n]`) operations |
| 2119 | # are surprisingly slow even in the case they do not invoke any GPU ops. |
| 2120 | # Minimize the PyTorch ops in this method as much as possible. |
| 2121 | # Whenever making a change in this method, please benchmark the |
| 2122 | # performance to make sure it does not introduce any overhead. |
| 2123 | |
| 2124 | num_actual_tokens = attn_metadata.num_actual_tokens |
| 2125 | kv_cache_layout = attn_metadata.kv_cache_layout |
| 2126 | |
| 2127 | # FlashInfer treats uint8 KV cache as NVFP4. vLLM stores FP8 KV cache |
| 2128 | # as uint8 bytes, so pass FP8 caches with their logical dtype. |
| 2129 | if not self.is_kvcache_nvfp4 and kv_cache.dtype == torch.uint8: |
| 2130 | fp8_view_dtype = None |
| 2131 | if self.kv_cache_dtype in ("fp8", "fp8_e4m3", torch.float8_e4m3fn): |
| 2132 | fp8_view_dtype = torch.float8_e4m3fn |
| 2133 | elif self.kv_cache_dtype in ("fp8_e5m2", torch.float8_e5m2): |
| 2134 | fp8_view_dtype = torch.float8_e5m2 |
| 2135 | if fp8_view_dtype is not None: |
| 2136 | kv_cache = kv_cache.view(fp8_view_dtype) |
| 2137 | |
| 2138 | # Inputs and outputs may be padded for CUDA graphs. Keep the original |
| 2139 | # query for single-token XQA, whose non-ragged API requires one row for |
| 2140 | # every (including padded) decode request. |
| 2141 | query_padded = query |
| 2142 | query = query[:num_actual_tokens] |
| 2143 | output_padded = output |
| 2144 | output = output[:num_actual_tokens] |
| 2145 | |
| 2146 | if attn_metadata.use_cascade: |
| 2147 | # Cascade attention (rare case). |
| 2148 | assert attn_metadata.cascade_wrapper is not None |
| 2149 | stride_order = kv_cache_layout.layer_view_order |
| 2150 | if self.is_kvcache_nvfp4: |
| 2151 | kv_cache_views = tuple( |
| 2152 | cache.permute(*stride_order) |
| 2153 | for cache in kv_cache.split(self.num_kv_heads, dim=1) |
| 2154 | ) |
| 2155 | else: |
| 2156 | kv_perm = kv_cache.permute(*stride_order) |
| 2157 | kv_cache_views = kv_perm.split(self.head_size, dim=-1) |
| 2158 | kv_tuple = tuple( |
| 2159 | canonicalize_singleton_dim_strides(cache) for cache in kv_cache_views |
| 2160 | ) |
| 2161 | output.copy_(attn_metadata.cascade_wrapper.run(query, kv_tuple)) |
| 2162 | return output |
| 2163 | |
| 2164 | # When using spec decoding, num_decodes can be < num_decode_tokens |
| 2165 | # because some decode requests may have more than one query token. |
| 2166 | num_decode_tokens = attn_metadata.num_decode_tokens |
| 2167 | num_prefill_tokens = attn_metadata.num_prefill_tokens |
| 2168 | |
| 2169 | stride_order = kv_cache_layout.layer_view_order |
| 2170 | kv_cache_permute = kv_cache.permute(*stride_order) # HND and contiguous |
| 2171 | # Fix degenerate strides on any size-1 dimension (e.g. num_kv_heads=1 |
| 2172 | # with TP=8). PyTorch permits non-canonical strides on size-1 dims; |
| 2173 | # CUDA TMA requires ≥16-byte alignment on all non-outermost strides. |
| 2174 | # canonicalize_singleton_dim_strides patches metadata via as_strided — |
| 2175 | # zero-copy. See vllm.utils.torch_utils. |
| 2176 | fixed = canonicalize_singleton_dim_strides(kv_cache_permute) |
| 2177 | if fixed is not kv_cache_permute: |
| 2178 | logger.debug( |
| 2179 | "Canonicalized degenerate KV cache strides (FlashInfer): " |
| 2180 | "shape=%s, strides before=%s, strides after=%s", |
| 2181 | kv_cache_permute.shape, |
| 2182 | kv_cache_permute.stride(), |
| 2183 | fixed.stride(), |
| 2184 | ) |
| 2185 | kv_cache_permute = fixed |
| 2186 | |
| 2187 | # Split K/V — zero-copy views. NVFP4 stores K/V as separate head |
| 2188 | # groups; other dtypes pack K/V in the content dim. |
| 2189 | hs = self.head_size |
| 2190 | nvfp4_kv_data = None |
| 2191 | nvfp4_kv_block_scales = None |
| 2192 | if self.is_kvcache_nvfp4: |
| 2193 | k_cache, v_cache = kv_cache.split(self.num_kv_heads, dim=1) |
| 2194 | kv_cache_tuple = ( |
| 2195 | canonicalize_singleton_dim_strides(k_cache.permute(*stride_order)), |
| 2196 | canonicalize_singleton_dim_strides(v_cache.permute(*stride_order)), |
| 2197 | ) |
| 2198 | k_data, k_sf = nvfp4_split_data_scale(kv_cache_tuple[0]) |
| 2199 | v_data, v_sf = nvfp4_split_data_scale(kv_cache_tuple[1]) |
| 2200 | nvfp4_kv_data = (k_data, v_data) |
| 2201 | nvfp4_kv_block_scales = (k_sf, v_sf) |
| 2202 | else: |
| 2203 | kv_cache_tuple = kv_cache_permute.split(hs, dim=-1) |
| 2204 | |
| 2205 | use_dcp = self.dcp_world_size > 1 |
| 2206 | if decode_with_xqa: |
| 2207 | assert not use_dcp |
| 2208 | assert not self.is_kvcache_nvfp4 |
| 2209 | assert self.o_sf_scale is None |
| 2210 | assert output.dtype != FP4_DTYPE |
| 2211 | |
| 2212 | # Regular attention (common case). |
| 2213 | # Decodes are at the front and prefills are at the back. |
| 2214 | if num_prefill_tokens > 0: |
| 2215 | prefill_query = query[num_decode_tokens:] |
| 2216 | assert prefill_query.shape[0] == num_prefill_tokens |
| 2217 | |
| 2218 | # Convert query to the expected dtype for prefill if needed. |
| 2219 | prefill_query = self.maybe_quant_query( |
| 2220 | prefill_query, |
| 2221 | attn_metadata.q_data_type_prefill, |
| 2222 | layer._q_scale, |
| 2223 | ) |
| 2224 | |
| 2225 | if not prefill_use_trtllm: |
| 2226 | assert isinstance(attn_metadata.prefill, FIPrefill) |
| 2227 | prefill_wrapper = attn_metadata.prefill.wrapper |
| 2228 | assert prefill_wrapper is not None |
| 2229 | if use_dcp: |
| 2230 | if key is None or value is None: |
| 2231 | raise NotImplementedError( |
| 2232 | "FlashInfer DCP prefill does not support KV-sharing layers" |
| 2233 | ) |
| 2234 | assert isinstance(prefill_wrapper, BatchDCPPrefillWrapper) |
| 2235 | assert prefill_wrapper._context._window_left == self.window_left |
| 2236 | assert prefill_wrapper._context._logits_soft_cap == ( |
| 2237 | self.logits_soft_cap or 0.0 |
| 2238 | ) |
| 2239 | assert prefill_wrapper._context._sm_scale == self.scale |
| 2240 | assert not prefill_wrapper._context._causal |
| 2241 | assert prefill_wrapper._new_tokens._window_left == self.window_left |
| 2242 | assert prefill_wrapper._new_tokens._logits_soft_cap == ( |
| 2243 | self.logits_soft_cap or 0.0 |
| 2244 | ) |
| 2245 | assert prefill_wrapper._new_tokens._sm_scale == self.scale |
| 2246 | assert prefill_wrapper._new_tokens._causal |
| 2247 | |
| 2248 | prefill_wrapper.run( |
| 2249 | layer, |
| 2250 | prefill_query, |
| 2251 | kv_cache_tuple, |
| 2252 | key[num_decode_tokens:num_actual_tokens], |
| 2253 | value[num_decode_tokens:num_actual_tokens], |
| 2254 | out=output[num_decode_tokens:], |
| 2255 | ) |
| 2256 | else: |
| 2257 | assert isinstance( |
| 2258 | prefill_wrapper, BatchPrefillWithPagedKVCacheWrapper |
| 2259 | ) |
| 2260 | assert prefill_wrapper._window_left == self.window_left |
| 2261 | assert prefill_wrapper._logits_soft_cap == ( |
| 2262 | self.logits_soft_cap or 0.0 |
| 2263 | ) |
| 2264 | assert prefill_wrapper._sm_scale == self.scale |
| 2265 | assert prefill_wrapper._causal == attn_metadata.causal |
| 2266 | |
| 2267 | if self.is_kvcache_nvfp4: |
| 2268 | kv_cache_for_fi = nvfp4_kv_data |
| 2269 | else: |
| 2270 | kv_cache_for_fi = kv_cache_tuple |
| 2271 | kv_cache_sf = ( |
| 2272 | nvfp4_kv_block_scales if self.is_kvcache_nvfp4 else None |
| 2273 | ) |
| 2274 | |
| 2275 | # NVFP4 trtllm kernel only supports FP8 output. |
| 2276 | # Use a pre-allocated FP8 buffer and dequantize |
| 2277 | # afterwards. |
| 2278 | needs_fp8_out_prefill = ( |
| 2279 | self.is_kvcache_nvfp4 and output.dtype != FP8_DTYPE |
| 2280 | ) |
| 2281 | if needs_fp8_out_prefill: |
| 2282 | out_prefill = self._nvfp4_fp8_out[:num_prefill_tokens] |
| 2283 | else: |
| 2284 | out_prefill = output[num_decode_tokens:] |
| 2285 | |
| 2286 | if isinstance( |
| 2287 | prefill_wrapper, BatchAttentionWithAttentionSinkWrapper |
| 2288 | ): |
| 2289 | assert self.sinks is not None |
| 2290 | prefill_wrapper.run( |
| 2291 | prefill_query, |
| 2292 | kv_cache_for_fi, |
| 2293 | self.sinks, |
| 2294 | self.scale * layer._q_scale_float * layer._k_scale_float, |
| 2295 | v_scale=layer._v_scale_float, |
| 2296 | out=out_prefill, |
| 2297 | ) |
| 2298 | else: |
| 2299 | prefill_wrapper.run( |
| 2300 | prefill_query, |
| 2301 | kv_cache_for_fi, |
| 2302 | q_scale=layer._q_scale_float, |
| 2303 | k_scale=layer._k_scale_float, |
| 2304 | v_scale=layer._v_scale_float, |
| 2305 | out=out_prefill, |
| 2306 | kv_cache_sf=kv_cache_sf, |
| 2307 | ) |
| 2308 | |
| 2309 | if needs_fp8_out_prefill: |
| 2310 | output[ |
| 2311 | num_decode_tokens : num_decode_tokens + num_prefill_tokens |
| 2312 | ].copy_(out_prefill) |
| 2313 | else: |
| 2314 | assert isinstance(attn_metadata.prefill, TRTLLMPrefill) |
| 2315 | # prefill_query may be non-contiguous or have degenerate strides |
| 2316 | # on size=1 dims. contiguous() ensures memory layout; then |
| 2317 | # canonicalize_singleton_dim_strides fixes any remaining |
| 2318 | # degenerate strides on size=1 dims for TMA alignment. |
| 2319 | prefill_query = prefill_query.contiguous() |
| 2320 | prefill_query = canonicalize_singleton_dim_strides(prefill_query) |
| 2321 | workspace_buffer = _get_trtllm_workspace_buffer() |
| 2322 | block_tables_prefill = attn_metadata.prefill.block_tables |
| 2323 | seq_lens_prefill = attn_metadata.prefill.seq_lens |
| 2324 | |
| 2325 | # This path needs to be enabled with VLLM_KV_CACHE_LAYOUT = HND |
| 2326 | assert get_flashinfer_layout_string(kv_cache_layout) == "HND" |
| 2327 | assert is_strictly_contiguous(prefill_query) |
| 2328 | assert is_strictly_contiguous(workspace_buffer) |
| 2329 | assert is_strictly_contiguous(block_tables_prefill) |
| 2330 | assert is_strictly_contiguous(seq_lens_prefill) |
| 2331 | |
| 2332 | if output.dtype == FP4_DTYPE: |
| 2333 | assert self.o_sf_scale is not None |
| 2334 | out = FP4Tensor( |
| 2335 | data=output[num_decode_tokens:], |
| 2336 | scale=output_block_scale, |
| 2337 | scale_start_index=num_decode_tokens, |
| 2338 | original_shape=prefill_query.shape, |
| 2339 | ) |
| 2340 | else: |
| 2341 | assert self.o_sf_scale is None |
| 2342 | out = output[num_decode_tokens:] |
| 2343 | |
| 2344 | # NVFP4 trtllm kernel only supports FP8 output. |
| 2345 | # Use a pre-allocated FP8 buffer and dequantize afterwards. |
| 2346 | needs_fp8_out = self.is_kvcache_nvfp4 and output.dtype != FP8_DTYPE |
| 2347 | if needs_fp8_out: |
| 2348 | out = self._nvfp4_fp8_out[:num_prefill_tokens] |
| 2349 | |
| 2350 | prefill_kv_block_scales = None |
| 2351 | if self.is_kvcache_nvfp4: |
| 2352 | # NVFP4 trtllm-gen kernel requires FP8 query. |
| 2353 | assert attn_metadata.q_data_type_prefill == FP8_DTYPE, ( |
| 2354 | "NVFP4 KV cache requires FP8 quantized queries for " |
| 2355 | "trtllm-gen prefill. Set " |
| 2356 | "disable_flashinfer_q_quantization=False." |
| 2357 | ) |
| 2358 | mock_kv_cache = nvfp4_kv_data |
| 2359 | mock_block_table = block_tables_prefill |
| 2360 | prefill_kv_block_scales = nvfp4_kv_block_scales |
| 2361 | elif ( |
| 2362 | attn_metadata.q_data_type_prefill != FP8_DTYPE |
| 2363 | and self.kv_cache_dtype.startswith("fp8") |
| 2364 | ): |
| 2365 | # TRTLLM prefill attention does not support BF16 Q |
| 2366 | # and fp8 kv cache. So to enable prefill attention |
| 2367 | # with fp8 kv cache, we can construct a mock block |
| 2368 | # and mock kv cache with BF16 KV involved in the prefill. |
| 2369 | kv_cache_permute = canonicalize_singleton_dim_strides( |
| 2370 | kv_cache_permute |
| 2371 | ) |
| 2372 | kv_strides = kv_cache_permute.stride() |
| 2373 | assert ( |
| 2374 | kv_strides[-1] == 1 |
| 2375 | and kv_strides[-2] == kv_cache_permute.shape[-1] |
| 2376 | ), ( |
| 2377 | "KV cache inner dims (block_size, head_size) must be " |
| 2378 | f"contiguous, got strides {kv_strides}" |
| 2379 | ) |
| 2380 | # fp8 uses (B, H, N, 2*hs); reshape to (B, 2, H, N, hs) |
| 2381 | # for the dequant kernel — zero-copy view. The dequant |
| 2382 | # kernel handles the interleaved K/V block stride. |
| 2383 | B_kv, H_kv, N_kv = kv_cache_permute.shape[:3] |
| 2384 | kv_cache_5d = kv_cache_permute.view(B_kv, H_kv, N_kv, 2, hs) |
| 2385 | kv_cache_5d = kv_cache_5d.permute(0, 3, 1, 2, 4) |
| 2386 | mock_kv_cache, mock_block_table = trtllm_prefill_attn_kvfp8_dequant( |
| 2387 | kv_cache_5d, |
| 2388 | block_tables_prefill, |
| 2389 | layer._k_scale, |
| 2390 | layer._v_scale, |
| 2391 | attn_metadata.q_data_type_prefill, |
| 2392 | ) |
| 2393 | else: |
| 2394 | mock_kv_cache = kv_cache_tuple |
| 2395 | mock_block_table = block_tables_prefill |
| 2396 | |
| 2397 | trtllm_batch_context_with_kv_cache( |
| 2398 | query=prefill_query, |
| 2399 | kv_cache=mock_kv_cache, |
| 2400 | workspace_buffer=workspace_buffer, |
| 2401 | block_tables=mock_block_table, |
| 2402 | seq_lens=seq_lens_prefill, |
| 2403 | max_q_len=attn_metadata.prefill.max_q_len, |
| 2404 | max_kv_len=attn_metadata.prefill.max_seq_len, |
| 2405 | bmm1_scale=self.bmm1_scale, |
| 2406 | bmm2_scale=self.bmm2_scale, |
| 2407 | batch_size=attn_metadata.num_prefills, |
| 2408 | cum_seq_lens_q=attn_metadata.prefill.cum_seq_lens_q, |
| 2409 | cum_seq_lens_kv=attn_metadata.prefill.cum_seq_lens_kv, |
| 2410 | window_left=self.window_left, |
| 2411 | sinks=self.sinks, |
| 2412 | o_sf_scale=self.o_sf_scale, |
| 2413 | out=out, |
| 2414 | kv_cache_sf=prefill_kv_block_scales, |
| 2415 | ) |
| 2416 | |
| 2417 | if needs_fp8_out: |
| 2418 | output[ |
| 2419 | num_decode_tokens : num_decode_tokens + num_prefill_tokens |
| 2420 | ].copy_(out[:num_prefill_tokens]) |
| 2421 | |
| 2422 | if num_decode_tokens > 0: |
| 2423 | decode_query_tokens = num_decode_tokens |
| 2424 | if decode_with_xqa: |
| 2425 | assert isinstance(attn_metadata.decode, FlashInferTrtllmAPIDecode) |
| 2426 | if attn_metadata.decode.q_len_per_req == 1: |
| 2427 | decode_query_tokens = attn_metadata.num_decodes |
| 2428 | decode_query = query_padded[:decode_query_tokens] |
| 2429 | assert decode_query.shape[0] == decode_query_tokens |
| 2430 | |
| 2431 | # Convert query to the expected dtype for decode if needed. |
| 2432 | decode_query = self.maybe_quant_query( |
| 2433 | decode_query, |
| 2434 | attn_metadata.q_data_type_decode, |
| 2435 | layer._q_scale, |
| 2436 | ) |
| 2437 | |
| 2438 | if not decode_with_flashinfer_trtllm_api: |
| 2439 | assert isinstance(attn_metadata.decode, FIDecode) |
| 2440 | decode_wrapper = attn_metadata.decode.wrapper |
| 2441 | assert decode_wrapper is not None |
| 2442 | assert decode_wrapper._window_left == self.window_left |
| 2443 | assert decode_wrapper._logits_soft_cap == (self.logits_soft_cap or 0.0) |
| 2444 | assert decode_wrapper._sm_scale == self.scale |
| 2445 | |
| 2446 | if self.is_kvcache_nvfp4: |
| 2447 | kv_cache_for_fi = nvfp4_kv_data |
| 2448 | else: |
| 2449 | kv_cache_for_fi = kv_cache_tuple |
| 2450 | kv_cache_sf = nvfp4_kv_block_scales if self.is_kvcache_nvfp4 else None |
| 2451 | |
| 2452 | # NVFP4 kernel only supports FP8 output. |
| 2453 | # Use a pre-allocated FP8 buffer and dequantize afterwards. |
| 2454 | needs_fp8_out = self.is_kvcache_nvfp4 and output.dtype != FP8_DTYPE |
| 2455 | if needs_fp8_out: |
| 2456 | out_decode = self._nvfp4_fp8_out[:num_decode_tokens] |
| 2457 | else: |
| 2458 | out_decode = output[:num_decode_tokens] |
| 2459 | |
| 2460 | if use_dcp: |
| 2461 | decode_query = get_dcp_group().all_gather( |
| 2462 | decode_query.contiguous(), dim=-2 |
| 2463 | ) |
| 2464 | output_tmp = torch.empty_like(decode_query) |
| 2465 | lse = torch.empty( |
| 2466 | (decode_query.size(0), decode_query.size(1)), |
| 2467 | dtype=torch.float32, |
| 2468 | device=decode_query.device, |
| 2469 | ) |
| 2470 | decode_wrapper.run( |
| 2471 | decode_query, |
| 2472 | kv_cache_for_fi, |
| 2473 | q_scale=layer._q_scale_float, |
| 2474 | k_scale=layer._k_scale_float, |
| 2475 | v_scale=layer._v_scale_float, |
| 2476 | out=output_tmp, |
| 2477 | lse=lse, |
| 2478 | return_lse=True, |
| 2479 | kv_cache_sf=kv_cache_sf, |
| 2480 | sinks=self.sinks, |
| 2481 | ) |
| 2482 | output[:num_decode_tokens] = self.dcp_combine( |
| 2483 | output_tmp, |
| 2484 | lse, |
| 2485 | get_dcp_group(), |
| 2486 | ) |
| 2487 | else: |
| 2488 | decode_wrapper.run( |
| 2489 | decode_query, |
| 2490 | kv_cache_for_fi, |
| 2491 | q_scale=layer._q_scale_float, |
| 2492 | k_scale=layer._k_scale_float, |
| 2493 | v_scale=layer._v_scale_float, |
| 2494 | out=out_decode, |
| 2495 | kv_cache_sf=kv_cache_sf, |
| 2496 | sinks=self.sinks, |
| 2497 | ) |
| 2498 | |
| 2499 | if needs_fp8_out: |
| 2500 | output[:num_decode_tokens].copy_(out_decode) |
| 2501 | else: |
| 2502 | assert isinstance(attn_metadata.decode, FlashInferTrtllmAPIDecode) |
| 2503 | # decode_query may be non-contiguous or have degenerate strides |
| 2504 | # on size=1 dims. contiguous() ensures memory layout; then |
| 2505 | # canonicalize_singleton_dim_strides fixes any remaining |
| 2506 | # degenerate strides on size=1 dims for TMA alignment. |
| 2507 | decode_query = decode_query.contiguous() |
| 2508 | decode_query = canonicalize_singleton_dim_strides(decode_query) |
| 2509 | workspace_buffer = _get_trtllm_workspace_buffer() |
| 2510 | block_tables_decode = attn_metadata.decode.block_tables |
| 2511 | seq_lens_decode = attn_metadata.decode.seq_lens |
| 2512 | |
| 2513 | # trtllm-gen needs HND layout on SM100. XQA is selected |
| 2514 | # separately on SM90 and does not use this SM100 layout gate. |
| 2515 | if decode_with_trtllm_gen: |
| 2516 | assert get_flashinfer_layout_string(kv_cache_layout) == "HND" |
| 2517 | else: |
| 2518 | assert decode_with_xqa |
| 2519 | assert is_strictly_contiguous(decode_query) |
| 2520 | assert is_strictly_contiguous(workspace_buffer) |
| 2521 | assert is_strictly_contiguous(block_tables_decode) |
| 2522 | assert is_strictly_contiguous(seq_lens_decode) |
| 2523 | kv_cache_permute = canonicalize_singleton_dim_strides(kv_cache_permute) |
| 2524 | kv_strides = kv_cache_permute.stride() |
| 2525 | assert ( |
| 2526 | kv_strides[-1] == 1 and kv_strides[-2] == kv_cache_permute.shape[-1] |
| 2527 | ), ( |
| 2528 | "KV cache inner dims (block_size, head_size) must be " |
| 2529 | f"contiguous, got strides {kv_strides}" |
| 2530 | ) |
| 2531 | |
| 2532 | if use_dcp: |
| 2533 | assert decode_with_trtllm_gen |
| 2534 | if output.dtype == FP4_DTYPE: |
| 2535 | raise NotImplementedError( |
| 2536 | "DCP decode with FlashInfer trtllm-gen does not support " |
| 2537 | "FP4 attention output yet." |
| 2538 | ) |
| 2539 | decode_query = get_dcp_group().all_gather( |
| 2540 | decode_query.contiguous(), dim=-2 |
| 2541 | ) |
| 2542 | decode_query = canonicalize_singleton_dim_strides(decode_query) |
| 2543 | |
| 2544 | if decode_with_xqa: |
| 2545 | bmm1_scale = self.get_xqa_bmm1_scale( |
| 2546 | layer, attn_metadata.q_data_type_decode |
| 2547 | ) |
| 2548 | q_len_per_req: int | None = attn_metadata.decode.q_len_per_req |
| 2549 | |
| 2550 | flashinfer_xqa_batch_decode_with_kv_cache( |
| 2551 | query=decode_query, |
| 2552 | kv_cache=kv_cache_tuple, |
| 2553 | workspace_buffer=workspace_buffer, |
| 2554 | block_tables=block_tables_decode, |
| 2555 | seq_lens=seq_lens_decode, |
| 2556 | max_seq_len=attn_metadata.decode.max_seq_len, |
| 2557 | bmm1_scale=bmm1_scale, |
| 2558 | bmm2_scale=self.bmm2_scale, |
| 2559 | window_left=self.window_left, |
| 2560 | out=output_padded[:decode_query_tokens], |
| 2561 | sinks=self.sinks, |
| 2562 | kv_layout=get_flashinfer_layout_string(kv_cache_layout), |
| 2563 | q_len_per_req=q_len_per_req, |
| 2564 | mask=attn_metadata.decode.mask, |
| 2565 | q_cu_seq_lens=attn_metadata.decode.q_cu_seq_lens, |
| 2566 | ) |
| 2567 | return output_padded |
| 2568 | |
| 2569 | assert decode_with_trtllm_gen |
| 2570 | if output.dtype == FP4_DTYPE: |
| 2571 | assert self.o_sf_scale is not None |
| 2572 | out = FP4Tensor( |
| 2573 | data=output[:num_decode_tokens], |
| 2574 | scale=output_block_scale, |
| 2575 | scale_start_index=0, |
| 2576 | original_shape=decode_query.shape, |
| 2577 | ) |
| 2578 | else: |
| 2579 | assert self.o_sf_scale is None |
| 2580 | out = output[:num_decode_tokens] |
| 2581 | |
| 2582 | # NVFP4 trtllm kernel only supports FP8 output. |
| 2583 | # Use a pre-allocated FP8 buffer and dequantize afterwards. |
| 2584 | needs_fp8_out = self.is_kvcache_nvfp4 and output.dtype != FP8_DTYPE |
| 2585 | if needs_fp8_out: |
| 2586 | out = self._nvfp4_fp8_out[:num_decode_tokens] |
| 2587 | |
| 2588 | q_cu_seq_lens = attn_metadata.decode.q_cu_seq_lens |
| 2589 | max_q_len = None |
| 2590 | q_len_per_req = None |
| 2591 | if q_cu_seq_lens is not None: |
| 2592 | # q_len_per_req must stay None for the kernel's varlen path. |
| 2593 | max_q_len = attn_metadata.decode.q_len_per_req |
| 2594 | elif num_decode_tokens % attn_metadata.num_decodes != 0: |
| 2595 | # This gets triggered when the dummy_run forces |
| 2596 | # attention to be initialized with q_len = 0 |
| 2597 | q_len_per_req = 1 |
| 2598 | else: |
| 2599 | q_len_per_req = num_decode_tokens // attn_metadata.num_decodes |
| 2600 | |
| 2601 | lse = None |
| 2602 | if use_dcp: |
| 2603 | out = torch.empty( |
| 2604 | decode_query.shape, |
| 2605 | dtype=output.dtype, |
| 2606 | device=decode_query.device, |
| 2607 | ) |
| 2608 | lse = torch.empty( |
| 2609 | (decode_query.size(0), decode_query.size(1)), |
| 2610 | dtype=torch.float32, |
| 2611 | device=decode_query.device, |
| 2612 | ) |
| 2613 | |
| 2614 | trtllm_batch_decode_with_kv_cache( |
| 2615 | query=decode_query, |
| 2616 | kv_cache=( |
| 2617 | nvfp4_kv_data if self.is_kvcache_nvfp4 else kv_cache_tuple |
| 2618 | ), |
| 2619 | workspace_buffer=workspace_buffer, |
| 2620 | block_tables=block_tables_decode, |
| 2621 | seq_lens=seq_lens_decode, |
| 2622 | max_seq_len=attn_metadata.decode.max_seq_len, |
| 2623 | bmm1_scale=self.bmm1_scale, |
| 2624 | bmm2_scale=self.bmm2_scale, |
| 2625 | window_left=self.window_left, |
| 2626 | sinks=self.sinks, |
| 2627 | o_sf_scale=self.o_sf_scale, |
| 2628 | out=out, |
| 2629 | kv_layout=get_flashinfer_layout_string(kv_cache_layout), |
| 2630 | backend=attn_metadata.decode.kernel.value, |
| 2631 | q_len_per_req=q_len_per_req, |
| 2632 | max_q_len=max_q_len, |
| 2633 | cum_seq_lens_q=q_cu_seq_lens, |
| 2634 | kv_cache_sf=( |
| 2635 | nvfp4_kv_block_scales if self.is_kvcache_nvfp4 else None |
| 2636 | ), |
| 2637 | lse=lse, |
| 2638 | return_lse=self.need_to_return_lse_for_decode, |
| 2639 | ) |
| 2640 | |
| 2641 | if use_dcp: |
| 2642 | assert isinstance(out, torch.Tensor) |
| 2643 | assert lse is not None |
| 2644 | output[:num_decode_tokens] = self.dcp_combine( |
| 2645 | out, |
| 2646 | lse, |
| 2647 | get_dcp_group(), |
| 2648 | seq_lens=seq_lens_decode, |
| 2649 | query_start_loc=attn_metadata.decode.dcp_query_start_loc, |
| 2650 | ) |
| 2651 | elif needs_fp8_out: |
| 2652 | output[:num_decode_tokens].copy_(out) |
| 2653 | return output_padded |
| 2654 | |
| 2655 | def do_kv_cache_update( |
| 2656 | self, |
| 2657 | layer: torch.nn.Module, |
| 2658 | key: torch.Tensor, |
| 2659 | value: torch.Tensor, |
| 2660 | kv_cache: torch.Tensor, |
| 2661 | slot_mapping: torch.Tensor, |
| 2662 | ) -> None: |
| 2663 | if self.kv_sharing_target_layer_name is None: |
| 2664 | # Reshape the input keys and values and store them in the cache. |
| 2665 | # Skip this if sharing KV cache with an earlier attention layer. |
| 2666 | # NOTE(woosuk): Here, key and value are padded while slot_mapping is |
| 2667 | # not padded. However, we don't need to do key[:num_actual_tokens] |
| 2668 | # and value[:num_actual_tokens] because the reshape_and_cache_flash |
| 2669 | # op uses the slot_mapping's shape to determine the number of |
| 2670 | # actual tokens. |
| 2671 | if self.is_kvcache_nvfp4: |
| 2672 | # (B, 2*H, N, full_dim) -> ((B, N, H, full_dim), |
| 2673 | # (B, N, H, full_dim)); |
| 2674 | # K heads first, then V heads. |
| 2675 | k_cache, v_cache = kv_cache.transpose(1, 2).split( |
| 2676 | self.num_kv_heads, dim=-2 |
| 2677 | ) |
| 2678 | else: |
| 2679 | # (B, H, N, 2*hs) -> ((B, N, H, hs), (B, N, H, hs)) |
| 2680 | k_cache, v_cache = kv_cache.transpose(1, 2).split( |
| 2681 | self.head_size, dim=-1 |
| 2682 | ) |
| 2683 | torch.ops._C_cache_ops.reshape_and_cache_flash( |
| 2684 | key, |
| 2685 | value, |
| 2686 | k_cache, |
| 2687 | v_cache, |
| 2688 | slot_mapping, |
| 2689 | self.cache_dtype, |
| 2690 | layer._k_scale, |
| 2691 | layer._v_scale, |
| 2692 | ) |
| 2693 | |
| 2694 | |
| 2695 | def fast_plan_decode( |
| 2696 | self, # decode wrapper |
| 2697 | indptr_cpu: torch.Tensor, |
| 2698 | indices: torch.Tensor, |
| 2699 | last_page_len_cpu: torch.Tensor, |
| 2700 | seq_lens_cpu: torch.Tensor, |
| 2701 | num_qo_heads: int, |
| 2702 | num_kv_heads: int, |
| 2703 | head_dim: int, |
| 2704 | page_size: int, |
| 2705 | pos_encoding_mode: str = "NONE", |
| 2706 | window_left: int = -1, |
| 2707 | logits_soft_cap: float | None = None, |
| 2708 | q_data_type: str | torch.dtype | None = "float16", |
| 2709 | kv_data_type: str | torch.dtype | None = None, |
| 2710 | o_data_type: str | torch.dtype | None = None, |
| 2711 | data_type: str | torch.dtype | None = None, |
| 2712 | sm_scale: float | None = None, |
| 2713 | rope_scale: float | None = None, |
| 2714 | rope_theta: float | None = None, |
| 2715 | non_blocking: bool = True, |
| 2716 | fixed_split_size: int = -1, |
| 2717 | disable_split_kv: bool = False, |
| 2718 | ) -> None: |
| 2719 | """A faster version of BatchDecodeWithPagedKVCacheWrapper::plan used for |
| 2720 | cudagraph capture/replay, while the no cudagraph version turns back |
| 2721 | to the original plan. |
| 2722 | using original plan after passing host-side buffers: |
| 2723 | - only host-to-device copy of indptr and last_page_len buffers |
| 2724 | Modifications for cudagraph: |
| 2725 | - only host-to-device copy of indptr and last_page_len buffers. |
| 2726 | - avoid device-to-device copy of indices buffer. |
| 2727 | |
| 2728 | Part of the code get inspiration from the original plan from FlashInfer repo |
| 2729 | and the implementation of fast_decode_plan for FlashInfer in SGlang repo. |
| 2730 | """ |
| 2731 | # Warm up with the original plan if it is first call, and always run the |
| 2732 | # original plan if we run for dynamic shape. For fixed shape (cudagraph), |
| 2733 | # this warm up is to generate the _cached_module for the decode wrapper. |
| 2734 | if not self.is_cuda_graph_enabled or getattr(self, "vllm_first_call", True): |
| 2735 | # plan() copies qo_indptr from flashinfer's internal cached host |
| 2736 | # arange buffer (_get_range_buf) with non_blocking=True; pin the |
| 2737 | # cache entry so the copy stays async. |
| 2738 | pin_host_range_buf(len(last_page_len_cpu) + 1) |
| 2739 | self.plan( |
| 2740 | indptr=indptr_cpu, |
| 2741 | indices=indices, |
| 2742 | last_page_len=last_page_len_cpu, |
| 2743 | num_qo_heads=num_qo_heads, |
| 2744 | num_kv_heads=num_kv_heads, |
| 2745 | head_dim=head_dim, |
| 2746 | page_size=page_size, |
| 2747 | pos_encoding_mode=pos_encoding_mode, |
| 2748 | window_left=window_left, |
| 2749 | logits_soft_cap=logits_soft_cap, |
| 2750 | q_data_type=q_data_type, |
| 2751 | kv_data_type=kv_data_type, |
| 2752 | o_data_type=o_data_type, |
| 2753 | data_type=data_type, |
| 2754 | sm_scale=sm_scale, |
| 2755 | rope_scale=rope_scale, |
| 2756 | rope_theta=rope_theta, |
| 2757 | non_blocking=non_blocking, |
| 2758 | block_tables=None, |
| 2759 | seq_lens=seq_lens_cpu, |
| 2760 | fixed_split_size=fixed_split_size, |
| 2761 | disable_split_kv=disable_split_kv, |
| 2762 | ) |
| 2763 | self.vllm_first_call = False |
| 2764 | return |
| 2765 | |
| 2766 | assert self.is_cuda_graph_enabled, "Should be cudagraph only here" |
| 2767 | |
| 2768 | fast_decode_plan( |
| 2769 | self, |
| 2770 | indptr=indptr_cpu, |
| 2771 | indices=indices, |
| 2772 | last_page_len=last_page_len_cpu, |
| 2773 | num_qo_heads=num_qo_heads, |
| 2774 | num_kv_heads=num_kv_heads, |
| 2775 | head_dim=head_dim, |
| 2776 | page_size=page_size, |
| 2777 | pos_encoding_mode=pos_encoding_mode, |
| 2778 | window_left=window_left, |
| 2779 | logits_soft_cap=logits_soft_cap, |
| 2780 | q_data_type=q_data_type, |
| 2781 | kv_data_type=kv_data_type, |
| 2782 | data_type=data_type, |
| 2783 | sm_scale=sm_scale, |
| 2784 | rope_scale=rope_scale, |
| 2785 | rope_theta=rope_theta, |
| 2786 | non_blocking=non_blocking, |
| 2787 | fixed_split_size=fixed_split_size, |
| 2788 | disable_split_kv=disable_split_kv, |
| 2789 | ) |
| 2790 | |
| 2791 | |
| 2792 | @triton.jit |
| 2793 | def _copy_page_indices_kernel( |
| 2794 | page_indices, |
| 2795 | block_table, |
| 2796 | block_table_stride, |
| 2797 | cu_num_blocks, |
| 2798 | BLOCK_SIZE: tl.constexpr, |
| 2799 | ): |
| 2800 | req_idx = tl.program_id(0) |
| 2801 | row_ptr = block_table + req_idx * block_table_stride |
| 2802 | start_idx = tl.load(cu_num_blocks + req_idx) |
| 2803 | end_idx = tl.load(cu_num_blocks + req_idx + 1) |
| 2804 | num_blocks = end_idx - start_idx |
| 2805 | |
| 2806 | offset = tl.arange(0, BLOCK_SIZE) |
| 2807 | for i in tl.range(0, num_blocks, BLOCK_SIZE): |
| 2808 | block_ids = tl.load(row_ptr + i + offset, mask=i + offset < num_blocks) |
| 2809 | tl.store( |
| 2810 | page_indices + start_idx + i + offset, |
| 2811 | block_ids, |
| 2812 | mask=i + offset < num_blocks, |
| 2813 | ) |