Floor CP HiCache tail splits to page boundaries
CP HiCache ownership is page-granular, so a backed radix node must not be split inside a padded physical tail page. When a shorter hit would require an interior tail-page boundary, matching now floors to the previous page boundary and sacrifices the sub-page cache prefix instead of splitting ownership metadata. Constraint: Host/device/draft CP HiCache metadata tracks page owners and padded physical spans. Rejected: Split one padded tail page across two radix nodes | it would require half-page ownership semantics and risks double-counting capacity. Confidence: high Scope-risk: moderate Directive: Keep exact valid-tail hits, but floor partial backed-node splits to page boundaries unless metadata gains explicit sub-page ownership. Tested: Remote py_compile for hiradix_cache.py and test_cp_hicache_metadata.py in g0034 container. Tested: Remote pytest targeted backed-tail split tests plus exact valid-tail hit test: 3 passed. Tested: Remote pytest test_cp_hicache_metadata.py test_cp_hicache_load_back_owner_lanes.py: 92 passed, 5 warnings. Not-tested: Live ETE traffic under divergent short-prefix prompts. Co-authored-by: OmX <omx@oh-my-codex.dev>
This commit is contained in:
@@ -648,13 +648,18 @@ Implemented C7 slice:
|
||||
- `_key_match_paged()` now returns the true valid prefix length for a partial
|
||||
final page.
|
||||
|
||||
Remaining C7 limitation:
|
||||
Implemented C7 split policy:
|
||||
|
||||
- Splitting an already-backed CP HiCache node inside a padded tail page still
|
||||
needs a deliberate design. The current safe slice supports exact valid-tail
|
||||
hits and writes; divergent requests that force a split inside a physical tail
|
||||
page are still a follow-up because splitting one physical host page across two
|
||||
radix nodes would otherwise double-count or lose ownership metadata.
|
||||
- Cache management treats a page as the minimum ownership unit. If matching an
|
||||
already-backed CP HiCache node would split inside a padded physical tail page,
|
||||
the match is floored to the previous page boundary instead of splitting that
|
||||
physical page.
|
||||
- Exact valid-tail hits still report the valid length. The floor policy only
|
||||
applies when a shorter/divergent request would require a new radix boundary
|
||||
inside the tail page.
|
||||
- If the floored boundary is zero, the match returns the parent/root node and
|
||||
sacrifices the sub-page prefix. This avoids half-page owner accounting and
|
||||
keeps host/device/draft metadata page-granular.
|
||||
|
||||
### C8. Owner-lane capacity must be padded-page based end to end
|
||||
|
||||
|
||||
@@ -3350,6 +3350,15 @@ class HiRadixCache(RadixCache):
|
||||
and child.id in getattr(self, "pending_host_backups", {})
|
||||
):
|
||||
raise HiCachePendingBackupSplit(child)
|
||||
if (
|
||||
self._uses_cp_hicache
|
||||
and self.page_size > 1
|
||||
and self._node_backuped(child)
|
||||
and prefix_len % self.page_size != 0
|
||||
):
|
||||
prefix_len = prefix_len // self.page_size * self.page_size
|
||||
if prefix_len == 0:
|
||||
break
|
||||
new_node = self._split_node(child.key, child, prefix_len)
|
||||
if not new_node.evicted:
|
||||
value.append(new_node.value)
|
||||
|
||||
@@ -2339,6 +2339,104 @@ class TestHiRadixCacheCPLoadBack(CustomTestCase):
|
||||
self.assertIs(result.last_device_node, root)
|
||||
self.assertIs(result.last_host_node, node)
|
||||
|
||||
def test_cp_backed_tail_split_floors_to_page_boundary(self):
|
||||
cache = HiRadixCache.__new__(HiRadixCache)
|
||||
cache._uses_cp_hicache = True
|
||||
cache.device = "cpu"
|
||||
cache.disable = False
|
||||
cache.page_size = 4
|
||||
cache.ongoing_write_through = {}
|
||||
cache.pending_host_backups = {}
|
||||
cache.cache_controller = types.SimpleNamespace(has_draft_hicache=False)
|
||||
cache.get_child_key_fn = lambda key: tuple(key.token_ids[:4])
|
||||
cache.key_match_fn = lambda child_key, key: _key_match_paged(
|
||||
child_key, key, page_size=4
|
||||
)
|
||||
cache.maybe_bigram_convert = lambda key: (key, None)
|
||||
root = TreeNode()
|
||||
root.key = RadixKey([])
|
||||
root.value = torch.empty((0,), dtype=torch.int64)
|
||||
root.host_len = 0
|
||||
root.children = {}
|
||||
cache.root_node = root
|
||||
node = TreeNode()
|
||||
node.id = 141
|
||||
node.parent = root
|
||||
node.key = RadixKey(list(range(6)))
|
||||
node.value = None
|
||||
node.host_value = None
|
||||
node.host_len = 6
|
||||
node.cp_hicache = CpHiCacheNodeMetadata(
|
||||
logical_len=6,
|
||||
padded_len=8,
|
||||
owned_positions=torch.tensor([0, 1, 2, 3], dtype=torch.int64),
|
||||
host_indices=torch.tensor([50, 51, 52, 53], dtype=torch.int64),
|
||||
page_owners=torch.tensor([0, 1], dtype=torch.int8),
|
||||
page_size=4,
|
||||
)
|
||||
root.children[(0, 1, 2, 3)] = node
|
||||
|
||||
result = cache.match_prefix(MatchPrefixParams(key=RadixKey([0, 1, 2, 3, 4])))
|
||||
|
||||
self.assertEqual(result.device_indices.tolist(), [])
|
||||
self.assertEqual(result.host_hit_length, 4)
|
||||
self.assertIs(result.last_device_node, root)
|
||||
self.assertEqual(result.last_host_node.key.token_ids, [0, 1, 2, 3])
|
||||
self.assertEqual(result.last_host_node.host_len, 4)
|
||||
self.assertEqual(result.last_host_node.cp_hicache.logical_len, 4)
|
||||
self.assertEqual(result.last_host_node.cp_hicache.padded_len, 4)
|
||||
child = result.last_host_node.children[(4, 5)]
|
||||
self.assertEqual(child.key.token_ids, [4, 5])
|
||||
self.assertEqual(child.host_len, 2)
|
||||
self.assertEqual(child.cp_hicache.logical_len, 2)
|
||||
self.assertEqual(child.cp_hicache.padded_len, 4)
|
||||
|
||||
def test_cp_backed_tail_split_before_first_page_returns_root_match(self):
|
||||
cache = HiRadixCache.__new__(HiRadixCache)
|
||||
cache._uses_cp_hicache = True
|
||||
cache.device = "cpu"
|
||||
cache.disable = False
|
||||
cache.page_size = 4
|
||||
cache.ongoing_write_through = {}
|
||||
cache.pending_host_backups = {}
|
||||
cache.cache_controller = types.SimpleNamespace(has_draft_hicache=False)
|
||||
cache.get_child_key_fn = lambda key: tuple(key.token_ids[:4])
|
||||
cache.key_match_fn = lambda child_key, key: _key_match_paged(
|
||||
child_key, key, page_size=4
|
||||
)
|
||||
cache.maybe_bigram_convert = lambda key: (key, None)
|
||||
root = TreeNode()
|
||||
root.key = RadixKey([])
|
||||
root.value = torch.empty((0,), dtype=torch.int64)
|
||||
root.host_len = 0
|
||||
root.children = {}
|
||||
cache.root_node = root
|
||||
node = TreeNode()
|
||||
node.id = 142
|
||||
node.parent = root
|
||||
node.key = RadixKey(list(range(6)))
|
||||
node.value = None
|
||||
node.host_value = None
|
||||
node.host_len = 6
|
||||
node.cp_hicache = CpHiCacheNodeMetadata(
|
||||
logical_len=6,
|
||||
padded_len=8,
|
||||
owned_positions=torch.tensor([0, 1, 2, 3], dtype=torch.int64),
|
||||
host_indices=torch.tensor([50, 51, 52, 53], dtype=torch.int64),
|
||||
page_owners=torch.tensor([0, 1], dtype=torch.int8),
|
||||
page_size=4,
|
||||
)
|
||||
root.children[(0, 1, 2, 3)] = node
|
||||
|
||||
result = cache.match_prefix(MatchPrefixParams(key=RadixKey([0, 1, 2])))
|
||||
|
||||
self.assertEqual(result.device_indices.tolist(), [])
|
||||
self.assertEqual(result.host_hit_length, 0)
|
||||
self.assertIs(result.last_device_node, root)
|
||||
self.assertIs(result.last_host_node, root)
|
||||
self.assertIs(root.children[(0, 1, 2, 3)], node)
|
||||
self.assertEqual(node.key.token_ids, list(range(6)))
|
||||
|
||||
def test_non_cp_match_prefix_uses_root_when_no_host_backup_exists(self):
|
||||
cache = HiRadixCache.__new__(HiRadixCache)
|
||||
cache._uses_cp_hicache = False
|
||||
|
||||
Reference in New Issue
Block a user