evictor_v2.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import enum
  2. from abc import ABC, abstractmethod
  3. from typing import OrderedDict, Tuple
  4. class EvictionPolicy(enum.Enum):
  5. """Enum for eviction policy used by make_evictor to instantiate the correct
  6. Evictor subclass.
  7. """
  8. LRU = enum.auto()
  9. class Evictor(ABC):
  10. """The Evictor subclasses should be used by the BlockAllocator class to
  11. handle eviction of freed PhysicalTokenBlocks.
  12. """
  13. @abstractmethod
  14. def __init__(self):
  15. pass
  16. @abstractmethod
  17. def __contains__(self, block_id: int) -> bool:
  18. pass
  19. @abstractmethod
  20. def evict(self) -> Tuple[int, int]:
  21. """Runs the eviction algorithm and returns the evicted block's
  22. content hash along with physical block id along with physical block id
  23. """
  24. pass
  25. @abstractmethod
  26. def add(self, block_id: int, content_hash: int, num_hashed_tokens: int,
  27. last_accessed: float):
  28. """Adds block to the evictor, making it a candidate for eviction"""
  29. pass
  30. @abstractmethod
  31. def update(self, block_id: int, last_accessed: float):
  32. """Update corresponding block's access time in metadata"""
  33. pass
  34. @abstractmethod
  35. def remove(self, block_id: int):
  36. """Remove a given block id from the cache."""
  37. pass
  38. @property
  39. @abstractmethod
  40. def num_blocks(self) -> int:
  41. pass
  42. class BlockMetaData():
  43. """Data structure for storing key data describe cached block, so that
  44. evitor could use to make its decision which one to choose for eviction
  45. Here we use physical block id as the dict key, as there maybe several
  46. blocks with the same content hash, but their physical id is unique.
  47. """
  48. def __init__(self, content_hash: int, num_hashed_tokens: int,
  49. last_accessed: float):
  50. self.content_hash = content_hash
  51. self.num_hashed_tokens = num_hashed_tokens
  52. self.last_accessed = last_accessed
  53. class LRUEvictor(Evictor):
  54. """Evicts in a least-recently-used order using the last_accessed timestamp
  55. that's recorded in the PhysicalTokenBlock. If there are multiple blocks with
  56. the same last_accessed time, then the one with the largest num_hashed_tokens
  57. will be evicted. If two blocks each have the lowest last_accessed time and
  58. highest num_hashed_tokens value, then one will be chose arbitrarily
  59. """
  60. def __init__(self):
  61. self.free_table: OrderedDict[int, BlockMetaData] = OrderedDict()
  62. def __contains__(self, block_id: int) -> bool:
  63. return block_id in self.free_table
  64. def evict(self) -> Tuple[int, int]:
  65. if len(self.free_table) == 0:
  66. raise ValueError("No usable cache memory left")
  67. evicted_block, evicted_block_id = None, None
  68. # The blocks with the lowest timestamps should be placed consecutively
  69. # at the start of OrderedDict. Loop through all these blocks to
  70. # find the one with maximum number of hashed tokens.
  71. for _id, block in self.free_table.items():
  72. if evicted_block is None:
  73. evicted_block, evicted_block_id = block, _id
  74. continue
  75. if evicted_block.last_accessed < block.last_accessed:
  76. break
  77. if evicted_block.num_hashed_tokens < block.num_hashed_tokens:
  78. evicted_block, evicted_block_id = block, _id
  79. assert evicted_block is not None
  80. assert evicted_block_id is not None
  81. self.free_table.pop(evicted_block_id)
  82. return evicted_block_id, evicted_block.content_hash
  83. def add(self, block_id: int, content_hash: int, num_hashed_tokens: int,
  84. last_accessed: float):
  85. self.free_table[block_id] = BlockMetaData(content_hash,
  86. num_hashed_tokens,
  87. last_accessed)
  88. def update(self, block_id: int, last_accessed: float):
  89. self.free_table[block_id].last_accessed = last_accessed
  90. def remove(self, block_id: int):
  91. if block_id not in self.free_table:
  92. raise ValueError(
  93. "Attempting to remove block that's not in the evictor")
  94. self.free_table.pop(block_id)
  95. @property
  96. def num_blocks(self) -> int:
  97. return len(self.free_table)
  98. def make_evictor(eviction_policy: EvictionPolicy) -> Evictor:
  99. if eviction_policy == EvictionPolicy.LRU:
  100. return LRUEvictor()
  101. else:
  102. raise ValueError(f"Unknown cache eviction policy: {eviction_policy}")