@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 843b222723
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 15, 11:00 UTC

[Hardware][Intel-Gaudi] Support Automatic Prefix Caching on HPU (#17648)

Signed-off-by: Agata Dobrzyniewicz <adobrzyniewicz@habana.ai>

Agata Dobrzyniewicz committed May 8, 2025 at 07:37 UTC 843b222723b659e4b80d71d3ffb4944266af1d74
3 files changed +146 -56
vllm/attention/backends/hpu_attn.py
+20 -12
@@ -57,16 +57,16 @@ class HPUAttentionBackend(AttentionBackend):
57 def swap_blocks(
58 src_kv_cache: torch.Tensor,
59 dst_kv_cache: torch.Tensor,
60 - src_to_dst: Dict[int, int],
60 + src_to_dsts: torch.Tensor,
61 ) -> None:
62 - HPUPagedAttention.swap_blocks(src_kv_cache, dst_kv_cache, src_to_dst)
62 + HPUPagedAttention.swap_blocks(src_kv_cache, dst_kv_cache, src_to_dsts)
63
64 @staticmethod
65 def copy_blocks(
66 kv_caches: List[torch.Tensor],
67 - src_to_dists: Dict[int, List[int]],
67 + src_to_dsts: torch.Tensor,
68 ) -> None:
69 - HPUPagedAttention.copy_blocks(kv_caches, src_to_dists)
69 + HPUPagedAttention.copy_blocks(kv_caches, src_to_dsts)
70
71
72 @dataclass
@@ -77,6 +77,7 @@ class HPUAttentionMetadata(HPUPagedAttentionMetadata, AttentionMetadata):
77 is_prompt: bool
78 attn_bias: Optional[torch.Tensor]
79 seq_lens_tensor: Optional[torch.Tensor]
80 + context_lens_tensor: Optional[torch.Tensor]
81
82
83 class HPUAttentionImpl(AttentionImpl, torch.nn.Module):
@@ -198,8 +199,7 @@ class HPUAttentionImpl(AttentionImpl, torch.nn.Module):
199 key_cache = None
200 value_cache = None
201 if attn_metadata.is_prompt and self.attn_type \
201 - is not AttentionType.ENCODER_ONLY \
202 - and attn_metadata.block_list is None:
202 + is not AttentionType.ENCODER_ONLY:
203 key = key.unflatten(0, (block_indices.size(0), -1))
204 value = value.unflatten(0, (block_indices.size(0), -1))
205 if kv_cache is not None and isinstance(kv_cache, tuple):
@@ -229,6 +229,9 @@ class HPUAttentionImpl(AttentionImpl, torch.nn.Module):
229 attn_bias = attn_bias.tile((1, self.num_kv_heads, 1, 1))
230 attn_bias.add_(position_bias)
231
232 + block_list = attn_metadata.block_list if attn_metadata \
233 + and attn_metadata.block_list is not None else None
234 +
235 out = ops.prompt_attention(
236 impl=self.prefill_impl,
237 query=query.view(query_shape),
@@ -237,23 +240,25 @@ class HPUAttentionImpl(AttentionImpl, torch.nn.Module):
240 is_causal=True,
241 attn_bias=attn_bias,
242 valid_seq_lengths=attn_metadata.seq_lens_tensor,
240 - **self.common_attention_args())
243 + **self.common_attention_args(block_list, key_cache,
244 + value_cache))
245 output = out.reshape(batch_size, seq_len, hidden_size)
246 else:
247 # Decoding run.
248 output = HPUPagedAttention.forward_decode(
249 query=query,
246 - key_cache=key_cache,
247 - value_cache=value_cache,
248 - block_list=attn_metadata.block_list,
250 block_mapping=attn_metadata.block_mapping,
251 block_bias=attn_metadata.attn_bias,
252 block_groups=attn_metadata.block_groups,
252 - **self.common_attention_args())
253 + **self.common_attention_args(attn_metadata.block_list,
254 + key_cache, value_cache))
255 # Reshape the output tensor.
256 return output.view(batch_size, seq_len, hidden_size)
257
256 - def common_attention_args(self):
258 + def common_attention_args(self,
259 + block_list=None,
260 + key_cache=None,
261 + value_cache=None):
262 fsdpa_op = self.fused_scaled_dot_product_attention.apply \
263 if self.fused_scaled_dot_product_attention is not None else None
264 return {
@@ -266,6 +271,9 @@ class HPUAttentionImpl(AttentionImpl, torch.nn.Module):
271 'keys_fetch_func': self.k_cache.fetch_from_cache,
272 'values_fetch_func': self.v_cache.fetch_from_cache,
273 'softmax_op': self.softmax,
274 + 'block_list': block_list,
275 + 'key_cache': key_cache,
276 + 'value_cache': value_cache,
277 }
278
279
vllm/attention/ops/hpu_paged_attn.py
+9 -27
@@ -5,7 +5,7 @@
5 ###############################################################################
6
7 from dataclasses import dataclass
8 -from typing import Dict, List, Optional, Tuple
8 +from typing import List, Optional, Tuple
9
10 import torch
11 from vllm_hpu_extension import cache_ops, ops
@@ -63,43 +63,25 @@ class HPUPagedAttention:
63 def forward_decode(**kwargs) -> torch.Tensor:
64 return ops.flat_pa(**kwargs)
65
66 - @staticmethod
67 - def forward_prefix(
68 - query: torch.Tensor,
69 - key: torch.Tensor,
70 - value: torch.Tensor,
71 - key_cache: torch.Tensor,
72 - value_cache: torch.Tensor,
73 - block_tables: torch.Tensor,
74 - subquery_start_loc: torch.Tensor,
75 - seq_lens_tensor: torch.Tensor,
76 - context_lens: torch.Tensor,
77 - max_query_len: int,
78 - alibi_slopes: Optional[torch.Tensor],
79 - sliding_window: Optional[int],
80 - ) -> torch.Tensor:
81 - raise NotImplementedError(
82 - "forward_prefix is not implemented for HPUPagedAttention")
83 -
66 @staticmethod
67 def swap_blocks(
86 - src_kv_cache: torch.Tensor,
87 - dst_kv_cache: torch.Tensor,
88 - src_to_dst: Dict[int, int],
68 + src_kv_cache: Tuple[torch.Tensor, torch.Tensor],
69 + dst_kv_cache: Tuple[torch.Tensor, torch.Tensor],
70 + src_to_dsts: torch.Tensor,
71 ) -> None:
72 src_key_cache = src_kv_cache[0]
73 dst_key_cache = dst_kv_cache[0]
92 - cache_ops.swap_blocks(src_key_cache, dst_key_cache, src_to_dst)
74 + cache_ops.swap_blocks(src_key_cache, dst_key_cache, src_to_dsts)
75
76 src_value_cache = src_kv_cache[1]
77 dst_value_cache = dst_kv_cache[1]
96 - cache_ops.swap_blocks(src_value_cache, dst_value_cache, src_to_dst)
78 + cache_ops.swap_blocks(src_value_cache, dst_value_cache, src_to_dsts)
79
80 @staticmethod
81 def copy_blocks(
100 - kv_caches: List[torch.Tensor],
101 - src_to_dists: Dict[int, List[int]],
82 + kv_caches: List[Tuple[torch.Tensor, torch.Tensor]],
83 + src_to_dsts: torch.Tensor,
84 ) -> None:
85 key_caches = [kv_cache[0] for kv_cache in kv_caches]
86 value_caches = [kv_cache[1] for kv_cache in kv_caches]
105 - cache_ops.copy_blocks(key_caches, value_caches, src_to_dists)
87 + cache_ops.copy_blocks(key_caches, value_caches, src_to_dsts)
vllm/worker/hpu_model_runner.py
+117 -17
@@ -14,7 +14,7 @@ import math
14 import os
15 import time
16 from array import array
17 -from enum import IntEnum
17 +from enum import Enum, IntEnum
18 from typing import (TYPE_CHECKING, Any, Callable, Dict, List, NamedTuple,
19 Optional, Set, Tuple, Type, TypeVar, Union)
20
@@ -75,6 +75,12 @@ LORA_WARMUP_RANK = 8
75 DUMMY_TOKEN_ID = -1
76
77
78 +class PhaseType(Enum):
79 + PREFILL = 'prefill'
80 + PREFIX_PREFILL = 'prefix_prefill'
81 + DECODE = 'decode'
82 +
83 +
84 def subtuple(obj: object,
85 typename: str,
86 to_copy: List[str],
@@ -213,20 +219,40 @@ class HpuModelAdapter:
219
220 def _set_attn_bias(self, attn_metadata, batch_size, seq_len, device,
221 dtype):
216 - prefill_metadata = attn_metadata
217 - if prefill_metadata is None or self.prefill_use_fusedsdpa:
222 + if (attn_metadata is None
223 + or (self.prefill_use_fusedsdpa \
224 + and attn_metadata.block_list is None)
225 + or not attn_metadata.is_prompt):
226 return attn_metadata
227
228 + prefill_metadata = attn_metadata
229 +
230 seq_lens_t = prefill_metadata.seq_lens_tensor
231 + context_lens_t = prefill_metadata.context_lens_tensor
232 + query_lens_t = seq_lens_t - context_lens_t
233 +
234 + block_list = attn_metadata.block_list
235 + max_context_len = (block_list.size(-1) //
236 + batch_size if block_list is not None else 0)
237 + max_context_len = max_context_len * self.block_size
238 + past_mask = torch.arange(0,
239 + max_context_len,
240 + dtype=torch.int32,
241 + device=device)
242 + past_mask = (past_mask.view(1, -1).expand(batch_size, -1).ge(
243 + context_lens_t.view(-1, 1)).view(batch_size, 1, -1).expand(
244 + batch_size, seq_len, -1).view(batch_size, 1, seq_len, -1))
245 +
246 len_mask = (torch.arange(0, seq_len, device=device,
247 dtype=torch.int32).view(1, seq_len).ge(
223 - seq_lens_t.unsqueeze(-1)).view(
248 + query_lens_t.unsqueeze(-1)).view(
249 batch_size, 1, 1, seq_len))
250 causal_mask = torch.triu(torch.ones((batch_size, 1, seq_len, seq_len),
251 device=device,
252 dtype=torch.bool),
253 diagonal=1)
254 mask = causal_mask.logical_or(len_mask)
255 + mask = torch.concat((past_mask, mask), dim=-1)
256 attn_bias = (torch.zeros_like(mask, dtype=dtype).masked_fill_(
257 mask, -math.inf))
258 attn_metadata = prefill_metadata._replace(attn_bias=attn_bias)
@@ -517,6 +543,11 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
543 False, self.max_model_len)
544 self.graphed_buckets: Set[Any] = set()
545 self._set_gc_threshold()
546 + if self.vllm_config.cache_config.enable_prefix_caching:
547 + os.environ.setdefault("VLLM_CONTIGUOUS_PA", "False")
548 + assert os.environ.get(
549 + "VLLM_CONTIGUOUS_PA",
550 + "").lower() != "true", "Contiguous PA doesn't support APC"
551 self.use_contiguous_pa = envs.VLLM_USE_HPU_CONTIGUOUS_CACHE_FETCH
552
553 # For multi-step scheduling
@@ -702,6 +733,10 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
733 computed_block_nums) > 0 and self.sliding_window is None:
734 # Prefix is not supported with sliding_window
735 context_len = len(computed_block_nums) * self.block_size
736 + if context_len == seq_len \
737 + and self.vllm_config.cache_config.enable_prefix_caching:
738 + # Fully cached prompt - compute only last token
739 + context_len = context_len - 1
740 prompt_tokens = prompt_tokens[context_len:]
741 prefix_block_tables.append(computed_block_nums)
742 elif self.scheduler_config.chunked_prefill_enabled:
@@ -779,12 +814,33 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
814 if lora_id > 0:
815 lora_requests.add(seq_group_metadata.lora_request)
816
782 - lora_index_mapping += [lora_id] * (max_prompt_len - context_len)
817 + lora_index_mapping += [lora_id] * max_prompt_len
818 lora_prompt_mapping.extend(
819 [lora_id] *
785 - (max_prompt_len - context_len
820 + (max_prompt_len
821 if seq_group_metadata.sampling_params.prompt_logprobs else 1))
822
823 + if any(context_lens):
824 + assert not self.scheduler_config.chunked_prefill_enabled
825 + # prefix caching
826 +
827 + max_num_block = max(len(bt) for bt in prefix_block_tables)
828 + prefix_block_list = list(
829 + itertools.chain.from_iterable(
830 + bt if len(bt) == max_num_block else bt +
831 + ([_PAD_BLOCK_ID] * (max_num_block - len(bt)))
832 + for bt in prefix_block_tables))
833 +
834 + pad_len = len(prefix_block_list)
835 + prefix_block_list = pad_list(prefix_block_list, pad_len,
836 + _PAD_BLOCK_ID)
837 +
838 + prefix_block_list_tensor = torch.tensor(prefix_block_list,
839 + dtype=torch.long,
840 + device=self.device)
841 + else:
842 + prefix_block_list_tensor = None
843 +
844 input_tokens = make_tensor_with_pad(input_tokens,
845 max_len=max_prompt_len,
846 pad=0,
@@ -807,11 +863,15 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
863 dtype=torch.long,
864 device=self.device)
865
866 + context_lens_tensor = torch.tensor(context_lens,
867 + dtype=torch.long,
868 + device=self.device)
869 +
870 block_indices, block_offsets = precompute_indices_and_offsets(
871 self.block_size, slot_mapping, True)
872 attn_metadata = self.attn_backend.make_metadata(
873 is_prompt=True,
814 - block_list=None,
874 + block_list=prefix_block_list_tensor,
875 block_mapping=None,
876 block_usage=None,
877 block_indices=block_indices,
@@ -819,6 +879,7 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
879 block_groups=None,
880 attn_bias=None,
881 seq_lens_tensor=seq_lens_tensor,
882 + context_lens_tensor=context_lens_tensor,
883 num_prefills=real_num_seqs,
884 num_prefill_tokens=sum_query_len,
885 num_decode_tokens=0,
@@ -987,6 +1048,7 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
1048 block_groups=block_groups,
1049 attn_bias=None,
1050 seq_lens_tensor=None,
1051 + context_lens_tensor=None,
1052 num_prefills=0,
1053 num_prefill_tokens=0,
1054 num_decode_tokens=num_decode_tokens,
@@ -1091,7 +1153,7 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
1153 # FIXME: We need to adjust selected_token_indices to accommodate
1154 # for padding
1155 max_len = input_tokens.size(1)
1094 - paddings = [max_len - s for s in seq_lens]
1156 + paddings = [max_len - q for q in query_lens]
1157 paddings = [0] + paddings[:-1]
1158 paddings = list(itertools.accumulate(paddings))
1159 paddings_prompt_logprobs = []
@@ -1187,9 +1249,17 @@ class HPUModelRunnerBase(ModelRunnerBase[TModelInputForHPU]):
1249 # input_hash(123) != input_hash(321)
1250 # input_hash("abc") != input_hash("cba")
1251 attention_metadata = subtuple(metadata, 'TrimmedAttentionMetadata', [
1190 - 'attn_bias', 'seq_lens_tensor', 'block_list', 'block_mapping',
1191 - 'block_usage', 'slot_mapping', 'is_prompt', 'block_indices',
1192 - 'block_offsets', 'block_groups'
1252 + 'attn_bias',
1253 + 'seq_lens_tensor',
1254 + 'context_lens_tensor',
1255 + 'block_list',
1256 + 'block_mapping',
1257 + 'block_usage',
1258 + 'slot_mapping',
1259 + 'is_prompt',
1260 + 'block_indices',
1261 + 'block_offsets',
1262 + 'block_groups',
1263 ])
1264 return attention_metadata
1265
@@ -1733,14 +1803,44 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
1803 from neural_compressor.torch.quantization import finalize_calibration
1804 finalize_calibration(self.model.model)
1805
1736 - def _check_config(self, batch_size, seq_len, is_prompt, warmup_mode):
1737 - cfg = (batch_size, seq_len, is_prompt)
1806 + def _num_blocks(self, attn_metadata):
1807 + if attn_metadata.block_list is None:
1808 + return 0
1809 + return attn_metadata.block_list.numel()
1810 +
1811 + def _phase(self, attn_metadata):
1812 + phase_type: PhaseType
1813 + is_prompt = attn_metadata.is_prompt
1814 + is_prefix_prefill = is_prompt and attn_metadata.block_list is not None
1815 + if is_prompt and is_prefix_prefill:
1816 + phase_type = PhaseType.PREFIX_PREFILL
1817 + elif is_prompt and not is_prefix_prefill:
1818 + phase_type = PhaseType.PREFILL
1819 + elif not is_prompt:
1820 + phase_type = PhaseType.DECODE
1821 + else:
1822 + raise ValueError("Unrecognized pass type, likely due to malformed "
1823 + "attention metadata")
1824 + return phase_type
1825 +
1826 + def _check_config(self, batch_size, seq_len, attn_metadata, warmup_mode):
1827 + is_prefix_caching = self.vllm_config.cache_config.enable_prefix_caching
1828 + cfg: Optional[tuple] = None
1829 + assert cfg is None, "Configs changed between 2D and 3D"
1830 + if is_prefix_caching:
1831 + phase = self._phase(attn_metadata)
1832 + num_blocks = self._num_blocks(attn_metadata)
1833 + cfg = (batch_size, seq_len, num_blocks, phase)
1834 + else:
1835 + phase = 'prompt' if attn_metadata.is_prompt else 'decode'
1836 + cfg = (batch_size, seq_len, phase)
1837 seen = cfg in self.seen_configs
1838 self.seen_configs.add(cfg)
1839 if not seen and not warmup_mode:
1741 - phase = 'prompt' if is_prompt else 'decode'
1742 - logger.warning("Configuration: (%s, %s, %s) was not warmed-up!",
1743 - phase, batch_size, seq_len)
1840 + logger.warning("Configuration: %s was not warmed-up!",
1841 + (phase.value, batch_size, seq_len,
1842 + num_blocks) if is_prefix_caching else
1843 + (phase, batch_size, seq_len))
1844
1845 def create_lora_mask(self, input_tokens: torch.Tensor, lora_ids: List[int],
1846 is_prompt: bool):
@@ -1912,7 +2012,7 @@ class HPUModelRunner(HPUModelRunnerBase[ModelInputForHPUWithSamplingMetadata]):
2012 batch_size = input_tokens.size(0)
2013 seq_len = self._seq_len(attn_metadata)
2014 use_graphs = self._use_graphs(batch_size, seq_len, is_prompt)
1915 - self._check_config(batch_size, seq_len, is_prompt, warmup_mode)
2015 + self._check_config(batch_size, seq_len, attn_metadata, warmup_mode)
2016
2017 lora_mask: torch.Tensor = None
2018 lora_logits_mask: torch.Tensor = None