Skip to content

Commit 81fa669

Browse files
jh-nvclaude
andauthored
fix(metrics): raise inter-token latency histogram ceiling to 80s (ai-dynamo#14839)
Signed-off-by: Jie Hao <jihao@nvidia.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 805a77f commit 81fa669

2 files changed

Lines changed: 54 additions & 13 deletions

File tree

‎docs/fern/pages/reference/components/frontend-configuration.mdx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -539,29 +539,29 @@ See the [KServe gRPC Frontend](../../developer-guide/knowledge-base/modular-comp
539539

540540
### Histogram buckets
541541

542-
The frontend's six latency and size histograms use log-spaced buckets, rounded to two significant figures, always starting with a `0` edge. Each histogram is tuned by three environment variables that share a prefix: `_MIN` and `_MAX` bound the range, and `_COUNT` is the total number of bucket edges **including** that leading `0`, so `COUNT - 1` edges are spread across the range. The inter-token latency default of `13` therefore means `0` plus twelve log-spaced edges. Every edge, including the top one, is rounded to two significant figures, so the highest exported edge is `MAX` rounded rather than `MAX` itself — see the `Top le` column below. These have no CLI equivalent, and they are read once while the frontend builds its metrics, so changing one requires a restart.
542+
The frontend's six latency and size histograms use log-spaced buckets, rounded to two significant figures, always starting with a `0` edge. Each histogram is tuned by three environment variables that share a prefix: `_MIN` and `_MAX` bound the range, and `_COUNT` is the total number of bucket edges **including** that leading `0`, so `COUNT - 1` edges are spread across the range. The inter-token latency default of `20` therefore means `0` plus nineteen log-spaced edges. Every edge, including the top one, is rounded to two significant figures, so the highest exported edge is `MAX` rounded rather than `MAX` itself — see the `Top le` column below. These have no CLI equivalent, and they are read once while the frontend builds its metrics, so changing one requires a restart.
543543

544544
| Histogram | Variable prefix | Min | Max | Count | Top `le` |
545545
|---|---|---|---|---|---|
546546
| `dynamo_frontend_request_duration_seconds` | `DYN_METRICS_REQUEST_DURATION` | `1.0` | `512.0` | `10` | `510` |
547547
| `dynamo_frontend_input_sequence_tokens` | `DYN_METRICS_INPUT_SEQUENCE` | `50.0` | `128000.0` | `12` | `130000` |
548548
| `dynamo_frontend_output_sequence_tokens` | `DYN_METRICS_OUTPUT_SEQUENCE` | `50.0` | `32000.0` | `10` | `32000` |
549549
| `dynamo_frontend_time_to_first_token_seconds` | `DYN_METRICS_TTFT` | `0.001` | `480.0` | `18` | `480` |
550-
| `dynamo_frontend_inter_token_latency_seconds` | `DYN_METRICS_ITL` | `0.001` | `2.0` | `13` | `2` |
550+
| `dynamo_frontend_inter_token_latency_seconds` | `DYN_METRICS_ITL` | `0.001` | `80.0` | `20` | `80` |
551551
| `dynamo_frontend_embedding_latency_seconds` | `DYN_METRICS_EMBEDDING_LATENCY` | `0.001` | `10.0` | `14` | `10` |
552552

553-
For example, to raise the inter-token latency ceiling from 2 seconds to 80:
553+
For example, to raise the output sequence ceiling from 32k tokens to 128k:
554554

555555
```bash
556-
export DYN_METRICS_ITL_MAX=80
557-
export DYN_METRICS_ITL_COUNT=20
556+
export DYN_METRICS_OUTPUT_SEQUENCE_MAX=131072
557+
export DYN_METRICS_OUTPUT_SEQUENCE_COUNT=12
558558
```
559559

560560
Rounding can move the top edge either way, so pick a `MAX` that is already two significant figures if you need an exact ceiling. Prometheus buckets are inclusive, so `MAX=123` exports `le=120` and only observations *greater than* 120 and up to 123 fall into `+Inf` — a value of exactly 120 still lands in the `le=120` bucket. `MAX=131072` exports `le=130000`. Two of the defaults above are affected: request duration is set to `512` but exports `510`, and input sequence is set to `128000` but exports `130000`.
561561

562562
`dynamo_frontend_cached_tokens` shares the input sequence buckets, so `DYN_METRICS_INPUT_SEQUENCE_*` changes both histograms.
563563

564-
A histogram's configuration is validated as a set: `MIN` must be above zero, below `MAX`, and `COUNT` must be between 1 and 512. If any part fails, the frontend warns and reverts **all three** values for that histogram together — setting `DYN_METRICS_ITL_MIN=5` against the default `MAX` of `2.0` also discards a perfectly valid `DYN_METRICS_ITL_COUNT=20`.
564+
A histogram's configuration is validated as a set: `MIN` must be above zero, below `MAX`, and `COUNT` must be between 1 and 512. If any part fails, the frontend warns and reverts **all three** values for that histogram together — setting `DYN_METRICS_ITL_MIN=100` against the default `MAX` of `80.0` also discards a perfectly valid `DYN_METRICS_ITL_COUNT=20`.
565565

566566
A value that cannot be parsed at all is handled per variable, not as a set: it warns, that one variable falls back to its default, and the others still apply.
567567

‎lib/llm/src/http/service/metrics.rs‎

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ impl Metrics {
827827
/// - `DYN_METRICS_INPUT_SEQUENCE_{MIN,MAX,COUNT}` - Input sequence length histogram (defaults: 50.0, 128000.0, 12)
828828
/// - `DYN_METRICS_OUTPUT_SEQUENCE_{MIN,MAX,COUNT}` - Output sequence length histogram (defaults: 50.0, 32000.0, 10)
829829
/// - `DYN_METRICS_TTFT_{MIN,MAX,COUNT}` - Time to first token histogram (defaults: 0.001, 480.0, 18)
830-
/// - `DYN_METRICS_ITL_{MIN,MAX,COUNT}` - Inter-token latency histogram (defaults: 0.001, 2.0, 13)
830+
/// - `DYN_METRICS_ITL_{MIN,MAX,COUNT}` - Inter-token latency histogram (defaults: 0.001, 80.0, 20)
831831
/// - `DYN_METRICS_EMBEDDING_LATENCY_{MIN,MAX,COUNT}` - End-to-end `/v1/embeddings` latency histogram (defaults: 0.001, 10.0, 14)
832832
///
833833
/// ## Model Configuration Metrics
@@ -1024,7 +1024,7 @@ impl Metrics {
10241024

10251025
// Inter-token latency buckets: configurable via DYN_METRICS_ITL_{MIN,MAX,COUNT}
10261026
let (itl_min, itl_max, itl_count) =
1027-
parse_bucket_config(env, env_metrics::DYN_METRICS_ITL, 0.001, 2.0, 13);
1027+
parse_bucket_config(env, env_metrics::DYN_METRICS_ITL, 0.001, 80.0, 20);
10281028
let inter_token_latency_buckets = generate_log_buckets(itl_min, itl_max, itl_count);
10291029

10301030
let inter_token_latency = HistogramVec::new(
@@ -2780,9 +2780,11 @@ mod tests {
27802780

27812781
#[test]
27822782
fn itl_ceiling_env_var_reaches_the_exported_le_labels() {
2783+
// Deliberately not the default (80.0, 20): a test that sets the env to the
2784+
// default value would pass even if the variable were ignored entirely.
27832785
let env = fake_env(&[
2784-
("DYN_METRICS_ITL_MAX", "80"),
2785-
("DYN_METRICS_ITL_COUNT", "20"),
2786+
("DYN_METRICS_ITL_MAX", "30"),
2787+
("DYN_METRICS_ITL_COUNT", "8"),
27862788
]);
27872789
let registry = Registry::new();
27882790
let metrics = Metrics::build(None, &env);
@@ -2802,9 +2804,47 @@ mod tests {
28022804
);
28032805
assert_eq!(
28042806
bounds.last().copied(),
2805-
Some(80.0),
2807+
Some(30.0),
28062808
"top finite bucket should follow DYN_METRICS_ITL_MAX, got {bounds:?}"
28072809
);
2810+
assert_eq!(
2811+
bounds.len(),
2812+
8,
2813+
"bucket count should follow DYN_METRICS_ITL_COUNT"
2814+
);
2815+
}
2816+
2817+
#[test]
2818+
fn itl_default_buckets_reach_80_seconds() {
2819+
// The ceiling was 2.0s, so any inter-token latency above it landed in `+Inf` and
2820+
// `histogram_quantile` pinned p99 at exactly 2.0 -- indistinguishable from a real
2821+
// 2s measurement. Guard the shipped default, not just the env-var override.
2822+
let registry = Registry::new();
2823+
let metrics = Metrics::build(None, &fake_env(&[]));
2824+
metrics.register(&registry).unwrap();
2825+
metrics
2826+
.inter_token_latency
2827+
.with_label_values(&["m"])
2828+
.observe(0.5);
2829+
2830+
let bounds = bucket_upper_bounds(
2831+
&registry,
2832+
&format!(
2833+
"{}_{}",
2834+
name_prefix::FRONTEND,
2835+
frontend_service::INTER_TOKEN_LATENCY_SECONDS
2836+
),
2837+
);
2838+
// The exact set is the contract. Note the bottom edge of 0.0018: vLLM's equivalent
2839+
// starts at 0.01 and cannot resolve anything faster than 10ms per token, so raising
2840+
// the ceiling must not cost the low-end resolution Dynamo has and vLLM does not.
2841+
assert_eq!(
2842+
bounds,
2843+
vec![
2844+
0.0, 0.0018, 0.0033, 0.0059, 0.011, 0.02, 0.035, 0.064, 0.12, 0.21, 0.38, 0.69,
2845+
1.2, 2.3, 4.1, 7.4, 13.0, 24.0, 44.0, 80.0
2846+
],
2847+
);
28082848
}
28092849

28102850
#[test]
@@ -2841,11 +2881,12 @@ mod tests {
28412881
#[test]
28422882
fn test_all_buckets_are_two_sig_figs() {
28432883
let test_cases = vec![
2844-
(1.0, 256.0, 10),
2884+
(1.0, 512.0, 10),
28452885
(50.0, 128000.0, 12),
28462886
(50.0, 32000.0, 10),
28472887
(0.001, 480.0, 18),
2848-
(0.001, 2.0, 13),
2888+
(0.001, 80.0, 20),
2889+
(0.001, 10.0, 14),
28492890
];
28502891

28512892
for (min, max, count) in test_cases {

0 commit comments

Comments
 (0)