cache_engine.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. """CacheEngine class for managing the KV cache."""
  2. from typing import Dict, List, Tuple
  3. import torch
  4. from loguru import logger
  5. from aphrodite.common.config import CacheConfig, ModelConfig, ParallelConfig
  6. from aphrodite.common.utils import in_wsl, is_neuron, STR_DTYPE_TO_TORCH_DTYPE
  7. KVCache = Tuple[torch.Tensor, torch.Tensor]
  8. class CacheEngine:
  9. """Manages the KV cache.
  10. This class is responsible for initializing and managing the GPU and CPU KV
  11. caches. It also provides methods for performing KV cache operations, such
  12. as swapping and copying.
  13. """
  14. def __init__(
  15. self,
  16. cache_config: CacheConfig,
  17. model_config: ModelConfig,
  18. parallel_config: ParallelConfig,
  19. ) -> None:
  20. self.cache_config = cache_config
  21. self.model_config = model_config
  22. self.parallel_config = parallel_config
  23. self.head_size = model_config.get_head_size()
  24. self.num_layers = model_config.get_num_layers(parallel_config)
  25. self.num_heads = model_config.get_num_kv_heads(parallel_config)
  26. self.block_size = cache_config.block_size
  27. self.num_gpu_blocks = cache_config.num_gpu_blocks
  28. self.num_cpu_blocks = cache_config.num_cpu_blocks
  29. # Skip initializing KV Cache for Neuron backend.
  30. if is_neuron():
  31. return
  32. if cache_config.cache_dtype == "auto":
  33. self.dtype = model_config.dtype
  34. else:
  35. self.dtype = STR_DTYPE_TO_TORCH_DTYPE[cache_config.cache_dtype]
  36. # Initialize the cache.
  37. self.gpu_cache = self.allocate_gpu_cache()
  38. self.cpu_cache = self.allocate_cpu_cache()
  39. def get_key_block_shape(self) -> Tuple[int, int, int, int]:
  40. element_size = torch.tensor([], dtype=self.dtype).element_size()
  41. x = 16 // element_size
  42. return (
  43. self.num_heads,
  44. self.head_size // x,
  45. self.block_size,
  46. x,
  47. )
  48. def get_value_block_shape(self) -> Tuple[int, int, int]:
  49. return (
  50. self.num_heads,
  51. self.head_size,
  52. self.block_size,
  53. )
  54. def allocate_gpu_cache(self) -> List[KVCache]:
  55. gpu_cache: List[KVCache] = []
  56. key_block_shape = self.get_key_block_shape()
  57. value_block_shape = self.get_value_block_shape()
  58. for _ in range(self.num_layers):
  59. key_blocks = torch.empty(
  60. size=(self.num_gpu_blocks, *key_block_shape),
  61. dtype=self.dtype,
  62. device="cuda",
  63. )
  64. value_blocks = torch.empty(
  65. size=(self.num_gpu_blocks, *value_block_shape),
  66. dtype=self.dtype,
  67. device="cuda",
  68. )
  69. gpu_cache.append((key_blocks, value_blocks))
  70. return gpu_cache
  71. def allocate_cpu_cache(self) -> List[KVCache]:
  72. cpu_cache: List[KVCache] = []
  73. key_block_shape = self.get_key_block_shape()
  74. value_block_shape = self.get_value_block_shape()
  75. pin_memory = not in_wsl()
  76. if not pin_memory:
  77. # Pinning memory in WSL is not supported.
  78. # https://docs.nvidia.com/cuda/wsl-user-guide/index.html#known-limitations-for-linux-cuda-applications
  79. logger.warning("Using 'pin_memory=False' as WSL is detected. "
  80. "This may slow down the performance.")
  81. for _ in range(self.num_layers):
  82. key_blocks = torch.empty(
  83. size=(self.num_cpu_blocks, *key_block_shape),
  84. dtype=self.dtype,
  85. pin_memory=pin_memory,
  86. device="cpu",
  87. )
  88. value_blocks = torch.empty(
  89. size=(self.num_cpu_blocks, *value_block_shape),
  90. dtype=self.dtype,
  91. pin_memory=pin_memory,
  92. device="cpu",
  93. )
  94. cpu_cache.append((key_blocks, value_blocks))
  95. return cpu_cache
  96. def _swap(
  97. self,
  98. src: List[KVCache],
  99. dst: List[KVCache],
  100. src_to_dst: Dict[int, int],
  101. ) -> None:
  102. from aphrodite._C import cache_ops
  103. for i in range(self.num_layers):
  104. src_key_cache, src_value_cache = src[i]
  105. dst_key_cache, dst_value_cache = dst[i]
  106. # Copy the key blocks.
  107. cache_ops.swap_blocks(src_key_cache, dst_key_cache, src_to_dst)
  108. # Copy the value blocks.
  109. cache_ops.swap_blocks(src_value_cache, dst_value_cache, src_to_dst)
  110. def swap_in(self, src_to_dst: Dict[int, int]) -> None:
  111. self._swap(self.cpu_cache, self.gpu_cache, src_to_dst)
  112. def swap_out(self, src_to_dst: Dict[int, int]) -> None:
  113. self._swap(self.gpu_cache, self.cpu_cache, src_to_dst)
  114. def copy(self, src_to_dsts: Dict[int, List[int]]) -> None:
  115. from aphrodite._C import cache_ops
  116. key_caches = [key_cache for key_cache, _ in self.gpu_cache]
  117. value_caches = [value_cache for _, value_cache in self.gpu_cache]
  118. # NOTE: This operation implicitly synchronizes the CPU and GPU.
  119. cache_ops.copy_blocks(key_caches, value_caches, src_to_dsts)
  120. @staticmethod
  121. def get_cache_block_size(
  122. block_size: int,
  123. cache_dtype: str,
  124. model_config: ModelConfig,
  125. parallel_config: ParallelConfig,
  126. ) -> int:
  127. head_size = model_config.get_head_size()
  128. num_heads = model_config.get_num_kv_heads(parallel_config)
  129. num_layers = model_config.get_num_layers(parallel_config)
  130. key_cache_block = block_size * num_heads * head_size
  131. value_cache_block = key_cache_block
  132. total = num_layers * (key_cache_block + value_cache_block)
  133. if cache_dtype == "auto":
  134. dtype = model_config.dtype
  135. else:
  136. dtype = STR_DTYPE_TO_TORCH_DTYPE[cache_dtype]
  137. dtype_size = _get_dtype_size(dtype)
  138. return dtype_size * total
  139. def _get_dtype_size(dtype: torch.dtype) -> int:
  140. return torch.tensor([], dtype=dtype).element_size()