vllm-block-pool
vllm/vllm/v1/core/block_pool.py · ef9339e · L681–830 · 固定 commit 上游
这是固定版本源码的只读摘录;可在线阅读,也可通过上游链接查看原文件。
681 def get_new_blocks(self, num_blocks: int) -> list[KVCacheBlock]:
682 """Get new blocks from the free block pool.
683
684 Note that we do not check block cache in this function.
685
686 Args:
687 num_blocks: The number of blocks to allocate.
688
689 Returns:
690 A list of new block.
691
692 """
693 if num_blocks > self.get_num_free_blocks():
694 raise ValueError(f"Cannot get {num_blocks} free blocks from the pool")
695
696 ret: list[KVCacheBlock] = self.free_block_queue.popleft_n(num_blocks)
697
698 if self._reuse_watchers:
699 self._notify_reuse(ret)
700
701 # In order to only iterate the list once, we duplicated code a bit
702 if self.enable_caching:
703 for block in ret:
704 self._maybe_evict_cached_block(block)
705 assert block.ref_cnt == 0
706 block.ref_cnt += 1
707 if self.metrics_collector:
708 self.metrics_collector.on_block_allocated(block)
709 else:
710 for block in ret:
711 assert block.ref_cnt == 0
712 block.ref_cnt += 1
713 if self.metrics_collector:
714 self.metrics_collector.on_block_allocated(block)
715 return ret
716
717 def _notify_reuse(self, blocks: Iterable[KVCacheBlock]) -> None:
718 for block in blocks:
719 watcher = self._reuse_watchers.pop(block.block_id, None)
720 if watcher is not None:
721 watcher(block)
722
723 def unpin_blocks(
724 self,
725 blocks: Iterable[KVCacheBlock],
726 on_reuse: Callable[[KVCacheBlock], None],
727 ) -> None:
728 """Release references to blocks that stay readable until reused.
729
730 The blocks become last-resort eviction candidates regardless of prefix
731 caching: they join the tail of the free queue and count as free.
732 ``on_reuse`` fires when ``get_new_blocks`` hands a block out (or the
733 cache is reset), so the caller can stop reading it.
734 """
735 released: list[KVCacheBlock] = []
736 for block in blocks:
737 assert block.ref_cnt > 0 and not block.is_null
738 block.ref_cnt -= 1
739 self._reuse_watchers[block.block_id] = on_reuse
740 if block.ref_cnt == 0:
741 released.append(block)
742 self.free_block_queue.append_n(released)
743
744 def _maybe_evict_cached_block(self, block: KVCacheBlock) -> bool:
745 """If a block is cached in `cached_block_hash_to_block`, we reset its hash
746 metadata and evict it from the cache.
747
748 Args:
749 block: The block to evict.
750
751 Returns:
752 True if the block is evicted, False otherwise.
753
754 """
755 # Clean up metrics tracking first to prevent leaks
756 if self.metrics_collector:
757 self.metrics_collector.on_block_evicted(block)
758
759 evicted_hashes = self._remove_cached_block_hashes(block)
760 if not evicted_hashes:
761 # The block doesn't have hash, eviction is not needed
762 return False
763
764 self._emit_block_removed_events(evicted_hashes)
765 return True
766
767 def touch(self, blocks: Sequence[KVCacheBlock]) -> None:
768 """Touch a block increases its reference count by 1, and may remove
769 the block from the free queue. This is used when a block is hit by
770 another request with the same prefix.
771
772 Args:
773 blocks: A list of blocks to touch.
774
775 """
776 for block in blocks:
777 if block.is_null:
778 continue
779 # ref_cnt=0 means this block is in the free list (i.e. eviction
780 # candidate), so remove it.
781 if block.ref_cnt == 0:
782 self.free_block_queue.remove(block)
783 block.ref_cnt += 1
784 if self.metrics_collector:
785 self.metrics_collector.on_block_accessed(block)
786
787 def is_block_writable(self, block: KVCacheBlock) -> bool:
788 """Return whether a block can be mutated by its sole owner."""
789 return not block.is_null and block.ref_cnt == 1 and block.block_hash is None
790
791 def free_blocks(
792 self, ordered_blocks: Iterable[KVCacheBlock], evict_first: bool = False
793 ) -> None:
794 """Free a list of blocks. The blocks should be ordered by their
795 eviction priority, where the first block will be evicted first.
796
797 Args:
798 ordered_blocks: A list of blocks to free ordered by their eviction
799 priority.
800 evict_first: Also put freed cached blocks at the front of the free
801 queue, so they are evicted before other cached blocks. For
802 blocks whose contents another tier already holds.
803
804 """
805 # Identify blocks with hash (LRU cache) and without it (never match APC)
806 blocks_to_evict_last = []
807 blocks_to_evict_first = []
808 other_pools: dict[BlockPool, list[KVCacheBlock]] = {}
809 for block in ordered_blocks:
810 if block.is_null: # never pooled; its ref_cnt is not maintained
811 continue
812 if block.pool is not None and block.pool is not self._weak_self:
813 pool = block.pool()
814 if pool is not None:
815 other_pools.setdefault(pool, []).append(block)
816 continue
817 block.ref_cnt -= 1
818 if block.ref_cnt == 0:
819 if block.block_hash is None or not self.enable_caching or evict_first:
820 # LIFO reuse of non-cached blocks for better GPU locality,
821 # and of cached ones when asked to evict them first.
822 blocks_to_evict_first.append(block)
823 else:
824 # FIFO reuse of cached blocks for LRU eviction behavior.
825 blocks_to_evict_last.append(block)
826
827 # Blocks to reuse first are prepended to the front of the free queue.
828 self.free_block_queue.prepend_n(blocks_to_evict_first)
829 # Blocks to reuse last are appended to the end of the free queue.
830 self.free_block_queue.append_n(blocks_to_evict_last)