main
py 181 lines 5.82 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Contains helpers that are applied to functions.
4
5 This is similar in concept to the `functools` module.
6 """
7
8 import inspect
9 import threading
10 from collections.abc import Callable, Mapping
11 from functools import lru_cache
12 from typing import Any, TypeVar
13
14 from typing_extensions import ParamSpec
15
16 from vllm.logger import init_logger
17
18 logger = init_logger(__name__)
19
20
21 P = ParamSpec("P")
22 T = TypeVar("T")
23 F = TypeVar("F", bound=Callable[..., Any])
24
25
26 def identity(value: T, **kwargs) -> T:
27 """Returns the first provided value."""
28 return value
29
30
31 def run_once(f: Callable[P, None]) -> Callable[P, None]:
32 def wrapper(*args: P.args, **kwargs: P.kwargs) -> None:
33 if wrapper.has_run: # type: ignore[attr-defined]
34 return
35
36 with wrapper.lock: # type: ignore[attr-defined]
37 if not wrapper.has_run: # type: ignore[attr-defined]
38 wrapper.has_run = True # type: ignore[attr-defined]
39 return f(*args, **kwargs)
40
41 wrapper.has_run = False # type: ignore[attr-defined]
42 wrapper.lock = threading.Lock() # type: ignore[attr-defined]
43 return wrapper
44
45
46 @lru_cache
47 def _supports_kw(
48 callable: Callable[..., object],
49 kw_name: str,
50 *,
51 requires_kw_only: bool = False,
52 allow_var_kwargs: bool = True,
53 ) -> bool:
54 """Internal cached implementation of supports_kw."""
55 params = inspect.signature(callable).parameters
56 if not params:
57 return False
58
59 param_val = params.get(kw_name)
60
61 # Types where the it may be valid, i.e., explicitly defined & nonvariadic
62 passable_kw_types = set(
63 (
64 inspect.Parameter.POSITIONAL_ONLY,
65 inspect.Parameter.POSITIONAL_OR_KEYWORD,
66 inspect.Parameter.KEYWORD_ONLY,
67 )
68 )
69
70 if param_val:
71 is_sig_param = param_val.kind in passable_kw_types
72 # We want kwargs only, but this is passable as a positional arg
73 if (
74 requires_kw_only
75 and is_sig_param
76 and param_val.kind != inspect.Parameter.KEYWORD_ONLY
77 ):
78 return False
79 if (requires_kw_only and param_val.kind == inspect.Parameter.KEYWORD_ONLY) or (
80 not requires_kw_only and is_sig_param
81 ):
82 return True
83
84 # If we're okay with var-kwargs, it's supported as long as
85 # the kw_name isn't something like *args, **kwargs
86 if allow_var_kwargs:
87 # Get the last param; type is ignored here because params is a proxy
88 # mapping, but it wraps an ordered dict, and they appear in order.
89 # Ref: https://docs.python.org/3/library/inspect.html#inspect.Signature.parameters
90 last_param = params[next(reversed(params))] # type: ignore
91 return (
92 last_param.kind == inspect.Parameter.VAR_KEYWORD
93 and last_param.name != kw_name
94 )
95
96 return False
97
98
99 def supports_kw(
100 callable: Callable[..., object],
101 kw_name: str,
102 *,
103 requires_kw_only: bool = False,
104 allow_var_kwargs: bool = True,
105 ) -> bool:
106 """Check if a keyword is a valid kwarg for a callable; if requires_kw_only
107 disallows kwargs names that can also be positional arguments.
108 """
109 # Unwrap bound methods so that the lru_cache key is the underlying
110 # function, not the instance. Caching bound methods pins the object
111 # (and all its GPU tensors) for the lifetime of the cache.
112 if hasattr(callable, "__func__"):
113 callable = callable.__func__
114 return _supports_kw(
115 callable,
116 kw_name,
117 requires_kw_only=requires_kw_only,
118 allow_var_kwargs=allow_var_kwargs,
119 )
120
121
122 def get_allowed_kwarg_only_overrides(
123 callable: Callable[..., object],
124 overrides: Mapping[str, object] | None,
125 *,
126 requires_kw_only: bool = True,
127 allow_var_kwargs: bool = False,
128 ) -> dict[str, Any]:
129 """Given a callable which has one or more keyword only params and a dict
130 mapping param names to values, drop values that can be not be kwarg
131 expanded to overwrite one or more keyword-only args. This is used in a
132 few places to handle custom processor overrides for multimodal models,
133 e.g., for profiling when processor options provided by the user
134 may affect the number of mm tokens per instance.
135
136 Args:
137 callable: Callable which takes 0 or more keyword only arguments.
138 If None is provided, all overrides names are allowed.
139 overrides: Potential overrides to be used when invoking the callable.
140 requires_kw_only: If True, only keyword-only params may be overridden.
141 allow_var_kwargs: Allows overrides that are expandable for var kwargs.
142
143 Returns:
144 Dictionary containing the kwargs to be leveraged which may be used
145 to overwrite one or more keyword only arguments when invoking the
146 callable.
147
148 """
149 if not overrides:
150 return {}
151
152 # Drop any mm_processor_kwargs provided by the user that
153 # are not kwargs, unless it can fit it var_kwargs param
154 filtered_overrides = {
155 kwarg_name: val
156 for kwarg_name, val in overrides.items()
157 if supports_kw(
158 callable,
159 kwarg_name,
160 requires_kw_only=requires_kw_only,
161 allow_var_kwargs=allow_var_kwargs,
162 )
163 }
164
165 # If anything is dropped, log a warning
166 dropped_keys = overrides.keys() - filtered_overrides.keys()
167 if dropped_keys:
168 if requires_kw_only:
169 logger.warning(
170 "The following intended overrides are not keyword-only args "
171 "and will be dropped: %s",
172 dropped_keys,
173 )
174 else:
175 logger.warning(
176 "The following intended overrides are not keyword args "
177 "and will be dropped: %s",
178 dropped_keys,
179 )
180
181 return filtered_overrides