Skip to content

[Feature] Per-request Observability: TTFT Breakdown + HiCache Metrics #28047

Description

@grissomsh

Problem

Currently, SGLang lacks per-request observability in two key areas:

  1. TTFT cannot be decomposed — there's no way to know how much of TTFT is queue wait, schedule delay, or GPU forward time. Operators can only see total TTFT, making prefill performance tuning guesswork. This problem exists regardless of whether HiCache is enabled.
  2. HiCache tier performance is invisible — with three-tier KV cache (GPU→CPU→NVMe) enabled, L2 DMA time, L3 prefetch I/O time, and per-tier hit/miss counts are not available per-request. No Prometheus metrics exist for cache hit rates, prefetch latency distributions, or eviction pressure.

Proposed Solution

A lightweight per-request trace + Prometheus metrics system covering both TTFT decomposition and HiCache observability, implemented across 5 files.

Part A: Per-request Trace (JSON log, debug level)

One JSON line per request at logger.debug level. The TTFT fields work universally; HiCache fields are gated by --enable-hierarchical-cache:

{
  "rid": "b3f3e0e1...",
  "prompt_tokens": 136921,
  "input_tokens_cache_hit": 119040,
  "input_tokens_no_cache_hit": 17881,
  "output_tokens": 4096,
  "L1_hit": 0,
  "L1_hit_pct": 0.0,
  "L2_hit": 119040,
  "L2_hit_pct": 86.9,
  "L3_hit": 0,
  "L3_hit_pct": 0.0,
  "L3_pf_alloc_ms": 0,
  "L3_pf_io_ms": 0,
  "L3_pf_tree_ms": 0,
  "queue_ms": 9.9,
  "schedule_ms": 2.4,
  "forward_ms": 1174.6,
  "TTFT_ms": 78907.9
}

TTFT Decomposition (universal — works with or without HiCache)

TTFT ≈ queue_ms + schedule_ms + forward_ms

Field Source Meaning
queue_ms time_stats.scheduler_recv_time → wait_queue_entry_time Tokenizer → enqueue
schedule_ms time_stats.wait_queue_entry_time → forward_entry_time Wait in queue + chunk waits
forward_ms time_stats.forward_entry_time → prefill_finished_time GPU prefill compute

HiCache Fields (gated by --enable-hierarchical-cache)

Per-tier hit token counts with percentages, L3 prefetch phase breakdown.

Why no L2 DMA time? cache_controller.load() is asynchronous — it allocates GPU memory and queues the DMA operation, then returns immediately. The actual DMA runs on a CUDA stream in a background thread (start_loading()). CPU-side time.perf_counter() cannot capture the DMA completion time. Since DMA bandwidth is fixed (PCIe 5.0 × 16 ≈ 64 GB/s), the transfer time is a known constant: L2_hit_tokens × 48 KB/token ÷ 64 GB/s. L2 token count alone is sufficient to characterize L2 performance.

L3 prefetch phase breakdown (key finding made possible by this instrumentation):

Phase P50 Meaning
L3_pf_alloc_ms 0.1ms Host memory allocation
L3_pf_io_ms 91ms Pure NVMe→CPU I/O
L3_pf_tree_ms 0.4ms Radix tree insertion

Without this breakdown, the total L3 prefetch time is often conflated with scheduler queue delays (~2.4s vs actual I/O ~91ms).

Part B: Prometheus Metrics

6 Histograms + 2 Counters exposed at /metrics:

Metric Type Description
sglang:hicache_l1_hit_tokens Histogram L1 hit tokens per request
sglang:hicache_l2_hit_tokens Histogram L2 hit tokens per request
sglang:hicache_l3_hit_tokens Histogram L3 hit tokens per request
sglang:hicache_l3_prefetch_seconds Histogram L3→L2 prefetch total duration
sglang:hicache_l3_pf_alloc_seconds Histogram L3 prefetch: alloc phase
sglang:hicache_l3_pf_io_wait_seconds Histogram L3 prefetch: I/O phase
sglang:hicache_l3_pf_tree_insert_seconds Histogram L3 prefetch: tree insert phase
sglang:hicache_cached_input_tokens_total Counter Cumulative cached tokens
sglang:hicache_uncached_input_tokens_total Counter Cumulative uncached tokens

Part C: Eviction Metrics

RadixCacheMetricsCollector.increment_eviction_num_tokens() called from cache eviction code paths (previously unused in HiMambaRadixCache):

  • L1→L2 eviction counter (tokens evicted from GPU to CPU)
  • L2→L3 eviction counter (tokens evicted from CPU to NVMe)

Write-back timing is excluded for the same reason as L2 DMA — cache_controller.write() is also asynchronous.

Implementation Scope

5 files, ~100 lines of instrumentation total:

File Change
scheduler.py _hicache_trace_one_request() method: trace JSON + metrics observe
metrics_collector.py 7 HiCache Histograms + 2 Counters
hi_mamba_radix_cache.py L2/L3 timing in load_back/write_backup/evict/evict_host
hiradix_cache.py Same for MHA models
cache_controller.py _page_transfer I/O timing via try/finally

Compatibility

  • TTFT decomposition fields work universally (no flag required). HiCache fields gated by --enable-hierarchical-cache
  • Trace output at logger.debug level only (no overhead at default info level), tp_rank == 0 only
  • HiCache metrics guarded by enable_metrics and --enable-hierarchical-cache
  • No breaking changes to existing APIs

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions