@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 77e10c9cab
Fork 0

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 12, 14:45 UTC

[Perf][Deepseek] optimize gather_and_maybe_dequant_cache kernel's perf for extremely long sequence (#28029)

Signed-off-by: ganyi <ygan@amd.com>

Pleaplusone committed Nov 25, 2025 at 10:05 UTC 77e10c9cab751c83de0b2200977212922cc3776f
6 files changed +132 -106
csrc/cache.h
+6 -5
@@ -41,11 +41,12 @@ void convert_fp8(torch::Tensor& dst_cache, torch::Tensor& src_cache,
41 const double scale, const std::string& kv_cache_dtype);
42
43 void gather_and_maybe_dequant_cache(
44 - torch::Tensor const& src_cache, // [NUM_BLOCKS, BLOCK_SIZE, ENTRIES...]
45 - torch::Tensor const& dst, // [TOT_TOKENS, ENTRIES...]
46 - torch::Tensor const& block_table, // [BATCH, BLOCK_INDICES]
47 - torch::Tensor const& cu_seq_lens, // [BATCH+1]
48 - int64_t batch_size, const std::string& kv_cache_dtype,
44 + torch::Tensor const& src_cache, // [NUM_BLOCKS, BLOCK_SIZE, ENTRIES...]
45 + torch::Tensor const& dst, // [TOT_TOKENS, ENTRIES...]
46 + torch::Tensor const& block_table, // [BATCH, BLOCK_INDICES]
47 + torch::Tensor const& cu_seq_lens, // [BATCH+1]
48 + torch::Tensor const& token_to_seq, // [MAX_TOKEN_ACROSS_CHUNKS]
49 + int64_t num_tokens, const std::string& kv_cache_dtype,
50 torch::Tensor const& scale,
51 std::optional<torch::Tensor> seq_starts = std::nullopt);
52
csrc/cache_kernels.cu
+86 -92
@@ -905,91 +905,79 @@ void convert_fp8(torch::Tensor& dst_cache, torch::Tensor& src_cache,
905 namespace vllm {
906
907 // grid is launched with dimensions (batch, num_splits)
908 -template <typename scalar_t, typename cache_t, Fp8KVCacheDataType kv_dt>
908 +template <typename scalar_t, typename cache_t, Fp8KVCacheDataType kv_dt,
909 + int ENTRY_SIZE, int CTA_SIZE>
910 __global__ void gather_and_maybe_dequant_cache(
910 - const cache_t* __restrict__ src_cache, // [NUM_BLOCKS, BLOCK_SIZE,
911 - // ENTRIES...]
912 - scalar_t* __restrict__ dst, // [TOT_TOKENS, ENTRIES...]
913 - const int32_t* __restrict__ block_table, // [BATCH, BLOCK_INDICES]
914 - const int32_t* __restrict__ cu_seq_lens, // [BATCH+1]
915 - const int32_t block_size, const int32_t entry_size,
911 + const cache_t* __restrict__ src_cache, // [NUM_BLOCKS, BLOCK_SIZE,
912 + // ENTRIES...]
913 + scalar_t* __restrict__ dst, // [TOT_TOKENS, ENTRIES...]
914 + const int32_t* __restrict__ block_table, // [BATCH, BLOCK_INDICES]
915 + const int32_t* __restrict__ cu_seq_lens, // [BATCH+1]
916 + const int32_t* __restrict__ token_to_seq, // [MAX_TOKEN_ACROSS_CHUNK]
917 + const int32_t num_tokens, const int32_t block_size,
918 const int64_t block_table_stride, const int64_t cache_block_stride,
919 const int64_t cache_entry_stride, const int64_t dst_entry_stride,
920 const float* __restrict__ scale,
921 const int32_t* __restrict__ seq_starts) { // Optional: starting offsets per
922 // batch
923 + constexpr int vec_size = sizeof(float4) / sizeof(scalar_t);
924 + using ltype = vllm::vec_n_t<cache_t, vec_size>;
925 + using stype = vllm::vec_n_t<scalar_t, vec_size>;
926 + // We are adding this for code readability which will be optimized out when
927 + // build in release.
928 + assert(CTA_SIZE == blockDim.x);
929
922 - const int64_t bid = blockIdx.x; // Batch ID
923 - const int32_t num_splits = gridDim.y;
924 - const int32_t split = blockIdx.y;
925 - const int32_t seq_start = cu_seq_lens[bid];
926 - const int32_t seq_end = cu_seq_lens[bid + 1];
927 - const int32_t seq_len = seq_end - seq_start;
928 - const int32_t tot_blocks = cuda_utils::ceil_div(seq_len, block_size);
929 - const int32_t split_blocks = cuda_utils::ceil_div(tot_blocks, num_splits);
930 -
931 - const int32_t split_start = split * split_blocks;
932 - const int32_t split_end = min((split + 1) * split_blocks, tot_blocks);
933 -
934 - const bool is_active_split = (split_start < tot_blocks);
935 - const bool is_last_split = (split_end == tot_blocks);
936 -
937 - if (!is_active_split) return;
938 -
939 - int32_t full_blocks_end = split_end;
940 - int32_t partial_block_size = 0;
941 -
942 - // Adjust the pointer for the block_table for this batch.
943 - // If seq_starts is provided, compute an offset based on (seq_starts[bid] /
944 - // page_size)
945 - const int32_t batch_offset = bid * block_table_stride;
946 - int32_t offset = 0;
947 - if (seq_starts != nullptr) {
948 - offset = seq_starts[bid] / block_size;
949 - }
950 - const int32_t* batch_block_table = block_table + batch_offset + offset;
951 -
952 - // Adjust dst pointer based on the cumulative sequence lengths.
953 - dst += seq_start * dst_entry_stride;
954 -
955 - if (is_last_split) {
956 - partial_block_size = seq_len % block_size;
957 - if (partial_block_size) full_blocks_end -= 1;
958 - }
930 +#pragma unroll
931 + for (int token_id = blockIdx.x; token_id < num_tokens;
932 + token_id += gridDim.x) {
933 + int64_t batch_id = token_to_seq[token_id];
934 + int64_t batch_start = cu_seq_lens[batch_id];
935 + int64_t batch_end = cu_seq_lens[batch_id + 1];
936 + int32_t batch_offset = token_id - batch_start;
937 +
938 + if (token_id >= batch_end) return;
939 + int32_t offset = 0;
940 + if (seq_starts != nullptr) {
941 + offset = seq_starts[batch_id];
942 + }
943 + batch_offset += offset;
944 + int32_t block_table_id = batch_offset / block_size;
945 + int32_t slot_id = batch_offset % block_size;
946 + int32_t block_table_offset = batch_id * block_table_stride + block_table_id;
947 + int32_t block_id = block_table[block_table_offset];
948 + int64_t cache_offset =
949 + block_id * cache_block_stride + slot_id * cache_entry_stride;
950 + constexpr int32_t vec_iter_cnt = ENTRY_SIZE / vec_size;
951 + scalar_t* dst_ = dst + token_id * dst_entry_stride;
952 + cache_t* src_ = const_cast<cache_t*>(src_cache) + cache_offset;
953
960 - auto copy_entry = [&](const cache_t* __restrict__ _src,
961 - scalar_t* __restrict__ _dst) {
962 - for (int i = threadIdx.x; i < entry_size; i += blockDim.x) {
954 +#pragma unroll
955 + for (int idx = threadIdx.x; idx < vec_iter_cnt; idx += CTA_SIZE) {
956 if constexpr (kv_dt == Fp8KVCacheDataType::kAuto) {
964 - _dst[i] = static_cast<scalar_t>(_src[i]);
957 + reinterpret_cast<stype*>(dst_)[idx] =
958 + static_cast<stype>(reinterpret_cast<ltype*>(src_)[idx]);
959 } else {
966 - _dst[i] =
967 - fp8::scaled_convert<scalar_t, cache_t, kv_dt>(_src[i], *scale);
960 + ltype loaded_val = reinterpret_cast<ltype*>(src_)[idx];
961 + stype store_val;
962 +#pragma unroll
963 + for (int j = 0; j < vec_size; ++j) {
964 + store_val.val[j] = fp8::scaled_convert<scalar_t, cache_t, kv_dt>(
965 + loaded_val.val[j], *scale);
966 + }
967 + reinterpret_cast<stype*>(dst_)[idx] = store_val;
968 }
969 }
970 - };
971 -
972 - const auto loop_end =
973 - std::min((int64_t)full_blocks_end, block_table_stride - offset);
974 - for (int pid = split_start; pid < loop_end; ++pid) {
975 - auto block_id = batch_block_table[pid];
976 - auto block_start_ptr = src_cache + block_id * cache_block_stride;
977 - auto block_dst_ptr = dst + pid * block_size * dst_entry_stride;
978 - for (int eid = 0; eid < block_size; ++eid) {
979 - copy_entry(block_start_ptr + eid * cache_entry_stride,
980 - block_dst_ptr + eid * dst_entry_stride);
981 - }
982 - }
983 -
984 - if (partial_block_size) {
985 - if (offset + full_blocks_end < block_table_stride) {
986 - auto block_id = batch_block_table[full_blocks_end];
987 - auto block_start_ptr = src_cache + block_id * cache_block_stride;
988 - auto block_dst_ptr =
989 - dst + full_blocks_end * block_size * dst_entry_stride;
990 - for (int eid = 0; eid < partial_block_size; ++eid) {
991 - copy_entry(block_start_ptr + eid * cache_entry_stride,
992 - block_dst_ptr + eid * dst_entry_stride);
970 + // process tail
971 + constexpr int32_t tail_cnt = ENTRY_SIZE % vec_size;
972 + dst_ = dst_ + ENTRY_SIZE - tail_cnt;
973 + src_ = src_ + ENTRY_SIZE - tail_cnt;
974 +#pragma unroll
975 + for (int idx = threadIdx.x; idx < tail_cnt; idx += CTA_SIZE) {
976 + if constexpr (kv_dt == Fp8KVCacheDataType::kAuto) {
977 + dst_[idx] = static_cast<scalar_t>(src_[idx]);
978 + } else {
979 + dst_[idx] =
980 + fp8::scaled_convert<scalar_t, cache_t, kv_dt>(src_[idx], *scale);
981 }
982 }
983 }
@@ -1001,34 +989,38 @@ __global__ void gather_and_maybe_dequant_cache(
989 // SCALAR_T is the data type of the destination tensor.
990 // CACHE_T is the stored data type of kv-cache.
991 // KV_DTYPE is the real data type of kv-cache.
1004 -#define CALL_GATHER_CACHE(SCALAR_T, CACHE_T, KV_DTYPE) \
1005 - vllm::gather_and_maybe_dequant_cache<SCALAR_T, CACHE_T, KV_DTYPE> \
1006 - <<<grid, block, 0, stream>>>( \
1007 - reinterpret_cast<CACHE_T*>(src_cache.data_ptr()), \
1008 - reinterpret_cast<SCALAR_T*>(dst.data_ptr()), \
1009 - block_table.data_ptr<int32_t>(), cu_seq_lens.data_ptr<int32_t>(), \
1010 - block_size, entry_size, block_table_stride, cache_block_stride, \
1011 - cache_entry_stride, dst_entry_stride, \
1012 - reinterpret_cast<const float*>(scale.data_ptr()), seq_starts_ptr);
992 +#define CALL_GATHER_CACHE(SCALAR_T, CACHE_T, KV_DTYPE) \
993 + vllm::gather_and_maybe_dequant_cache<SCALAR_T, CACHE_T, KV_DTYPE, 576, \
994 + thread_block_size> \
995 + <<<grid, block, 0, stream>>>( \
996 + reinterpret_cast<CACHE_T*>(src_cache.data_ptr()), \
997 + reinterpret_cast<SCALAR_T*>(dst.data_ptr()), \
998 + block_table.data_ptr<int32_t>(), cu_seq_lens.data_ptr<int32_t>(), \
999 + token_to_seq.data_ptr<int32_t>(), num_tokens, block_size, \
1000 + block_table_stride, cache_block_stride, cache_entry_stride, \
1001 + dst_entry_stride, reinterpret_cast<const float*>(scale.data_ptr()), \
1002 + seq_starts_ptr);
1003
1004 // Gather sequences from the cache into the destination tensor.
1005 // - cu_seq_lens contains the cumulative sequence lengths for each batch
1006 // - block_table contains the cache block indices for each sequence
1007 +// - token_to_seq contains the back mapping from token_id to batch_id
1008 // - Optionally, seq_starts (if provided) offsets the starting block index by
1009 // (seq_starts[bid] / page_size)
1010 void gather_and_maybe_dequant_cache(
1020 - torch::Tensor const& src_cache, // [NUM_BLOCKS, BLOCK_SIZE, ENTRIES...]
1021 - torch::Tensor const& dst, // [TOT_TOKENS, ENTRIES...]
1022 - torch::Tensor const& block_table, // [BATCH, BLOCK_INDICES]
1023 - torch::Tensor const& cu_seq_lens, // [BATCH+1]
1024 - int64_t batch_size, const std::string& kv_cache_dtype,
1011 + torch::Tensor const& src_cache, // [NUM_BLOCKS, BLOCK_SIZE, ENTRIES...]
1012 + torch::Tensor const& dst, // [TOT_TOKENS, ENTRIES...]
1013 + torch::Tensor const& block_table, // [BATCH, BLOCK_INDICES]
1014 + torch::Tensor const& cu_seq_lens, // [BATCH+1]
1015 + torch::Tensor const& token_to_seq, // [MAX_TOKEN_ACROSS_CHUNKS]
1016 + int64_t num_tokens, const std::string& kv_cache_dtype,
1017 torch::Tensor const& scale,
1018 std::optional<torch::Tensor> seq_starts = std::nullopt) {
1019 at::cuda::OptionalCUDAGuard device_guard(src_cache.device());
1020 const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
1021
1022 int32_t block_size = src_cache.size(1);
1031 - int32_t entry_size = src_cache.flatten(2, -1).size(2);
1023 + int32_t head_dim = dst.size(-1);
1024
1025 TORCH_CHECK(block_table.dtype() == torch::kInt32,
1026 "block_table must be int32");
@@ -1038,6 +1030,9 @@ void gather_and_maybe_dequant_cache(
1030 TORCH_CHECK(seq_starts.value().dtype() == torch::kInt32,
1031 "seq_starts must be int32");
1032 }
1033 + TORCH_CHECK(head_dim == 576,
1034 + "gather_and_maybe_dequant_cache only support the head_dim to 576 "
1035 + "for better performance")
1036
1037 TORCH_CHECK(src_cache.device() == dst.device(),
1038 "src_cache and dst must be on the same device");
@@ -1055,10 +1050,9 @@ void gather_and_maybe_dequant_cache(
1050 int64_t cache_entry_stride = src_cache.stride(1);
1051 int64_t dst_entry_stride = dst.stride(0);
1052
1058 - // Decide on the number of splits based on the batch size.
1059 - int num_splits = batch_size > 128 ? 2 : batch_size > 64 ? 4 : 16;
1060 - dim3 grid(batch_size, num_splits);
1061 - dim3 block(1024);
1053 + constexpr int32_t thread_block_size = 64;
1054 + dim3 grid(num_tokens);
1055 + dim3 block(thread_block_size);
1056
1057 const int32_t* seq_starts_ptr =
1058 seq_starts.has_value() ? seq_starts.value().data_ptr<int32_t>() : nullptr;
csrc/torch_bindings.cpp
+2 -1
@@ -695,7 +695,8 @@ TORCH_LIBRARY_EXPAND(CONCAT(TORCH_EXTENSION_NAME, _cache_ops), cache_ops) {
695 cache_ops.def(
696 "gather_and_maybe_dequant_cache(Tensor src_cache, Tensor! dst, "
697 " Tensor block_table, Tensor cu_seq_lens, "
698 - " int batch_size, "
698 + " Tensor token_to_seq, "
699 + " int num_tokens, "
700 " str kv_cache_dtype, "
701 " Tensor scale, Tensor? seq_starts) -> ()");
702 cache_ops.impl("gather_and_maybe_dequant_cache", torch::kCUDA,
tests/kernels/attention/test_cache.py
+9 -3
@@ -921,12 +921,16 @@ def test_gather_and_maybe_dequant_cache_mla(
921 )
922 _fill_mla_cache(src_cache, kv_cache_dtype=kv_cache_dtype)
923
924 - seq_len_tensor = torch.randint(0, max_seq_len + 1, (batch_size,), device=device)
924 + seq_len_tensor = torch.randint(
925 + max_seq_len, max_seq_len + 1, (batch_size,), device=device
926 + )
927
928 total_tokens = seq_len_tensor.sum()
929 cu_seq_lens = torch.empty((batch_size + 1), dtype=torch.int32, device=device)
930 cu_seq_lens[0] = 0
931 cu_seq_lens[1:] = seq_len_tensor.cumsum(dim=0).to(dtype=torch.int32)
932 + token_to_seq = torch.arange(0, batch_size, dtype=torch.int32, device=device)
933 + token_to_seq = torch.repeat_interleave(token_to_seq, seq_len_tensor)
934 print("seq_len_tensor", seq_len_tensor)
935
936 tot_blocks_tensor = (seq_len_tensor + block_size - 1) // block_size
@@ -977,7 +981,8 @@ def test_gather_and_maybe_dequant_cache_mla(
981 dst,
982 block_table,
983 cu_seq_lens,
980 - batch_size,
984 + token_to_seq,
985 + total_tokens,
986 kv_cache_dtype,
987 scale,
988 None,
@@ -990,7 +995,8 @@ def test_gather_and_maybe_dequant_cache_mla(
995 dst,
996 block_table,
997 cu_seq_lens,
993 - batch_size,
998 + token_to_seq,
999 + total_tokens,
1000 kv_cache_dtype,
1001 scale,
1002 None,
vllm/_custom_ops.py
+4 -2
@@ -2201,7 +2201,8 @@ def gather_and_maybe_dequant_cache(
2201 dst: torch.Tensor,
2202 block_table: torch.Tensor,
2203 cu_seq_lens: torch.Tensor,
2204 - batch_size: int,
2204 + token_to_seq: torch.Tensor,
2205 + num_tokens: int,
2206 kv_cache_dtype: str,
2207 scale: torch.Tensor,
2208 seq_starts: torch.Tensor | None = None,
@@ -2211,7 +2212,8 @@ def gather_and_maybe_dequant_cache(
2212 dst,
2213 block_table,
2214 cu_seq_lens,
2214 - batch_size,
2215 + token_to_seq,
2216 + num_tokens,
2217 kv_cache_dtype,
2218 scale,
2219 seq_starts,
vllm/v1/attention/backends/mla/common.py
+25 -3
@@ -340,6 +340,8 @@ class MLACommonPrefillMetadata:
340 max_seq_lens: list[int]
341 seq_lens: torch.Tensor
342 workspace: torch.Tensor
343 + token_to_seq: torch.Tensor
344 + chunk_total_token: list[int]
345
346 # for mla DCP
347 padded_local_chunk_seq_lens: list[list[int]] | None = None
@@ -839,6 +841,19 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
841 torch.cumsum(
842 chunk_seq_lens, dim=1, out=cu_seq_lens_cpu[:, 1:], dtype=torch.int32
843 )
844 + chunk_total_token = cu_seq_lens_cpu[:, -1]
845 +
846 + max_token_num_over_chunk = chunk_total_token.max().item()
847 + token_to_seq_tensor_cpu = torch.zeros(
848 + [num_chunks, max_token_num_over_chunk], dtype=torch.int32
849 + )
850 + range_idx = torch.arange(num_prefills, dtype=torch.int32)
851 + for i in range(num_chunks):
852 + chunk_token_to_seq_tensor = torch.repeat_interleave(
853 + range_idx, chunk_seq_lens[i]
854 + )
855 + chunk_len = chunk_token_to_seq_tensor.shape[0]
856 + token_to_seq_tensor_cpu[i, :chunk_len] = chunk_token_to_seq_tensor
857
858 if self.dcp_world_size > 1:
859 local_context_lens_allranks = get_dcp_local_seq_lens(
@@ -906,6 +921,10 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
921 seq_tot=padded_local_chunk_seq_lens.sum(dim=1).tolist(),
922 max_seq_lens=chunk_seq_lens.max(dim=1).values.tolist(),
923 seq_lens=chunk_seq_lens,
924 + token_to_seq=token_to_seq_tensor_cpu.to(
925 + device, non_blocking=True
926 + ),
927 + chunk_total_token=chunk_total_token.tolist(),
928 workspace=self.chunked_prefill_workspace,
929 padded_local_chunk_seq_lens=padded_local_chunk_seq_lens.tolist(),
930 local_context_lens_allranks=local_context_lens_allranks.tolist(),
@@ -922,6 +941,10 @@ class MLACommonMetadataBuilder(AttentionMetadataBuilder[M]):
941 seq_tot=chunk_seq_lens.sum(dim=1).tolist(),
942 max_seq_lens=chunk_seq_lens.max(dim=1).values.tolist(),
943 seq_lens=chunk_seq_lens,
944 + token_to_seq=token_to_seq_tensor_cpu.to(
945 + device, non_blocking=True
946 + ),
947 + chunk_total_token=chunk_total_token,
948 workspace=self.chunked_prefill_workspace,
949 )
950
@@ -1638,16 +1661,15 @@ class MLACommonImpl(MLACommonBaseImpl[M], Generic[M]):
1661 output = None
1662 iters = len(prefill_metadata.chunked_context.seq_tot)
1663 workspace = prefill_metadata.chunked_context.workspace
1641 -
1664 for i in range(iters):
1665 toks = prefill_metadata.chunked_context.seq_tot[i]
1644 -
1666 ops.gather_and_maybe_dequant_cache(
1667 src_cache=kv_c_and_k_pe_cache,
1668 dst=workspace,
1669 block_table=prefill_metadata.block_table,
1670 cu_seq_lens=prefill_metadata.chunked_context.cu_seq_lens[i],
1650 - batch_size=attn_metadata.num_prefills,
1671 + token_to_seq=prefill_metadata.chunked_context.token_to_seq[i],
1672 + num_tokens=prefill_metadata.chunked_context.chunk_total_token[i],
1673 kv_cache_dtype=self.kv_cache_dtype,
1674 scale=k_scale,
1675 seq_starts=prefill_metadata.chunked_context.starts[i],