Fix CP HiCache catch_up_all_layers fallback on chunked-prefill final chunk
Under overlap scheduling a chunked request's final-chunk write-backup prepare read a stale `is_chunked` (>0): that per-tick counter is decremented in process_batch_result, which the overlap loop runs AFTER the run_batch prepare hook. So prepare floored `backup_end` to a page boundary (the intermediate-chunk rule) and dropped the now-complete final tail page, while the final non-chunked insert builds the radix node at full unaligned length. The exact-equality attach predicate (prepared.logical_len == len(value)) then failed by (num_tokens-1) mod page_size, dropped the per-layer overlap backup, and forced the serial all-layer catch-up (~89% of large chunked requests in prod). Decouple the floor decision from the stale counter: the scheduler marks `req.cp_backup_is_intermediate_chunk = (req is self.chunked_req)` in `_prepare_hicache_write_backups_before_forward` (the live chunked_req identity is the authoritative "will be chunked further" signal, set this tick before run_batch), and the prepare candidate builder floors on that flag instead of `is_chunked`. Intermediate chunks still floor; only the misclassified final chunk now backs up its full tail and attaches the overlap backup. Tests: update the chunked prepare test to the new flag; add a regression test that a final chunk with stale is_chunked reserves the full tail; add an offline repro that drives the real prepare/insert/probe and sweeps unaligned tails. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2990,6 +2990,23 @@ class Scheduler(
|
||||
):
|
||||
return
|
||||
|
||||
# Tell the CP HiCache backup prepare which req (if any) is a genuine
|
||||
# INTERMEDIATE chunk, i.e. will be prefilled further next round, so it
|
||||
# floors off its still-incomplete tail page; every other req in this
|
||||
# batch completes here and must back up its full (now-complete) tail.
|
||||
#
|
||||
# We mark it from the live `chunked_req` identity rather than letting
|
||||
# prepare read `req.is_chunked`, because `is_chunked` is a per-tick
|
||||
# counter whose decrement lags one iteration under overlap scheduling
|
||||
# (it is decremented in process_batch_result, which runs AFTER this
|
||||
# run_batch). On a request's final chunk it therefore reads stale (>0),
|
||||
# making prepare floor off the now-complete tail page -> prepared
|
||||
# logical_len != final radix node len -> the overlap backup is dropped
|
||||
# and the node falls back to the serial all-layer catch-up.
|
||||
carried_chunked_req = self.chunked_req
|
||||
for req in batch.reqs:
|
||||
req.cp_backup_is_intermediate_chunk = req is carried_chunked_req
|
||||
|
||||
prepare_batch_fn = getattr(
|
||||
self.tree_cache, "prepare_write_backups_for_reqs", None
|
||||
)
|
||||
|
||||
@@ -2581,12 +2581,22 @@ class HiRadixCache(RadixCache):
|
||||
token_ids = req.fill_ids
|
||||
keys = convert_to_bigram_key(token_ids) if self.is_eagle else token_ids
|
||||
backup_end = len(keys)
|
||||
if getattr(req, "is_chunked", 0) > 0 and self.page_size > 1:
|
||||
# Chunked prefill should still populate CP HiCache, but only for
|
||||
# pages whose KV has become fully available. Leaving the current
|
||||
# sub-page tail out of the prepared write avoids creating a
|
||||
# scheduler-visible stale tail that the next chunk has to prune
|
||||
# while the same request still owns/locks it.
|
||||
if getattr(req, "cp_backup_is_intermediate_chunk", False) and self.page_size > 1:
|
||||
# Only a genuine INTERMEDIATE chunk floors off its tail page: a later
|
||||
# chunk of the same request will extend it, so backing up that still
|
||||
# incomplete page now would create a scheduler-visible stale tail the
|
||||
# next chunk has to prune while this request still owns/locks it. The
|
||||
# FINAL chunk's tail page is complete and must be backed up in full,
|
||||
# to match the radix node that the final (non-chunked) insertion
|
||||
# builds -- otherwise prepared logical_len != len(value) and the
|
||||
# exact-equality attach predicate drops the overlap backup.
|
||||
#
|
||||
# NOTE: we must NOT key this off `req.is_chunked`. It is a per-tick
|
||||
# counter whose decrement lags one iteration under overlap scheduling
|
||||
# and so reads stale (>0) on the final chunk. The scheduler sets
|
||||
# `cp_backup_is_intermediate_chunk` from the live `chunked_req`
|
||||
# identity in `_prepare_hicache_write_backups_before_forward`, which
|
||||
# runs in the same run_batch immediately before this.
|
||||
backup_end = floor_to_page_len(backup_end, self.page_size)
|
||||
if backup_end <= 0:
|
||||
logger.debug(
|
||||
|
||||
Reference in New Issue
Block a user