You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Based on source code analysis of both feat/unified-metrics branches.
Consolidated from v1 (architecture-focused) and v2 (unified-metrics branch perspective).
Prometheus multiprocess via PROMETHEUS_MULTIPROC_DIR
Single-process (frontend)
2. Unified Metrics (Present in Both)
These metrics exist in both frameworks on the feat/unified-metrics branches with compatible names and semantics.
Request Lifecycle & Timestamps
Both engines track the same request lifecycle stages, though they use different variable names and clock types:
arrival / received ← HTTP request hits the API server
│ vLLM: arrival_time (time.time())
│ SGLang: received_time / created_time (time.time())
▼ [tokenization, input preprocessing, IPC to engine core]
queued ← request enters scheduler's waiting queue
│ vLLM: queued_ts (time.monotonic())
│ SGLang: wait_queue_entry_time (time.perf_counter())
▼ [waiting for KV cache / compute budget]
scheduled ← scheduler picks request into a running batch
│ vLLM: scheduled_ts (time.monotonic())
│ SGLang: forward_entry_time (time.perf_counter())
▼ [GPU forward pass - prefill]
first_token ← first output token produced
│ vLLM: first_token_ts (time.monotonic())
│ SGLang: first_token_time (time.time())
▼ [decode iterations]
last_token / finished ← final output token produced
vLLM: last_token_ts (time.monotonic()) / iteration_timestamp (time.time())
SGLang: finished_time (time.time())
Clock differences: vLLM uses time.monotonic() for engine-core timestamps (queued → last_token) and time.time() for frontend timestamps (arrival, iteration). SGLang uses time.perf_counter() for scheduler timestamps and time.time() for API-level timestamps. Timestamps on different clocks cannot be subtracted from each other, so each metric uses a consistent clock pair.
Key distinction:arrival_time ≠ queued_ts. The gap between them includes tokenization, input preprocessing, and IPC transfer from frontend to engine core. The e2e_request_latency uses arrival/finished (wall-clock), while request_queue_time uses queued/scheduled (monotonic).
Core Latency Histograms
Metric Name
SGLang
vLLM
Computation Match?
e2e_request_latency_seconds
Histogram
Histogram
YES — both measure wall-clock from request arrival to completion. SGLang: finished_time - created_time. vLLM: iteration_ts - arrival_time.
time_to_first_token_seconds
Histogram
Histogram
YES — SGLang: first_token_time - created_time. vLLM: iteration_ts - arrival_time (on first token iteration). Both use wall-clock, both include queue wait.
inter_token_latency_seconds
Histogram
Histogram
SIMILAR (SGLang normalizes by num_new_tokens; vLLM measures individual gaps)
Metrics are per-connector (each connector defines its own via build_prom_metrics()). The NIXL connector is the most instrumented.
Queue Depth Metrics (SGLang-only, no vLLM equivalent)
These metrics have no vLLM equivalent because vLLM's disaggregation does not use explicit queues.
Metric Name
Type
Description
num_prefill_prealloc_queue_reqs
Gauge
Prefill bootstrap queue depth
num_prefill_inflight_queue_reqs
Gauge
Prefill inflight (transfer in progress) queue depth
num_decode_prealloc_queue_reqs
Gauge
Decode preallocation queue depth
num_decode_transfer_queue_reqs
Gauge
Decode transfer (receiving KV) queue depth
KV Transfer Performance (overlap exists)
Concept
SGLang Metric
Type
vLLM Metric (NIXL)
Type
Notes
Transfer speed
kv_transfer_speed_gb_s
Gauge
kv_transfer_speed_gb_s
Gauge
Exact match (vLLM explicitly SGLang-compatible)
Transfer latency
kv_transfer_latency_ms
Gauge
nixl_xfer_time_seconds
Histogram
vLLM more detailed (histogram vs gauge), different unit
Bytes transferred
kv_transfer_total_mb
Gauge
nixl_bytes_transferred
Histogram
vLLM more detailed (histogram vs gauge), raw bytes
Bootstrap/post time
kv_transfer_bootstrap_ms
Gauge
nixl_post_time_seconds
Histogram
Similar concept, different granularity
Alloc wait time
kv_transfer_alloc_ms
Gauge
—
—
No vLLM equivalent
Failure & Retry Counters
Concept
SGLang Metric
vLLM Metric (NIXL)
Notes
Transfer failures
num_transfer_failed_reqs_total
nixl_num_failed_transfers
Similar semantics
Bootstrap failures
num_bootstrap_failed_reqs_total
—
No vLLM equivalent (no separate bootstrap phase)
Prefill retries
num_prefill_retries_total
—
No vLLM equivalent (uses recompute/fail policy instead)
Failed notifications
—
nixl_num_failed_notifications
vLLM/NIXL-specific
KV expiration
—
nixl_num_kv_expired_reqs
vLLM/NIXL-specific
vLLM NIXL-Specific Metrics (no SGLang equivalent)
Metric Name
Type
Description
nixl_post_time_seconds
Histogram
Post-transfer processing time
nixl_num_descriptors
Histogram
Number of descriptors per transfer
nixl_num_failed_notifications
Counter
Failed NIXL notifications
nixl_num_kv_expired_reqs
Counter
Requests with expired KV (tracked on P instance)
Architecture Impact on Unification
The queue depth gauges cannot be added to vLLM without redesigning its disaggregation architecture. The transfer performance and failure metrics have partial overlap — kv_transfer_speed_gb_s is already unified, but the remaining vLLM metrics are NIXL-specific (prefixed nixl_*) rather than generic. Adding generic transfer gauges (kv_transfer_latency_ms, kv_transfer_total_mb) to vLLM would be moderate effort — the data exists in NixlKVConnectorStats but would need to be surfaced as connector-agnostic metrics.
Retraction Detail
Metric Name
Type
SGLang
vLLM
Notes
num_retracted_requests_total
Counter
YES
YES
Unified name (was num_preemptions_total in vLLM)
num_retractions
Histogram
YES
YES
Per-request preemption count distribution, same buckets
Number of cached prompt tokens (local + external). Similar to SGLang's cached_tokens_total
prompt_tokens_recomputed
Counter
—
Number of cached tokens recomputed for forward pass (e.g., last token recompute when entire prompt is cached)
Cache Counters (more granular)
Metric Name
Type
Description
prefix_cache_queries
Counter
Prefix cache query count (tokens)
external_prefix_cache_queries
Counter
Cross-instance cache queries
external_prefix_cache_hits
Counter
Cross-instance cache hits
Speculative Decoding Counters (more granular)
Metric Name
Type
Description
spec_decode_num_draft_tokens
Counter
Draft token count
spec_decode_num_accepted_tokens
Counter
Accepted token count
Engine State
Metric Name
Type
Description
engine_sleep_state
Gauge
Engine sleep/wake state tracking
corrupted_requests
Counter
Requests with NaN logits (opt-in)
LoRA Info
Metric Name
Type
Description
lora_requests_info
Gauge
LoRA request info with adapter names
PD Disaggregation — NIXL Connector (see also §3 for cross-framework comparison)
Metric Name
Type
Description
nixl_xfer_time_seconds
Histogram
Transfer duration per NIXL KV cache transfer
nixl_post_time_seconds
Histogram
Post-transfer processing time
nixl_bytes_transferred
Histogram
Bytes transferred per transfer
nixl_num_descriptors
Histogram
Number of descriptors per transfer
nixl_num_failed_transfers
Counter
Failed NIXL transfers
nixl_num_failed_notifications
Counter
Failed NIXL notifications
nixl_num_kv_expired_reqs
Counter
Requests with expired KV (P instance)
kv_transfer_speed_gb_s
Gauge
KV transfer speed in GB/s (SGLang-compatible)
Config
Metric Name
Type
Description
cache_config_info
Gauge
Cache configuration info
Deprecated
Metric Name
Type
Description
time_per_output_token_seconds
Histogram
Deprecated alias for ITL (hidden by default)
5. Detailed Computation Differences
All core latency metrics are now aligned across both frameworks on the feat/unified-metrics branches. They use the same formula (different variable names for the same timestamps).
Aligned Latency Metrics
Metric
Unified Formula
SGLang Variables
vLLM Variables
e2e_request_latency_seconds
end_time - arrival_time
finished_time - created_time
iteration_timestamp - arrival_time
time_to_first_token_seconds
first_token_time - arrival_time
first_token_time - created_time
iteration_timestamp - arrival_time (at first token)
request_queue_time_seconds
scheduled_time - queued_time
forward_entry_time - wait_queue_entry_time
scheduled_ts - queued_ts
request_prefill_time_seconds
first_token_time - scheduled_time
first_token_time_perf - forward_entry
first_token_ts - scheduled_ts
request_decode_time_seconds
end_time - first_token_time
finished_time_perf - first_token_time_perf
last_token_ts - first_token_ts
request_inference_time_seconds
end_time - scheduled_time
finished_time_perf - forward_entry
last_token_ts - scheduled_ts
request_time_per_output_token_seconds
decode_time / (gen_tokens - 1)
Same
Same
Inter-Token Latency (remaining difference)
Framework
Computation
Notes
SGLang
(new_time - last_time) / num_new_tokens
Normalizes when multiple tokens arrive together
vLLM
engine_core_timestamp - last_token_ts
Measures individual inter-token gaps
This is the one remaining computation difference. SGLang normalizes by num_new_tokens when batched token delivery occurs; vLLM measures each gap individually. Both now also provide the per-request mean TPOT via request_time_per_output_token_seconds which is computed identically.
Requires PromQL to compute ratio, but more flexible
Throughput (gen_throughput)
Framework
Method
Notes
SGLang
num_generated_tokens / gap_latency
Computed in scheduler process
vLLM
accumulated_tokens / delta_time (every ~5s)
Computed in frontend process
Both compute tokens/second over a sliding window. The implementation differs due to architecture (SGLang: multi-process, vLLM: single frontend process) but the semantics are the same.
Recommendation: The _total suffix is the OpenMetrics standard for counters. Both approaches are valid. Keep framework-specific labels but ensure the core model_name label is consistent.
vLLM uses the prometheus_fastapi_instrumentator default buckets.
SGLang-Only: Routing Key Tracking
SGLang also tracks routing_keys_active (Gauge) — the number of unique routing keys with active requests, used for multi-tenant load balancing via the x-smg-routing-key header.
9. Request Type Counters
Both frameworks track request types for multi-modal and structured output workloads. These counters are defined in a shared module (request_metrics.py) and incremented across all three API endpoints: /v1/chat/completions, /v1/completions, and /v1/responses.
Metrics
Metric Name
Type
Description
Present In
request_type_image_total
Counter
Requests containing images
Both
request_type_video_total
Counter
Requests containing video_url content parts
Both
request_type_tool_call_total
Counter
Requests with tools defined and tool_choice != "none"
Both
request_type_structured_output_total
Counter
Requests using any structured output method
Both
API Endpoint Coverage
Counter
Chat (/v1/chat/completions)
Completions (/v1/completions)
Responses (/v1/responses)
request_type_image_total
SGLang + vLLM (image_url parts)
N/A
SGLang + vLLM (input_image items)
request_type_video_total
SGLang + vLLM (video_url parts)
N/A
N/A (not supported by Responses API)
request_type_tool_call_total
SGLang + vLLM
N/A
SGLang + vLLM
request_type_structured_output_total
SGLang + vLLM
SGLang + vLLM
vLLM only (text.format)
Notes:
Completions API does not support images, videos, or tool calls in either framework.
SGLang's Responses API does not support structured output (text.format / response_format), so the structured output counter is vLLM-only for responses.
The Responses API does not support video input in either framework (no input_video type in the OpenAI Responses API spec).
Classification Logic
Image
Chat: Both iterate over request.messages[*].content parts and check part.type == "image_url".
Responses: Both check request.input items for top-level input_image type or nested input_image/image_url in message content parts.
Each counter increments at most once per request (flags, not per-part counts).
Video
Chat only: Both check part.type == "video_url" in message content parts.
Tool Call
Both use identical logic across chat and responses:
ifrequest.toolsandrequest.tool_choice!="none":
This counts requests that enable tool calling (tools defined + not explicitly disabled). It does NOT count whether the model actually invoked a tool.
Structured Output
Both frameworks now cover all structured output methods:
Method
SGLang
vLLM
response_format: json_schema
✓ (via response_format.type check)
✓ (via response_format.type check)
response_format: json_object
✓ (via response_format.type check)
✓ (via response_format.type check)
response_format: structural_tag
✓ (via response_format.type check)
✓ (via response_format.type check)
regex
✓ (top-level request.regex field)
✓ (via request.structured_outputs)
ebnf / grammar
✓ (top-level request.ebnf field)
✓ (via request.structured_outputs)
choice
N/A (not a SGLang chat field)
✓ (via request.structured_outputs)
json (extra_body)
N/A (SGLang uses response_format)
✓ (via request.structured_outputs)
text.format (Responses API)
N/A (not supported in SGLang)
✓ (json_schema, json_object)
API design difference: SGLang exposes structured output options as top-level request fields (regex, ebnf), while vLLM uses extra_body={"structured_outputs": {...}} which maps to a StructuredOutputsParams object.
SGLang Grammar Metrics (Engine-Level, Distinct from Request Type Counters)
SGLang has additional engine-level grammar metrics that are separate from the chat-level request_type_structured_output_total:
Metric
Type
Layer
Description
num_so_requests_total
Counter
Tokenizer Manager
All requests with grammar at completion (any endpoint, not just chat)
Key distinction: request_type_structured_output_total fires at the HTTP/chat entrypoint when a request arrives. num_so_requests_total fires at the backend when a request with grammar completes (covers all endpoints: /generate, /v1/completions, /v1/chat/completions). num_grammar_total counts grammar engine operations, not user requests.
10. Config Info Metrics
Both frameworks expose configuration as info-style gauges (value always 1.0) with configuration details in labels.
model_config_info
Label
SGLang
vLLM
model
Yes
Yes
served_model_name
Yes
Yes
dtype
Yes
Yes
max_model_len
Yes (context_length)
Yes
max_total_tokens
Yes
No (vLLM-specific: see cache_config_info)
max_output_length
Yes
No (vLLM uses max_new_tokens in generation_config)
quantization
Yes
Yes
enforce_eager
Yes (disable_cuda_graph)
Yes
gpu_type
Yes
Yes
parallel_config_info
Label
SGLang
vLLM
tensor_parallel_size
Yes
Yes
pipeline_parallel_size
Yes
Yes
data_parallel_size
Yes
Yes
expert_parallel_size / enable_expert_parallel
expert_parallel_size
enable_expert_parallel
gpu_count
Yes
Yes
speculative_config_info
Label
SGLang
vLLM
spec_enabled
Yes
Yes
spec_algorithm / spec_method
spec_algorithm
spec_method
spec_num_draft_tokens / spec_num_tokens
spec_num_draft_tokens
spec_num_tokens
spec_num_steps
Yes
No
spec_eagle_topk
Yes
No
spec_draft_model
Yes
Yes
detailed_config_info (NEW — both frameworks)
Bundles scheduler, compilation, attention, and environment settings into a single info gauge.
Label
SGLang
vLLM
stream_interval
Yes
Yes
attention_backend
Yes
Yes
sampling_backend
Yes
No
grammar_backend
Yes
No
chunked_prefill_size
Yes
No
schedule_policy
Yes
No
compilation_mode
No
Yes
compilation_backend
No
Yes
cudagraph_mode
No
Yes
flash_attn_version
No
Yes
flashinfer_moe_backend
No
Yes
cache_config_info (vLLM-only)
vLLM additionally exposes cache configuration info. SGLang does not have a direct equivalent.
11. Architecture Differences
vLLM Architecture
vLLM records all per-request latency metrics in the frontend process (AsyncLLM/OutputProcessor):
Recommendation: Align on the _total suffix (OpenMetrics standard for counters).
Bucket Alignment
For consistent P50/P99 calculations across frameworks, align bucket boundaries for:
time_to_first_token_seconds — vLLM has better sub-100ms coverage
inter_token_latency_seconds — SGLang has better sub-10ms coverage
e2e_request_latency_seconds — different ranges and granularity
Recommendation: Adopt the union of both bucket sets, or make vLLM buckets configurable.
Next Steps for Full Parity
SGLang: Add request_prefill_kv_computed_tokens and iteration_tokens_total
SGLang: Consider adding prompt_tokens_by_source breakdown (local_compute, local_cache_hit, external_kv_transfer) to match new vLLM token provenance tracking
vLLM: Add prompt_tokens_by_source, prompt_tokens_cached, prompt_tokens_recomputed to _METRIC_NAME_MAP in the OTel bridge for proper _total suffix alignment
vLLM: Add structured output/grammar metrics if/when structured output support is added
Both: Consider adopting iteration_tokens_total as a shared metric for batch utilization
To unify metrics naming between vLLM and SGLang, we previously renamed metrics directly in
each framework's source code (removing vllm: / sglang: prefixes, aligning _total
suffixes, etc.). This approach works but creates a maintenance burden:
Every rebase onto upstream requires resolving conflicts in metrics definition files
Upstream PRs that add new metrics need manual renaming
Two separate codebases must be kept in sync
Solution: Rename in the Prom-to-OTEL bridge
Instead of modifying source code, apply metric name transformations in the bridge thread
(otel_instrumentation.py :: start_prom_to_otel_bridge), which already scrapes Prometheus
metrics and re-exports them as OTEL instruments. The bridge already has a
_sanitize_metric_name function that transforms names — extend it with a mapping dictionary.
Define a mapping dictionary in otel_instrumentation.py that maps native Prometheus
metric names to unified OTEL metric names
Apply the mapping in _sanitize_metric_name() before creating OTEL instruments
Mapping dictionary
The dictionary handles three kinds of transformations:
Prefix stripping — vllm_ / sglang_ → common name
Suffix alignment — _total presence/absence
Name differences — metrics that have completely different names across frameworks
# Native Prometheus name → unified OTEL name# Only entries that need renaming; unlisted metrics pass through with prefix stripped._METRIC_NAME_MAP: dict[str, str] = {
# --- Token counters (suffix alignment) ---# vLLM uses no _total suffix; SGLang uses _total"vllm_prompt_tokens": "prompt_tokens_total",
"sglang_prompt_tokens_total": "prompt_tokens_total",
"vllm_generation_tokens": "generation_tokens_total",
"sglang_generation_tokens_total": "generation_tokens_total",
# --- Request counters (suffix alignment) ---"vllm_request_success": "request_success_total",
"sglang_request_success_total": "request_success_total",
# --- Metrics with identical names after prefix strip (just strip prefix) ---# e.g. vllm_e2e_request_latency_seconds → e2e_request_latency_seconds# sglang_e2e_request_latency_seconds → e2e_request_latency_seconds# These are handled by the default prefix-strip logic, no explicit entry needed.# --- Metrics with different names across frameworks ---# Add entries here when the name differs beyond just prefix/suffix.# Example (hypothetical):# "vllm_num_requests_running": "num_requests_running",# "sglang_running_req_count": "num_requests_running",
}
def_sanitize_metric_name(name: str) ->str:
name=name.replace(":", "_")
# Check explicit mapping firstifnamein_METRIC_NAME_MAP:
return_METRIC_NAME_MAP[name]
# Default: strip known prefixesforprefixin ("vllm_", "sglang_"):
ifname.startswith(prefix):
returnname[len(prefix):]
returnname
What this changes
Aspect
Before (source-code renaming)
After (bridge-side renaming)
vLLM source code
Modified metric names
Untouched — keep native vllm: prefix
SGLang source code
Modified metric names
Untouched — keep native sglang: prefix
Rebase conflicts
Frequent (metrics files change often)
None (no source changes)
New upstream metrics
Need manual renaming
Auto-stripped prefix; add to map only if name differs
Mapping maintenance
Scattered across source files
Single dictionary in otel_instrumentation.py
Prometheus /metrics endpoint
Shows unified names
Shows native names (only OTEL export is unified)
OTEL collector
Receives unified names
Receives unified names (same outcome)
Considerations
Prometheus dashboards that scrape /metrics directly will see native names (vllm_*).
Only the OTEL export path gets unified names. This is acceptable if all production
monitoring goes through the OTEL collector.
The mapping dictionary should be kept in sync with this comparison doc (§2) when new
shared metrics are added.
For metrics unique to one framework (§3, §4), no mapping is needed — they pass through
with prefix stripped.
Note (v2-0.16.0): The new prompt_tokens_by_source, prompt_tokens_cached, and
prompt_tokens_recomputed counters are NOT yet in _METRIC_NAME_MAP. They will be
auto-stripped of the vllm_ prefix but will NOT get _total suffix alignment in the
OTel export. Consider adding them to the map for consistency.