main
py 222 lines 9.68 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import math
5 from functools import cached_property
6 from itertools import pairwise
7 from typing import Any, Literal
8
9 from packaging.version import parse
10 from pydantic import Field, field_validator, model_validator
11
12 from vllm import version
13 from vllm.config.utils import config
14 from vllm.utils.hashing import safe_hash
15
16 DetailedTraceModules = Literal["model", "worker", "all"]
17
18
19 @config
20 class ObservabilityConfig:
21 """Configuration for observability - metrics and tracing."""
22
23 show_hidden_metrics_for_version: str | None = None
24 """Enable deprecated Prometheus metrics that have been hidden since the
25 specified version. For example, if a previously deprecated metric has been
26 hidden since the v0.7.0 release, you use
27 `--show-hidden-metrics-for-version=0.7` as a temporary escape hatch while
28 you migrate to new metrics. The metric is likely to be removed completely
29 in an upcoming release."""
30
31 @cached_property
32 def show_hidden_metrics(self) -> bool:
33 """Check if the hidden metrics should be shown."""
34 if self.show_hidden_metrics_for_version is None:
35 return False
36 return version._prev_minor_version_was(self.show_hidden_metrics_for_version)
37
38 otlp_traces_endpoint: str | None = None
39 """Target URL to which OpenTelemetry traces will be sent."""
40
41 collect_detailed_traces: list[DetailedTraceModules] | None = None
42 """It makes sense to set this only if `--otlp-traces-endpoint` is set. If
43 set, it will collect detailed traces for the specified modules. This
44 involves use of possibly costly and or blocking operations and hence might
45 have a performance impact.
46
47 Note that collecting detailed timing information for each request can be
48 expensive."""
49
50 per_request_spec_decode_metrics: Literal["none", "summary", "detailed"] = "none"
51 """Include per-request speculative-decoding acceptance metrics in the
52 response under `metrics.speculative_decoding`. `none` disables; `summary` adds mean
53 acceptance length, draft acceptance rate, and the step-by-draft-length
54 histogram; `detailed` additionally records the ordered per-step
55 accepted/proposed arrays (one entry per verify step). Only reported for
56 single-sequence requests (`n == 1`), mirroring the timing metrics. No effect
57 unless speculative decoding is enabled. Independent of `--disable-log-stats`.
58 This is the per-request response-body counterpart of the aggregate
59 `vllm:spec_decode_*` Prometheus metrics. The response field is experimental
60 and its shape may change in a future release."""
61
62 kv_cache_metrics: bool = False
63 """Enable KV cache residency metrics (lifetime, idle time, reuse gaps).
64 Uses sampling to minimize overhead.
65 Requires log stats to be enabled (i.e., --disable-log-stats not set)."""
66
67 kv_cache_metrics_sample: float = Field(default=0.01, gt=0, le=1)
68 """Sampling rate for KV cache metrics (0.0, 1.0]. Default 0.01 = 1% of blocks."""
69
70 custom_histogram_buckets: dict[str, list[float]] | None = None
71 """Custom Prometheus histogram bucket boundaries, as a JSON mapping from
72 bucket-family key to a list of strictly increasing, positive, finite
73 upper bounds. When a family key is present, its list replaces the default
74 buckets for every histogram in that family; families not listed keep
75 their defaults. Known families: `request_latency`, `time_to_first_token`,
76 `inter_token_latency`, `iteration_tokens`, `request_params_n`,
77 `request_num_preemptions`, `request_tokens`, `kv_cache_residency`. Example:
78 `--custom-histogram-buckets '{"request_latency": [0.01, 0.05, 0.1, 0.5]}'`.
79 Note that every extra bucket adds one time series per metric and label
80 set."""
81
82 cudagraph_metrics: bool = False
83 """Enable CUDA graph metrics (number of padded/unpadded tokens, runtime cudagraph
84 dispatch modes, and their observed frequencies at every logging interval)."""
85
86 enable_layerwise_nvtx_tracing: bool = False
87 """Enable layerwise NVTX tracing. This traces the execution of each layer or
88 module in the model and attach information such as input/output shapes to
89 nvtx range markers. Noted that this doesn't work with CUDA graphs enabled."""
90
91 enable_mfu_metrics: bool = False
92 """Enable Model FLOPs Utilization (MFU) metrics."""
93
94 enable_mm_processor_stats: bool = False
95 """Enable collection of timing statistics for multimodal processor operations.
96 This is for internal use only (e.g., benchmarks) and is not exposed as a CLI
97 argument."""
98
99 enable_logging_iteration_details: bool = False
100 """Enable detailed logging of iteration details.
101 If set, vllm EngineCore will log iteration details
102 This includes number of context/generation requests and tokens
103 and the elapsed cpu time for the iteration."""
104
105 jit_monitor_mode: Literal["warn", "error"] = "warn"
106 """How to handle post-warmup JIT compilation events."""
107
108 jit_monitor_verbose: bool = False
109 """Log every monitored JIT compile with runtime details. This can emit many
110 logs and add overhead, so it is intended for debugging."""
111
112 @cached_property
113 def collect_model_forward_time(self) -> bool:
114 """Whether to collect model forward time for the request."""
115 return self.collect_detailed_traces is not None and (
116 "model" in self.collect_detailed_traces
117 or "all" in self.collect_detailed_traces
118 )
119
120 @cached_property
121 def collect_model_execute_time(self) -> bool:
122 """Whether to collect model execute time for the request."""
123 return self.collect_detailed_traces is not None and (
124 "worker" in self.collect_detailed_traces
125 or "all" in self.collect_detailed_traces
126 )
127
128 def compute_hash(self) -> str:
129 """WARNING: Whenever a new field is added to this config,
130 ensure that it is included in the factors list if
131 it affects the computation graph.
132
133 Provide a hash that uniquely identifies all the configs
134 that affect the structure of the computation
135 graph from input ids/embeddings to the final hidden states,
136 excluding anything before input ids/embeddings and after
137 the final hidden states.
138 """
139 # no factors to consider.
140 # this config will not affect the computation graph.
141 factors: list[Any] = []
142 hash_str = safe_hash(str(factors).encode(), usedforsecurity=False).hexdigest()
143 return hash_str
144
145 @field_validator("show_hidden_metrics_for_version")
146 @classmethod
147 def _validate_show_hidden_metrics_for_version(cls, value: str | None) -> str | None:
148 if value is not None:
149 # Raises an exception if the string is not a valid version.
150 parse(value)
151 return value
152
153 @field_validator("otlp_traces_endpoint")
154 @classmethod
155 def _validate_otlp_traces_endpoint(cls, value: str | None) -> str | None:
156 if value is not None:
157 from vllm.tracing import is_tracing_available, otel_import_error_traceback
158
159 if not is_tracing_available():
160 raise ValueError(
161 "OpenTelemetry is not available. Unable to configure "
162 "'otlp_traces_endpoint'. Ensure OpenTelemetry packages are "
163 f"installed. Original error:\n{otel_import_error_traceback}"
164 )
165 return value
166
167 @field_validator("custom_histogram_buckets", mode="before")
168 @classmethod
169 def _reject_bool_histogram_bounds(cls, value: object) -> object:
170 """Reject booleans before pydantic silently coerces them to floats."""
171 if isinstance(value, dict):
172 for family, buckets in value.items():
173 if not isinstance(buckets, list):
174 continue
175 for bound in buckets:
176 if isinstance(bound, bool):
177 raise ValueError(
178 f"custom_histogram_buckets[{family!r}]: bound "
179 f"{bound!r} must be a number, not a boolean"
180 )
181 return value
182
183 @field_validator("custom_histogram_buckets")
184 @classmethod
185 def _validate_custom_histogram_buckets(
186 cls, value: dict[str, list[float]] | None
187 ) -> dict[str, list[float]] | None:
188 if value is None:
189 return value
190 from vllm.v1.metrics.buckets import BUCKET_FAMILY_KEYS
191
192 for family, buckets in value.items():
193 if family not in BUCKET_FAMILY_KEYS:
194 raise ValueError(
195 f"custom_histogram_buckets: unknown bucket family "
196 f"{family!r}; known families: {sorted(BUCKET_FAMILY_KEYS)}"
197 )
198 if not buckets:
199 raise ValueError(
200 f"custom_histogram_buckets[{family!r}]: bucket list "
201 "must not be empty"
202 )
203 for bound in buckets:
204 if not math.isfinite(bound) or bound <= 0:
205 raise ValueError(
206 f"custom_histogram_buckets[{family!r}]: bound "
207 f"{bound!r} must be finite and greater than 0"
208 )
209 if any(a >= b for a, b in pairwise(buckets)):
210 raise ValueError(
211 f"custom_histogram_buckets[{family!r}]: bounds {buckets} "
212 "must be strictly increasing"
213 )
214 return value
215
216 @model_validator(mode="after")
217 def _validate_tracing_config(self):
218 if self.collect_detailed_traces and not self.otlp_traces_endpoint:
219 raise ValueError(
220 "collect_detailed_traces requires `--otlp-traces-endpoint` to be set."
221 )
222 return self