Class RadixCache
Organizes KV cache activations in a compressed prefix tree (radix tree) so that requests sharing a common token prefix — such as a shared system prompt — can reuse already-computed KV entries without re-computation.
Each edge in the tree stores a contiguous span of token IDs together with
the corresponding GPU KV cache slot indices (held as an int64
Tensor). When a new request arrives the longest matching prefix is
found in O(|key|) time, and only the remaining suffix needs to be
computed.
Page alignment
WhenpageSize > 1 all key lengths are rounded down to the nearest
multiple of pageSize before matching or inserting, aligning them to
hardware KV cache block boundaries.
Namespace isolation
An optionalextraKey string (e.g., a LoRA adapter ID or cache salt)
can be attached to each insert/match call. Entries with different
extraKey values are kept in disjoint subtrees and never share prefix
nodes.
Reference counting and eviction
Every node carries alock reference count.
Calling incLockRef(RadixTreeNode) prevents a node and all its ancestors from being
evicted; decLockRef(RadixTreeNode) releases the protection. Eviction follows an
LRU policy applied to evictable leaves — nodes whose
lockRef is zero and that have no live (non-evicted) children.
Tensor ownership
EachRadixTreeNode owns its RadixTreeNode.value tensor and is
responsible for closing it when the node is evicted or split. The
MatchResult returned by matchPrefix(int[]) is AutoCloseable:
use try-with-resources to release the underlying tensor automatically.- See Also:
-
Constructor Summary
ConstructorsConstructorDescriptionConstructs a cache with the default page size of 1.RadixCache(int pageSize) Constructor. -
Method Summary
Modifier and TypeMethodDescriptionintdecLockRef(RadixTreeNode node) Decrements the lock reference count onnodeand all its ancestors up to (but not including) the root.intEvicts LRU leaf nodes until at leastnumTokensKV cache token slots have been freed, callingfreeCallbackfor each evicted node'sTensor.intReturns the total number of evictable (unlocked) tokens currently cached.intincLockRef(RadixTreeNode node) Increments the lock reference count onnodeand all its ancestors up to (but not including) the root, protecting them from eviction.Inserts a token sequence and its KV cache slot indices into the radix tree.Inserts a token sequence and its KV cache slot indices into the radix tree.matchPrefix(int[] tokenIds) Finds the longest cached prefix of the given token sequence.matchPrefix(int[] tokenIds, String extraKey) Finds the longest cached prefix of the given token sequence within a namespace.voidPrints the radix tree structure to stdout for debugging.intReturns the total number of protected (locked) tokens currently cached.voidreset()Resets the cache, discarding all stored KV data and reference counts.intReturns the total number of tokens in the cache (evictable + protected).
-
Constructor Details
-
RadixCache
public RadixCache(int pageSize) Constructor.- Parameters:
pageSize- the number of tokens per KV cache page (must be ≥ 1).- Throws:
IllegalArgumentException- ifpageSize < 1.
-
RadixCache
public RadixCache()Constructs a cache with the default page size of 1.
-
-
Method Details
-
reset
public void reset()Resets the cache, discarding all stored KV data and reference counts. Every liveRadixTreeNode.valuetensor is closed before the tree is cleared. The root sentinel is re-created with an empty key. -
matchPrefix
Finds the longest cached prefix of the given token sequence. Equivalent to callingmatchPrefix(int[], String)with anullextra key.- Parameters:
tokenIds- the token ID sequence to look up.- Returns:
- the match result. Use try-with-resources to release the enclosed
tensor automatically, or call
MatchResult.close()explicitly.
-
matchPrefix
Finds the longest cached prefix of the given token sequence within a namespace.The search is limited to nodes whose
extraKeymatches the supplied value, so entries from different namespaces are never confused.If the match ends inside a stored node (partial match), the node is automatically split at the boundary, improving future lookups. The evictable size is unchanged because the total number of tokens is conserved.
This call refreshes the
RadixTreeNode.lastAccessTimeof every visited node, feeding the LRU eviction policy.- Parameters:
tokenIds- the token ID sequence to look up.extraKey- optional namespace tag, ornullfor the default namespace.- Returns:
- the match result. Use try-with-resources to release the enclosed
tensor automatically, or call
MatchResult.close()explicitly.
-
insert
Inserts a token sequence and its KV cache slot indices into the radix tree. Equivalent to callinginsert(int[], Tensor, String, int)with anullextra key and priority 0.- Parameters:
tokenIds- the token ID sequence.kvIndices- a 1-Dint64tensor of KV cache slot indices, one per token (kvIndices.length() >= tokenIds.length).- Returns:
- the insert result.
-
insert
Inserts a token sequence and its KV cache slot indices into the radix tree.The insert walks the tree consuming as many tokens as match existing edges. Existing nodes that partially overlap with the new key are split. The page-aligned portion of the key not yet in the tree is stored as a new leaf node backed by a freshly-allocated
int64tensor.The returned
InsertResult.prefixLen()indicates how many leading tokens were already cached. The caller can free the duplicate KV slot entries for those positions (indices[0, prefixLen)).- Parameters:
tokenIds- the token ID sequence to cache.kvIndices- a 1-Dint64tensor of KV cache slot indices (kvIndices.length() >= tokenIds.length).extraKey- optional namespace tag, ornullfor the default namespace.priority- eviction priority; higher values delay eviction.- Returns:
- the insert result.
-
evict
Evicts LRU leaf nodes until at leastnumTokensKV cache token slots have been freed, callingfreeCallbackfor each evicted node'sTensor.Eviction always starts from the least-recently-used evictable leaf. When a leaf is evicted its parent may itself become a new evictable leaf (if all other children were already evicted) and is then eligible for further eviction in the same call.
The
freeCallbackreceives ownership of the tensor and is responsible for releasing it (e.g., by callingTensor.close()). IffreeCallbackisnullthe tensor is closed immediately.- Parameters:
numTokens- the minimum number of token slots to reclaim.freeCallback- invoked with theint64KV index tensor of each evicted node. May benull.- Returns:
- the actual number of token slots freed (may exceed
numTokens).
-
incLockRef
Increments the lock reference count onnodeand all its ancestors up to (but not including) the root, protecting them from eviction.Nodes transition from the evictable pool to the protected pool as their lock count becomes positive for the first time.
- Parameters:
node- the node to protect (may benull, which is a no-op).- Returns:
- the change in
evictableSize(always ≤ 0).
-
decLockRef
Decrements the lock reference count onnodeand all its ancestors up to (but not including) the root. When a node's count reaches zero it re-enters the evictable pool and becomes eligible for LRU eviction.- Parameters:
node- the node to release (may benull, which is a no-op).- Returns:
- the change in
evictableSize(always ≥ 0).
-
evictableSize
public int evictableSize()Returns the total number of evictable (unlocked) tokens currently cached.- Returns:
- evictable token count.
-
protectedSize
public int protectedSize()Returns the total number of protected (locked) tokens currently cached.- Returns:
- protected token count.
-
totalSize
public int totalSize()Returns the total number of tokens in the cache (evictable + protected). Traverses the live (non-evicted) subtree iteratively.- Returns:
- total cached token count.
-
prettyPrint
public void prettyPrint()Prints the radix tree structure to stdout for debugging.
-