envs.py: environment variables as a dict of lambdas
Code version: vLLM @ 3e44c0d · 2026-10-22
Purpose#
vllm/envs.py is the one place where vLLM declares its environment variables. Code reads a variable as envs.VLLM_X. The module computes the value from os.environ on each read, until the process turns on the environment variable cache.
| File | vllm/envs.py, 2,493 lines of Python. No classes. One dict with 317 entries; 16 helper functions; the module functions __getattr__, __dir__, is_set, enable_envs_cache, disable_envs_cache and compile_factors. |
|---|---|
| Main object | environment_variables: dict[str, Callable[[], Any]] (vllm/envs.py:618). |
| Runs in | Every vLLM process, and setup.py at build time (setup.py:56). Reads happen in the thread of the caller. |
| Called by | About 300 modules in vllm/ import it as import vllm.envs as envs or from vllm import envs (derived from a search). |
| Calls | os.environ, os.getenv; a few readers import from vllm.utils or urllib3 when they run. |
| Not in this file | The table of all variables (Environment variables, generated from this file); validate_environ, which warns about unknown names (vllm/platforms/interface.py:1459-1467); the callers of enable_envs_cache. |
Where it sits#
Panel 3 of the diagram on How configuration works shows the environment as a separate channel next to VllmConfig. Each spawned process inherits os.environ and imports this module again.
Who calls it#
- Almost every part of vLLM reads
envs.X. Some modules copy a value once at import, for examplefrom vllm.envs import VLLM_ENGINE_READY_TIMEOUT_S(vllm/v1/engine/core_client.py:29). EngineCore.__init__andWorkerProc.__init__callenable_envs_cache()at their end (vllm/v1/engine/core.py:241-243,vllm/v1/executor/multiproc_executor.py:708-710).cleanup_dist_env_and_memorycallsdisable_envs_cache()(vllm/distributed/parallel_state.py:2543-2544).- The compile backend and the AOT cache call
compile_factors()(vllm/compilation/backends.py:1059,vllm/compilation/caching.py:576). vllm/collect_env.pyimports the dict (vllm/collect_env.py:20).
What it calls#
Only the standard library at import. The readers call os.getenv when they run. use_aot_compile imports a torch version helper (vllm/envs.py:378-387); get_vllm_port imports urllib3 only on a parse error (vllm/envs.py:532-544).
Key classes and functions#
| Lines | Section | Role |
|---|---|---|
vllm/envs.py:14-336 | if TYPE_CHECKING: block | 310 typed names with defaults, for type checkers only. |
vllm/envs.py:339-350 | get_default_cache_root, get_default_config_root | XDG_* roots, else ~/.cache and ~/.config. |
vllm/envs.py:353-371 | maybe_convert_int, maybe_convert_bool, maybe_convert_json_str_or_file | Parsers where "not set" means None. |
vllm/envs.py:374-397 | disable_compile_cache, use_aot_compile, use_mega_aot_artifact | Readers with a computed default. |
vllm/envs.py:400-514 | env_with_choices, env_list_with_choices, env_set_with_choices | Readers that check the value against a list. |
vllm/envs.py:517-544 | get_vllm_port | Parses VLLM_PORT; a URI raises. |
vllm/envs.py:547-572 | _generate_shm_name, get_env_or_set_default | A reader that writes a generated value back into os.environ. |
vllm/envs.py:575-615 | Doc markers, _resolve_rust_cli_path | Markers for the docs generator; the Rust binary path. |
vllm/envs.py:618-2285 | environment_variables | The 317 readers. |
vllm/envs.py:2291-2299 | __getattr__ | envs.X calls the reader. |
vllm/envs.py:2302-2337 | _is_envs_cache_enabled, enable_envs_cache, disable_envs_cache | The cache. |
vllm/envs.py:2340-2348 | __dir__, is_set | All names; "is it in os.environ?". |
vllm/envs.py:2351-2493 | compile_factors | The environment part of the compile cache key. |
Walk through the main methods#
The two declarations#
Each variable has a typed line in the TYPE_CHECKING block, for example:
LOCAL_RANK: int = 0
CUDA_VISIBLE_DEVICES: str | None = None
VLLM_ENGINE_READY_TIMEOUT_S: int = 600
VLLM_CHAT_TEMPLATE_RENDER_TIMEOUT: float = 30.0Python does not run this block, so these defaults have no effect. They give type checkers and editors a type for envs.VLLM_ENGINE_READY_TIMEOUT_S. The run-time reader is the dict entry:
# Timeout in seconds for waiting for engine cores to become ready
# during startup. Default is 600 seconds (10 minutes).
"VLLM_ENGINE_READY_TIMEOUT_S": lambda: int(
os.environ.get("VLLM_ENGINE_READY_TIMEOUT_S", "600")
),The dict has 317 entries: 295 lambdas, 16 env_with_choices readers, one env_set_with_choices, one get_env_or_set_default and four named functions (derived from the source with ast). The code comment above each key is its description. Two comments split the dict: "Installation Time Env Vars" and "Runtime Env Vars" (vllm/envs.py:619, vllm/envs.py:710).
A reader with choices#
def _get_validated_env() -> str | None:
value = os.getenv(env_name)
if value is None:
return default
# Resolve choices if it's a callable (for lazy loading)
actual_choices = choices() if callable(choices) else choices
if not case_sensitive:
check_value = value.lower()
check_choices = [choice.lower() for choice in actual_choices]
else:
check_value = value
check_choices = actual_choices
if check_value not in check_choices:
raise ValueError(
f"Invalid value '{value}' for {env_name}. "
f"Valid options: {actual_choices}."
)
return value
return _get_validated_env- Lines 420-422. Not set: the default, with no check.
- Lines 434-438. A value that is not in the list raises
ValueError. The error comes at the first read, not at start-up.
The list form splits at commas and strips spaces (vllm/envs.py:472). The plain lambda of VLLM_PLUGINS does not strip them (vllm/envs.py:1163-1167). So VLLM_PLUGINS="a, b" gives ["a", " b"], and no plugin is named " b" (derived).
__getattr__: a read is a function call#
def __getattr__(name: str):
"""Gets environment variables lazily.
NOTE: After enable_envs_cache() invocation (which triggered after service
initialization), all environment variables will be cached.
"""
if name in environment_variables:
return environment_variables[name]()
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")The module defines no attribute VLLM_X. So Python calls this module-level __getattr__ for envs.VLLM_X (PEP 562), and also for from vllm.envs import VLLM_X. Line 2298 calls the reader each time. The from ... import form keeps the value of that one moment in the importing module.
enable_envs_cache: freeze the values#
def enable_envs_cache() -> None:
"""Enables caching of environment variables. This is useful for performance
reasons, as it avoids the need to re-evaluate environment variables on
every call.
NOTE: Currently, it's invoked after service initialization to reduce
runtime overhead. This also means that environment variables should NOT
be updated after the service is initialized.
"""
if _is_envs_cache_enabled():
# Avoid wrapping functools.cache multiple times
return
# Tag __getattr__ with functools.cache
global __getattr__
__getattr__ = functools.cache(__getattr__)
# Cache all environment variables
for key in environment_variables:
__getattr__(key)- Lines 2317-2319. A second call does nothing.
_is_envs_cache_enabledchecks for thecache_clearattribute offunctools.cache(vllm/envs.py:2302-2305). - Lines 2321-2322. The module
__getattr__is replaced by a cached version of itself. - Lines 2325-2326. Every reader runs once now, so the cache holds all 317 values.
disable_envs_cache puts the original function back through __wrapped__ (vllm/envs.py:2329-2337). is_set(name) does not use the reader: it checks name in os.environ (vllm/envs.py:2344-2348).
compile_factors: the environment in the compile cache key#
factors: dict[str, object] = {}
for factor, getter in environment_variables.items():
if factor in ignored_factors:
continue
try:
raw = getter()
except Exception as exc: # pragma: no cover - defensive logging
logger.warning(
"Skipping environment variable %s while hashing compile factors: %s",
factor,
exc,
)
continue
factors[factor] = normalize_value(raw)Every variable that is not in ignored_factors joins the key. The ignore list has 82 names, for example VLLM_PORT, VLLM_CACHE_ROOT, the logging variables and VLLM_API_KEY (vllm/envs.py:2356-2447). A reader that raises is skipped with a warning (lines 2456-2464). Nine Ray variables join the key too (lines 2468-2491). The compile backend hashes this dict into the env hash of the compile cache (vllm/compilation/backends.py:1059-1060).
Deep dive#
The sections below give the full detail. Open them one at a time, or use Open all deep dives at the top of the page.
Deep diveProcesses and the cache: when a value is fixed
| Process | Environment comes from | Cache | Effect |
|---|---|---|---|
| API server | the shell | never on | Each envs.X reads os.environ again. A comment in the metrics code says the same (vllm/v1/metrics/stats.py:479-481). |
| Engine core | a copy of the API server environment at spawn | on at the end of EngineCore.__init__ | Reads during start-up are live; later reads return the cached value. |
| Worker process (TP > 1) | a copy of the engine core environment at spawn | on at the end of WorkerProc.__init__ | Same as the engine core. |
| Worker with TP = 1 | it is in the engine core process | the engine core's cache | One module, one cache. |
Code in vLLM can write os.environ before it spawns a child. The CLI sets VLLM_WORKER_MULTIPROC_METHOD=spawn (vllm/entrypoints/common/startup.py:147-149), and VllmConfig.__post_init__ can set VLLM_USE_BREAKABLE_CUDAGRAPH=1 (vllm/config/vllm.py:824-831). The children see these values. A reader made with get_env_or_set_default writes its generated default into os.environ, so a child that spawns later gets the same value (vllm/envs.py:555-572).
Deep diveWorked example: VLLM_ENGINE_READY_TIMEOUT_S
- The shell has
VLLM_ENGINE_READY_TIMEOUT_S=1200. - The API server imports
vllm/v1/engine/core_client.py. Line 29 runsfrom vllm.envs import VLLM_ENGINE_READY_TIMEOUT_S.__getattr__calls the reader once:int("1200")= 1200 (vllm/envs.py:812-814). - The client uses that module constant for its wait:
poll(timeout=1200 * 1000)milliseconds (vllm/v1/engine/core_client.py:757-759). - A change of
os.environafter the import does not change the constant, although the API server has no cache. - The name is not in
ignored_factors, so the value 1200 is also part of the env hash of the compile cache (derived fromvllm/envs.py:2356-2447).
A value such as 20m is not accepted: int("20m") raises ValueError at the import of core_client.py.
Deep diveError paths: when a bad value raises
| Situation | Result | Where |
|---|---|---|
A value outside the list of an env_with_choices reader | ValueError("Invalid value ...") at the first read | vllm/envs.py:434-438 |
true for a bool(int(...)) reader | ValueError at the read | vllm/envs.py:1086-1088 (an example) |
VLLM_PORT is a URI (a Kubernetes service named vllm) | ValueError with a hint | vllm/envs.py:532-543 |
Unknown VLLM_* name | warning in create_engine_config; ValueError with --fail-on-environ-validation | vllm/platforms/interface.py:1459-1467 |
Unknown attribute envs.NOT_A_VAR | AttributeError | vllm/envs.py:2299 |
A reader raises inside compile_factors | warning; the name is skipped | vllm/envs.py:2456-2464 |
Deep diveEnvironment variables and config fields
Some settings exist in both forms. The config field is then the main form, and the variable gives its default. For example, LoggingConfig.log_level takes its default from VLLM_LOGGING_LEVEL (vllm/config/logging.py:26-28), and the flag --log-level overrides it. vllm/config/utils.py has helpers that read a deprecated variable into a config field with a warning (vllm/config/utils.py:458-518); no code in vllm/ calls them at this commit (derived from a search). The page How configuration works compares the two channels.
Related concepts#
- Environment variables: every variable with its default, its reader and its users.
- How configuration works: flags,
VllmConfigand the environment side by side. - How vLLM finds and loads plugins:
VLLM_PLUGINSand the cache, in practice. - torch.compile and CUDA graphs in vLLM: the compile cache key that uses
compile_factors.
Check yourself#
- 1What is the real default of a variable: the value in theeasy
TYPE_CHECKINGblock or the fallback in the dict?Show answer
The fallback in the dict. Python does not run the
TYPE_CHECKINGblock at run time (vllm/envs.py:14);__getattr__calls the dict entry (vllm/envs.py:2297-2298). - 2You changemedium
os.environ["VLLM_LOG_STATS_INTERVAL"]inside the API server while it runs. Does the next read ofenvs.VLLM_LOG_STATS_INTERVALsee the new value?Show answer
Yes, in the API server. No front-end code calls
enable_envs_cache, so each read calls the reader again (vllm/envs.py:2291-2299). The engine core and the workers would not see it: they have their own environment and their own cache. - 3Why doesmedium
VLLM_PLUGINS="a, b"not load a plugin namedb?Show answer
The reader splits at commas and does not strip spaces, so the second name is
" b"(vllm/envs.py:1163-1167). - 4You sethard
VLLM_ENGINE_READY_TIMEOUT_Sto a new value and restart with the same flags. Why can the start compile the model again?Show answer
compile_factorsputs every variable that is not in the ignore list into the env hash (vllm/envs.py:2451-2466).VLLM_ENGINE_READY_TIMEOUT_Sis not in the list (vllm/envs.py:2356-2447), and the cache folder name includes the env hash (vllm/compilation/backends.py:1091-1097). (Derived.)