class_docstr: Allocator class for managing which blocks are free/used in the
  blocked KV-cache. This is a simple allocator that uses a linked list
  to keep track of which blocks are free/used. The cost of allocation/deallocation
  is O(blocks), where blocks is the number of blocks to allocate/deallocate.
  
  TODO(cmikeh2): Evaluate performance of this allocator and migrate
  to C++ if necessary.
docstr: Initialize an allocator with `num_blocks` blocks. This requires at least
  `num_blocks` * 4 bytes of host memory.
  
  Parameters:
      num_blocks (int): The number of blocks to allocate.
docstr_tok: ["Initialize","an","allocator","with","`","num_blocks","`","blocks",".","This","requires","at","least","`","num_blocks","`","*","4","bytes","of","host","memory",".","Parameters",":","num_blocks","(","int",")",":","The","number","of","blocks","to","allocate","."]
code: def __init__(self, num_blocks: int) -> None:
      """
      Initialize an allocator with `num_blocks` blocks. This requires at least
      `num_blocks` * 4 bytes of host memory.
  
      Parameters:
          num_blocks (int): The number of blocks to allocate.
      """
  
      if num_blocks < 1:
          raise ValueError(
              f"Blocked KV-cache must have at least 1 block, provided {num_blocks}"
          )
  
      self._num_blocks = num_blocks
      self._blocks = torch.arange(
          1,
          num_blocks + 1,
          dtype=torch.int32,
          device="cpu",
          pin_memory=True,
      )
      self._head = 0
      self._free_blocks = num_blocks
  
imports: from typing import Iterable, Union
  import torch
label_desc: Use image node_id 1 to create a new BlockedAllocator object  with example: obj = BlockedAllocator(num_blocks)