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 15, 01:00 UTC
[Nvidia] Integrate SM100 cudnn prefill API to MLA prefill (#20411)
Signed-off-by: Elfie Guo <elfieg@nvidia.com> Co-authored-by: Elfie Guo <eflieg@nvidia.com>
Elfie Guo committed
Jul 15, 2025 at 17:56 UTC
30800b01c23d38e5bcba3bac88dc98c92d650ee0
2 files changed
+113
-5
vllm/envs.py
+5
@@ -139,6 +139,7 @@ if TYPE_CHECKING:
139
VLLM_ROCM_QUICK_REDUCE_CAST_BF16_TO_FP16: bool = True
140
VLLM_ROCM_QUICK_REDUCE_MAX_SIZE_BYTES_MB: Optional[int] = None
141
VLLM_NIXL_ABORT_REQUEST_TIMEOUT: int = 120
142
+ VLLM_USE_CUDNN_PREFILL: bool = False
143
VLLM_LOOPBACK_IP: str = ""
144
145
@@ -962,6 +963,10 @@ environment_variables: dict[str, Callable[[], Any]] = {
963
"VLLM_NIXL_ABORT_REQUEST_TIMEOUT":
964
lambda: int(os.getenv("VLLM_NIXL_ABORT_REQUEST_TIMEOUT", "120")),
965
966
+ # Controls whether or not to use cudnn prefill
967
+ "VLLM_USE_CUDNN_PREFILL":
968
+ lambda: bool(int(os.getenv("VLLM_USE_CUDNN_PREFILL", "0"))),
969
+
970
# If set to 1, use the TRTLLM Decode Attention backend in flashinfer.
971
"VLLM_USE_TRTLLM_DECODE_ATTENTION":
972
lambda: os.getenv("VLLM_USE_TRTLLM_DECODE_ATTENTION", None),
vllm/v1/attention/backends/mla/common.py
+108
-5
@@ -194,6 +194,7 @@ from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, Union
194
195
import torch
196
197
+import vllm.envs as envs
198
from vllm import _custom_ops as ops
199
from vllm.attention.backends.abstract import (AttentionBackend, AttentionLayer,
200
AttentionMetadata,
@@ -225,6 +226,8 @@ except ImportError:
226
227
try:
228
from flashinfer import BatchPrefillWithRaggedKVCacheWrapper
229
+ from flashinfer.prefill import ( # noqa: F401
230
+ cudnn_batch_prefill_with_kv_cache)
231
flashinfer_available = True
232
except ImportError:
233
flashinfer_available = False
@@ -236,6 +239,8 @@ if TYPE_CHECKING:
239
240
logger = init_logger(__name__)
241
242
+CUDNN_WORKSPACE_SIZE = 12800
243
+
244
245
class MLACommonBackend(AttentionBackend):
246
@@ -294,6 +299,7 @@ class MLACommonPrefillMetadata:
299
starts: torch.Tensor
300
seq_tot: list[int]
301
max_seq_lens: list[int]
302
+ seq_lens: torch.Tensor
303
workspace: torch.Tensor
304
305
block_table: torch.Tensor
@@ -309,6 +315,17 @@ class FlashInferPrefillMetadata(MLACommonPrefillMetadata):
315
default_factory=list)
316
317
318
+@dataclass
319
+class CudnnPrefillMetadata(MLACommonPrefillMetadata):
320
+
321
+ class ChunkedContextMetadata(
322
+ MLACommonPrefillMetadata.ChunkedContextMetadata):
323
+ seq_lens: torch.Tensor
324
+
325
+ query_seq_lens: Optional[torch.Tensor] = None
326
+ cudnn_workspace: Optional[torch.Tensor] = None
327
+
328
+
329
@dataclass
330
class MLACommonDecodeMetadata:
331
block_table: torch.Tensor
@@ -351,7 +368,8 @@ class MLACommonMetadata(Generic[D]):
368
369
decode: Optional[D] = None
370
prefill: Optional[Union[MLACommonPrefillMetadata,
354
- FlashInferPrefillMetadata]] = None
371
+ FlashInferPrefillMetadata,
372
+ CudnnPrefillMetadata]] = None
373
374
def __post_init__(self):
375
if self.head_dim is not None:
@@ -362,13 +380,19 @@ M = TypeVar("M", bound=MLACommonMetadata)
380
381
382
def use_flashinfer_prefill() -> bool:
365
- if flashinfer_available:
383
+ if flashinfer_available and not envs.VLLM_USE_CUDNN_PREFILL:
384
# For blackwell default to flashinfer prefill if its available since
385
# its faster than FA2.
386
return current_platform.has_device_capability(100)
387
return False
388
389
390
+def use_cudnn_prefill() -> bool:
391
+ if flashinfer_available and envs.VLLM_USE_CUDNN_PREFILL:
392
+ return current_platform.has_device_capability(100)
393
+ return False
394
+
395
+
396
# Currently 394MB, this can be tuned based on GEMM sizes used.
397
# Choosen to be the same as sglang:
398
# https://github.com/sgl-project/sglang/blob/766392c6bda2558b61ce6d1c1bfd8081a549e1f1/python/sglang/global_config.py#L37
@@ -427,11 +451,15 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
451
dtype=model_config.dtype,
452
device=runner.device,
453
)
454
+
455
self.block_table = block_table
456
457
+ self._use_cudnn_prefill = use_cudnn_prefill()
458
self._use_fi_prefill = use_flashinfer_prefill()
433
- self.prefill_metadata_cls = FlashInferPrefillMetadata \
434
- if self._use_fi_prefill else MLACommonPrefillMetadata
459
+ self.prefill_metadata_cls = (
460
+ FlashInferPrefillMetadata
461
+ if self._use_fi_prefill else CudnnPrefillMetadata
462
+ if self._use_cudnn_prefill else MLACommonPrefillMetadata)
463
464
if self._use_fi_prefill:
465
self._workspace_buffer = torch.empty(
@@ -447,6 +475,13 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
475
self._global_hyperparameters = infer_global_hyperparameters(
476
get_per_layer_parameters(runner.vllm_config, MLACommonImpl))
477
478
+ if self._use_cudnn_prefill:
479
+ self.cudnn_workspace = torch.empty(
480
+ CUDNN_WORKSPACE_SIZE * scheduler_config.max_num_seqs,
481
+ dtype=torch.int8,
482
+ device=runner.device,
483
+ )
484
+
485
def _build_fi_prefill_wrappers(self, prefill: FlashInferPrefillMetadata):
486
qo_indptr = prefill.query_start_loc
487
@@ -692,15 +727,24 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
727
out=cu_seq_lens_cpu[:, 1:],
728
dtype=torch.int32)
729
730
+ chunked_context_metadata_cls = \
731
+ CudnnPrefillMetadata.ChunkedContextMetadata \
732
+ if self._use_cudnn_prefill else \
733
+ MLACommonPrefillMetadata.ChunkedContextMetadata
734
+
735
chunked_context_metadata = \
696
- MLACommonPrefillMetadata.ChunkedContextMetadata(
736
+ chunked_context_metadata_cls(
737
cu_seq_lens=cu_seq_lens_cpu.to(device, non_blocking=True),
738
starts=chunk_starts.to(device, non_blocking=True),
739
seq_tot=chunk_seq_lens.sum(dim=1).tolist(),
740
max_seq_lens=chunk_seq_lens.max(dim=1).values.tolist(),
741
+ seq_lens=chunk_seq_lens,
742
workspace=self.chunked_prefill_workspace,
743
)
744
745
+ if self._use_cudnn_prefill:
746
+ chunked_context_metadata.seq_lens = chunk_seq_lens
747
+
748
assert max(chunked_context_metadata.max_seq_lens) <= \
749
self.chunked_prefill_workspace_size
750
@@ -711,6 +755,12 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
755
chunked_context=chunked_context_metadata,
756
)
757
758
+ if self._use_cudnn_prefill:
759
+ assert isinstance(prefill_metadata, CudnnPrefillMetadata)
760
+ prefill_metadata.query_seq_lens = prefill_query_start_loc[1:] \
761
+ - prefill_query_start_loc[:-1]
762
+ prefill_metadata.cudnn_workspace = self.cudnn_workspace
763
+
764
decode_metadata = None
765
if self._num_decodes > 0:
766
decode_metadata = self._build_decode(
@@ -794,6 +844,12 @@ class MLACommonImpl(MLAAttentionImpl[M], Generic[M]):
844
self._run_prefill_context_chunk = self._run_prefill_context_chunk_fi
845
self._run_prefill_new_tokens = self._run_prefill_new_tokens_fi
846
self._pad_v = False
847
+ elif use_cudnn_prefill():
848
+ logger.debug_once("Using CUDNN prefill for MLA")
849
+ self._run_prefill_context_chunk = \
850
+ self._run_prefill_context_chunk_cudnn
851
+ self._run_prefill_new_tokens = self._run_prefill_new_tokens_cudnn
852
+ self._pad_v = False
853
else: # Use FlashAttention
854
logger.debug_once("Using FlashAttention prefill for MLA")
855
self._run_prefill_context_chunk = self._run_prefill_context_chunk_fa
@@ -882,6 +938,29 @@ class MLACommonImpl(MLAAttentionImpl[M], Generic[M]):
938
return_lse=return_softmax_lse,
939
)
940
941
+ def _run_prefill_new_tokens_cudnn(self, prefill: MLACommonPrefillMetadata,
942
+ q, k, v, return_softmax_lse):
943
+ assert isinstance(prefill, CudnnPrefillMetadata)
944
+ assert prefill.query_seq_lens is not None
945
+ output, lse = cudnn_batch_prefill_with_kv_cache(
946
+ q=q,
947
+ k_cache=k,
948
+ v_cache=v,
949
+ scale=self.scale,
950
+ workspace_buffer=prefill.cudnn_workspace,
951
+ max_token_per_sequence=prefill.max_query_len,
952
+ max_sequence_kv=prefill.max_query_len,
953
+ actual_seq_lens_q=prefill.query_seq_lens.view(-1, 1, 1, 1),
954
+ actual_seq_lens_kv=prefill.query_seq_lens.view(-1, 1, 1, 1),
955
+ causal=True,
956
+ return_lse=True, # do not support False for now
957
+ is_cuda_graph_compatible=
958
+ True, #Indicates actual_seq_lens are on GPU or CPU.
959
+ )
960
+ if return_softmax_lse:
961
+ return output, lse
962
+ return output
963
+
964
def _run_prefill_context_chunk_fa(self, prefill: MLACommonPrefillMetadata,
965
chunk_idx: int, q, k, v):
966
assert prefill.chunked_context is not None
@@ -908,6 +987,30 @@ class MLACommonImpl(MLAAttentionImpl[M], Generic[M]):
987
return_lse=True,
988
)
989
990
+ def _run_prefill_context_chunk_cudnn(self,
991
+ prefill: MLACommonPrefillMetadata,
992
+ chunk_idx: int, q, k, v):
993
+ assert isinstance(prefill, CudnnPrefillMetadata)
994
+ assert prefill.chunked_context is not None
995
+ assert prefill.chunked_context.seq_lens[chunk_idx] is not None
996
+ assert prefill.query_seq_lens is not None
997
+ return cudnn_batch_prefill_with_kv_cache(
998
+ q=q,
999
+ k_cache=k,
1000
+ v_cache=v,
1001
+ scale=self.scale,
1002
+ workspace_buffer=prefill.cudnn_workspace,
1003
+ max_token_per_sequence=prefill.max_query_len,
1004
+ max_sequence_kv=prefill.chunked_context.max_seq_lens[chunk_idx],
1005
+ actual_seq_lens_q=prefill.query_seq_lens.view(-1, 1, 1, 1),
1006
+ actual_seq_lens_kv=prefill.chunked_context.seq_lens[chunk_idx].
1007
+ view(-1, 1, 1, 1),
1008
+ causal=False,
1009
+ return_lse=True,
1010
+ is_cuda_graph_compatible=
1011
+ True, #Indicates actual_seq_lens are on GPU or CPU.
1012
+ )
1013
+
1014
def _v_up_proj(self, x):
1015
# Convert from (B, N, L) to (N, B, L)
1016
x = x.view(-1, self.num_heads, self.kv_lora_rank).transpose(0, 1)