Skip to content

[metrics] KV age at hit and eviction, lifetime and reuse count at eviction, for RadixCache and HiRadixCache - #38559

Open
gilfordting wants to merge 7 commits into
sgl-project:mainfrom
gilfordting:kv-age-metrics
Open

gilfordting wants to merge 7 commits into
sgl-project:mainfrom
gilfordting:kv-age-metrics

Conversation

@gilfordting

@gilfordting gilfordting commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Motivation

Today the radix cache reports how much it evicts (sglang:evicted_tokens_total) and how long a pass takes (sglang:eviction_duration_seconds), but not how old the evicted content was, how long it had lived, whether it was ever reused, and nothing at all about how old content is when it gets re-used. Those distributions together are what tells an operator whether a cache tier is thrashing:

  • age at eviction: seconds since a node was last matched when it leaves a tier.
  • age at hit: seconds since a node was last matched when a new request matches it. This is the traffic's reuse-time distribution.
  • lifetime at eviction: seconds since the node was created. Answers "does content survive an hour" (the TTL question in [RFC] Agent-Aware KV Cache Phase 1 for Agentic Workloads #24656).
  • reuse count at eviction: hit_count. Answers "are we evicting content that was never reused" (the LFU question).

If the eviction-age curve sits to the right of the hit-age curve, evictions land on content the traffic had finished with. If the two overlap, pages are evicted shortly before they would have been reused, and each of those is a miss paid for with capacity. With HiCache the same comparison per tier separates "L1 is too small for the reuse window" from "L2 is too small for the session length", which pick different fixes (--hicache-size, --hicache-write-policy, --max-running-requests).

Observability only, no behaviour change.

Modifications

Four new series on RadixCacheMetricsCollector, next to the existing eviction metrics (gated by --enable-metrics). All share tier (device | host) and outcome labels so one PromQL template covers every tier:

  • sglang:kv_age_seconds{event,tier,outcome} (histogram, 1s–2h): seconds since last match, one observation per node.
    • event="hit", outcome="hit": recorded once per request, on its first match_prefix, for every node on the matched path, before the timestamps are refreshed. tier=host means the node was matched via its host copy.
    • event="evict", tier="device", outcome="demoted": device copy freed, host copy kept.
    • event="evict", tier="device", outcome="dropped": data destroyed (write-through unbacked, or write-back drop under host pressure).
    • event="evict", tier="host", outcome="dropped": host tier end of life.
  • sglang:kv_age_tokens_total{event,tier,outcome,age_le} (counter): the same events weighted by tokens, bucketed by the same edges. Node counts over-weight small leaves; this answers "how many tokens did we evict at age > 30 min".
  • sglang:kv_lifetime_seconds{tier,outcome} (histogram): now - creation_time at removal.
  • sglang:kv_reuses{tier,outcome} (histogram, 0..100): hit_count at removal. One request lifecycle inserts twice (end of prefill, at finish), so a node cached once and never reused reads 2. Reads 0 under write_back, which does not maintain hit_count; the docstring says so.

Why once per request. The scheduler re-matches waiting requests every round, and cache_unfinished_req re-matches right after inserting. Observing every match put every prompt into the ≤1s bucket as a "device hit" (first live run below). A per-Req flag consumed by take_kv_age_hit_observation() in base_prefix_cache.py makes only the request's first match observe; request-less matches never do. Eviction ages are unaffected.

Caches covered. UnifiedRadixCache (the default tree cache) with the Python tree core, plus the legacy RadixCache and HiRadixCache. The unified tree core keeps a logical clock in last_access_time, so UnifiedTreeNode gains wall-clock twins (last_access_wall, creation_wall) refreshed at the same sites; the interface gains an optional kv_age_observer slot that UnifiedRadixCache fills when metrics are on, and the Python core emits at the match-chain refresh and at the four removal sites (_delete_unbacked_device_leaf, _demote, _evict_host_leaf, the write-back drop_subtree_no_host descendants). The Rust core inherits the no-op default. The pass counter #28507 proposed is not added: sglang:eviction_duration_seconds_count already is that number.

Files:

  • python/sglang/srt/observability/metrics_collector.py: buckets, kv_age_bucket(), the four series, observe_kv_age(), observe_kv_eviction().
  • python/sglang/srt/mem_cache/base_prefix_cache.py: take_kv_age_hit_observation().
  • python/sglang/srt/managers/schedule_batch.py: Req.kv_age_hit_observed.
  • python/sglang/srt/mem_cache/unified_cache/unified_tree_core_interface.py, unified_tree_core.py, unified_radix_cache.py: unified path.
  • python/sglang/srt/mem_cache/radix_cache.py, hiradix_cache.py: legacy path (_observe_kv_eviction() helper, match hooks).
  • test/registered/unit/observability/test_kv_age_metrics.py: 9 CPU tests: bucket labels, label routing, eviction fan-out, and end-to-end insert / match / re-match / evict on both a CPU RadixCache and a CPU UnifiedRadixCache, asserting one hit per request and the right token weights.

Example queries:

# p90 idle age of content demoted from GPU, vs p90 age of content re-hit on GPU
histogram_quantile(0.9, sum by (le) (rate(sglang:kv_age_seconds_bucket{event="evict",tier="device",outcome="demoted"}[5m])))
histogram_quantile(0.9, sum by (le) (rate(sglang:kv_age_seconds_bucket{event="hit",tier="device"}[5m])))

# share of host-evicted tokens that were idle less than 10 minutes (L2 thrashing indicator)
sum(rate(sglang:kv_age_tokens_total{event="evict",tier="host",age_le=~"1|5|10|30|60|120|300|600"}[5m]))
  / sum(rate(sglang:kv_age_tokens_total{event="evict",tier="host"}[5m]))

# share of GPU-evicted nodes never reused (one lifecycle = 2 inserts)
sum(rate(sglang:kv_reuses_bucket{tier="device",le="2"}[5m])) / sum(rate(sglang:kv_reuses_count{tier="device"}[5m]))

Test process (live, HiCache, UnifiedRadixCache)

Server: this branch on lmsysorg/sglang:nightly-dev-cu13-20260908, Qwen2.5-3B-Instruct, 1×H100, --enable-metrics --enable-hierarchical-cache --hicache-size 1 --hicache-write-policy write_through --max-total-tokens 16384 --page-size 32 (16k-token L1, ~29k-token host pool). Startup log: Tree cache initialized: source=default impl=UnifiedRadixCache … hicache_attached=True. Metrics scraped into Mimir and read back through Grafana; Grafana and the direct /metrics scrape agreed on every count.

Workload (~1,070-token prompts): A1 12 cold → A2 same 12 → 60s → B1 10 new → 60s → C1 A again → 30s → B2 240 new → C2 A again.

series event tier outcome nodes tokens ages
kv_age_seconds hit device hit 13 12,640 mean 1.1s (A2, 2s after A1)
kv_age_seconds hit host hit 13 12,640 mean 122s (C1, A last matched at A2)
kv_age_seconds evict device demoted 311 286k 266 ≤5s (B2 churn), 23 in 60–120s (A demoted during B1)
kv_age_seconds evict host dropped 283 263k 254 ≤5s (B2 churn), 16 in 30–60s (A, last hit at C1), 12 in 60–120s (B1)
kv_lifetime_seconds evict device demoted 311 mean 16s
kv_reuses evict device demoted 311 23 ≤1, 298 ≤2, 13 in 3–5 (A, reused twice)

cached_tokens_total{cache_source="device"} = 12,640 and {cache_source="host"} = 12,640, matching the two hit series exactly. C2 reported 0% cached and added no hit samples (host flushed by B2, visible as the 16 host drops aged 30–60s). hicache_dropped_tokens_total stayed 0.

Negative check first. The same server on the branch before the unified-core commit evicted 367k tokens and demoted 358k with zero kv_age samples, which is how the default-cache gap was found. A run before the once-per-request gate recorded 386,784 device-hit tokens, equal to the total prompt tokens of all phases including cold ones, from the post-insert re-match.

Relation to existing work

  • [metrics] Add cache eviction lifetime/frequency metrics (L1 + L2) #28507 ([metrics] Add cache eviction lifetime/frequency metrics (L1 + L2), open since June, currently conflicting) proposes eviction age since creation, idle since last access, reuse count, L2 eviction age and a pass counter, as separate metric names per tier, for RadixCache/HiRadixCache only. This PR covers the same quantities under one label schema, also on the default UnifiedRadixCache, and adds the hit-age series, the demoted/dropped split, and token weighting. Happy to coordinate with its author on which lands, or to fold the hit-age half into [metrics] Add cache eviction lifetime/frequency metrics (L1 + L2) #28507 if maintainers prefer its metric names.
  • Why not update_eviction_metrics. Review on [metrics] Add cache eviction lifetime/frequency metrics (L1 + L2) #28507 asked for its eviction-age hook to move into update_eviction_metrics. That hook runs once per eviction pass with two scalars (tokens freed, pass duration). Age, lifetime and reuse count are per node, and one pass removes anywhere from one to hundreds of nodes with different ages, so recording them there would need the pass to collect a list of per-node samples and hand it up, which is the same amount of plumbing with an extra allocation per pass. This PR instead observes at the four node-removal sites (_delete_unbacked_device_leaf, _demote, _evict_host_leaf, the write-back subtree drop) through one helper, and leaves update_eviction_metrics untouched so the existing pass-level metrics are unaffected. The same helper shape is used in the legacy caches (RadixCache._observe_kv_eviction).
  • hicache: restore eviction/load-back bandwidth and byte-level metrics #31884 splits evicted_tokens_total into backuped vs regular; the outcome label here carries the same distinction on the age series. No code conflict.
  • [RadixCache] Preserve suffix timestamp on split #24892 (closed, stale): partial match refreshes the whole child's timestamp before the split. The hit hook observes the pre-refresh timestamp, so hit ages are unaffected.
  • Covers the "eviction pressure" part of [Feature] Per-request Observability: TTFT Breakdown + HiCache Metrics #28047.

Cost

Measured with a CPU micro-benchmark (page size 1, one chain of K nodes x 64 tokens, real prometheus_client series, medians of 300 matches / 20 evictions; script in the PR discussion on request):

op K hooks off hooks on delta per node
UnifiedRadixCache.match_prefix 32 166 µs 262 µs +96 µs 3.0 µs
UnifiedRadixCache.match_prefix 128 635 µs 1004 µs +369 µs 2.9 µs
RadixCache.match_prefix 128 765 µs ~1000 µs ~+300 µs ~2.5 µs
evict (both caches) 32 / 128 21–105 ms within run-to-run noise of the free itself ≤ ~15 µs bound (3 observes + 1 inc)

The match hook fires once per request (first match_prefix only) for each node on the matched path, so a 100k-token prompt matching 30 radix nodes adds ~90 µs to that request's scheduling, once. Label children are resolved once per label combination and cached on the collector (_kv_age_child etc.); before that the same hook cost ~7.3 µs per node, almost all of it in labels(). Eviction adds three histogram observations and one counter increment per removed node; the eviction pass is dominated by freeing KV slots and the delta did not separate from noise at these sizes.

Limitations

  • SWARadixCache, MambaRadixCache, HiMambaRadixCache, RadixCacheCpp, LMCRadixCache, and the Rust tree core (SGLANG_UNIFIED_RADIX_TREE_CORE_BACKEND=rust) are not instrumented; the cache_type label makes the gap visible.
  • Buckets are fixed. The repo convention for configurable buckets is a --*-buckets server arg via generate_buckets; can follow if wanted.
  • kv_reuses reads 0 under write_back.

Accuracy Tests

N/A, no model output change.

Speed Tests and Profiling

Not benchmarked; see Cost. No visible change in step time on the validation server at this scale.

Checklist

🤖 Generated with Claude Code


CI States

Latest PR Test (Base): 🚫 Run #34326152544
Latest PR Test (Extra): ❌ Run #34326152466
Latest PR Test (AMD ROCm 10): ❌ Run #34326152766

…adixCache

Add sglang:kv_age_seconds{event,tier,outcome} and its token-weighted
companion sglang:kv_age_tokens_total{...,age_le}: seconds since a radix node
was last matched, observed when it is matched again (event=hit, tier=device
or host) and when it leaves a tier (event=evict; device demoted/dropped,
host dropped). Comparing the hit and evict curves per tier shows whether
a tier evicts content shortly before the traffic reuses it.

Observability only, no eviction behaviour change. CPU unit tests included.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added hicache Hierarchical Caching for SGLang unified-radix-cache labels Sep 8, 2026
…e eviction hook

Record TreeNode.creation_time age and hit_count when a node leaves a tier,
under the same tier/outcome labels as kv_age_seconds, so the series cover
the quantities proposed in sgl-project#28507 (lifetime, idle, reuses, host eviction)
with one label schema. RadixCache._observe_kv_eviction is the single call
site helper; HiRadixCache inherits it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gilfordting gilfordting changed the title [metrics] Record KV age at hit and at eviction for RadixCache and HiRadixCache [metrics] KV age at hit and eviction, lifetime and reuse count at eviction, for RadixCache and HiRadixCache Sep 8, 2026
gilfordting and others added 4 commits September 8, 2026 19:56
…ree core

UnifiedRadixCache is the default tree cache, and its nodes live in the tree
core with logical (counter) timestamps, so the RadixCache hooks never fired
there. Add wall-clock twins of last_access_time / creation_time on
UnifiedTreeNode, an optional kv_age_observer slot on the tree-core interface
(no-op unless installed; the Rust core inherits the default), and emit from
the Python core at the match chain refresh and at the four removal sites:
device drop, device demote, host leaf eviction, and write-back subtree drop.
UnifiedRadixCache installs the observer when metrics are enabled and forwards
to the same collector methods RadixCache uses.

Verified negatively first: a HiCache server on this branch's previous commit
(UnifiedRadixCache, write_through) evicted 367k tokens with no kv_age
samples; the CPU test now exercises the unified path too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Live validation showed two artifacts: cache_unfinished_req re-matches a
request right after inserting it, so every prompt registered as a device hit
at age ~0, and waiting requests are re-matched every scheduling round, so
queue time showed up as reuse age. Gate the hit observation on a per-Req flag
(take_kv_age_hit_observation) so only a request's first match_prefix records
ages; request-less matches never do. Eviction ages are unaffected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…o never-reused reads 2

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The hooks run once per node on the scheduler thread. Resolving the label
children once per label combination instead of calling labels() on every
observation takes the match hook from ~7.3 us to ~3.0 us per node on a CPU
micro-benchmark (128-node chain, real prometheus_client series).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gilfordting

Copy link
Copy Markdown
Contributor Author

/rerun-group unit/mem_cache

@gilfordting

Copy link
Copy Markdown
Contributor Author

/rerun-group unit/observability

@gilfordting

Copy link
Copy Markdown
Contributor Author

/rerun-test test_metrics.py test_radix_cache_hit.py test_unified_radix_cache_kl_full.py test_hicache_variants.py test_hicache_storage.py

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Results for /rerun-group unit/mem_cache:

🚀 1-gpu-5090 (18 tests): ✅ View workflow run

cd test/ && python3 registered/unit/mem_cache/test_asymmetric_mha_pool.py
cd test/ && python3 registered/unit/mem_cache/test_decode_retraction_backup.py
cd test/ && python3 registered/unit/mem_cache/test_dsa_pool_host_unit.py
cd test/ && python3 registered/unit/mem_cache/test_hicache_load_back_timing.py
cd test/ && python3 registered/unit/mem_cache/test_hicache_nixl_storage.py
cd test/ && python3 registered/unit/mem_cache/test_hiradix_cache_unit.py
cd test/ && python3 registered/unit/mem_cache/test_mamba_path_state_cap.py
cd test/ && python3 registered/unit/mem_cache/test_mamba_unittest.py
cd test/ && python3 registered/unit/mem_cache/test_minimax_sparse_pool_host_unit.py
cd test/ && python3 registered/unit/mem_cache/test_radix_cache_unit.py
cd test/ && python3 registered/unit/mem_cache/test_rust_unified_radix_cache_bench.py
cd test/ && python3 registered/unit/mem_cache/test_rust_unified_radix_cache_unittest.py
cd test/ && python3 registered/unit/mem_cache/test_swa_lock_release_lifecycle.py
cd test/ && python3 registered/unit/mem_cache/test_unified_handout_zeroing.py
cd test/ && python3 registered/unit/mem_cache/test_unified_mla_block_table.py
cd test/ && python3 registered/unit/mem_cache/test_unified_mla_gpu_parity.py
cd test/ && python3 registered/unit/mem_cache/test_unified_radix_cache_bench.py
cd test/ && python3 registered/unit/mem_cache/test_unified_radix_cache_unittest.py

🚀 ubuntu-latest (75 tests): ✅ View workflow run

cd test/ && python3 registered/unit/mem_cache/test_asymmetric_mha_pool_host_unit.py
cd test/ && python3 registered/unit/mem_cache/test_buffer_mode_sidecar.py
cd test/ && python3 registered/unit/mem_cache/test_decode_radix_lock_ref.py
cd test/ && python3 registered/unit/mem_cache/test_dllm_fdfo_kv_reuse.py
cd test/ && python3 registered/unit/mem_cache/test_dsa_layer_shard_utils.py
cd test/ && python3 registered/unit/mem_cache/test_dsv4_c4_state_lifecycle.py
cd test/ && python3 registered/unit/mem_cache/test_dsv4_compress_write_pad.py
cd test/ && python3 registered/unit/mem_cache/test_embedding_cache_controller.py
cd test/ && python3 registered/unit/mem_cache/test_evict_policy.py
cd test/ && python3 registered/unit/mem_cache/test_flashkda_strided_state_access.py
cd test/ && python3 registered/unit/mem_cache/test_full_loc_fast_path.py
cd test/ && python3 registered/unit/mem_cache/test_hicache_dcp_host_pool.py
cd test/ && python3 registered/unit/mem_cache/test_hicache_file_lru_unit.py
cd test/ && python3 registered/unit/mem_cache/test_hicache_host_register.py
cd test/ && python3 registered/unit/mem_cache/test_hicache_nixl_cleaner.py
cd test/ && python3 registered/unit/mem_cache/test_hicache_staged_write_back_dispatch.py
cd test/ && python3 registered/unit/mem_cache/test_hiradix_pp_sync_drain.py
cd test/ && python3 registered/unit/mem_cache/test_hisparse_allocator.py
cd test/ && python3 registered/unit/mem_cache/test_hisparse_max_token_pool_size.py
cd test/ && python3 registered/unit/mem_cache/test_hybrid_pool_assembler.py
cd test/ && python3 registered/unit/mem_cache/test_inkling_sconv_strided_conv_state.py
cd test/ && python3 registered/unit/mem_cache/test_kda_fused_decode_strided_state.py
cd test/ && python3 registered/unit/mem_cache/test_kv_index_translator.py
cd test/ && python3 registered/unit/mem_cache/test_layout_compat.py
cd test/ && python3 registered/unit/mem_cache/test_linker_pool_assembler.py
cd test/ && python3 registered/unit/mem_cache/test_mamba_donated_alloc_ratio.py
cd test/ && python3 registered/unit/mem_cache/test_mamba_state_transfer_buffers.py
cd test/ && python3 registered/unit/mem_cache/test_mem_cache_utils.py
cd test/ && python3 registered/unit/mem_cache/test_mem_pool_host.py
cd test/ && python3 registered/unit/mem_cache/test_minimax_sparse_pool_pd_unit.py
cd test/ && python3 registered/unit/mem_cache/test_mla_host_dedup_primitives.py
cd test/ && python3 registered/unit/mem_cache/test_mmap_allocator.py
cd test/ && python3 registered/unit/mem_cache/test_mooncake_group_semantics.py
cd test/ && python3 registered/unit/mem_cache/test_mooncake_standalone_dummy_mamba.py
cd test/ && python3 registered/unit/mem_cache/test_mooncake_tenant_config.py
cd test/ && python3 registered/unit/mem_cache/test_multi_ended_allocator.py
cd test/ && python3 registered/unit/mem_cache/test_mxfp8_scale_transfer_buffers.py
cd test/ && python3 registered/unit/mem_cache/test_page_major_layout.py
cd test/ && python3 registered/unit/mem_cache/test_paged_allocator_lazy_release.py
cd test/ && python3 registered/unit/mem_cache/test_paged_free_segment.py
cd test/ && python3 registered/unit/mem_cache/test_pd_envelope_transfer_layout.py
cd test/ && python3 registered/unit/mem_cache/test_pure_swa_chunk_cache.py
cd test/ && python3 registered/unit/mem_cache/test_quantized_kv_pool.py
cd test/ && python3 registered/unit/mem_cache/test_radix_cache_cpp_unit.py
cd test/ && python3 registered/unit/mem_cache/test_radix_cache_slru_accuracy.py
cd test/ && python3 registered/unit/mem_cache/test_radix_force_miss.py
cd test/ && python3 registered/unit/mem_cache/test_registry.py
cd test/ && python3 registered/unit/mem_cache/test_replayssm_ring_accounting.py
cd test/ && python3 registered/unit/mem_cache/test_retraction_mamba_backup.py
cd test/ && python3 registered/unit/mem_cache/test_rust_tree_core.py
cd test/ && python3 registered/unit/mem_cache/test_rust_tree_core_integration.py
cd test/ && python3 registered/unit/mem_cache/test_session_token_share_unit.py
cd test/ && python3 registered/unit/mem_cache/test_session_unified_radix_cache.py
cd test/ && python3 registered/unit/mem_cache/test_streaming_session_unit.py
cd test/ && python3 registered/unit/mem_cache/test_swa_alloc_extend_page_estimation.py
cd test/ && python3 registered/unit/mem_cache/test_swa_cpu_copy_filter.py
cd test/ && python3 registered/unit/mem_cache/test_swa_locked_full_recover_unified.py
cd test/ && python3 registered/unit/mem_cache/test_swa_pool_v_head_dim.py
cd test/ && python3 registered/unit/mem_cache/test_tree_core_registry.py
cd test/ && python3 registered/unit/mem_cache/test_umbp_host_allocator.py
cd test/ && python3 registered/unit/mem_cache/test_unified_byte_accounting.py
cd test/ && python3 registered/unit/mem_cache/test_unified_byte_budget_sizing.py
cd test/ && python3 registered/unit/mem_cache/test_unified_cache_linker.py
cd test/ && python3 registered/unit/mem_cache/test_unified_capacity_memo.py
cd test/ && python3 registered/unit/mem_cache/test_unified_free_no_host_sync.py
cd test/ && python3 registered/unit/mem_cache/test_unified_mamba_views.py
cd test/ && python3 registered/unit/mem_cache/test_unified_mha_views.py
cd test/ && python3 registered/unit/mem_cache/test_unified_mla_views.py
cd test/ && python3 registered/unit/mem_cache/test_unified_npool_sweep.py
cd test/ && python3 registered/unit/mem_cache/test_unified_radix_allocation_eviction.py
cd test/ && python3 registered/unit/mem_cache/test_unified_radix_hicache_dispatch.py
cd test/ && python3 registered/unit/mem_cache/test_unified_radix_lock_ref.py
cd test/ && python3 registered/unit/mem_cache/test_unified_swa_shared_virtual_ids.py
cd test/ && python3 registered/unit/mem_cache/test_unified_tri_pool.py
cd test/ && python3 registered/unit/mem_cache/test_uno_allocation_sizing.py

🚀 4-gpu-b200 (1 test): ✅ View workflow run

cd test/ && python3 registered/unit/mem_cache/test_dsa_layer_split_broadcast.py

🚀 1-gpu-h100 (2 tests): ✅ View workflow run

cd test/ && python3 registered/unit/mem_cache/test_swa_eviction_boundary.py
cd test/ && python3 registered/unit/mem_cache/test_swa_unittest.py

registered/unit/mem_cache/test_umbp_store.py: test/registered/unit/mem_cache/test_umbp_store.py is registered for AMD (suite stage-a-test-1-gpu-small-amd), not for CUDA or CPU; rerun-test.yml has no AMD job. Rerun it with /rerun-failed-ci, or dispatch the AMD workflow manually.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Results for /rerun-group unit/observability:

🚀 ubuntu-latest (12 tests): ✅ View workflow run

cd test/ && python3 registered/unit/observability/test_cpu_monitor.py
cd test/ && python3 registered/unit/observability/test_forward_pass_metrics.py
cd test/ && python3 registered/unit/observability/test_func_timer.py
cd test/ && python3 registered/unit/observability/test_label_transform.py
cd test/ && python3 registered/unit/observability/test_metrics_utils.py
cd test/ && python3 registered/unit/observability/test_ray_wrappers.py
cd test/ && python3 registered/unit/observability/test_req_time_stats.py
cd test/ && python3 registered/unit/observability/test_request_metrics_exporter.py
cd test/ && python3 registered/unit/observability/test_scheduler_stage_metrics.py
cd test/ && python3 registered/unit/observability/test_startup_func_log_and_timer.py
cd test/ && python3 registered/unit/observability/test_stat_loggers_di.py
cd test/ && python3 registered/unit/observability/test_trace.py

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Results for /rerun-test test_metrics.py test_radix_cache_hit.py test_unified_radix_cache_kl_full.py test_hicache_variants.py test_hicache_storage.py:

🚀 1-gpu-5090 (3 tests): ✅ View workflow run

cd test/ && python3 registered/observability/test_metrics.py
cd test/ && python3 registered/radix_cache/test_radix_cache_hit.py
cd test/ && python3 registered/hicache/test_hicache_storage.py

🚀 2-gpu-h100 (1 test): ✅ View workflow run

cd test/ && python3 registered/radix_cache/unified_radix_tree/test_unified_radix_cache_kl_full.py

🚀 1-gpu-h100 (1 test): ✅ View workflow run

cd test/ && python3 registered/hicache/test_hicache_variants.py

@gilfordting

Copy link
Copy Markdown
Contributor Author

/tag-and-rerun-ci

@gilfordting

Copy link
Copy Markdown
Contributor Author

/tag-and-rerun-ci

The memoized label children for the eviction observer were stored in the
instance __dict__ under "_kv_eviction_children", the same name as the
helper method that fills them. After the first eviction the attribute
shadowed the method, so the second eviction on a collector raised
"TypeError: 'dict' object is not callable" and took the scheduler down
(seen in test_basic_sanity_eagle3 on CI).

Initialize the three caches in __init__ under names distinct from the
helper methods, and add a regression test that observes repeated
evictions and hits on one collector and checks each label child is
resolved exactly once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hicache Hierarchical Caching for SGLang run-ci unified-radix-cache

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant