cache_engine.py 5.6 KB

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