Skip to content

Commit fda044e

Browse files
fluffy314cursoragent
authored andcommitted
fix(prefill): resolve promoted final snapshots directly
Use the longest available chained boundary so Primary hot promotion stores one complete snapshot without copying every intermediate checkpoint, while preserving full-prefix integrity. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 9212de1 commit fda044e

4 files changed

Lines changed: 32 additions & 14 deletions

File tree

docs/adr/0016-distributed-prefill-kv-cache-network.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Remote cache access happens only before decode:
2929

3030
1. Tokenize the request and compute chained fixed-size block hashes.
3131
2. Query the local cache and compatible live peers concurrently.
32-
3. Select the longest contiguous prefix whose transfer/import cost is lower
32+
3. Select the longest available chained-prefix snapshot whose transfer/import cost is lower
3333
than local prefill recomputation.
3434
4. Transfer one immutable snapshot at the selected prefix boundary.
3535
5. Import it, compute the missing suffix locally, then decode entirely locally.
@@ -162,7 +162,9 @@ allowing suffix-only prefill.
162162
### Arbitrary block/hole reuse
163163

164164
Rejected. Later K/V depends on the complete preceding token sequence and
165-
positions. Only the longest contiguous prefix is safe.
165+
positions. A stored snapshot must cover that complete sequence. Intermediate
166+
checkpoint entries may be absent, however, because the chained hash and final
167+
snapshot already commit and contain the full prefix through that boundary.
166168

167169
### SMB/NFS snapshot files
168170

docs/ops/distributed-prefill-kv-network.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ Expected invariants:
206206

207207
- both node cards appear within two gossip intervals;
208208
- stale nodes disappear after TTL;
209-
- remote lookup returns only the longest contiguous prefix;
209+
- remote lookup returns the longest available chained-prefix snapshot;
210210
- imported snapshot checksum and compatibility fingerprint match;
211211
- remote failure falls back to local prefill;
212212
- no remote RPC occurs in autoregressive decode;

inference_engine/distributed/prefill_cache.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
The cache stores an opaque restorable snapshot at selected token-block
44
boundaries. Model-specific adapters own serialization/import; this module owns
55
deterministic prefix hashing, exact compatibility matching,
6-
longest-contiguous-prefix lookup, leases, accounting, and memory-pressure
6+
longest chained-prefix lookup, leases, accounting, and memory-pressure
77
eviction. A hit transfers only the snapshot at the longest matched boundary.
88
99
Decode never reads this store. A requester imports a hit once, computes the
@@ -208,30 +208,35 @@ def lookup(
208208
lease_seconds: float = DEFAULT_LEASE_SECONDS,
209209
now: float | None = None,
210210
) -> PrefixLease:
211-
"""Lease the longest contiguous prefix held by this store."""
211+
"""Lease the longest available chained-prefix snapshot.
212+
213+
A chained hash at boundary N commits all preceding token blocks, so a
214+
promoted final snapshot remains valid even when intermediate boundary
215+
snapshots are absent or have been evicted.
216+
"""
212217
if lease_seconds <= 0:
213218
raise ValueError("lease_seconds must be > 0")
214219
now = time.time() if now is None else now
215220
with self._lock:
216221
self._expire_leases(now)
217-
matched: list[CacheBlock] = []
218-
for raw_hash in block_hashes:
222+
snapshot = None
223+
hit_block_count = 0
224+
for index, raw_hash in enumerate(block_hashes):
219225
block_hash = bytes(raw_hash)
220226
block = self._blocks.get(block_hash)
221-
if block is None:
222-
break
223-
matched.append(block)
224-
self._blocks.move_to_end(block_hash)
225-
if not matched:
227+
if block is not None:
228+
snapshot = block
229+
hit_block_count = index + 1
230+
if snapshot is None:
226231
self._lookup_misses += 1
227232
return PrefixLease("", (), 0, 0, 0, self._epoch, now, bytes(32))
233+
self._blocks.move_to_end(snapshot.block_hash)
228234
self._lookup_hits += 1
229235
lease_id = secrets.token_urlsafe(18)
230-
snapshot = matched[-1]
231236
lease = PrefixLease(
232237
lease_id=lease_id,
233238
block_hashes=(snapshot.block_hash,),
234-
hit_block_count=len(matched),
239+
hit_block_count=hit_block_count,
235240
hit_token_count=snapshot.token_count,
236241
transfer_bytes=snapshot.nbytes,
237242
cache_epoch=self._epoch,

tests/inference_engine/distributed/test_prefill_cache.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ def test_store_miss_expiry_collision_and_lru():
7272
store.fetch(lease.lease_id, now=22.0)
7373

7474

75+
def test_lookup_uses_sparse_promoted_final_snapshot():
76+
store = PrefixCacheStore(_compat(), max_bytes=100, node_id="head")
77+
hashes = chained_block_hashes([1, 2, 3, 4], _compat())
78+
final = CacheBlock.create(hashes[1], 4, b"full-snapshot")
79+
store.put(final)
80+
lease = store.lookup(hashes)
81+
assert lease.hit_block_count == 2
82+
assert lease.hit_token_count == 4
83+
assert store.fetch(lease.lease_id) == (final,)
84+
85+
7586
def test_validation_and_stats():
7687
with pytest.raises(ValueError, match="max_bytes"):
7788
PrefixCacheStore(_compat(), max_bytes=0, node_id="x")

0 commit comments

Comments
 (0)