[None][feat] Attribute KV-cache hits to storage tiers in metrics (gpu/host/disk/remote) - #18584
Draft
zheyuf wants to merge 3 commits into
Draft
[None][feat] Attribute KV-cache hits to storage tiers in metrics (gpu/host/disk/remote)#18584zheyuf wants to merge 3 commits into
zheyuf wants to merge 3 commits into
Conversation
…/host/disk/remote)
Prefix-cache hits are reported as one aggregate today (kv_cache_hit_rate,
kv_cache_iter_reused_blocks_total, prompt_cached_tokens_total), so with host
offloading enabled operators cannot tell whether reused blocks were resident on
the GPU or copied back from host memory. Transfer volume cannot reconstruct it
either: blocks also move on resume after preemption and on prefetch.
Every KV cache manager now attributes a reused block to the tier that held it
when the prefix was matched, before any onboard copy, and the split is exported
end to end:
* Schema: KvCacheStats::reusedBlocks{Gpu,Host,Disk,Remote},
KvCacheIterationStats::iterReusedBlocks{Gpu,Host,Disk,Remote}, the same four
fields on executor::KvCacheStats (JSON + binary serialization, nanobind) and
a KvCacheTier enum whose values are shared with Python
(tensorrt_llm/metrics/enums.py, CACHE_TIER_LABELS = gpu,host,disk,remote,none).
* KVCacheManager (C++): WindowBlockManager::onboardAndAllocateBlocks decides the
tier from isPrimary() of the matched block (partial-copy source) before
onboardBlock(); secondary is host for transfer mode DRAM and disk for
GDS/POSIX. Connector-matched whole blocks move from missed to reused(remote)
so the aggregate hit rate and the tier split agree. Per request the manager
records ordered (tier, tokens) runs for the window that bounds the
prepopulated length (LlmRequest::getReuseTierSegments, bound as
reuse_tier_segments).
* KVCacheManagerV2 (C++ backend and Python backend, kept in sync): reused
blocks are split per attention life cycle by page cache level; the request
segments use the worst level among the life cycles that load a block and
"none" for blocks outside every sliding window (skipped by reuse, nothing
loaded). Exposed as _KVCache.reuse_tier_segments.
* Python: LlmRequest.cached_tokens_by_tier derives the request split from the
segments and the latched cached_tokens, so the two request-level counters
agree by construction; it travels on LlmResult/GenerationResult into
usage.prompt_tokens_details.cached_tokens_details, including through
disaggregated serving (ctx_usage and the KV-transfer aux buffer).
* Prometheus (MetricsCollector):
- trtllm_kv_cache_reused_blocks_by_tier_total{cache_tier}: sums to
trtllm_kv_cache_iter_reused_blocks_total
- trtllm_prompt_cached_tokens_by_tier_total{cache_tier}: sums to
trtllm_prompt_cached_tokens_total for every attributed request
All tier series are pre-seeded to 0; no existing metric changes shape.
* Warmup: per-iteration KV deltas accumulated during warmup are drained after
warmup so the iteration counters start from the same point as the baselined
cumulative counters (the V1 baseline also covers the new fields).
* Docs: docs/source/features/kvcache.md, "Cache Hit Metrics by Storage Tier".
Tests: kvCacheManagerTest (ReuseTierAttribution{Gpu,Host}Test),
tests/unittest/metrics/test_collector.py::TestCacheTierMetrics,
tests/unittest/_torch/executor/test_kv_cache_tier_split.py,
tests/unittest/kv_cache_manager_v2_tests/test_kv_cache_tier_attribution.py
(both V2 backends), test_stats_serializer.py and test_kv_cache_stats_api.py
updated for the new fields. Verified end to end with trtllm-serve
(Qwen2.5-0.5B, 4096-token GPU pool + host cache) on B300 for both managers:
the second pass of an evicted prompt reports 1998 cached tokens all in
cache_tier="host" and 63 host blocks == iter_reused_blocks.
Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
- drop the per-request reusedByTierDelta (computed, never read) and the unused kNumKvCacheTiers constant - copy the cumulative per-tier reused-block counts into the executor KvCacheStats so the get_stats() JSON actually carries the new fields - remove the unreachable 'none' fallback in split_cached_tokens_by_tier: cached_tokens and the segments both derive from prepopulated_prompt_len - inline the one-line tier index helper in the V2 Python backend; drop a defensive getattr on a validated UsageInfo Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
- kvCacheManagerV2StatsTest: pass the per-tier split to recordReuse (build break) - V1: attribute with the largest attention window's runs clipped to the prepopulated length, so hybrid Mamba placeholders and SWA anchors no longer hide the real tier; guard the connector reclassification on block reuse and count only whole blocks beyond the block-aligned local prefix - LlmRequest: manager segments are ignored once cached_tokens is latched (preempted sequences re-added) and for disaggregated generation requests, and a breakdown that does not cover the cached prefix is never reported - usage: cached_tokens_details is omitted from the wire when absent, fakes without the attribute are tolerated, harmony (gpt-oss) responses carry it - style: kGpu/kHost/kDisk/kRemote/kNone enumerators, guard getNumReusedBlocksByTier against kNone, collector derives the iteration keys from the tier labels, test_aux unpacks the 4-tuple, copyright years Signed-off-by: Zheyu Fu <zheyuf@nvidia.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[None][feat] Attribute KV-cache hits to storage tiers in metrics (gpu / host / disk / remote)
Description
Problem. With host offloading (
host_cache_size) a prefix-cache hit can be served from GPUmemory or copied back from host memory (or disk / an external store), but
kv_cache_hit_rate,kv_cache_iter_reused_blocks_totalandprompt_cached_tokens_totalreport one aggregate.kv_cache_host_utilizationsays how full the host pool is, not whether it served anything, andkv_cache_onboard_bytes_totalcannot be used to reconstruct hits because blocks also move onresume after preemption and on prefetch. Operators cannot answer "is CPU offloading paying off",
and benchmark harnesses that price cached vs. uncached tokens get no tier information.
Solution. The KV cache manager attributes every reused block to the tier it was served from
at the moment the block is matched, before any onboard copy, and the split is exported end to end:
KvCacheStats::reusedBlocks{Gpu,Host,Disk,Remote},KvCacheIterationStats::iterReusedBlocks{Gpu,Host,Disk,Remote}, the same four fields onexecutor::KvCacheStats(JSON + binary serialization, nanobind),KvCacheTierenum sharedwith Python (
tensorrt_llm/metrics/enums.py::CACHE_TIER_LABELS).KVCacheManager(C++): tier decided inonboardAndAllocateBlocksbeforeonboardBlock()(
isPrimary()on the matched block / partial-copy source; secondary ishostfor transfer modeDRAM and
diskfor GDS/POSIX). Connector-matched whole blocks move from missed toreused(
remote) so aggregate hit rate and tier split agree. Per request the manager recordsordered
(tier, tokens)runs for the window that bounds the prepopulated length(
LlmRequest.reuse_tier_segments).KVCacheManagerV2: per attention life cycle, reused blocks are split bypage.cache_level; therequest segments use the worst level among the life cycles that load a block, and
noneforblocks outside every sliding window (skipped by reuse, nothing loaded).
LlmRequest.cached_tokens_by_tierderives the request-level split from the segmentsand the latched
cached_tokens, so the two request counters agree by construction. The splittravels with the response (
LlmResult.cached_tokens_by_tier) intousage.prompt_tokens_details.cached_tokens_details, including through disaggregated serving(
ctx_usageand the KV-transfer aux buffer).MetricsCollector):trtllm_kv_cache_reused_blocks_by_tier_total{cache_tier}— sums totrtllm_kv_cache_iter_reused_blocks_totaltrtllm_prompt_cached_tokens_by_tier_total{cache_tier}— sums totrtllm_prompt_cached_tokens_totalfor every request that carried an attributioncache_tiervalues pre-seeded to 0; no existing metric changes shape.iteration counters start from the same point as the already-baselined cumulative counters.
docs/source/features/kvcache.md, "Cache Hit Metrics by Storage Tier" (semantics,invariants, warmup, disagg, per-manager tier meaning).
Behavior changes to note: (1) with a KV cache connector, whole connector-filled blocks now count
as reuse (
remote) instead of misses, sokv_cache_hit_ratereflects avoided recomputation;(2)
kv_cache_iter_*counters no longer include warmup dummy requests.Test Coverage
tests/unittest/metrics/test_collector.py::TestCacheTierMetrics— pre-seeding, request splitsums to aggregate, no guessing without attribution,
nonetier, V1 window and V2 lifecycleblock aggregation.
tests/unittest/_torch/executor/test_kv_cache_tier_split.py— segment-to-dict split, prefixtrimming, sum invariant.
tests/unittest/kv_cache_manager_v2_tests/test_kv_cache_tier_attribution.py— V2 gpu / host /none attribution against the runtime manager (host hits cross-checked with onboard traffic).
cpp/tests/unit_tests/batch_manager/kvCacheManagerTest.cpp—KVCacheManagerReuseTierAttributionGpuTest,KVCacheManagerReuseTierAttributionHostTest.host cache, prompt A -> fillers -> prompt A again shows
cache_tier="host"hits for bothmanagers and matching
cached_tokens_detailsin the response usage.PR Checklist
🤖 Generated with Claude Code