Reduce CP HiCache L2 allocator scan cost
Host HiCache reservations were paying token-level free-slot scans when trying to preserve page contiguity. The allocator now keeps a lazy page-extent index so availability checks and contiguous-preferred allocations avoid materializing the full 220GB-equivalent free-slot metadata path. The companion benchmark models steady-state L2 churn near full occupancy, including burn-in and historical node-size effects, so LPF/RDMA descriptor quality can be separated from ETE noise. Constraint: CP HiCache host allocations are page-shaped, but existing callers may still read free_slots directly. Rejected: Sort and scan free_slots on each alloc_contiguous_preferred call | measured ms-level CPU overhead on 220GB-equivalent metadata. Rejected: Remove free_slots compatibility | storage/tests still rely on the public tensor surface. Confidence: medium Scope-risk: moderate Directive: Do not reintroduce per-allocation full free_slots scans on HostKVCache; preserve page-extent metadata or benchmark before changing allocator shape. Tested: Local py_compile for memory_pool_host.py, allocator benchmark, and related tests. Tested: Local test_cp_hicache_allocator_bench.py 10 passed. Tested: Remote g0034 test_hicache_controller_cp.py 67 passed; test_cp_hicache_allocator_bench.py 10 passed. Tested: Remote 220GB-equivalent host_churn benchmark: contiguous path reduced from ms-level to ~30-292us p50 depending on fragmentation. Not-tested: Full CUDA ETE run after allocator change. Not-tested: Production long-run fragmentation behavior under live traffic.
This commit is contained in:
@@ -5311,3 +5311,154 @@ C123 full-suite update:
|
||||
- The CPU stub now also gives `sgl_kernel.kvcacheio` a module-level
|
||||
`__getattr__`, so named imports resolve to inert functions without importing
|
||||
the native extension.
|
||||
|
||||
### C124 — 2026-06-02 L2 host churn benchmark must model fragmented steady-state eviction
|
||||
|
||||
Finding:
|
||||
|
||||
- The earlier host allocator benchmark measured one cold allocation against a
|
||||
synthetic `free_slots` layout. That is not enough for production HiCache: L2
|
||||
host cache can be ~220 GB, runs near full occupancy, and repeatedly frees old
|
||||
nodes before reserving a new node.
|
||||
- If prefill nodes and measured allocation requests have the same page size, a
|
||||
random eviction workload can still produce unrealistically contiguous freed
|
||||
chunks. This hides the allocator/pathology we care about for layer-page-first
|
||||
(LPF) transfer planning.
|
||||
|
||||
Correction:
|
||||
|
||||
- Added `benchmark/hicache/bench_cp_hicache_allocator_overhead.py --bench
|
||||
host_churn` for steady-state L2 metadata churn.
|
||||
- The benchmark now records both CPU allocation latency and transfer-descriptor
|
||||
quality proxies:
|
||||
- `pf_desc`: page-first descriptor count, one descriptor per selected page.
|
||||
- `lpf_desc_mean` / `lpf_ratio`: layer-page-first descriptor count after
|
||||
coalescing consecutive physical page runs.
|
||||
- `run_p50` / `max_run_mean`: selected physical-page run quality.
|
||||
- Added `--host-churn-prefill-node-pages` so the filled resident set can use a
|
||||
different historical node size than the new allocation request. This models
|
||||
cache built from many small chunks, then later serving larger extensions.
|
||||
|
||||
Scope:
|
||||
|
||||
- This is CPU metadata-only. It does not allocate real 220 GB KV buffers and it
|
||||
does not test CUDA kernel bandwidth.
|
||||
- It is intended to decide whether L2 `HostKVCache.alloc_contiguous_preferred()`
|
||||
/ future L2 bucket allocator work is on the actual hot path, and whether LPF
|
||||
layout can get enough physical-page coalescing to help H2D/D2H/RDMA.
|
||||
|
||||
C124 validation update:
|
||||
|
||||
- Local CPU unit coverage: `test_cp_hicache_allocator_bench.py` passes with the
|
||||
fragmented prefill-node churn case.
|
||||
- Remote `g0034` container validation: py_compile passed and the benchmark unit
|
||||
file passed (`9 passed`).
|
||||
- Remote 220GB-equivalent production `HostKVCache` churn sample
|
||||
(`pages=34375`, `occ=0.97`, `request_pages=512`, `evict_pages=512`):
|
||||
- `prefill_node_pages=1`: FIFO p50 ~11 us but LPF run quality is poor
|
||||
(`lpf_ratio≈0.663`, `run_p50≈505`). Contiguous-preferred p50 ~700 us and
|
||||
still cannot find a good run under this fragmentation.
|
||||
- `prefill_node_pages=64`: FIFO p50 ~9 us and LPF run quality is much better
|
||||
(`lpf_ratio≈0.012`, `run_p50≈8`). Contiguous-preferred p50 ~499 us.
|
||||
- Interpretation: current production `alloc_contiguous_preferred()` can add
|
||||
hundreds of microseconds on fragmented 220GB-equivalent metadata. The
|
||||
benchmark now makes this measurable separately from ETE noise; it also shows
|
||||
that node-size history strongly affects LPF transfer coalescing potential.
|
||||
|
||||
### C125 — 2026-06-02 Host churn benchmark needs explicit burn-in to avoid cold free-tail bias
|
||||
|
||||
Finding:
|
||||
|
||||
- A high-occupancy prefill still leaves an initial contiguous free tail. With
|
||||
small `warmup` and small requests, measured allocations can consume this cold
|
||||
tail before they ever allocate from evicted fragmented nodes.
|
||||
- That can overstate LPF run quality and understate the allocation/search cost we
|
||||
expect after the service has churned for a while.
|
||||
|
||||
Correction plan:
|
||||
|
||||
- Add a separate `--host-churn-burnin` iteration count. Burn-in iterations run
|
||||
the same evict+allocate cycle but are not measured and are independent of
|
||||
benchmark warmup.
|
||||
- Use burn-in to exhaust the initial free tail before collecting steady-state
|
||||
latency and run-quality samples.
|
||||
|
||||
C125 validation update:
|
||||
|
||||
- Local RED/GREEN: `test_host_churn_burnin_exposes_fragmented_evicted_nodes_after_cold_tail`
|
||||
first failed on missing `burnin`, then passed after adding the burn-in path.
|
||||
- Local full benchmark unit file: `10 passed` plus py_compile.
|
||||
- Remote `g0034` container: py_compile passed and
|
||||
`test_cp_hicache_allocator_bench.py` passed (`10 passed`).
|
||||
- Remote 220GB-equivalent production `HostKVCache` sample with `burnin=20`,
|
||||
`occ=0.97`, `evict_pages=512`:
|
||||
- `request_pages=64`, `prefill_node_pages=1`: FIFO p50 ~11 us but LPF quality
|
||||
is worst case (`lpf_ratio=1.000`, all single-page runs). The contiguous
|
||||
search can cost ~2.6 ms p50.
|
||||
- `request_pages=512`, `prefill_node_pages=1`: FIFO p50 ~10 us, LPF quality
|
||||
remains worst case (`lpf_ratio=1.000`). Contiguous search p50 ~576 us and
|
||||
cannot improve run quality because no 512-page run exists.
|
||||
- `prefill_node_pages=64`: LPF quality is much better (`lpf_ratio≈0.012–0.031`)
|
||||
but contiguous search can still cost ~0.5–1.5 ms p50.
|
||||
- Interpretation: this benchmark now exposes the relevant tradeoff: FIFO is cheap
|
||||
but can create very high descriptor count for LPF/RDMA when historical nodes
|
||||
are tiny; naive contiguous search can be milliseconds on 220GB-equivalent host
|
||||
metadata. A future L2 allocator should avoid full free-list scans and preserve
|
||||
larger free extents/buckets rather than searching linearly per allocation.
|
||||
|
||||
### C126 — 2026-06-02 L2 HostKVCache allocator uses lazy page extents instead of full free-slot scans
|
||||
|
||||
Finding:
|
||||
|
||||
- `HostKVCache.alloc_contiguous_preferred()` previously scanned/materialized the
|
||||
full token-level `free_slots` tensor to discover contiguous physical page runs.
|
||||
On 220GB-equivalent host metadata this could cost hundreds of microseconds to
|
||||
milliseconds per reservation.
|
||||
- The full scan is the wrong shape for CP HiCache: allocations and releases are
|
||||
page-shaped, and the fast path only needs page-run metadata plus a token-index
|
||||
tensor for the chosen pages.
|
||||
|
||||
Correction:
|
||||
|
||||
- `HostKVCache` now maintains a lazy page-extent index:
|
||||
- `free_slots` remains a compatibility property and is materialized lazily only
|
||||
when external code reads it.
|
||||
- `available_size()` reads an integer token count and does not materialize.
|
||||
- `free()` converts page-shaped token indices into page runs and merges them
|
||||
into sorted extents.
|
||||
- `alloc_contiguous_preferred()` first checks the largest free extent for a
|
||||
single-run allocation; if no run is large enough, it falls back to a batched
|
||||
fragmented run allocation without scanning/sorting the whole `free_slots`
|
||||
tensor.
|
||||
- The fallback is still page-shaped and returns normal token indices. It avoids
|
||||
silent corruption: overlapping/double-free page extents raise, and non-page
|
||||
shaped frees disable the extent index with a warning before falling back to the
|
||||
legacy tensor path.
|
||||
|
||||
C126 validation update:
|
||||
|
||||
- Remote RED/GREEN target: the new lazy extent-index unit first failed on missing
|
||||
`_free_slots_dirty`, then passed after implementation.
|
||||
- Remote `test_hicache_controller_cp.py`: `67 passed`.
|
||||
- Remote `test_cp_hicache_allocator_bench.py`: `10 passed`.
|
||||
- Remote 220GB-equivalent production `HostKVCache` churn sample with `burnin=20`,
|
||||
`occ=0.97`, `evict_pages=512` after optimization:
|
||||
- `request_pages=64`, `prefill_node_pages=64`: contiguous p50 ~31 us.
|
||||
- `request_pages=512`, `prefill_node_pages=64`: contiguous p50 ~86 us.
|
||||
- `request_pages=512`, `prefill_node_pages=1`: contiguous p50 ~292 us; this
|
||||
is no longer a full free-list scan, but mostly fragmented selection plus
|
||||
constructing a 32768-token host-index tensor.
|
||||
- Compared with the previous C125 sample, the contiguous path drops from roughly
|
||||
0.5–2.6 ms p50 to roughly 30–292 us p50 on this benchmark shape.
|
||||
|
||||
Remaining risk:
|
||||
|
||||
- `alloc()` now uses the extent-backed fragmented allocation when the extent
|
||||
index is active, so exact FIFO order is no longer the internal contract. This
|
||||
should be acceptable for host KV slots because callers require unique free
|
||||
page-shaped slots, not FIFO identity, but future code must not depend on
|
||||
token-level FIFO order.
|
||||
- Very fragmented tiny historical nodes still produce poor LPF coalescing. That
|
||||
requires higher-level eviction/allocation policy to preserve larger free runs;
|
||||
the allocator now avoids the worst CPU scan cost but cannot create physical
|
||||
contiguity that the free set does not contain.
|
||||
|
||||
Reference in New Issue
Block a user