A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 11, 05:00 UTC
[Cleanup] Refactor FlashInferMetadataBuilder (#29128)
Signed-off-by: Benjamin Chislett <bchislett@nvidia.com> Signed-off-by: Lucas Wilkinson <lwilkins@redhat.com> Co-authored-by: Lucas Wilkinson <lwilkins@redhat.com>
Benjamin Chislett committed
Dec 18, 2025 at 17:45 UTC
d6b3d39b6d87c847cfc9b9a865204ef0a6fcec12
1 file changed
+403
-269
vllm/v1/attention/backends/flashinfer.py
+403
-269
@@ -16,6 +16,7 @@ from flashinfer import (
16
from flashinfer.decode import _get_range_buf, trtllm_batch_decode_with_kv_cache
17
from flashinfer.prefill import trtllm_batch_context_with_kv_cache
18
from flashinfer.utils import FP4Tensor
19
+from typing_extensions import override
20
21
from vllm import envs
22
from vllm.attention.backends.abstract import (
@@ -59,6 +60,7 @@ from vllm.v1.attention.backends.utils import (
60
split_decodes_and_prefills,
61
)
62
from vllm.v1.kv_cache_interface import AttentionSpec
63
+from vllm.v1.utils import CpuGpuBuffer
64
65
FLASHINFER_WORKSPACE_BUFFER_SIZE_BATCH_INVARIANT = 2048 * 1024 * 1024
66
@@ -181,7 +183,6 @@ class BatchDCPPrefillWrapper:
183
paged_kv_indptr_cpu: torch.Tensor,
184
paged_kv_indices: torch.Tensor,
185
paged_kv_last_page_len_cpu: torch.Tensor,
184
- prefill_start: int,
186
page_size: int,
187
num_qo_heads: int,
188
dcp_world_size: int,
@@ -200,7 +201,7 @@ class BatchDCPPrefillWrapper:
201
qo_indptr_cpu,
202
paged_kv_indptr_cpu,
203
paged_kv_indices,
203
- paged_kv_last_page_len_cpu[prefill_start:],
204
+ paged_kv_last_page_len_cpu,
205
num_qo_heads * dcp_world_size,
206
num_kv_heads,
207
head_dim,
@@ -380,40 +381,103 @@ class FlashInferBackend(AttentionBackend):
381
382
383
@dataclass
383
-class FlashInferMetadata:
384
- num_actual_tokens: int # Number of tokens excluding padding.
384
+class FIPrefill:
385
+ """Metadata for the native FlashInfer prefill pathway (non-TRTLLM)."""
386
386
- # The data type of the query
387
- q_data_type: torch.dtype
387
+ wrapper: BatchPrefillWithPagedKVCacheWrapper | BatchDCPPrefillWrapper
388
389
- slot_mapping: torch.Tensor
389
391
- # For flashinfer trtllm batch decode
390
+@dataclass
391
+class FIDecode:
392
+ """Metadata for the native FlashInfer decode pathway (non-TRTLLM)."""
393
+
394
+ wrapper: BatchDecodeWithPagedKVCacheWrapper
395
+
396
+
397
+@dataclass
398
+class TRTLLMPrefill:
399
+ """Metadata for the TRTLLM prefill pathway."""
400
+
401
+ block_tables: torch.Tensor
402
+ """
403
+ The slice of the block table tensor corresponding *only* to prefill requests.
404
+ Shape: [num_prefills, max_num_blocks_per_seq]
405
+ """
406
+
407
+ seq_lens: torch.Tensor
408
+ """
409
+ The slice of the sequence lengths tensor corresponding *only* to prefill requests.
410
+ Shape: [num_prefills]
411
+ """
412
+
413
+ cum_seq_lens_q: torch.Tensor
414
+ cum_seq_lens_kv: torch.Tensor
415
+
416
max_q_len: int
393
- max_q_len_prefill: int
417
+ """
418
+ The maximum query length *among prefill requests*.
419
+ """
420
+
421
max_seq_len: int
422
+ """The maximum sequence length for KV Cache."""
423
+
424
+
425
+@dataclass
426
+class TRTLLMDecode:
427
+ """Metadata for the TRTLLM decode pathway."""
428
+
429
+ block_tables: torch.Tensor
430
+ """
431
+ The slice of the block table tensor corresponding *only* to decode requests.
432
+ Shape: [num_decodes, max_num_blocks_per_seq]
433
+ """
434
+
435
seq_lens: torch.Tensor
396
- block_table_tensor: torch.Tensor
397
- prefill_use_trtllm: bool
398
- decode_use_trtllm: bool
436
+ """
437
+ The slice of the sequence lengths tensor corresponding *only* to decode requests.
438
+ Shape: [num_decodes]
439
+ """
440
+
441
+ max_seq_len: int
442
+ """The maximum sequence length for KV Cache."""
443
+
444
+
445
+@dataclass
446
+class FlashInferMetadata:
447
+ num_actual_tokens: int
448
+ """Total number of tokens in the batch (excluding padding)."""
449
+
450
+ slot_mapping: torch.Tensor
451
+ """Tensor for writing K/V to the cache. Shape: [num_actual_tokens]"""
452
+
453
+ q_data_type: torch.dtype
454
400
- # For handling prefill decode split
455
num_decodes: int
456
num_decode_tokens: int
457
num_prefills: int
458
num_prefill_tokens: int
459
406
- # For cascade attention (CPU for planning).
407
- use_cascade: bool
460
+ prefill: FIPrefill | TRTLLMPrefill | None
461
+ """
462
+ Holds the metadata for the prefill portion of the batch.
463
+ Will be `None` if `num_prefill_tokens == 0`.
464
+ """
465
+
466
+ decode: FIDecode | TRTLLMDecode | None
467
+ """
468
+ Holds the metadata for the decode portion of the batch.
469
+ Will be `None` if `num_decode_tokens == 0`.
470
+ """
471
409
- prefill_wrapper: (
410
- BatchPrefillWithPagedKVCacheWrapper | BatchDCPPrefillWrapper | None
411
- ) = None
412
- decode_wrapper: BatchDecodeWithPagedKVCacheWrapper | None = None
413
- cascade_wrapper: MultiLevelCascadeAttentionWrapper | None = None
472
+ # --- Special Case: Cascade Attention ---
473
415
- qo_indptr_gpu: torch.Tensor | None = None
416
- paged_kv_indptr_gpu: torch.Tensor | None = None
474
+ use_cascade: bool
475
+ """
476
+ If True, the entire batch is a cascade attention call, and the
477
+ `prefill` and `decode` fields will both be None.
478
+ """
479
+
480
+ cascade_wrapper: MultiLevelCascadeAttentionWrapper | None
481
482
483
class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
@@ -482,6 +546,7 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
546
self.dcp_world_size = 1
547
self.dcp_rank = 0
548
self.dcp_kv_cache_interleave_size = 1
549
+ self.use_dcp = self.dcp_world_size > 1
550
551
self.num_qo_heads = self.model_config.get_num_attention_heads(
552
self.vllm_config.parallel_config
@@ -535,34 +600,14 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
600
"sinks, please use trtllm on blackwell or flash attention on "
601
"earlier GPUs."
602
)
538
- # Preparing persistent buffers (device-side)
539
- self.paged_kv_indptr = torch.zeros(
540
- max_num_reqs + 1, dtype=torch.int32, device=self.device
541
- )
542
- self.paged_kv_indices = torch.zeros(
543
- max_num_pages, # max num pages possible
544
- dtype=torch.int32,
545
- device=self.device,
546
- )
547
- self.paged_kv_last_page_len = torch.zeros(
548
- max_num_reqs, dtype=torch.int32, device=self.device
549
- )
550
- # host-side buffer
551
- pin_memory = is_pin_memory_available()
552
- self.paged_kv_indptr_cpu = torch.zeros(
553
- max_num_reqs + 1, dtype=torch.int32, device="cpu", pin_memory=pin_memory
554
- )
555
- self.paged_kv_indptr_np = self.paged_kv_indptr_cpu.numpy()
556
- self.paged_kv_indptr_buffer = torch.zeros_like(
557
- self.paged_kv_indptr_cpu, pin_memory=pin_memory
558
- )
559
- self.paged_kv_indices_cpu = torch.zeros(
560
- max_num_pages, dtype=torch.int32, device="cpu", pin_memory=pin_memory
561
- )
562
- self.paged_kv_last_page_len_cpu = torch.zeros(
563
- max_num_reqs, dtype=torch.int32, device="cpu", pin_memory=pin_memory
564
- )
565
- self.paged_kv_last_page_len_np = self.paged_kv_last_page_len_cpu.numpy()
603
+ # Preparing persistent buffers
604
+ self.pin_memory = is_pin_memory_available()
605
+ self.paged_kv_indptr = self._make_buffer(max_num_reqs + 1)
606
+ self.paged_kv_indptr_cpu_buffer = torch.zeros_like(
607
+ self.paged_kv_indptr.cpu, pin_memory=self.pin_memory
608
+ ) # Extra buffer for mutable paged_kv_indptr.cpu in cuda graph mode
609
+ self.paged_kv_indices = self._make_buffer(max_num_pages)
610
+ self.paged_kv_last_page_len = self._make_buffer(max_num_reqs)
611
612
if self.head_dim == 256 and current_platform.is_device_capability_family(100):
613
# https://github.com/flashinfer-ai/flashinfer/issues/1993 reports that
@@ -573,6 +618,18 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
618
"passing --block-size 32 or --block-size 64."
619
)
620
621
+ def _make_buffer(
622
+ self, *size: int | torch.SymInt, dtype: torch.dtype = torch.int32
623
+ ) -> CpuGpuBuffer:
624
+ return CpuGpuBuffer(
625
+ *size,
626
+ dtype=dtype,
627
+ device=self.device,
628
+ pin_memory=self.pin_memory,
629
+ with_numpy=True,
630
+ )
631
+
632
+ @override # type: ignore[misc]
633
@classmethod
634
def get_cudagraph_support(
635
cls: type["FlashInferMetadataBuilder"],
@@ -607,7 +664,7 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
664
self,
665
) -> BatchPrefillWithPagedKVCacheWrapper | BatchDCPPrefillWrapper:
666
if self._prefill_wrapper is None:
610
- if self.dcp_world_size > 1:
667
+ if self.use_dcp:
668
self._prefill_wrapper = BatchDCPPrefillWrapper(
669
workspace_buffer=self._get_workspace_buffer(),
670
)
@@ -626,9 +683,9 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
683
684
if decode_wrapper is None:
685
if use_cudagraph:
629
- paged_kv_indptr = self.paged_kv_indptr[: batch_size + 1]
630
- paged_kv_indices = self.paged_kv_indices
631
- paged_kv_last_page_len = self.paged_kv_last_page_len[:batch_size]
686
+ paged_kv_indptr = self.paged_kv_indptr.gpu[: batch_size + 1]
687
+ paged_kv_indices = self.paged_kv_indices.gpu
688
+ paged_kv_last_page_len = self.paged_kv_last_page_len.gpu[:batch_size]
689
else:
690
paged_kv_indptr = None
691
paged_kv_indices = None
@@ -661,99 +718,43 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
718
)
719
return self._cascade_wrapper
720
664
- def build(
721
+ def _compute_flashinfer_kv_metadata(
722
self,
666
- common_prefix_len: int,
667
- common_attn_metadata: CommonAttentionMetadata,
668
- fast_build: bool = False,
669
- ) -> FlashInferMetadata:
670
- num_reqs = common_attn_metadata.num_reqs
671
- num_actual_tokens = common_attn_metadata.num_actual_tokens
672
- num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
673
- split_decodes_and_prefills(
674
- common_attn_metadata,
675
- decode_threshold=self.reorder_batch_threshold,
676
- require_uniform=True,
677
- )
678
- )
679
-
680
- page_size = self.page_size
681
- max_q_len = common_attn_metadata.max_query_len
682
- max_seq_len = common_attn_metadata.max_seq_len
683
- seq_lens = common_attn_metadata.seq_lens
684
- seq_lens_cpu = common_attn_metadata.seq_lens_cpu
685
- block_table_tensor = common_attn_metadata.block_table_tensor
686
- qo_indptr_cpu = common_attn_metadata.query_start_loc_cpu
687
-
688
- if self.dcp_world_size > 1:
689
- if num_prefills > 0:
690
- qo_indptr_prefill_cpu = (
691
- qo_indptr_cpu[num_decodes:] - qo_indptr_cpu[num_decodes]
692
- )
693
- query_lens_prefill_cpu = (
694
- qo_indptr_prefill_cpu[1:] - qo_indptr_prefill_cpu[:-1]
695
- )
696
- seq_lens_cpu[num_decodes:] = (
697
- seq_lens_cpu[num_decodes:] - query_lens_prefill_cpu
698
- )
699
-
700
- seq_lens_cpu = get_dcp_local_seq_lens(
701
- seq_lens_cpu,
702
- self.dcp_world_size,
703
- self.dcp_rank,
704
- self.dcp_kv_cache_interleave_size,
705
- )
706
-
707
- seq_lens_np = seq_lens_cpu.numpy()
708
- num_blocks_np = (seq_lens_np + (page_size - 1)) // page_size
709
-
710
- use_cascade = common_prefix_len > 0
711
- if use_cascade:
712
- # Grab the blocks of the shared prefix from the first request.
713
- assert common_prefix_len % page_size == 0
714
- num_common_kv_blocks = common_prefix_len // page_size
715
-
716
- # Create CPU versions directly for cascade (no GPU versions needed)
717
- shared_qo_indptr_cpu = torch.tensor(
718
- [0, num_actual_tokens], dtype=torch.int32, device="cpu"
719
- )
720
- shared_kv_page_indptr_cpu = torch.tensor(
721
- [0, num_common_kv_blocks], dtype=torch.int32, device="cpu"
722
- )
723
- shared_kv_page_indices_cpu = block_table_tensor[0, :num_common_kv_blocks]
724
- shared_kv_last_page_len_cpu = torch.tensor(
725
- [page_size], dtype=torch.int32, device="cpu"
726
- )
723
+ num_blocks_np: np.ndarray,
724
+ seq_lens_np: np.ndarray,
725
+ block_table_tensor: torch.Tensor,
726
+ num_reqs: int,
727
+ page_size: int,
728
+ ) -> torch.Tensor:
729
+ """
730
+ Compute paged_kv_indptr, paged_kv_indices, paged_kv_last_page_len for FlashInfer
731
+ attention.
732
728
- # Remove the blocks of the shared prefix from all requests.
729
- block_table_tensor = block_table_tensor[:, num_common_kv_blocks:]
730
- num_blocks_np -= num_common_kv_blocks
731
- else:
732
- shared_qo_indptr_cpu = None
733
- shared_kv_page_indptr_cpu = None
734
- shared_kv_page_indices_cpu = None
735
- shared_kv_last_page_len_cpu = None
733
+ Results are stored in self.paged_kv_indptr,
734
+ self.paged_kv_indices, self.paged_kv_last_page_len buffers.
735
736
+ Returns paged_kv_indices, a GPU tensor with shape [num_actual_pages].
737
+ """
738
# write self.paged_kv_indptr_cpu inplace (0-index is always 0)
739
np.cumsum(
740
num_blocks_np,
741
dtype=np.int32,
741
- out=self.paged_kv_indptr_np[1 : num_reqs + 1],
742
+ out=self.paged_kv_indptr.np[1 : num_reqs + 1],
743
)
744
# NOTE(woosuk): Because self.paged_kv_indptr_cpu can be modified
745
# after this line (e.g., for cuda graphs), we need to copy the data to
746
# self.paged_kv_indptr_buffer to avoid race condition.
746
- self.paged_kv_indptr_buffer[: num_reqs + 1] = self.paged_kv_indptr_cpu[
747
+ self.paged_kv_indptr_cpu_buffer[: num_reqs + 1] = self.paged_kv_indptr.cpu[
748
: num_reqs + 1
749
]
749
- paged_kv_indptr = self.paged_kv_indptr[: num_reqs + 1]
750
+ paged_kv_indptr = self.paged_kv_indptr.gpu[: num_reqs + 1]
751
paged_kv_indptr.copy_(
751
- self.paged_kv_indptr_buffer[: num_reqs + 1], non_blocking=True
752
+ self.paged_kv_indptr_cpu_buffer[: num_reqs + 1], non_blocking=True
753
)
754
755
# write self.paged_kv_indices inplace
755
- num_actual_pages = self.paged_kv_indptr_np[num_reqs]
756
- paged_kv_indices = self.paged_kv_indices[:num_actual_pages]
756
+ num_actual_pages = self.paged_kv_indptr.np[num_reqs]
757
+ paged_kv_indices = self.paged_kv_indices.gpu[:num_actual_pages]
758
_copy_page_indices_kernel[(num_reqs,)](
759
paged_kv_indices,
760
block_table_tensor,
@@ -764,12 +765,41 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
765
766
# write self.paged_kv_last_page_len_cpu inplace
767
paged_kv_last_page_len_np = seq_lens_np % page_size
767
- self.paged_kv_last_page_len_np[:num_reqs] = np.where(
768
+ self.paged_kv_last_page_len.np[:num_reqs] = np.where(
769
(paged_kv_last_page_len_np == 0) & (seq_lens_np != 0),
770
page_size,
771
paged_kv_last_page_len_np,
772
)
773
+ return paged_kv_indices
774
775
+ def build(
776
+ self,
777
+ common_prefix_len: int,
778
+ common_attn_metadata: CommonAttentionMetadata,
779
+ fast_build: bool = False,
780
+ ) -> FlashInferMetadata:
781
+ num_reqs = common_attn_metadata.num_reqs
782
+ num_actual_tokens = common_attn_metadata.num_actual_tokens
783
+ num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
784
+ split_decodes_and_prefills(
785
+ common_attn_metadata,
786
+ decode_threshold=self.reorder_batch_threshold,
787
+ require_uniform=True,
788
+ )
789
+ )
790
+
791
+ page_size = self.page_size
792
+ max_seq_len = common_attn_metadata.max_seq_len
793
+ seq_lens = common_attn_metadata.seq_lens
794
+ block_table_tensor = common_attn_metadata.block_table_tensor
795
+ qo_indptr = common_attn_metadata.query_start_loc
796
+ qo_indptr_cpu = common_attn_metadata.query_start_loc_cpu
797
+
798
+ # Step 1: Decide which dispatch modes to use:
799
+ # - Cascade attention (distinct mode)
800
+ # - Prefill (FI native or TRTLLM)
801
+ # - Decode (FI native or TRTLLM)
802
+ use_cascade = common_prefix_len > 0
803
uses_spec_reorder = self.reorder_batch_threshold > 1
804
prefill_use_trtllm = use_trtllm_attention(
805
self.num_qo_heads,
@@ -788,7 +818,14 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
818
self.use_trtllm_decode_attention and self.dcp_world_size <= 1
819
)
820
791
- if not (prefill_use_trtllm and decode_use_trtllm):
821
+ all_uses_trtllm = (num_prefills == 0 or prefill_use_trtllm) and (
822
+ num_decodes == 0 or decode_use_trtllm
823
+ )
824
+ is_only_trtllm_decode = num_prefills == 0 and (
825
+ num_decodes > 0 and decode_use_trtllm
826
+ )
827
+
828
+ if not all_uses_trtllm:
829
if self.has_sinks:
830
raise NotImplementedError(
831
"FlashInfer backend currently does not support attention "
@@ -813,28 +850,102 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
850
# fall back to model dtype.
851
self.q_data_type = self.model_config.dtype
852
853
+ # Step 2: Initialize the output metadata
854
+ # Leave prefill/decode/cascade_wrapper empty, to be populated
855
+ # case by case depending on the batch contents and backend selection.
856
attn_metadata = FlashInferMetadata(
857
num_actual_tokens=num_actual_tokens,
818
- q_data_type=self.q_data_type,
858
slot_mapping=common_attn_metadata.slot_mapping,
820
- max_q_len=max_q_len,
821
- max_q_len_prefill=max_q_len,
822
- max_seq_len=max_seq_len,
823
- seq_lens=seq_lens,
824
- block_table_tensor=block_table_tensor,
825
- prefill_use_trtllm=prefill_use_trtllm,
826
- decode_use_trtllm=decode_use_trtllm,
859
+ q_data_type=self.q_data_type,
860
num_decodes=num_decodes,
861
num_decode_tokens=num_decode_tokens,
862
num_prefills=num_prefills,
863
num_prefill_tokens=num_prefill_tokens,
864
use_cascade=use_cascade,
865
+ prefill=None,
866
+ decode=None,
867
+ cascade_wrapper=None,
868
)
869
834
- paged_kv_indptr_cpu = self.paged_kv_indptr_cpu[: 1 + num_reqs]
835
- paged_kv_last_page_len_cpu = self.paged_kv_last_page_len_cpu[:num_reqs]
870
+ # Guard access to seq_lens_cpu, which may not always be needed
871
+ # and can be expensive to retrieve in async mode.
872
+ needs_seq_lens_cpu = self.use_dcp or use_cascade or not is_only_trtllm_decode
873
+ seq_lens_cpu = common_attn_metadata.seq_lens_cpu if needs_seq_lens_cpu else None
874
+ seq_lens_np = seq_lens_cpu.numpy() if seq_lens_cpu is not None else None
875
+ num_blocks_np = (
876
+ (seq_lens_np + (page_size - 1)) // page_size
877
+ if seq_lens_np is not None
878
+ else None
879
+ )
880
+
881
+ # Adjust seq_lens_cpu for DCP
882
+ if self.use_dcp:
883
+ assert seq_lens_cpu is not None
884
+ if num_prefills > 0:
885
+ qo_indptr_prefill_cpu = (
886
+ qo_indptr_cpu[num_decodes:] - qo_indptr_cpu[num_decodes]
887
+ )
888
+ query_lens_prefill_cpu = (
889
+ qo_indptr_prefill_cpu[1:] - qo_indptr_prefill_cpu[:-1]
890
+ )
891
+ seq_lens_cpu[num_decodes:] = (
892
+ seq_lens_cpu[num_decodes:] - query_lens_prefill_cpu
893
+ )
894
+
895
+ seq_lens_cpu = get_dcp_local_seq_lens(
896
+ seq_lens_cpu,
897
+ self.dcp_world_size,
898
+ self.dcp_rank,
899
+ self.dcp_kv_cache_interleave_size,
900
+ )
901
+
902
+ # Adjust num_block_np for cascade attention
903
+ if use_cascade:
904
+ assert num_blocks_np is not None
905
+ assert common_prefix_len % page_size == 0
906
+ num_common_kv_blocks = common_prefix_len // page_size
907
+ num_blocks_np -= num_common_kv_blocks
908
+
909
+ # Compute paged_kv_indices if necessary
910
+ needs_paged_kv_indices = use_cascade or not is_only_trtllm_decode
911
+ if needs_paged_kv_indices:
912
+ assert num_blocks_np is not None
913
+ assert seq_lens_np is not None
914
+ paged_kv_indices = self._compute_flashinfer_kv_metadata(
915
+ num_blocks_np,
916
+ seq_lens_np,
917
+ block_table_tensor,
918
+ num_reqs,
919
+ page_size,
920
+ )
921
+ else:
922
+ paged_kv_indices = None
923
+
924
+ # Early-out for cascade attention
925
+ if use_cascade:
926
+ # Grab the blocks of the shared prefix from the first request.
927
+ num_common_kv_blocks = common_prefix_len // page_size
928
+
929
+ # Create CPU versions directly for cascade (no GPU versions needed)
930
+ shared_qo_indptr_cpu = torch.tensor(
931
+ [0, num_actual_tokens], dtype=torch.int32, device="cpu"
932
+ )
933
+ shared_kv_page_indptr_cpu = torch.tensor(
934
+ [0, num_common_kv_blocks], dtype=torch.int32, device="cpu"
935
+ )
936
+ shared_kv_page_indices_cpu = block_table_tensor[0, :num_common_kv_blocks]
937
+ shared_kv_last_page_len_cpu = torch.tensor(
938
+ [page_size], dtype=torch.int32, device="cpu"
939
+ )
940
+
941
+ # Remove the blocks of the shared prefix from all requests.
942
+ block_table_tensor = block_table_tensor[:, num_common_kv_blocks:]
943
+ num_blocks_np -= num_common_kv_blocks
944
+
945
+ assert paged_kv_indices is not None
946
+ paged_kv_indptr_cpu = self.paged_kv_indptr.cpu[: 1 + num_reqs]
947
+ paged_kv_last_page_len_cpu = self.paged_kv_last_page_len.cpu[:num_reqs]
948
837
- if attn_metadata.use_cascade:
949
attn_metadata.cascade_wrapper = self._get_cascade_wrapper()
950
attn_metadata.cascade_wrapper.plan(
951
[shared_qo_indptr_cpu, qo_indptr_cpu],
@@ -852,91 +963,107 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
963
q_data_type=self.q_data_type,
964
kv_data_type=self.kv_cache_dtype,
965
)
855
- else:
856
- # Regular attention (common case).
857
- # Decodes are at the front and prefills are at the back.
858
- num_prefills = attn_metadata.num_prefills
859
- num_decodes = attn_metadata.num_decodes
860
- if num_prefills > 0:
861
- # Decodes are first so prefills start after the last decode
862
- prefill_start = num_decodes
863
- attn_metadata.prefill_wrapper = self._get_prefill_wrapper()
864
- assert qo_indptr_cpu[prefill_start:].shape[0] == num_prefills + 1
865
- assert paged_kv_indptr_cpu[prefill_start:].shape[0] == num_prefills + 1
866
- assert (
867
- paged_kv_last_page_len_cpu[prefill_start:].shape[0] == num_prefills
966
+ return attn_metadata
967
+
968
+ # Step 3: Handle prefill and decode pathways case by case
969
+ ## PREFILL PATHWAY
970
+ if num_prefills > 0:
971
+ # Slices for shared prefill metadata
972
+ prefill_start = num_decodes
973
+ qo_indptr_prefill_cpu = (
974
+ qo_indptr_cpu[prefill_start:] - qo_indptr_cpu[prefill_start]
975
+ )
976
+ assert qo_indptr_prefill_cpu.shape[0] == num_prefills + 1
977
+
978
+ if prefill_use_trtllm:
979
+ # Create GPU versions
980
+ qo_indptr_prefill_gpu = (
981
+ qo_indptr[prefill_start:] - qo_indptr[prefill_start]
982
+ )
983
+ paged_kv_indptr_prefill_gpu = self.paged_kv_indptr.gpu[
984
+ prefill_start : num_reqs + 1
985
+ ]
986
+ # Compute max_q_len for prefill requests
987
+ query_lens_prefill_cpu = (
988
+ qo_indptr_prefill_cpu[1:] - qo_indptr_prefill_cpu[:-1]
989
)
869
- # Since prefill_wrapper.run() will be called with
870
- # query[num_decode_tokens:] we need to adjust the qo_indptr
871
- # to be relative to the start of the prefill queries.
872
- qo_indptr_cpu = (
873
- qo_indptr_cpu[prefill_start:] - qo_indptr_cpu[prefill_start]
990
+ max_q_len_prefill = int(query_lens_prefill_cpu.max().item())
991
+ attn_metadata.prefill = TRTLLMPrefill(
992
+ block_tables=block_table_tensor[prefill_start:],
993
+ seq_lens=seq_lens[prefill_start:],
994
+ cum_seq_lens_q=qo_indptr_prefill_gpu,
995
+ cum_seq_lens_kv=paged_kv_indptr_prefill_gpu,
996
+ max_q_len=max_q_len_prefill,
997
+ max_seq_len=max_seq_len,
998
)
875
- paged_kv_indptr_cpu = paged_kv_indptr_cpu[prefill_start:]
876
-
877
- # Recompute max_q_len for the slice of requests we are using
878
- # for prefills. This can be different from max_q_len when
879
- # we have a non-uniform batch with some short decodes offloaded
880
- # to the prefill pathway
881
- query_lens_prefill = qo_indptr_cpu[1:] - qo_indptr_cpu[:-1]
882
- attn_metadata.max_q_len_prefill = int(query_lens_prefill.max().item())
883
-
884
- if not attn_metadata.prefill_use_trtllm:
885
- if self.dcp_world_size > 1:
886
- assert isinstance(
887
- attn_metadata.prefill_wrapper, BatchDCPPrefillWrapper
888
- )
889
- attn_metadata.prefill_wrapper.plan(
890
- qo_indptr_cpu=qo_indptr_cpu,
891
- paged_kv_indptr_cpu=paged_kv_indptr_cpu,
892
- paged_kv_indices=paged_kv_indices,
893
- paged_kv_last_page_len_cpu=paged_kv_last_page_len_cpu,
894
- prefill_start=prefill_start,
895
- page_size=self.page_size,
896
- num_qo_heads=self.num_qo_heads,
897
- dcp_world_size=self.dcp_world_size,
898
- num_kv_heads=self.num_kv_heads,
899
- head_dim=self.head_dim,
900
- sm_scale=self.sm_scale,
901
- window_left=self.window_left,
902
- logits_soft_cap=self.logits_soft_cap,
903
- q_data_type=self.q_data_type,
904
- kv_cache_dtype=self.kv_cache_dtype,
905
- prefill_fixed_split_size=self.prefill_fixed_split_size,
906
- disable_split_kv=self.disable_split_kv,
907
- )
908
- else:
909
- assert isinstance(
910
- attn_metadata.prefill_wrapper,
911
- BatchPrefillWithPagedKVCacheWrapper,
912
- )
913
- attn_metadata.prefill_wrapper.plan(
914
- qo_indptr_cpu,
915
- paged_kv_indptr_cpu,
916
- paged_kv_indices,
917
- paged_kv_last_page_len_cpu[prefill_start:],
918
- self.num_qo_heads,
919
- self.num_kv_heads,
920
- self.head_dim,
921
- self.page_size,
922
- causal=True,
923
- sm_scale=self.sm_scale,
924
- window_left=self.window_left,
925
- logits_soft_cap=self.logits_soft_cap,
926
- q_data_type=self.q_data_type,
927
- kv_data_type=self.kv_cache_dtype,
928
- fixed_split_size=self.prefill_fixed_split_size,
929
- disable_split_kv=self.disable_split_kv,
930
- )
999
+ else:
1000
+ prefill_wrapper = self._get_prefill_wrapper()
1001
+ # Slicing CPU buffers that are only needed for FI native prefills
1002
+ paged_kv_last_page_len_prefill_cpu = self.paged_kv_last_page_len.cpu[
1003
+ prefill_start:num_reqs
1004
+ ]
1005
+ assert paged_kv_last_page_len_prefill_cpu.shape[0] == num_prefills
1006
+ paged_kv_indptr_prefill_cpu = self.paged_kv_indptr.cpu[
1007
+ prefill_start : num_reqs + 1
1008
+ ]
1009
+ assert paged_kv_indptr_prefill_cpu.shape[0] == num_prefills + 1
1010
+ if self.use_dcp:
1011
+ assert isinstance(prefill_wrapper, BatchDCPPrefillWrapper)
1012
+ prefill_wrapper.plan(
1013
+ qo_indptr_cpu=qo_indptr_prefill_cpu,
1014
+ paged_kv_indptr_cpu=paged_kv_indptr_prefill_cpu,
1015
+ paged_kv_indices=paged_kv_indices,
1016
+ paged_kv_last_page_len_cpu=paged_kv_last_page_len_prefill_cpu,
1017
+ page_size=self.page_size,
1018
+ num_qo_heads=self.num_qo_heads,
1019
+ dcp_world_size=self.dcp_world_size,
1020
+ num_kv_heads=self.num_kv_heads,
1021
+ head_dim=self.head_dim,
1022
+ sm_scale=self.sm_scale,
1023
+ window_left=self.window_left,
1024
+ logits_soft_cap=self.logits_soft_cap,
1025
+ q_data_type=self.q_data_type,
1026
+ kv_cache_dtype=self.kv_cache_dtype,
1027
+ prefill_fixed_split_size=self.prefill_fixed_split_size,
1028
+ disable_split_kv=self.disable_split_kv,
1029
+ )
1030
else:
932
- attn_metadata.qo_indptr_gpu = qo_indptr_cpu.to(
933
- self.device, non_blocking=True
1031
+ assert isinstance(
1032
+ prefill_wrapper,
1033
+ BatchPrefillWithPagedKVCacheWrapper,
1034
)
935
- attn_metadata.paged_kv_indptr_gpu = paged_kv_indptr_cpu.to(
936
- self.device, non_blocking=True
1035
+ prefill_wrapper.plan(
1036
+ qo_indptr_prefill_cpu,
1037
+ paged_kv_indptr_prefill_cpu,
1038
+ paged_kv_indices,
1039
+ paged_kv_last_page_len_prefill_cpu,
1040
+ self.num_qo_heads,
1041
+ self.num_kv_heads,
1042
+ self.head_dim,
1043
+ self.page_size,
1044
+ causal=True,
1045
+ sm_scale=self.sm_scale,
1046
+ window_left=self.window_left,
1047
+ logits_soft_cap=self.logits_soft_cap,
1048
+ q_data_type=self.q_data_type,
1049
+ kv_data_type=self.kv_cache_dtype,
1050
+ fixed_split_size=self.prefill_fixed_split_size,
1051
+ disable_split_kv=self.disable_split_kv,
1052
)
1053
+ attn_metadata.prefill = FIPrefill(wrapper=prefill_wrapper)
1054
939
- if num_decodes > 0:
1055
+ ## DECODE PATHWAY
1056
+ if num_decodes > 0:
1057
+ if decode_use_trtllm:
1058
+ assert num_decode_tokens % num_decodes == 0, (
1059
+ "TRTLLM decode requires uniform query lengths per request."
1060
+ )
1061
+ attn_metadata.decode = TRTLLMDecode(
1062
+ block_tables=block_table_tensor[:num_decodes],
1063
+ seq_lens=seq_lens[:num_decodes],
1064
+ max_seq_len=max_seq_len,
1065
+ )
1066
+ else:
1067
pure_decode = num_prefills == 0
1068
use_cudagraph = (
1069
self.enable_cuda_graph
@@ -945,33 +1072,33 @@ class FlashInferMetadataBuilder(AttentionMetadataBuilder[FlashInferMetadata]):
1072
)
1073
num_input_tokens = num_decode_tokens
1074
948
- attn_metadata.decode_wrapper = self._get_decode_wrapper(
1075
+ decode_wrapper = self._get_decode_wrapper(
1076
num_input_tokens, use_cudagraph
1077
)
951
- if not attn_metadata.decode_use_trtllm:
952
- # Use the persistent buffer with padding length,
953
- # instead of the same address but chunked version
954
- # in atten_metadata when using cudagraph.
955
- fast_plan_decode(
956
- attn_metadata.decode_wrapper,
957
- self.paged_kv_indptr_cpu[: num_input_tokens + 1],
958
- paged_kv_indices,
959
- self.paged_kv_last_page_len_cpu[:num_input_tokens],
960
- seq_lens_cpu[:num_input_tokens],
961
- self.num_qo_heads * self.dcp_world_size,
962
- self.num_kv_heads,
963
- self.head_dim,
964
- self.page_size,
965
- # Disable flashinfer's pos encoding and use vllm's rope.
966
- pos_encoding_mode="NONE",
967
- sm_scale=self.sm_scale,
968
- window_left=self.window_left,
969
- logits_soft_cap=self.logits_soft_cap,
970
- q_data_type=self.q_data_type,
971
- kv_data_type=self.kv_cache_dtype,
972
- fixed_split_size=self.decode_fixed_split_size,
973
- disable_split_kv=self.disable_split_kv,
974
- )
1078
+ # Use the persistent buffer with padding length,
1079
+ # instead of the same address but chunked version
1080
+ # in atten_metadata when using cudagraph.
1081
+ fast_plan_decode(
1082
+ decode_wrapper,
1083
+ self.paged_kv_indptr.cpu[: num_input_tokens + 1],
1084
+ paged_kv_indices,
1085
+ self.paged_kv_last_page_len.cpu[:num_input_tokens],
1086
+ seq_lens_cpu[:num_input_tokens],
1087
+ self.num_qo_heads * self.dcp_world_size,
1088
+ self.num_kv_heads,
1089
+ self.head_dim,
1090
+ self.page_size,
1091
+ # Disable flashinfer's pos encoding and use vllm's rope.
1092
+ pos_encoding_mode="NONE",
1093
+ sm_scale=self.sm_scale,
1094
+ window_left=self.window_left,
1095
+ logits_soft_cap=self.logits_soft_cap,
1096
+ q_data_type=self.q_data_type,
1097
+ kv_data_type=self.kv_cache_dtype,
1098
+ fixed_split_size=self.decode_fixed_split_size,
1099
+ disable_split_kv=self.disable_split_kv,
1100
+ )
1101
+ attn_metadata.decode = FIDecode(wrapper=decode_wrapper)
1102
return attn_metadata
1103
1104
def use_cascade_attention(self, *args, **kwargs) -> bool:
@@ -1104,6 +1231,9 @@ class FlashInferImpl(AttentionImpl):
1231
if self.bmm2_scale is None:
1232
self.bmm2_scale = layer._v_scale_float
1233
1234
+ prefill_use_trtllm = isinstance(attn_metadata.prefill, TRTLLMPrefill)
1235
+ decode_use_trtllm = isinstance(attn_metadata.decode, TRTLLMDecode)
1236
+
1237
# The attn+quant fusion happens when output_scale is provided.
1238
if output_scale is None:
1239
assert output_block_scale is None, (
@@ -1113,8 +1243,8 @@ class FlashInferImpl(AttentionImpl):
1243
assert attn_metadata.q_data_type == FP8_DTYPE, (
1244
"Query must be FP8 when attn+quant fusion happened."
1245
)
1116
- assert (
1117
- attn_metadata.prefill_use_trtllm and attn_metadata.decode_use_trtllm
1246
+ assert (attn_metadata.num_prefills == 0 or prefill_use_trtllm) and (
1247
+ attn_metadata.num_decodes == 0 or decode_use_trtllm
1248
), "Must use TRT-LLM attn"
1249
1250
if output.dtype == FP8_DTYPE:
@@ -1191,22 +1321,25 @@ class FlashInferImpl(AttentionImpl):
1321
1322
# When using spec decoding, num_decodes can be < num_decode_tokens
1323
# because some decode requests may have more than one query token.
1194
- num_decodes = attn_metadata.num_decodes
1324
num_decode_tokens = attn_metadata.num_decode_tokens
1325
num_prefill_tokens = attn_metadata.num_prefill_tokens
1326
1327
stride_order = FlashInferBackend.get_kv_cache_stride_order()
1328
kv_cache_permute = kv_cache.permute(*stride_order)
1329
+
1330
+ use_dcp = self.dcp_world_size > 1
1331
+
1332
# Regular attention (common case).
1333
# Decodes are at the front and prefills are at the back.
1334
if num_prefill_tokens > 0:
1203
- prefill_wrapper = attn_metadata.prefill_wrapper
1335
prefill_query = query[num_decode_tokens:]
1336
assert prefill_query.shape[0] == num_prefill_tokens
1206
- assert prefill_wrapper is not None
1337
1208
- if not attn_metadata.prefill_use_trtllm:
1209
- if self.dcp_world_size > 1:
1338
+ if not prefill_use_trtllm:
1339
+ assert isinstance(attn_metadata.prefill, FIPrefill)
1340
+ prefill_wrapper = attn_metadata.prefill.wrapper
1341
+ assert prefill_wrapper is not None
1342
+ if use_dcp:
1343
assert isinstance(prefill_wrapper, BatchDCPPrefillWrapper)
1344
assert prefill_wrapper._context._window_left == self.window_left
1345
assert prefill_wrapper._context._logits_soft_cap == (
@@ -1247,11 +1380,12 @@ class FlashInferImpl(AttentionImpl):
1380
out=output[num_decode_tokens:],
1381
)
1382
else:
1383
+ assert isinstance(attn_metadata.prefill, TRTLLMPrefill)
1384
# prefill_query may be non-contiguous
1385
prefill_query = prefill_query.contiguous()
1386
workspace_buffer = _get_trtllm_gen_workspace_buffer()
1253
- block_tables_prefill = attn_metadata.block_table_tensor[num_decodes:]
1254
- seq_lens_prefill = attn_metadata.seq_lens[num_decodes:]
1387
+ block_tables_prefill = attn_metadata.prefill.block_tables
1388
+ seq_lens_prefill = attn_metadata.prefill.seq_lens
1389
1390
# This path needs to be enabled with VLLM_KV_CACHE_LAYOUT = HND
1391
assert get_kv_cache_layout() == "HND"
@@ -1298,13 +1432,13 @@ class FlashInferImpl(AttentionImpl):
1432
workspace_buffer=workspace_buffer,
1433
block_tables=mock_block_table,
1434
seq_lens=seq_lens_prefill,
1301
- max_q_len=attn_metadata.max_q_len_prefill,
1302
- max_kv_len=attn_metadata.max_seq_len,
1435
+ max_q_len=attn_metadata.prefill.max_q_len,
1436
+ max_kv_len=attn_metadata.prefill.max_seq_len,
1437
bmm1_scale=self.bmm1_scale,
1438
bmm2_scale=self.bmm2_scale,
1439
batch_size=attn_metadata.num_prefills,
1306
- cum_seq_lens_q=attn_metadata.qo_indptr_gpu,
1307
- cum_seq_lens_kv=attn_metadata.paged_kv_indptr_gpu,
1440
+ cum_seq_lens_q=attn_metadata.prefill.cum_seq_lens_q,
1441
+ cum_seq_lens_kv=attn_metadata.prefill.cum_seq_lens_kv,
1442
window_left=self.window_left,
1443
sinks=self.sinks,
1444
o_sf_scale=self.o_sf_scale,
@@ -1312,17 +1446,18 @@ class FlashInferImpl(AttentionImpl):
1446
)
1447
1448
if num_decode_tokens > 0:
1315
- decode_wrapper = attn_metadata.decode_wrapper
1449
decode_query = query[:num_decode_tokens]
1450
assert decode_query.shape[0] == num_decode_tokens
1318
- assert decode_wrapper is not None
1451
1320
- if not attn_metadata.decode_use_trtllm:
1452
+ if not decode_use_trtllm:
1453
+ assert isinstance(attn_metadata.decode, FIDecode)
1454
+ decode_wrapper = attn_metadata.decode.wrapper
1455
+ assert decode_wrapper is not None
1456
assert decode_wrapper._window_left == self.window_left
1457
assert decode_wrapper._logits_soft_cap == (self.logits_soft_cap or 0.0)
1458
assert decode_wrapper._sm_scale == self.scale
1459
1325
- if self.dcp_world_size > 1:
1460
+ if use_dcp:
1461
decode_query = get_dcp_group().all_gather(
1462
decode_query.contiguous(), dim=-2
1463
)
@@ -1357,12 +1492,11 @@ class FlashInferImpl(AttentionImpl):
1492
)
1493
else:
1494
# decode_query may be non-contiguous
1495
+ assert isinstance(attn_metadata.decode, TRTLLMDecode)
1496
decode_query = decode_query.contiguous()
1497
workspace_buffer = _get_trtllm_gen_workspace_buffer()
1362
- block_tables_decode = attn_metadata.block_table_tensor[
1363
- :num_decode_tokens
1364
- ]
1365
- seq_lens_decode = attn_metadata.seq_lens[:num_decode_tokens]
1498
+ block_tables_decode = attn_metadata.decode.block_tables
1499
+ seq_lens_decode = attn_metadata.decode.seq_lens
1500
1501
# This path needs to be enabled with VLLM_KV_CACHE_LAYOUT = HND
1502
assert get_kv_cache_layout() == "HND"
@@ -1397,7 +1531,7 @@ class FlashInferImpl(AttentionImpl):
1531
workspace_buffer=workspace_buffer,
1532
block_tables=block_tables_decode,
1533
seq_lens=seq_lens_decode,
1400
- max_seq_len=attn_metadata.max_seq_len,
1534
+ max_seq_len=attn_metadata.decode.max_seq_len,
1535
bmm1_scale=self.bmm1_scale,
1536
bmm2_scale=self.bmm2_scale,
1537
window_left=self.window_left,