[HiCache] buffer mode: decide staged-fetch fate against the live tree (#36834)
This commit is contained in:
@@ -3549,16 +3549,18 @@ class Scheduler(
|
||||
# Buffer mode: surface a staged prefetch as the request's host
|
||||
# hit (consumed through init_load_back) plus its SWA window,
|
||||
# which consumption allocates and the request lock pins —
|
||||
# uncharged, the batch alloc can OOM. Set AFTER
|
||||
# uncharged, the batch alloc can OOM. Planned against the same
|
||||
# live prefix admission uses, so only the splice-able span
|
||||
# tail is charged and unusable holds are freed. Set AFTER
|
||||
# init_next_round_input (which recomputes host_hit). Mamba
|
||||
# (fenced in init_hicache) will need the same charge via
|
||||
# mamba_host_hit_length.
|
||||
held_tokens = self.tree_cache.staged_prefetch_tokens(req.rid)
|
||||
held_tokens, held_swa_tokens = self.tree_cache.plan_staged_splice(
|
||||
req.rid, len(req.prefix_indices)
|
||||
)
|
||||
if held_tokens > 0:
|
||||
req.host_hit_length = held_tokens
|
||||
req.swa_host_hit_length = (
|
||||
self.tree_cache.staged_prefetch_swa_tokens(req.rid)
|
||||
)
|
||||
req.swa_host_hit_length = held_swa_tokens
|
||||
res = adder.add_one_req(
|
||||
req,
|
||||
has_chunked_req=(self.chunked_req is not None),
|
||||
|
||||
@@ -148,6 +148,21 @@ def _untrack_content_refs(refs: dict[str, int], hash_values: list[str]) -> None:
|
||||
refs[h] = n
|
||||
|
||||
|
||||
def staged_splice_tokens(f: _StagedPrefetch, device_prefix_len: int) -> int:
|
||||
"""Tokens a staged prefetch can still splice beyond the live device
|
||||
prefix; 0 = unusable hold (prefix shrunk below the span, span fully
|
||||
device-resident, or the trim would cut into a staged aux trailing
|
||||
window — aux pools splice whole or not at all)."""
|
||||
span_end = f.matched_len + f.num_tokens
|
||||
if device_prefix_len < f.matched_len or device_prefix_len >= span_end:
|
||||
return 0
|
||||
splice_tokens = span_end - device_prefix_len
|
||||
for t in f.aux_xfers:
|
||||
if t.host_indices is not None and t.host_indices.numel() > splice_tokens:
|
||||
return 0
|
||||
return splice_tokens
|
||||
|
||||
|
||||
def validate_buffer_only_stack(
|
||||
sidecar_pool_specs: list, swa_component: Optional[SWAComponent]
|
||||
) -> None:
|
||||
@@ -239,7 +254,9 @@ class BufferModePipeline:
|
||||
# prefill admission, and load-backs in flight (keyed by synthetic
|
||||
# negative ack id).
|
||||
self.pending_hit_allocs: deque = deque()
|
||||
self._prefetch_prefix_ctx: dict[str, list[int]] = {}
|
||||
self._prefetch_prefix_ctx: dict[
|
||||
str, tuple[list[int], Optional[str], Optional[str]]
|
||||
] = {}
|
||||
self.staged_prefetches: dict[str, _StagedPrefetch] = {}
|
||||
self.ongoing_buffer_load_back: dict[int, _OngoingBufferLoadBack] = {}
|
||||
# Backup pipeline: FIFO intents awaiting a D2H slot, node ids
|
||||
@@ -622,15 +639,21 @@ class BufferModePipeline:
|
||||
|
||||
# ---- load back pipeline (storage -> staging -> device) ----
|
||||
|
||||
def try_lock_anchor(self, req_id: str, anchor_node_id: NodeId) -> None:
|
||||
"""Pin the device anchor at IO commit so eviction cannot invalidate
|
||||
the splice; drift resolves at consumption, and the cap keeps queued
|
||||
holds from making the pool unevictable (over-cap launches unlocked)."""
|
||||
if not self.anchor_lock_enabled or req_id in self.anchor_locks:
|
||||
return
|
||||
prefix_tokens = self._prefetch_prefix_ctx.get(req_id)
|
||||
if not prefix_tokens:
|
||||
return # root anchor: nothing to pin
|
||||
def try_lock_anchor(self, req_id: str) -> str:
|
||||
"""Pin the staged prefetch's device anchor so eviction cannot
|
||||
invalidate the splice, finding it by re-matching the live tree
|
||||
(carried node ids go stale via splits and eviction; the walk is
|
||||
O(prefix path)). Returns "locked", "no_anchor" (nothing to pin),
|
||||
"cap_skip" (over cap; launches unlocked), or "anchor_lost" (splice
|
||||
base gone — the caller cancels the storage IO)."""
|
||||
if not self.anchor_lock_enabled:
|
||||
return "no_anchor"
|
||||
if req_id in self.anchor_locks:
|
||||
return "locked"
|
||||
prefix_ctx = self._prefetch_prefix_ctx.get(req_id)
|
||||
if not prefix_ctx or not prefix_ctx[0]:
|
||||
return "no_anchor" # root anchor: nothing to pin
|
||||
prefix_tokens, extra_key, cache_salt = prefix_ctx
|
||||
matched_len = len(prefix_tokens)
|
||||
if self.anchor_locked_tokens_ + matched_len > self.anchor_lock_cap_tokens:
|
||||
self._anchor_lock_cap_skips += 1
|
||||
@@ -646,23 +669,28 @@ class BufferModePipeline:
|
||||
matched_len,
|
||||
self.anchor_lock_cap_tokens,
|
||||
)
|
||||
return
|
||||
return "cap_skip"
|
||||
cache = self._cache
|
||||
try:
|
||||
node = cache.tree_core.node_by_id(anchor_node_id)
|
||||
except KeyError:
|
||||
return # anchor deleted; fetch unlocked
|
||||
if node.component_data[BASE_COMPONENT_TYPE].value is None:
|
||||
# Evicted since enqueue; fetch unlocked.
|
||||
logger.warning("HiCache anchor evicted before IO commit req=%s", req_id)
|
||||
return
|
||||
lock_params = cache.inc_lock_ref(anchor_node_id).to_dec_params()
|
||||
match = cache.match_prefix(
|
||||
MatchPrefixParams(
|
||||
key=RadixKey(
|
||||
array("q", prefix_tokens),
|
||||
extra_key=extra_key,
|
||||
is_bigram=cache.tree_core.is_eagle,
|
||||
cache_salt=cache_salt,
|
||||
)
|
||||
)
|
||||
)
|
||||
if len(match.device_indices) < matched_len:
|
||||
return "anchor_lost"
|
||||
lock_params = cache.inc_lock_ref(match.last_device_node).to_dec_params()
|
||||
self.anchor_locks[req_id] = _AnchorLock(
|
||||
node_id=anchor_node_id,
|
||||
node_id=match.last_device_node,
|
||||
lock_params=lock_params,
|
||||
tokens=matched_len,
|
||||
)
|
||||
self.anchor_locked_tokens_ += matched_len
|
||||
return "locked"
|
||||
|
||||
def release_anchor_lock(self, req_id: str) -> None:
|
||||
"""Drop a staged prefetch's anchor lock (idempotent; called at every
|
||||
@@ -677,10 +705,42 @@ class BufferModePipeline:
|
||||
f"after releasing {req_id}"
|
||||
)
|
||||
|
||||
def set_prefix_ctx(self, req_id: str, matched_prefix_tokens) -> None:
|
||||
"""Record the device-matched prefix at prefetch enqueue; consumed at
|
||||
staging commit to build the full-span tree key."""
|
||||
self._prefetch_prefix_ctx[req_id] = list(matched_prefix_tokens or [])
|
||||
def staged_span_covered(self, req_id: str, span_tokens: int) -> bool:
|
||||
"""True when the live device tree already covers the fetch's whole
|
||||
would-be span (prefix + the storage-hit tokens): nothing would be
|
||||
left to splice at consumption, so the IO-commit caller cancels
|
||||
before the bounce alloc and the storage read."""
|
||||
info = self._cache.ongoing_prefetch.get(req_id)
|
||||
if info is None or span_tokens <= 0:
|
||||
return False
|
||||
prefix_tokens, _, _ = self._prefetch_prefix_ctx[req_id]
|
||||
span_key = info.prefetch_key
|
||||
full_tokens = array("q", prefix_tokens)
|
||||
full_tokens.extend(span_key[:span_tokens].token_ids)
|
||||
key = RadixKey(
|
||||
full_tokens,
|
||||
extra_key=span_key.extra_key,
|
||||
is_bigram=self._cache.tree_core.is_eagle,
|
||||
cache_salt=span_key.cache_salt,
|
||||
)
|
||||
match = self._cache.match_prefix(MatchPrefixParams(key=key))
|
||||
return len(match.device_indices) >= len(key)
|
||||
|
||||
def set_prefix_ctx(
|
||||
self,
|
||||
req_id: str,
|
||||
matched_prefix_tokens,
|
||||
extra_key: Optional[str] = None,
|
||||
cache_salt: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Record the device-matched prefix (and its tree-key namespace) at
|
||||
prefetch enqueue; consumed at staging commit to build the full-span
|
||||
tree key, and by try_lock_anchor to re-match a stale anchor."""
|
||||
self._prefetch_prefix_ctx[req_id] = (
|
||||
list(matched_prefix_tokens or []),
|
||||
extra_key,
|
||||
cache_salt,
|
||||
)
|
||||
|
||||
def pop_prefix_ctx(self, req_id: str) -> None:
|
||||
self._prefetch_prefix_ctx.pop(req_id, None)
|
||||
@@ -713,7 +773,8 @@ class BufferModePipeline:
|
||||
comp_xfers,
|
||||
) = cache.ongoing_prefetch.pop(req_id)
|
||||
cc = cache.cache_controller
|
||||
prefix_tokens = self._prefetch_prefix_ctx.pop(req_id, None)
|
||||
prefix_ctx = self._prefetch_prefix_ctx.pop(req_id, None)
|
||||
prefix_tokens = prefix_ctx[0] if prefix_ctx is not None else None
|
||||
aux_xfers = [x for xfers in comp_xfers.values() for x in xfers]
|
||||
|
||||
if num_tokens == 0 or prefix_tokens is None:
|
||||
@@ -751,11 +812,29 @@ class BufferModePipeline:
|
||||
cache.prefetch_loaded_tokens_by_reqid[req_id] = num_tokens
|
||||
return True
|
||||
|
||||
def staged_prefetch_tokens(self, req_id: str) -> int:
|
||||
"""Tokens a staged prefetch would splice (0 = no hold); surfaced by the
|
||||
scheduler as the request's host_hit_length."""
|
||||
def plan_staged_splice(
|
||||
self, req_id: str, device_prefix_len: int
|
||||
) -> tuple[int, int]:
|
||||
"""(kv, swa) host-hit tokens consumption will splice given the
|
||||
request's live device prefix, so admission charges no phantom
|
||||
tokens. Frees a hold that can no longer splice: surfaced as 0 but
|
||||
kept, it would leak — the adder only consumes surfaced host hits."""
|
||||
f = self.staged_prefetches.get(req_id)
|
||||
return f.num_tokens if f is not None else 0
|
||||
if f is None:
|
||||
return 0, 0
|
||||
splice_tokens = staged_splice_tokens(f, device_prefix_len)
|
||||
if splice_tokens == 0:
|
||||
logger.info(
|
||||
"HiCache staged prefetch released req=%s matched=%d "
|
||||
"device_prefix=%d tokens=%d",
|
||||
req_id,
|
||||
f.matched_len,
|
||||
device_prefix_len,
|
||||
f.num_tokens,
|
||||
)
|
||||
self.release_staged_hold(req_id)
|
||||
return 0, 0
|
||||
return splice_tokens, self.staged_prefetch_swa_tokens(req_id)
|
||||
|
||||
def staged_prefetch_swa_tokens(self, req_id: str) -> int:
|
||||
"""SWA device tokens consuming this staged prefetch will allocate (the
|
||||
@@ -773,7 +852,9 @@ class BufferModePipeline:
|
||||
def init_load_back(self, params: InitLoadBackParams) -> tuple[torch.Tensor, NodeId]:
|
||||
"""Consume the staged prefetch at prefill admission: device alloc,
|
||||
layer-gated H2D, and a plain insert so downstream sees ordinary tree
|
||||
state; invalid holds drop and the request recomputes.
|
||||
state. The splice base is the request's live device prefix — growth
|
||||
trims to the span tail beyond it; unusable holds drop and the
|
||||
request recomputes.
|
||||
|
||||
Ownership contract: cc.load queues the H2D before insert adjudicates
|
||||
ownership, so the live pre-checks below must prove the insert can
|
||||
@@ -794,6 +875,9 @@ class BufferModePipeline:
|
||||
self.release_anchor_lock(req.rid)
|
||||
self._free_staging_now(f.host_indices, f.aux_xfers)
|
||||
cc.prefetch_tokens_occupied -= f.occupied_tokens
|
||||
# Nothing spliced: keep the surfaced host-hit fields truthful.
|
||||
req.host_hit_length = 0
|
||||
req.swa_host_hit_length = 0
|
||||
return unchanged
|
||||
|
||||
# A hold staged under a different namespace than the consuming request
|
||||
@@ -809,20 +893,24 @@ class BufferModePipeline:
|
||||
)
|
||||
return _drop()
|
||||
|
||||
# Splice-validity: the span only fits if the device prefix still
|
||||
# ends exactly at the enqueue-time matched_len.
|
||||
if len(req.prefix_indices) != f.matched_len:
|
||||
splice_base = len(req.prefix_indices)
|
||||
splice_tokens = staged_splice_tokens(f, splice_base)
|
||||
if splice_tokens == 0:
|
||||
logger.warning(
|
||||
"HiCache staged prefetch dropped req=%s reason=%s matched=%d "
|
||||
"now=%d tokens_wasted=%d locked=%s",
|
||||
"HiCache staged prefetch dropped req=%s matched=%d now=%d "
|
||||
"tokens_wasted=%d locked=%s",
|
||||
req.rid,
|
||||
"growth" if len(req.prefix_indices) > f.matched_len else "shrink",
|
||||
f.matched_len,
|
||||
len(req.prefix_indices),
|
||||
splice_base,
|
||||
f.num_tokens,
|
||||
req.rid in self.anchor_locks,
|
||||
)
|
||||
return _drop()
|
||||
trim_tokens = splice_base - f.matched_len
|
||||
assert trim_tokens % cache.page_size == 0, (
|
||||
f"staged splice trim not page-aligned req={req.rid}: "
|
||||
f"matched={f.matched_len} splice_base={splice_base}"
|
||||
)
|
||||
|
||||
key = RadixKey(
|
||||
array("q", f.key_tokens),
|
||||
@@ -832,20 +920,21 @@ class BufferModePipeline:
|
||||
).page_aligned(cache.page_size)
|
||||
span_end = f.matched_len + f.num_tokens
|
||||
|
||||
# Live ownership pre-check: the unified length detects anchor drift,
|
||||
# Live ownership pre-check at the splice base: the unified length
|
||||
# detects a stale request view (req matched before a later publish),
|
||||
# full_kv_hit_length detects FULL overlap the insert would dedup-free
|
||||
# (an SWA tombstone can mask live FULL from the unified match alone).
|
||||
live = cache.match_prefix(MatchPrefixParams(key=key))
|
||||
if (
|
||||
len(live.device_indices) != f.matched_len
|
||||
or live.full_kv_hit_length != f.matched_len
|
||||
len(live.device_indices) != splice_base
|
||||
or live.full_kv_hit_length != splice_base
|
||||
):
|
||||
logger.warning(
|
||||
"HiCache staged prefetch dropped req=%s reason=overlap "
|
||||
"matched=%d live_unified=%d live_full=%d tokens_wasted=%d "
|
||||
"splice_base=%d live_unified=%d live_full=%d tokens_wasted=%d "
|
||||
"locked=%s",
|
||||
req.rid,
|
||||
f.matched_len,
|
||||
splice_base,
|
||||
len(live.device_indices),
|
||||
live.full_kv_hit_length,
|
||||
f.num_tokens,
|
||||
@@ -859,20 +948,20 @@ class BufferModePipeline:
|
||||
avail = cache.token_to_kv_pool_allocator.full_available_size()
|
||||
else:
|
||||
avail = cache.token_to_kv_pool_allocator.available_size()
|
||||
if avail < f.num_tokens:
|
||||
needed = f.num_tokens - avail
|
||||
if avail < splice_tokens:
|
||||
needed = splice_tokens - avail
|
||||
cache.evict_for_alloc(EvictParams(num_tokens=needed))
|
||||
if cache.supports_swa():
|
||||
avail = cache.token_to_kv_pool_allocator.full_available_size()
|
||||
else:
|
||||
avail = cache.token_to_kv_pool_allocator.available_size()
|
||||
if avail < f.num_tokens:
|
||||
if avail < splice_tokens:
|
||||
# Genuinely no room (locked pages): recompute.
|
||||
return _drop()
|
||||
|
||||
load_back_id = -(f.operation_id) - 1
|
||||
device_indices = cc.load(
|
||||
host_indices=f.host_indices,
|
||||
host_indices=f.host_indices[trim_tokens:],
|
||||
node_id=load_back_id,
|
||||
extra_pools=f.aux_xfers or None,
|
||||
)
|
||||
@@ -905,7 +994,7 @@ class BufferModePipeline:
|
||||
InsertParams(
|
||||
key=key,
|
||||
value=torch.cat([req.prefix_indices, device_indices]),
|
||||
prev_prefix_len=f.matched_len,
|
||||
prev_prefix_len=splice_base,
|
||||
swa_evicted_seqlen=(
|
||||
max(0, span_end - len(swa_dev)) if swa_dev is not None else 0
|
||||
),
|
||||
@@ -913,15 +1002,17 @@ class BufferModePipeline:
|
||||
)
|
||||
self.ongoing_buffer_load_back[load_back_id] = _OngoingBufferLoadBack(
|
||||
req_id=f.req_id,
|
||||
num_tokens=f.num_tokens,
|
||||
num_tokens=splice_tokens,
|
||||
occupied_tokens=f.occupied_tokens,
|
||||
aux_xfers=f.aux_xfers,
|
||||
# The full staged bounce (not the trimmed H2D source): the ack
|
||||
# frees it whole, trimmed head included.
|
||||
host_indices=f.host_indices,
|
||||
hash_values=f.hash_values,
|
||||
)
|
||||
m = cache.match_prefix(MatchPrefixParams(key=key))
|
||||
self.release_anchor_lock(req.rid)
|
||||
canonical = m.device_indices[f.matched_len : span_end]
|
||||
canonical = m.device_indices[splice_base:span_end]
|
||||
if len(m.device_indices) < span_end or not torch.equal(
|
||||
canonical, device_indices
|
||||
):
|
||||
@@ -930,7 +1021,7 @@ class BufferModePipeline:
|
||||
raise RuntimeError(
|
||||
f"HiCache buffer load-back ownership violation req={f.req_id}: "
|
||||
f"insert prefix_len={insert_result.prefix_len} "
|
||||
f"expected={f.matched_len}, adopted={len(m.device_indices)} "
|
||||
f"expected={splice_base}, adopted={len(m.device_indices)} "
|
||||
f"span_end={span_end}, canonical_matches_incoming="
|
||||
f"{len(m.device_indices) >= span_end and torch.equal(canonical, device_indices)}; "
|
||||
f"in-flight H2D targets freed slots"
|
||||
@@ -965,9 +1056,11 @@ class BufferModePipeline:
|
||||
cache.storage_metrics_collector.log_prefetched_tokens(f.num_tokens)
|
||||
return True
|
||||
|
||||
def release_aborted_staged(self, rid: str) -> bool:
|
||||
"""Free an aborted request's staged prefetch (nothing device-side
|
||||
exists yet — only the bounce). Returns True when a hold existed."""
|
||||
def release_staged_hold(self, rid: str) -> bool:
|
||||
"""Free a staged hold outright — anchor pin, host bounce (KV + aux),
|
||||
occupancy grant; nothing device-side exists yet. Called for aborts
|
||||
and for holds that can no longer splice. Returns True when a hold
|
||||
existed."""
|
||||
self.release_anchor_lock(rid)
|
||||
staged = self.staged_prefetches.pop(rid, None)
|
||||
if staged is None:
|
||||
|
||||
@@ -254,6 +254,8 @@ class UnifiedRadixCache(BasePrefixCache):
|
||||
"issued": 0,
|
||||
"declined_too_short": 0,
|
||||
"declined_rate_limited": 0,
|
||||
"declined_anchor_lost": 0,
|
||||
"declined_device_covered": 0,
|
||||
"revoked_insufficient": 0,
|
||||
"revoked_full_miss": 0,
|
||||
"l3_demand_requests": 0,
|
||||
@@ -1788,11 +1790,16 @@ class UnifiedRadixCache(BasePrefixCache):
|
||||
comp_xfers,
|
||||
)
|
||||
if buffer_mode:
|
||||
self.buffer_pipeline.set_prefix_ctx(req_id, matched_prefix_tokens)
|
||||
# Pin the just-matched anchor now: deferred to hit-alloc it is
|
||||
# often already deleted under churn (silent unlocked launch).
|
||||
# The hit-alloc call remains as an idempotent second chance.
|
||||
self.buffer_pipeline.try_lock_anchor(req_id, last_host_node_id)
|
||||
self.buffer_pipeline.set_prefix_ctx(
|
||||
req_id,
|
||||
matched_prefix_tokens,
|
||||
extra_key=extra_key,
|
||||
cache_salt=cache_salt,
|
||||
)
|
||||
# Pin the just-matched anchor now: deferred to IO commit it is
|
||||
# often already deleted under churn. The IO-commit call remains
|
||||
# as the second chance that decides the fetch's fate.
|
||||
self.buffer_pipeline.try_lock_anchor(req_id)
|
||||
else:
|
||||
# Cache mode reserves the requested span up front; buffer mode
|
||||
# grants occupancy later at hit-alloc time, sized to the hit.
|
||||
@@ -2054,12 +2061,14 @@ class UnifiedRadixCache(BasePrefixCache):
|
||||
return True
|
||||
return False
|
||||
|
||||
def staged_prefetch_tokens(self, req_id: str) -> int:
|
||||
"""Tokens a staged buffer-mode prefetch would splice (0 = no hold);
|
||||
surfaced by the scheduler as the request's host_hit_length."""
|
||||
def plan_staged_splice(
|
||||
self, req_id: str, device_prefix_len: int
|
||||
) -> tuple[int, int]:
|
||||
"""(kv, swa) host-hit tokens a staged buffer-mode prefetch will splice
|
||||
given the request's live device prefix; frees unusable holds."""
|
||||
if self.buffer_pipeline is None:
|
||||
return 0
|
||||
return self.buffer_pipeline.staged_prefetch_tokens(req_id)
|
||||
return 0, 0
|
||||
return self.buffer_pipeline.plan_staged_splice(req_id, device_prefix_len)
|
||||
|
||||
def staged_prefetch_swa_tokens(self, req_id: str) -> int:
|
||||
"""SWA device tokens consuming a staged buffer-mode prefetch will
|
||||
@@ -2074,7 +2083,7 @@ class UnifiedRadixCache(BasePrefixCache):
|
||||
self._storage_prefetch_missed_rids.discard(rid)
|
||||
if (
|
||||
self.buffer_pipeline is not None
|
||||
and self.buffer_pipeline.release_aborted_staged(rid)
|
||||
and self.buffer_pipeline.release_staged_hold(rid)
|
||||
):
|
||||
return
|
||||
if rid not in self.ongoing_prefetch:
|
||||
@@ -2246,6 +2255,25 @@ class UnifiedRadixCache(BasePrefixCache):
|
||||
# prefetches ahead of us are consumed. The op stays in
|
||||
# ongoing_prefetch, so wait_complete keeps gating admission.
|
||||
return False
|
||||
if buffer_mode:
|
||||
# IO commit: pin before the bounce alloc so a cancel is a
|
||||
# plain revoke and a parked op keeps its pin; a fetch whose
|
||||
# splice base is gone is not worth its storage read.
|
||||
if self.buffer_pipeline.try_lock_anchor(req_id) == "anchor_lost":
|
||||
self._prefetch_outcome_stats["declined_anchor_lost"] += 1
|
||||
# Span still L3-resident: arm the paced retry to re-fetch
|
||||
# from the shorter post-loss match.
|
||||
self._storage_prefetch_missed_rids.add(req_id)
|
||||
self.revoke_pending_prefetch(req_id)
|
||||
return True
|
||||
if self.buffer_pipeline.staged_span_covered(
|
||||
req_id, operation.storage_hit_count
|
||||
):
|
||||
# Live tree already covers the span: nothing left to
|
||||
# splice, so skip the storage read.
|
||||
self._prefetch_outcome_stats["declined_device_covered"] += 1
|
||||
self.revoke_pending_prefetch(req_id)
|
||||
return True
|
||||
alloc_len = operation.storage_hit_count
|
||||
host_indices = cc.mem_pool_host.alloc(alloc_len)
|
||||
if host_indices is None:
|
||||
@@ -2273,10 +2301,6 @@ class UnifiedRadixCache(BasePrefixCache):
|
||||
self.ongoing_prefetch[req_id] = info._replace(host_indices=host_indices)
|
||||
if buffer_mode:
|
||||
cc.prefetch_tokens_occupied += alloc_len
|
||||
# IO commit: pin the anchor until consumption. Do not read
|
||||
# attributes off `operation` here — alternative cache
|
||||
# controllers may expose a narrower surface.
|
||||
self.buffer_pipeline.try_lock_anchor(req_id, info.anchor_node_id)
|
||||
cc.prefetch_buffer.put(operation)
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user