Standalone · Serving

Dynamo observability: the straggler score

Disaggregated serving hides its problems in the tail. The slowest decode worker becomes the latency of every request it touches. Here's the metric that finds it, and the dashboard that makes it visible.

There is no metric named `straggler`. There is only the gap between your best worker and your worst, and whether you're measuring it. In disaggregated serving, that gap is the difference between a p50 that looks great and a p99 that's killing your SLO.

Why disaggregation hides stragglers

In a disaggregated setup, prefill and decode run on different machines. A request's TTFT (time to first token) is set by the prefill path; its ITL (inter-token latency) is set by the decode path. If one decode worker is slow, every request routed to it pays the tax, and the p99 of the whole fleet follows that worker around.

The problem: a single slow worker is invisible in the aggregate. The p99 TTFT histogram doesn't tell you which worker is slow, or whether it's consistently slow or just having a bad moment.

The straggler score

There's no `straggler` metric in Dynamo. You derive it. The score is the gap between the worst worker and the median, per worker type:

max by (model) (dynamo_frontend_worker_last_time_to_first_token_seconds{worker_type="decode"})
-
quantile by (model) (0.5, dynamo_frontend_worker_last_time_to_first_token_seconds{worker_type="decode"})

When this number is small, your fleet is uniform. When it spikes, you have a straggler. The score tells you that you have a problem; the worker table tells you who.

The worker load heatmap

Before the score, there's the load. Dynamo exposes per-worker load gauges: `worker_active_prefill_tokens` and `worker_active_decode_blocks`. These tell you whether a worker is busy because it's loaded or slow because it's sick.

This is the same distinction as the EFA fabric counters: load vs health, and never conflate them.

The pipeline stages

Dynamo's frontend exposes per-stage request counts and durations: `preprocess`, `route`, `dispatch`, `accepted`, `received`, `transport_roundtrip`. The sum of `preprocess|route|dispatch` is roughly "waiting for first token".

Stacking `dynamo_frontend_stage_requests` by stage × phase shows you where requests wait, which is the fastest way to localize a regression.

Router queues and KV

The KV-aware router is the heart of disaggregated serving. Its health is three numbers:

When the KV hit rate is high and transfer latency is low, disaggregation is paying for itself. When the transfer cost eats the prefill savings, it's not.

The dashboard that answers questions

Not charts, answers. The panels that matter, in order:

  1. SLO strip - TTFT/ITL/E2E p50-p99 vs targets, with error-budget burn.
  2. Pipeline stages - where requests wait.
  3. Straggler table - top-N workers by last TTFT/ITL.
  4. Worker load heatmap - loaded vs sick.
  5. Router queues + KV - is the router the bottleneck.

Everything else is Explore.

The takeaway

Disaggregated serving moves the bottleneck to the tail. Measure the gap between your best and worst worker, and you'll find the straggler before your users do.

Sources

Back to the blog