vLLM Explained

envs.py: environment variables as a dict of lambdas

IntermediateCode mapabout 6 min read + 3 min deep dives

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.

Filevllm/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 objectenvironment_variables: dict[str, Callable[[], Any]] (vllm/envs.py:618).
Runs inEvery vLLM process, and setup.py at build time (setup.py:56). Reads happen in the thread of the caller.
Called byAbout 300 modules in vllm/ import it as import vllm.envs as envs or from vllm import envs (derived from a search).
Callsos.environ, os.getenv; a few readers import from vllm.utils or urllib3 when they run.
Not in this fileThe 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#

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#

LinesSectionRole
vllm/envs.py:14-336if TYPE_CHECKING: block310 typed names with defaults, for type checkers only.
vllm/envs.py:339-350get_default_cache_root, get_default_config_rootXDG_* roots, else ~/.cache and ~/.config.
vllm/envs.py:353-371maybe_convert_int, maybe_convert_bool, maybe_convert_json_str_or_fileParsers where "not set" means None.
vllm/envs.py:374-397disable_compile_cache, use_aot_compile, use_mega_aot_artifactReaders with a computed default.
vllm/envs.py:400-514env_with_choices, env_list_with_choices, env_set_with_choicesReaders that check the value against a list.
vllm/envs.py:517-544get_vllm_portParses VLLM_PORT; a URI raises.
vllm/envs.py:547-572_generate_shm_name, get_env_or_set_defaultA reader that writes a generated value back into os.environ.
vllm/envs.py:575-615Doc markers, _resolve_rust_cli_pathMarkers for the docs generator; the Rust binary path.
vllm/envs.py:618-2285environment_variablesThe 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_cacheThe cache.
vllm/envs.py:2340-2348__dir__, is_setAll names; "is it in os.environ?".
vllm/envs.py:2351-2493compile_factorsThe 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.0

Python 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
  1. Lines 420-422. Not set: the default, with no check.
  2. 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)
  1. Lines 2317-2319. A second call does nothing. _is_envs_cache_enabled checks for the cache_clear attribute of functools.cache (vllm/envs.py:2302-2305).
  2. Lines 2321-2322. The module __getattr__ is replaced by a cached version of itself.
  3. 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 dive

Processes and the cache: when a value is fixed

ProcessEnvironment comes fromCacheEffect
API serverthe shellnever onEach envs.X reads os.environ again. A comment in the metrics code says the same (vllm/v1/metrics/stats.py:479-481).
Engine corea copy of the API server environment at spawnon 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 spawnon at the end of WorkerProc.__init__Same as the engine core.
Worker with TP = 1it is in the engine core processthe engine core's cacheOne 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 dive

Worked example: VLLM_ENGINE_READY_TIMEOUT_S

  1. The shell has VLLM_ENGINE_READY_TIMEOUT_S=1200.
  2. The API server imports vllm/v1/engine/core_client.py. Line 29 runs from vllm.envs import VLLM_ENGINE_READY_TIMEOUT_S. __getattr__ calls the reader once: int("1200") = 1200 (vllm/envs.py:812-814).
  3. The client uses that module constant for its wait: poll(timeout=1200 * 1000) milliseconds (vllm/v1/engine/core_client.py:757-759).
  4. A change of os.environ after the import does not change the constant, although the API server has no cache.
  5. The name is not in ignored_factors, so the value 1200 is also part of the env hash of the compile cache (derived from vllm/envs.py:2356-2447).

A value such as 20m is not accepted: int("20m") raises ValueError at the import of core_client.py.

Deep dive

Error paths: when a bad value raises

SituationResultWhere
A value outside the list of an env_with_choices readerValueError("Invalid value ...") at the first readvllm/envs.py:434-438
true for a bool(int(...)) readerValueError at the readvllm/envs.py:1086-1088 (an example)
VLLM_PORT is a URI (a Kubernetes service named vllm)ValueError with a hintvllm/envs.py:532-543
Unknown VLLM_* namewarning in create_engine_config; ValueError with --fail-on-environ-validationvllm/platforms/interface.py:1459-1467
Unknown attribute envs.NOT_A_VARAttributeErrorvllm/envs.py:2299
A reader raises inside compile_factorswarning; the name is skippedvllm/envs.py:2456-2464
Deep dive

Environment 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.

Check yourself#

  1. 1
    What is the real default of a variable: the value in the TYPE_CHECKING block or the fallback in the dict?
    easy
    Show answer

    The fallback in the dict. Python does not run the TYPE_CHECKING block at run time (vllm/envs.py:14); __getattr__ calls the dict entry (vllm/envs.py:2297-2298).

  2. 2
    You change os.environ["VLLM_LOG_STATS_INTERVAL"] inside the API server while it runs. Does the next read of envs.VLLM_LOG_STATS_INTERVAL see the new value?
    medium
    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.

  3. 3
    Why does VLLM_PLUGINS="a, b" not load a plugin named b?
    medium
    Show answer

    The reader splits at commas and does not strip spaces, so the second name is " b" (vllm/envs.py:1163-1167).

  4. 4
    You set VLLM_ENGINE_READY_TIMEOUT_S to a new value and restart with the same flags. Why can the start compile the model again?
    hard
    Show answer

    compile_factors puts every variable that is not in the ignore list into the env hash (vllm/envs.py:2451-2466). VLLM_ENGINE_READY_TIMEOUT_S is 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.)