@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / c2bd2196fc
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 15, 21:00 UTC

[v1][Metrics] Add design doc (#12745)

Signed-off-by: Mark McLoughlin <markmc@redhat.com> Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Harry Mellor <19981378+hmellor@users.noreply.github.com> Co-authored-by: Cody Yu <hao.yu.cody@gmail.com>

Mark McLoughlin committed Mar 4, 2025 at 20:36 UTC c2bd2196fc8faf2a9a3e5e79931839ae60d2ab9d
5 files changed +713
docs/source/assets/design/v1/metrics/intervals-1.png
Binary files /dev/null and b/docs/source/assets/design/v1/metrics/intervals-1.png differ
docs/source/assets/design/v1/metrics/intervals-2.png
Binary files /dev/null and b/docs/source/assets/design/v1/metrics/intervals-2.png differ
docs/source/assets/design/v1/metrics/intervals-3.png
Binary files /dev/null and b/docs/source/assets/design/v1/metrics/intervals-3.png differ
docs/source/design/v1/metrics.md new
+712
@@ -0,0 +1,712 @@
1 +# Metrics
2 +
3 +Ensure the v1 LLM Engine exposes a superset of the metrics available in v0.
4 +
5 +## Objectives
6 +
7 +- Achieve parity of metrics between v0 and v1.
8 +- The priority use case is accessing these metrics via Prometheus as this is what we expect to be used in production environments.
9 +- Logging support - i.e. printing metrics to the info log - is provided for more ad-hoc testing, debugging, development, and exploratory use cases.
10 +
11 +## Background
12 +
13 +Metrics in vLLM can be categorized as follows:
14 +
15 +1. Server-level metrics: these are global metrics that track the state and performance of the LLM engine. These are typically exposed as Gauges or Counters in Prometheus.
16 +2. Request-level metrics: these are metrics that track the characteristics - e.g. size and timing - of individual requests. These are typically exposed as Histrograms in Prometheus, and are often the SLO that an SRE monitoring vLLM will be tracking.
17 +
18 +The mental model is that the "Server-level Metrics" explain why the "Request-level Metrics" are what they are.
19 +
20 +### v0 Metrics
21 +
22 +In v0, the following metrics are exposed via a Prometheus-compatible `/metrics` endpoint using the `vllm:` prefix:
23 +
24 +- `vllm:num_requests_running` (Gauge)
25 +- `vllm:num_requests_swapped` (Gauge)
26 +- `vllm:num_requests_waiting` (Gauge)
27 +- `vllm:gpu_cache_usage_perc` (Gauge)
28 +- `vllm:cpu_cache_usage_perc` (Gauge)
29 +- `vllm:gpu_prefix_cache_hit_rate` (Gauge)
30 +- `vllm:cpu_prefix_cache_hit_rate` (Gauge)
31 +- `vllm:prompt_tokens_total` (Counter)
32 +- `vllm:generation_tokens_total` (Counter)
33 +- `vllm:request_success_total` (Counter)
34 +- `vllm:request_prompt_tokens` (Histogram)
35 +- `vllm:request_generation_tokens` (Histogram)
36 +- `vllm:time_to_first_token_seconds` (Histogram)
37 +- `vllm:time_per_output_token_seconds` (Histogram)
38 +- `vllm:e2e_request_latency_seconds` (Histogram)
39 +- `vllm:request_queue_time_seconds` (Histogram)
40 +- `vllm:request_inference_time_seconds` (Histogram)
41 +- `vllm:request_prefill_time_seconds` (Histogram)
42 +- `vllm:request_decode_time_seconds` (Histogram)
43 +- `vllm:request_max_num_generation_tokens` (Histogram)
44 +- `vllm:num_preemptions_total` (Counter)
45 +- `vllm:cache_config_info` (Gauge)
46 +- `vllm:lora_requests_info` (Gauge)
47 +- `vllm:tokens_total` (Counter)
48 +- `vllm:iteration_tokens_total` (Histogram)
49 +- `vllm:time_in_queue_requests` (Histogram)
50 +- `vllm:model_forward_time_milliseconds` (Histogram
51 +- `vllm:model_execute_time_milliseconds` (Histogram)
52 +- `vllm:request_params_n` (Histogram)
53 +- `vllm:request_params_max_tokens` (Histogram)
54 +- `vllm:spec_decode_draft_acceptance_rate` (Gauge)
55 +- `vllm:spec_decode_efficiency` (Gauge)
56 +- `vllm:spec_decode_num_accepted_tokens_total` (Counter)
57 +- `vllm:spec_decode_num_draft_tokens_total` (Counter)
58 +- `vllm:spec_decode_num_emitted_tokens_total` (Counter)
59 +
60 +These are documented under [Inferencing and Serving -> Production Metrics](project:../../serving/metrics.md).
61 +
62 +### Grafana Dashboard
63 +
64 +vLLM also provides [a reference example](https://docs.vllm.ai/en/latest/getting_started/examples/prometheus_grafana.html) for how to collect and store these metrics using Prometheus and visualize them using a Grafana dashboard.
65 +
66 +The subset of metrics exposed in the Grafana dashboard gives us an indication of which metrics are especially important:
67 +
68 +- `vllm:e2e_request_latency_seconds_bucket` - End to end request latency measured in seconds
69 +- `vllm:prompt_tokens_total` - Prompt Tokens/Sec
70 +- `vllm:generation_tokens_total` - Generation Tokens/Sec
71 +- `vllm:time_per_output_token_seconds` - Inter token latency (Time Per Output Token, TPOT) in second.
72 +- `vllm:time_to_first_token_seconds` - Time to First Token (TTFT) latency in seconds.
73 +- `vllm:num_requests_running` (also, `_swapped` and `_waiting`) - Number of requests in RUNNING, WAITING, and SWAPPED state
74 +- `vllm:gpu_cache_usage_perc` - Percentage of used cache blocks by vLLM.
75 +- `vllm:request_prompt_tokens` - Request prompt length
76 +- `vllm:request_generation_tokens` - request generation length
77 +- `vllm:request_success_total` - Number of finished requests by their finish reason: either an EOS token was generated or the max sequence length was reached
78 +- `vllm:request_queue_time_seconds` - Queue Time
79 +- `vllm:request_prefill_time_seconds` - Requests Prefill Time
80 +- `vllm:request_decode_time_seconds` - Requests Decode Time
81 +- `vllm:request_max_num_generation_tokens` - Max Generation Token in Sequence Group
82 +
83 +See [the PR which added this Dashboard](gh-pr:2316) for interesting and useful background on the choices made here.
84 +
85 +### Prometheus Client Library
86 +
87 +Prometheus support was initially added [using the aioprometheus library](gh-pr:1890), but a switch was made quickly to [prometheus_client](gh-pr:2730). The rationale is discussed in both linked PRs.
88 +
89 +### Multi-process Mode
90 +
91 +In v0, metrics are collected in the engine core process and we use multi-process mode to make them available in the API server process. See <gh-pr:7279>.
92 +
93 +### Built in Python/Process Metrics
94 +
95 +The following metrics are supported by default by `prometheus_client`, but the are not exposed with multiprocess mode is used:
96 +
97 +- `python_gc_objects_collected_total`
98 +- `python_gc_objects_uncollectable_total`
99 +- `python_gc_collections_total`
100 +- `python_info`
101 +- `process_virtual_memory_bytes`
102 +- `process_resident_memory_bytes`
103 +- `process_start_time_seconds`
104 +- `process_cpu_seconds_total`
105 +- `process_open_fds`
106 +- `process_max_fds`
107 +
108 +This is relevant because if we move away from multiprocess mode in v1,
109 +we get these back. However, it's questionable how relevant these are
110 +if they don't aggregate these stats for all processes that make up a
111 +vLLM instance.
112 +
113 +### v0 PRs and Issues
114 +
115 +For background, these are some of the relevant PRs which added the v0 metrics:
116 +
117 +- <gh-pr:1890>
118 +- <gh-pr:2316>
119 +- <gh-pr:2730>
120 +- <gh-pr:4464>
121 +- <gh-pr:7279>
122 +
123 +Also note the ["Even Better Observability"](gh-issue:3616) feature where e.g. [a detailed roadmap was laid out](gh-issue:3616#issuecomment-2030858781).
124 +
125 +## v1 Design
126 +
127 +### v1 PRs
128 +
129 +For background, here are the relevant v1 PRs relating to the v1
130 +metrics issue <gh-issue:10582>:
131 +
132 +- <gh-pr:11962>
133 +- <gh-pr:11973>
134 +- <gh-pr:10907>
135 +- <gh-pr:12416>
136 +- <gh-pr:12478>
137 +- <gh-pr:12516>
138 +- <gh-pr:12530>
139 +- <gh-pr:12561>
140 +- <gh-pr:12579>
141 +- <gh-pr:12592>
142 +- <gh-pr:12644>
143 +
144 +### Metrics Collection
145 +
146 +In v1, we wish to move computation and overhead out of the engine core
147 +process to minimize the time between each forward pass.
148 +
149 +The overall idea of V1 EngineCore design is:
150 +- EngineCore is the inner loop. Performance is most critical here
151 +- AsyncLLM is the outer loop. This is overlapped with GPU execution
152 + (ideally), so this is where any "overheads" should be if
153 + possible. So AsyncLLM.output_handler_loop is the ideal place for the
154 + metrics bookkeeping if possible.
155 +
156 +We will achieve this by collecting metrics in the frontend API server,
157 +and base these metrics on information we can glean from the
158 +`EngineCoreOutputs` returned by the engine core process to the
159 +frontend.
160 +
161 +### Interval Calculations
162 +
163 +Many of our metrics are the time interval between various events in
164 +the processing of a request. It is best practice to use timestamps
165 +based on "monotonic time" (`time.monotonic()`) rather than "wall-clock
166 +time" (`time.time()`) to calculate intervals as the former is
167 +unaffected by system clock changes (e.g. from NTP).
168 +
169 +It's also important to note that monotonic clocks differ between
170 +processes - each process has its own reference. point. So it is
171 +meaningless to compare monotonic timestamps from different processes.
172 +
173 +Therefore, in order to calculate an interval, we must compare two
174 +monotonic timestamps from the same process.
175 +
176 +### Scheduler Stats
177 +
178 +The engine core process will collect some key statistics from the
179 +scheduler - e.g. the number of requests that were scheduled or waiting
180 +after the last scheduler pass - and include those statistics in
181 +`EngineCoreOutputs`.
182 +
183 +### Engine Core Events
184 +
185 +The engine core will also record the timestamp of certain per-request
186 +events so that the frontend can calculate the interval between these
187 +events.
188 +
189 +The events are:
190 +
191 +- `QUEUED` - when the request was received by the engine core and
192 + added to the scheduler queue.
193 +- `SCHEDULED` - when the request was first scheduled for execution.
194 +- `PREEMPTED` - the request has been put back in the waiting queue
195 + in order to make room for other requests to complete. It will be
196 + re-scheduled in future and re-start its prefill phase.
197 +- `NEW_TOKENS` - when the output included in `EngineCoreOutput` was
198 + generated. Since this is common to all requests in a given
199 + iteration, we use a single timestamp on `EngineCoreOutputs` to
200 + record this event.
201 +
202 +And the calculated intervals are:
203 +
204 +- Queue interval - between `QUEUED` and most recent `SCHEDULED`.
205 +- Prefill interval - between most recent `SCHEDULED` and the subsequent
206 + first `NEW_TOKENS`.
207 +- Decode interval - between first (after the most recent `SCHEDULED`) and
208 + last `NEW_TOKENS`.
209 +- Inference interval - between most recent `SCHEDULED` and last `NEW_TOKENS`.
210 +- Inter-token interval - between successive `NEW_TOKENS`.
211 +
212 +Put another way:
213 +
214 +:::{image} /assets/design/v1/metrics/intervals-1.png
215 +:alt: Interval calculations - common case
216 +:::
217 +
218 +We explored the possibility of having the frontend calculate these
219 +intervals using the timing of events visible by the frontend. However,
220 +the frontend does not have visibility into the timing of the `QUEUED`
221 +and `SCHEDULED` events and, since we need to calculate intervals based
222 +on monotonic timestamps from the same process ... we need the engine
223 +core to record timestamps for all of these events.
224 +
225 +#### Interval Calculations vs Preemptions
226 +
227 +When a preemption occurs during decode, since any already generated
228 +tokens are reused, we consider the preemption as affecting the
229 +inter-token, decode, and inference intervals.
230 +
231 +:::{image} /assets/design/v1/metrics/intervals-2.png
232 +:alt: Interval calculations - preempted decode
233 +:::
234 +
235 +When a preemption occurs during prefill (assuming such an event
236 +is possible), we consider the preemption as affecting the
237 +time-to-first-token and prefill intervals.
238 +
239 +:::{image} /assets/design/v1/metrics/intervals-3.png
240 +:alt: Interval calculations - preempted prefill
241 +:::
242 +
243 +### Frontend Stats Collection
244 +
245 +As the frontend processes a single `EngineCoreOutputs` - i.e. the
246 +output from a single engine core iteration - it collects various
247 +statistics relating to that iteration:
248 +
249 +- The total number of new tokens generated in this iteration.
250 +- The total number of prompt tokens processed by the prefills that
251 + completed in this iteration.
252 +- The queue intervals for any requests that were scheduled in this
253 + iteration.
254 +- The prefill intervals for any requests that completed prefill in
255 + this iteration.
256 +- The inter-token intervals (Time Per Output Token, TPOT), for all
257 + requests included in this iteration.
258 +- The Time-To-First-Token (TTFT) for any requests that completed
259 + prefill in this iteration. However, we calculate this interval
260 + relative to when the request was first received by the frontend
261 + (`arrival_time`) in order to account for input processing time.
262 +
263 +For any requests that were completed in a given iteration, we also
264 +record:
265 +
266 +- The inference and decode intervals - relative to the scheduled and
267 + first token events, as described above.
268 +- End-to-end latency - the interval between frontend `arrival_time`
269 + and the frontend receiving the final token.
270 +
271 +### Metrics Publishing - Logging
272 +
273 +The `LoggingStatLogger` metrics publisher outputs a log `INFO` message
274 +every 5 seconds with some key metrics:
275 +
276 +- The current number of running/waiting requests
277 +- The current GPU cache usage
278 +- The number of prompt tokens processed per second over the past 5
279 + seconds
280 +- The number of new tokens generated per second over the past 5
281 + seconds
282 +- The prefix cache hit rate over the most recent 1k kv-cache block queries
283 +
284 +### Metrics Publishing - Prometheus
285 +
286 +The `PrometheusStatLogger` metrics publisher makes the metrics
287 +available via a `/metrics` HTTP endpoint in a Prometheus-compatible
288 +format. A Prometheus instance can then be configured to poll this
289 +endpoint (e.g. every second) and record the values in its time-series
290 +database. Prometheus is often used via Grafana, allowing these metrics
291 +to be graphed over time.
292 +
293 +Prometheus supports the following metric types:
294 +
295 +- Counter: a value that will increase over time, never reducing, and
296 + generally reset to zero when the vLLM instance restarts. For
297 + example, the number of tokens generated over the lifetime of the
298 + instance.
299 +- Gauge: a value that goes up and down, for example the number of
300 + requests currently scheduled for execution.
301 +- Histogram: a count of metric samples, recorded in buckets. For
302 + example, the number of requests whose TTFT was <1ms, <5ms, <10ms,
303 + <20ms, and so on.
304 +
305 +Prometheus metrics can also be labelled, allowing metrics to be
306 +combined according to matching labels. In vLLM, we add a `model_name`
307 +label to every metric which includes the name of the model served by
308 +that instance.
309 +
310 +Example output:
311 +
312 +```bash
313 +$ curl http://0.0.0.0:8000/metrics
314 +# HELP vllm:num_requests_running Number of requests in model execution batches.
315 +# TYPE vllm:num_requests_running gauge
316 +vllm:num_requests_running{model_name="meta-llama/Llama-3.1-8B-Instruct"} 8.0
317 +...
318 +# HELP vllm:generation_tokens_total Number of generation tokens processed.
319 +# TYPE vllm:generation_tokens_total counter
320 +vllm:generation_tokens_total{model_name="meta-llama/Llama-3.1-8B-Instruct"} 27453.0
321 +...
322 +# HELP vllm:request_success_total Count of successfully processed requests.
323 +# TYPE vllm:request_success_total counter
324 +vllm:request_success_total{finished_reason="stop",model_name="meta-llama/Llama-3.1-8B-Instruct"} 1.0
325 +vllm:request_success_total{finished_reason="length",model_name="meta-llama/Llama-3.1-8B-Instruct"} 131.0
326 +vllm:request_success_total{finished_reason="abort",model_name="meta-llama/Llama-3.1-8B-Instruct"} 0.0
327 +...
328 +# HELP vllm:time_to_first_token_seconds Histogram of time to first token in seconds.
329 +# TYPE vllm:time_to_first_token_seconds histogram
330 +vllm:time_to_first_token_seconds_bucket{le="0.001",model_name="meta-llama/Llama-3.1-8B-Instruct"} 0.0
331 +vllm:time_to_first_token_seconds_bucket{le="0.005",model_name="meta-llama/Llama-3.1-8B-Instruct"} 0.0
332 +vllm:time_to_first_token_seconds_bucket{le="0.01",model_name="meta-llama/Llama-3.1-8B-Instruct"} 0.0
333 +vllm:time_to_first_token_seconds_bucket{le="0.02",model_name="meta-llama/Llama-3.1-8B-Instruct"} 13.0
334 +vllm:time_to_first_token_seconds_bucket{le="0.04",model_name="meta-llama/Llama-3.1-8B-Instruct"} 97.0
335 +vllm:time_to_first_token_seconds_bucket{le="0.06",model_name="meta-llama/Llama-3.1-8B-Instruct"} 123.0
336 +vllm:time_to_first_token_seconds_bucket{le="0.08",model_name="meta-llama/Llama-3.1-8B-Instruct"} 138.0
337 +vllm:time_to_first_token_seconds_bucket{le="0.1",model_name="meta-llama/Llama-3.1-8B-Instruct"} 140.0
338 +vllm:time_to_first_token_seconds_count{model_name="meta-llama/Llama-3.1-8B-Instruct"} 140.0
339 +```
340 +
341 +Note - the choice of histogram buckets to be most useful to users
342 +across a broad set of use cases is not straightforward and will
343 +require refinement over time.
344 +
345 +### Cache Config Info
346 +
347 +`prometheus_client` has support for [Info
348 +metrics](https://prometheus.github.io/client_python/instrumenting/info/)
349 +which are equivalent to a `Gauge` whose value is permanently set to 1,
350 +but exposes interesting key/value pair information via labels. This is
351 +used for information about an instance that does not change - so it
352 +only needs to be observed at startup - and allows comparing across
353 +instances in Prometheus.
354 +
355 +We use this concept for the `vllm:cache_config_info` metric:
356 +
357 +```
358 +# HELP vllm:cache_config_info Information of the LLMEngine CacheConfig
359 +# TYPE vllm:cache_config_info gauge
360 +vllm:cache_config_info{block_size="16",cache_dtype="auto",calculate_kv_scales="False",cpu_offload_gb="0",enable_prefix_caching="False",gpu_memory_utilization="0.9",...} 1.0
361 +
362 +```
363 +
364 +However, `prometheus_client` has [never supported Info metrics in
365 +multiprocessing
366 +mode](https://github.com/prometheus/client_python/pull/300) - for
367 +[unclear
368 +reasons](gh-pr:7279#discussion_r1710417152). We
369 +simply use a `Gauge` metric set to 1 and
370 +`multiprocess_mode="mostrecent"` instead.
371 +
372 +### LoRA Metrics
373 +
374 +The `vllm:lora_requests_info` `Gauge` is somewhat similar, except the
375 +value is the current wall-clock time, and is updated every iteration.
376 +
377 +The label names used are:
378 +
379 +- `running_lora_adapters`: a per-adapter count of the number requests
380 + running using that adapter, formatted as a comma-separated string.
381 +- `waiting_lora_adapters`: similar, except counting requests that are
382 + waiting to be scheduled.
383 +- `max_lora` - the static "max number of LoRAs in a single batch."
384 + configuration.
385 +
386 +Encoding a running/waiting counts for multiple adapters in a
387 +comma-separated string seems quite misguided - we could use labels to
388 +distinguish between per-adapter counts. This should be revisited.
389 +
390 +Note that `multiprocess_mode="livemostrecent"` is used - the most
391 +recent metric is used, but only from currently running processes.
392 +
393 +This was added in
394 +<gh-pr:9477> and there is
395 +[at least one known
396 +user](https://github.com/kubernetes-sigs/gateway-api-inference-extension/pull/54). If
397 +we revisit this design and deprecate the old metric, we should reduce
398 +the need for a significant deprecation period by making the change in
399 +v0 also and asking this project to move to the new metric.
400 +
401 +### Prefix Cache metrics
402 +
403 +The discussion in <gh-issue:10582> about adding prefix cache metrics yielded
404 +some interesting points which may be relevant to how we approach
405 +future metrics.
406 +
407 +Every time the prefix cache is queried, we record the number of blocks
408 +queried and the number of queried blocks present in the cache
409 +(i.e. hits).
410 +
411 +However, the metric of interest is the hit rate - i.e. the number of
412 +hits per query.
413 +
414 +In the case of logging, we expect the user is best served by
415 +calculating the hit rate over a fixed number of the most recent
416 +queries (the interval is fixed to 1k most recent queries for now).
417 +
418 +In the case of Prometheus though, we should take advantage of the
419 +time-series nature of Prometheus and allow the user to calculate the
420 +hit rate over an interval of their choosing. For example, a PromQL
421 +query to calculate the hit interval of the past 5 minutes:
422 +
423 +```text
424 +rate(cache_query_hit[5m]) / rate(cache_query_total[5m])
425 +```
426 +
427 +To achieve this, we should record the queries and hits as counters in
428 +Prometheus, rather than recording the hit rate as a gauge.
429 +
430 +## Deprecated Metrics
431 +
432 +### How To Deprecate
433 +
434 +Deprecating metrics shouldn't be taken lightly. Users may not notice a
435 +metric has been deprecated, and may be quite inconvenienced when it is
436 +suddenly (from their perspective) when it is removed, even if there is
437 +an equivalent metric for them to use.
438 +
439 +As an example, see how `vllm:avg_prompt_throughput_toks_per_s` was
440 +[deprecated](gh-pr:2764) (with a
441 +comment in the code),
442 +[removed](gh-pr:12383), and then
443 +[noticed by a
444 +user](gh-issue:13218).
445 +
446 +In general:
447 +
448 +1) We should be cautious about deprecating metrics, especially since
449 + it can be hard to predict the user impact.
450 +2) We should include a prominent deprecation notice in the help string
451 + that is included in the `/metrics' output.
452 +3) We should list deprecated metrics in user-facing documentation and
453 + release notes.
454 +4) We should consider hiding deprecated metrics behind a CLI argument
455 + in order to give administrators [an escape
456 + hatch](https://kubernetes.io/docs/concepts/cluster-administration/system-metrics/#show-hidden-metrics)
457 + for some time before deleting them.
458 +
459 +### Unimplemented - `vllm:tokens_total`
460 +
461 +Added by <gh-pr:4464>, but apparently never implemented. This can just be
462 +removed.
463 +
464 +### Duplicated - Queue Time
465 +
466 +The `vllm:time_in_queue_requests` Histogram metric was added by
467 +<gh-pr:9659> and its calculation is:
468 +
469 +```
470 + self.metrics.first_scheduled_time = now
471 + self.metrics.time_in_queue = now - self.metrics.arrival_time
472 +```
473 +
474 +Two weeks later, <gh-pr:4464> added `vllm:request_queue_time_seconds` leaving
475 +us with:
476 +
477 +```
478 +if seq_group.is_finished():
479 + if (seq_group.metrics.first_scheduled_time is not None and
480 + seq_group.metrics.first_token_time is not None):
481 + time_queue_requests.append(
482 + seq_group.metrics.first_scheduled_time -
483 + seq_group.metrics.arrival_time)
484 + ...
485 + if seq_group.metrics.time_in_queue is not None:
486 + time_in_queue_requests.append(
487 + seq_group.metrics.time_in_queue)
488 +```
489 +
490 +This seems duplicative, and one of them should be removed. The latter
491 +is used by the Grafana dashboard, so we should deprecate or remove the
492 +former from v0.
493 +
494 +### Prefix Cache Hit Rate
495 +
496 +See above - we now expose 'queries' and 'hits' counters rather than a
497 +'hit rate' gauge.
498 +
499 +### KV Cache Offloading
500 +
501 +Two v0 metrics relate to a "swapped" preemption mode that is no
502 +longer relevant in v1:
503 +
504 +- `vllm:num_requests_swapped`
505 +- `vllm:cpu_cache_usage_perc`
506 +
507 +In this mode, when a request is preempted (e.g. to make room in KV
508 +cache to complete other requests), we swap kv cache blocks out to CPU
509 +memory. This is also known as "KV cache offloading" and is configured
510 +with `--swap-space` and `--preemption-mode`.
511 +
512 +In v0, [VLLM has long supported beam
513 +search](gh-issue:6226). The
514 +SequenceGroup encapsulated the idea of N Sequences which
515 +all shared the same prompt kv blocks. This enabled KV cache block
516 +sharing between requests, and copy-on-write to do branching. CPU
517 +swapping was intended for these beam search like cases.
518 +
519 +Later, the concept of prefix caching was introduced, which allowed KV
520 +cache blocks to be shared implicitly. This proved to be a better
521 +option than CPU swapping since blocks can be evicted slowly on demand
522 +and the part of the prompt that was evicted can be recomputed.
523 +
524 +SequenceGroup was removed in V1, although a replacement will be
525 +required for "parallel sampling" (`n>1`). [Beam search was moved out of
526 +the core (in
527 +V0)](gh-issue:8306). There was a
528 +lot of complex code for a very uncommon feature.
529 +
530 +In V1, with prefix caching being better (zero over head) and therefore
531 +on by default, the preemption and recompute strategy should work
532 +better.
533 +
534 +## Future Work
535 +
536 +### Parallel Sampling
537 +
538 +Some v0 metrics are only relevant in the context of "parallel
539 +sampling". This is where the `n` parameter in a request is used to
540 +request multiple completions from the same prompt.
541 +
542 +As part of adding parallel sampling support in <gh-pr:10980> we should
543 +also add these metrics.
544 +
545 +- `vllm:request_params_n` (Histogram)
546 +
547 +Observes the value of the 'n' parameter of every finished request.
548 +
549 +- `vllm:request_max_num_generation_tokens` (Histogram)
550 +
551 +Observes the maximum output length of all sequences in every finished
552 +sequence group. In the absence of parallel sampling, this is
553 +equivalent to `vllm:request_generation_tokens`.
554 +
555 +### Speculative Decoding
556 +
557 +Some v0 metrics are specific to "speculative decoding". This is where
558 +we generate candidate tokens using a faster, approximate method or
559 +model and then validate those tokens with the larger model.
560 +
561 +- `vllm:spec_decode_draft_acceptance_rate` (Gauge)
562 +- `vllm:spec_decode_efficiency` (Gauge)
563 +- `vllm:spec_decode_num_accepted_tokens_total` (Counter)
564 +- `vllm:spec_decode_num_draft_tokens_total` (Counter)
565 +- `vllm:spec_decode_num_emitted_tokens_total` (Counter)
566 +
567 +There is a PR under review (<gh-pr:12193>) to add "prompt lookup (ngram)"
568 +seculative decoding to v1. Other techniques will follow. We should
569 +revisit the v0 metrics in this context.
570 +
571 +Note - we should probably expose acceptance rate as separate accepted
572 +and draft counters, like we do for prefix caching hit rate. Efficiency
573 +likely also needs similar treatment.
574 +
575 +### Autoscaling and Load-balancing
576 +
577 +A common use case for our metrics is to support automated scaling of
578 +vLLM instances.
579 +
580 +For related discussion from the [Kubernetes Serving Working
581 +Group](https://github.com/kubernetes/community/tree/master/wg-serving),
582 +see:
583 +
584 +- [Standardizing Large Model Server Metrics in
585 + Kubernetes](https://docs.google.com/document/d/1SpSp1E6moa4HSrJnS4x3NpLuj88sMXr2tbofKlzTZpk)
586 +- [Benchmarking LLM Workloads for Performance Evaluation and
587 + Autoscaling in
588 + Kubernetes](https://docs.google.com/document/d/1k4Q4X14hW4vftElIuYGDu5KDe2LtV1XammoG-Xi3bbQ)
589 +- [Inference
590 + Perf](https://github.com/kubernetes-sigs/wg-serving/tree/main/proposals/013-inference-perf)
591 +- <gh-issue:5041> and <gh-pr:12726>.
592 +
593 +This is a non-trivial topic. Consider this comment from Rob:
594 +
595 +> I think this metric should focus on trying to estimate what the max
596 +> concurrency that will cause the average request length > queries per
597 +> second ... since this is really what will "saturate" the server.
598 +
599 +A clear goal is that we should expose the metrics required to detect
600 +this saturation point, so administrators can implement auto-scaling
601 +rules based on those. However, in order to do so, we need to have a
602 +clear view on how an administrator (and automated monitoring system)
603 +should judge an instance as approaching saturation:
604 +
605 +> To identify, what is the saturation point for model server compute
606 +> (the inflection point where we cannot get more throughput with a
607 +> higher request rate, but start to incur additional latency) so we
608 +> can autoscale effectively?
609 +
610 +### Metric Naming
611 +
612 +Our approach to naming metrics probably deserves to be revisited:
613 +
614 +1. The use of colons in metric names seems contrary to ["colons are
615 + reserved for user defined recording
616 + rules"](https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels)
617 +2. Most of our metrics follow the convention of ending with units, but
618 + not all do.
619 +3. Some of our metric names end with `_total`:
620 +
621 +```
622 +If there is a suffix of `_total` on the metric name, it will be removed. When
623 +exposing the time series for counter, a `_total` suffix will be added. This is
624 +for compatibility between OpenMetrics and the Prometheus text format, as OpenMetrics
625 +requires the `_total` suffix.
626 +```
627 +
628 +### Adding More Metrics
629 +
630 +There is no shortage of ideas for new metrics:
631 +
632 +- Examples from other projects like
633 + [TGI](https://github.com/IBM/text-generation-inference?tab=readme-ov-file#metrics)
634 +- Proposals arising from specific use cases, like the Kubernetes
635 + auto-scaling topic above
636 +- Proposals that might arise out of standardisation efforts like
637 + [OpenTelemetry Semantic Conventions for Gen
638 + AI](https://github.com/open-telemetry/semantic-conventions/tree/main/docs/gen-ai).
639 +
640 +We should be cautious in our approach to adding new metrics. While
641 +metrics are often relatively straightforward to add:
642 +
643 +1. They can be difficult to remove - see the section on deprecation
644 + above.
645 +2. They can have a meaningful performance impact when enabled. And
646 + metrics are usually of very limited use unless they can be enabled
647 + by default and in production.
648 +3. They have an impact on development and maintenance of the
649 + project. Every metric added to v0 has made this v1 effort more
650 + time-consuming, and perhaps not all metrics justify this ongoing
651 + investment in their maintenance.
652 +
653 +## Tracing - OpenTelemetry
654 +
655 +Metrics provide an aggregated view over time of the system's
656 +performance and health. Tracing, on the other hand, tracks individual
657 +requests as they move through different services and components. Both
658 +fall under the more general heading of "Observability".
659 +
660 +v0 has support for OpenTelemetry tracing:
661 +
662 +- Added by <gh-pr:4687>
663 +- Configured with `--oltp-traces-endpoint` and
664 + `--collect-detailed-traces`
665 +- [OpenTelemetry blog
666 + post](https://opentelemetry.io/blog/2024/llm-observability/)
667 +- [User-facing
668 + docs](https://docs.vllm.ai/en/latest/getting_started/examples/opentelemetry.html)
669 +- [Blog
670 + post](https://medium.com/@ronen.schaffer/follow-the-trail-supercharging-vllm-with-opentelemetry-distributed-tracing-aa655229b46f)
671 +- [IBM product
672 + docs](https://www.ibm.com/docs/en/instana-observability/current?topic=mgaa-monitoring-large-language-models-llms-vllm-public-preview)
673 +
674 +OpenTelemetry has a [Gen AI Working
675 +Group](https://github.com/open-telemetry/community/blob/main/projects/gen-ai.md).
676 +
677 +Since metrics is a big enough topic on its own, we are going to tackle
678 +the topic of tracing in v1 separately.
679 +
680 +### OpenTelemetry Model Forward vs Execute Time
681 +
682 +In v0, we have the following two metrics:
683 +
684 +- `vllm:model_forward_time_milliseconds` (Histogram) - The time spent
685 + in the model forward pass when this request was in the batch.
686 +- `vllm:model_execute_time_milliseconds` (Histogram) - The time spent
687 + in the model execute function. This will include model forward,
688 + block/sync across workers, cpu-gpu sync time and sampling time.
689 +
690 +These metrics are only enabled when OpenTelemetry tracing is enabled
691 +and if `--collect-detailed-traces=all/model/worker` is used. The
692 +documentation for this option states:
693 +
694 +> collect detailed traces for the specified "modules. This involves
695 +> use of possibly costly and or blocking operations and hence might
696 +> have a performance impact.
697 +
698 +The metrics were added by <gh-pr:7089> and who up in an OpenTelemetry trace
699 +as:
700 +
701 +```
702 +-> gen_ai.latency.time_in_scheduler: Double(0.017550230026245117)
703 +-> gen_ai.latency.time_in_model_forward: Double(3.151565277099609)
704 +-> gen_ai.latency.time_in_model_execute: Double(3.6468167304992676)
705 +```
706 +
707 +We already have `inference_time` and `decode_time` metrics, so the
708 +question is whether there are sufficiently common use cases for the
709 +higher-resolution timings to justify the overhead.
710 +
711 +Since we are going to treat the question of OpenTelemetry support
712 +separately, we will include these particular metrics under that topic.
docs/source/index.md
+1
@@ -158,6 +158,7 @@ design/multiprocessing
158 :maxdepth: 2
159
160 design/v1/prefix_caching
161 +design/v1/metrics
162 :::
163
164 % How to contribute to the vLLM project