vLLM Explained

config/vllm.py: VllmConfig, __post_init__ and compute_hash

AdvancedCode mapabout 10 min read + 2 min deep dives

Code version: vLLM @ 3e44c0d · 2026-10-22

Purpose#

vllm/config/vllm.py defines the VllmConfig: one object that holds every top-level config of the engine. Its __post_init__ turns a set of separate sub-configs into one consistent configuration.

Filevllm/config/vllm.py, 3,617 lines of Python. One class, VllmConfig, of about 3,160 lines; an enum, four level tables, eight fusion helpers, and the "current config" functions.
Main classVllmConfig (vllm/config/vllm.py:347-348).
Runs in__post_init__ runs where a VllmConfig is made: the API server process of vllm serve, and again in each engine core process after the handshake. The properties and compute_hash run in every process that holds a copy.
Called byEngineArgs.create_engine_config (construction); the engine core, the workers, the model runner and the compile backend (properties, compute_hash, get_current_vllm_config).
CallsThe platform hooks, Executor.get_class, the model registry and config classes, the KV connector factory (class lookup only).
Not in this fileThe sub-config classes (vllm/config/*.py), the flag parser and the batch defaults (engine/arg_utils.py: EngineArgs, the flags and create_engine_config), the compile backend that uses the hash (vllm/compilation/).

Where it sits#

Panel 2 of the diagram on How configuration works shows VllmConfig(...) at the end of create_engine_config, and the phases of __post_init__. Panel 3 shows the copies in the other processes.

Who calls it#

What it calls#

Key classes and functions#

LinesNameRole
vllm/config/vllm.py:75-128DEFAULT_BREAKABLE_CUDAGRAPH_ARCHITECTURESModel classes that get breakable CUDA graphs by default.
vllm/config/vllm.py:131-143OptimizationLevelO0 to O3.
vllm/config/vllm.py:156-251enable_*_fusionCallables that the level tables use to decide a fusion pass from the config.
vllm/config/vllm.py:254-344OPTIMIZATION_LEVEL_00 ... _03The defaults of each level.
vllm/config/vllm.py:347-457VllmConfig fields34 attributes.
vllm/config/vllm.py:459-575compute_hashThe 10-digit config hash.
vllm/config/vllm.py:586-597max_concurrent_batchesSize of the engine core batch queue.
vllm/config/vllm.py:712-749use_v2_model_runnerModel Runner V2 or V1.
vllm/config/vllm.py:854-874needs_dp_coordinatorWhether a DP coordinator process starts.
vllm/config/vllm.py:1005-1049_set_config_default, _apply_optimization_level_defaultsFill only fields that are None.
vllm/config/vllm.py:1492-2318__post_init__Cross-checks and late defaults.
vllm/config/vllm.py:2377-2636_set_cudagraph_sizesCUDA graph capture sizes.
vllm/config/vllm.py:2637-2713_set_compile_rangesToken ranges for compiled graphs.
vllm/config/vllm.py:2714-2774try_verify_and_update_configPer-architecture hooks, once.
vllm/config/vllm.py:2788-2823__str__The text of the "Initializing a V1 LLM engine" log line.
vllm/config/vllm.py:2999-3033_get_v2_model_runner_unsupported_featuresThe fallback reasons to Model Runner V1.
vllm/config/vllm.py:3381-3511Model validatorsPydantic checks after the constructor.
vllm/config/vllm.py:3516-3590set_current_vllm_config, get_current_vllm_configThe active config as a module global.

Walk through the main methods#

The fields#

    support different configs. Make sure the configs are valid for the platform
    you are using. Contents must be hashable."""
    instance_id: str = ""
    """The ID of the vLLM instance."""
    optimization_level: OptimizationLevel = OptimizationLevel.O2
    """The optimization level. These levels trade startup time cost for
    performance, with -O0 having the best startup time and -O3 having the best
    performance. -O2 is used by default. See OptimizationLevel for full
    description."""

    performance_mode: PerformanceMode = "balanced"
    """Performance mode for runtime behavior, 'balanced' is the default.
    'interactivity' favors low end-to-end per-request latency at small batch
    sizes (fine-grained CUDA graphs, latency-oriented kernels).
    'throughput' favors aggregate tokens/sec at high concurrency (larger CUDA
    graphs, more aggressive batching, throughput-oriented kernels)."""

Most fields are sub-configs with a default factory (vllm/config/vllm.py:357-422). The top-level settings are additional_config, instance_id, optimization_level (default O2), performance_mode (default "balanced") and shutdown_timeout (vllm/config/vllm.py:428-457). The generated table of all fields is on Config classes.

The optimization level tables#

OPTIMIZATION_LEVEL_02 = {
    "compilation_config": {
        "pass_config": {
            "fuse_norm_quant": enable_norm_fusion,
            "fuse_act_quant": enable_act_fusion,
            "fuse_allreduce_rms": enable_allreduce_rms_fusion,
            "fuse_attn_quant": IS_QUANTIZED,
            "fuse_act_padding": enable_norm_pad_fusion,
            "fuse_mla_dual_rms_norm": enable_mla_dual_rms_norm_fusion,
            "fuse_rope_kvcache": enable_rope_kvcache_fusion,
            "fuse_qk_norm_rope_kvcache": enable_qk_norm_rope_kvcache,
            "enable_qk_norm_rope_fusion": False,
            "fuse_rope_kvcache_cat_mla": enable_rope_kvcache_mla_fusion,
        },
        "cudagraph_mode": CUDAGraphMode.FULL_AND_PIECEWISE,
        "use_inductor_graph_partition": False,
    },
    "kernel_config": {
        "enable_flashinfer_autotune": True,
    },
}

O0 turns all fusions off and sets CUDA graphs to NONE; O1 uses PIECEWISE; O2 and O3 use FULL_AND_PIECEWISE and are equal at this commit (vllm/config/vllm.py:254-337). The docstring of O3 says "Currently the same as -O2s." (vllm/config/vllm.py:142-143). A value is either a constant or a function of the config, for example enable_allreduce_rms_fusion (vllm/config/vllm.py:178-202). apply_recursive walks the nested table. For each leaf it calls _set_config_default, which calls the function and writes the result only when the field is None (vllm/config/vllm.py:1014-1019):

        def apply_recursive(config_obj: Any, config_defaults: dict[str, Any]) -> None:
            """Recursively apply defaults to config_obj, using self as root."""
            for key, value in config_defaults.items():
                if not hasattr(config_obj, key):
                    continue

                current = getattr(config_obj, key)
                if isinstance(value, dict) and is_dataclass(current):
                    apply_recursive(current, value)
                else:
                    self._set_config_default(config_obj, key, value)

        apply_recursive(self, defaults)

So the tables never overwrite a value that the user or an earlier phase set. The page torch.compile and CUDA graphs in vLLM explains what each pass and mode does.

__post_init__, phase by phase#

The method is about 830 lines long. Its docstring is "Verify configs are valid & consistent with each other." (vllm/config/vllm.py:1493). We read it in nine phases.

Phase 1: early exits and model hooks.

        self.instance_id = f"{time.time_ns()}"

        if self.model_config is not None and self.model_config.is_submodel_config:
            # with_hf_config() view: the parent config was already validated,
            # and this view's empty architecture list makes the model-dependent checks
            # below unsafe (e.g. use_mla resolves the architecture registry).
            return

        self._resolve_mm_encoder_only()

        if self.is_mm_encoder_only and self.cache_config.enable_prefix_caching:
            # Such an instance publishes encoder embeddings and runs no language
            # model, so it holds no KV cache for prefix caching to reuse and its
            # coordinator would have no group to manage. Disable before
            # `try_verify_and_update_config` so model config hooks (e.g. the
            # hybrid mamba hook setting `mamba_block_size`) already see prefix
            # caching as disabled.
            logger.info(
                "Disabling prefix caching: this instance runs the "
                "multi-modal encoder only."
            )
            self.cache_config.enable_prefix_caching = False

        if self.performance_mode != "balanced":
            logger.info_once("Performance mode set to '%s'.", self.performance_mode)

        self.try_verify_and_update_config()
        self._reject_mismatched_float_kv_cache()
        self._resolve_and_verify_engram_config()

        self._check_supports_watermarking()
        # Models may have supplied their own DCP defaults above; anything still
        # unset falls back to the stock ones.
        self.parallel_config.set_dcp_defaults()

        if self.model_config is not None:
            self.model_config.verify_with_parallel_config(self.parallel_config)
            self.model_config.verify_dual_chunk_attention_config(self.load_config)

            self.parallel_config.is_moe_model = self.model_config.is_moe
  1. Line 1495. instance_id from the clock, for profile names.
  2. Lines 1497-1501. A sub-model view made by with_hf_config skips all checks.
  3. Lines 1505-1516. An instance that runs only the multimodal encoder turns prefix caching off.
  4. Line 1521. The per-architecture hooks run once; a flag on the model config stops a second run (vllm/config/vllm.py:2718-2721).
  5. Lines 1531-1534. The model checks itself against the parallel config, and the parallel config learns if the model is MoE.

Phase 2: quantization and weights. quant_config comes from the model and load configs (vllm/config/vllm.py:1586-1589). For most load formats, the model config may untie the word embeddings (lines 1594-1599).

Phase 3: async scheduling.

        elif self.scheduler_config.async_scheduling is None:
            # Enable async scheduling unless there is an incompatible option.
            if (
                self.model_config is not None
                and self.model_config.runner_type == "pooling"
            ):
                # The current implementation of asynchronous scheduling negatively
                # impacts performance of pooling models, so we disable by default.
                logger.debug(
                    "Disabling asynchronous scheduling by default for pooling model."
                )
                self.scheduler_config.async_scheduling = False
            elif not executor_supports_async_sched:
                logger.warning_once(
                    "Async scheduling will be disabled because it is not supported "
                    "with the `%s` distributed executor backend. ",
                    executor_backend,
                )
                self.scheduler_config.async_scheduling = False
            elif uses_rocm_deepep_ht_dbo:
                logger.warning_once(
                    "Async scheduling is disabled for ROCm DeepEP "
                    "high-throughput DBO because that combination can corrupt "
                    "DP+EP generation accuracy."
                )
                self.scheduler_config.async_scheduling = False
            elif v1_async_sched_incompat is not None:
                logger.warning_once(
                    "Async scheduling is disabled because the V1 model runner "
                    "does not support it with %s.",
                    v1_async_sched_incompat,
                )
                self.scheduler_config.async_scheduling = False
            else:
                self.scheduler_config.async_scheduling = True

An explicit --async-scheduling raises on a conflict; an unset value only switches it off, with a log (lines 1647-1663). The page Async scheduling: overlap the CPU and the GPU explains the feature.

Phase 4: eager mode and compile switches. --enforce-eager sets the compile mode and the CUDA graph mode to NONE (vllm/config/vllm.py:1745-1763). TORCH_COMPILE_DISABLE=1 sets the compile mode to NONE (lines 1765-1770). For some architectures, _maybe_enable_breakable_cudagraph writes VLLM_USE_BREAKABLE_CUDAGRAPH=1 into os.environ and turns compilation off (vllm/config/vllm.py:824-851).

Phase 5: platform defaults, compile mode, level defaults.

        current_platform.apply_config_platform_defaults(self)

        if self.compilation_config.mode is None:
            if self.optimization_level > OptimizationLevel.O0:
                self.compilation_config.mode = CompilationMode.VLLM_COMPILE
            else:
                self.compilation_config.mode = CompilationMode.NONE

        # By default, enable torch wrapping only when using custom Inductor lowering
        if self.compilation_config.ir_enable_torch_wrap is None:
            self.compilation_config.ir_enable_torch_wrap = (
                self.compilation_config.mode == CompilationMode.VLLM_COMPILE
                and self.compilation_config.backend == "inductor"
            )

        if all(s not in self.compilation_config.custom_ops for s in ("all", "none")):
            if (
                self.compilation_config.backend == "inductor"
                and self.compilation_config.mode != CompilationMode.NONE
            ):
                self.compilation_config.custom_ops.append("none")
            else:
                self.compilation_config.custom_ops.append("all")

        # This populates IR op priorities,
        # must happen after compilation mode and backend are decided,
        # but before fusion defaults are applied as those may depend on op priority.
        self.kernel_config.set_platform_defaults(self)

        default_config = OPTIMIZATION_LEVEL_TO_CONFIG[self.optimization_level]
        self._apply_optimization_level_defaults(default_config)
  1. Line 1804. The first platform hook. It runs before the level defaults, so a platform can set a field and keep it.
  2. Lines 1806-1810. No mode set: VLLM_COMPILE above O0, else NONE.
  3. Lines 1819-1826. The base entry of the custom op list: "none" with Inductor and compilation, else "all" (CustomOp, out-of-tree ops and IR op providers).
  4. Line 1831. The IR op priorities, before the fusion defaults that depend on them.
  5. Lines 1833-1834. The level table.

Phase 6: CUDA graphs. Piecewise modes need VLLM_COMPILE, else the mode falls back (vllm/config/vllm.py:1111-1131, vllm/config/vllm.py:1909-1911). On a platform with static graphs, pooling models get PIECEWISE, encoder-decoder models FULL_DECODE_ONLY, and some KV connectors PIECEWISE (vllm/config/vllm.py:1929-1980). With --enforce-eager, the eager branch clears the sizes. Then _set_cudagraph_sizes runs in both cases (vllm/config/vllm.py:1982-1992).

Phase 7: the platform's last word. current_platform.check_and_update_config(self) (vllm/config/vllm.py:2046). A comment right after it says that the platform "has the last word on async scheduling" (line 2048).

Phase 8: model runner and compile ranges.

        if self.use_v2_model_runner:
            self._disable_cudagraphs_for_v2_stock_torch_compile()
            self._validate_v2_model_runner()
        else:
            self._validate_v1_model_runner()

        self._validate_profiler_config()
        self._validate_batch_sharded_sampling()
        self._validate_adaptive_verification()

        # Re-compute compile ranges after platform-specific config updates
        # (e.g., XPU may lower max_num_batched_tokens when MLA is enabled)
        self._set_compile_ranges()

An unsupported feature normally selects Model Runner V1 (the use_v2_model_runner property below). It raises ValueError only when Model Runner V2 is forced, for example with VLLM_USE_V2_MODEL_RUNNER=1 (vllm/config/vllm.py:3283-3292, vllm/config/vllm.py:730-732). The compile ranges are computed again, because the platform hook can change max_num_batched_tokens (comment at lines 2081-2082).

Phase 9: KV transfer, hybrid KV cache manager, final checks. _post_init_kv_transfer_config resolves KV offloading (vllm/config/vllm.py:2199). The hybrid KV cache manager is turned off when the platform or the KV connector does not support it; an explicit --enable-hybrid-kv-cache-manager then raises (vllm/config/vllm.py:2236-2273). The last line logs the enabled compile passes (vllm/config/vllm.py:2318).

compute_hash#

        factors: list[Any] = []

        # summarize vllm config
        vllm_factors: list[Any] = []
        if include_version:
            from vllm import __version__

            vllm_factors.append(__version__)
        if self.model_config:
            vllm_factors.append(self.model_config.compute_hash())
            if (
                self.compilation_config
                and getattr(self.compilation_config, "compile_mm_encoder", False)
                and self.model_config.multimodal_config
            ):
                vllm_factors.append(self.model_config.multimodal_config.compute_hash())
        else:
            vllm_factors.append("None")
        if self.cache_config:
            vllm_factors.append(self.cache_config.compute_hash())
        else:
            vllm_factors.append("None")
        if self.parallel_config:
            vllm_factors.append(self.parallel_config.compute_hash())
        else:
            vllm_factors.append("None")
        if self.scheduler_config:
            vllm_factors.append(self.scheduler_config.compute_hash())

The method collects one hash per sub-config in a fixed order; a missing optional config adds "None". additional_config is hashed as sorted JSON (vllm/config/vllm.py:559-567). The result is the first 10 hex digits of safe_hash(str(factors)) (vllm/config/vllm.py:572-575), which is MD5, or SHA-256 when MD5 is not available (vllm/utils/hashing.py:106-121). The docstring warns that a new field must join the list if it changes the computation graph (vllm/config/vllm.py:460-468).

use_v2_model_runner and max_concurrent_batches#

    @property
    def use_v2_model_runner(self) -> bool:
        if self.attention_config.hisparse_config is not None:
            if envs.VLLM_USE_V2_MODEL_RUNNER is False:
                raise ValueError(
                    "HiSparse requires Model Runner V2; remove "
                    "VLLM_USE_V2_MODEL_RUNNER=0."
                )
            return True

        if getattr(self, "watermark_config", None) is not None:
            if envs.VLLM_USE_V2_MODEL_RUNNER is False:
                logger.info_once(
                    "Watermarking requires Model Runner V2 and overrides "
                    "VLLM_USE_V2_MODEL_RUNNER=0."
                )
            return True

        use_v2_model_runner = envs.VLLM_USE_V2_MODEL_RUNNER
        if use_v2_model_runner is not None:
            return use_v2_model_runner

        if not HAS_TRITON:
            logger.warning_once(
                "Model Runner V2 requires Triton; using the V1 model runner instead."
            )
            return False

        unsupported = self._get_v2_model_runner_unsupported_features()
        if unsupported:
            logger.warning_once(
                "Model Runner V2 does not yet support %s; using the V1 model "
                "runner instead.",
                ", ".join(unsupported),
            )
            return False

        return True
  1. Lines 714-728. HiSparse and watermarking need Model Runner V2. HiSparse with VLLM_USE_V2_MODEL_RUNNER=0 raises; watermarking ignores the 0.
  2. Lines 730-732. Otherwise the variable decides, if it is set.
  3. Lines 734-747. No Triton, or a feature from _get_v2_model_runner_unsupported_features: Model Runner V1, with a warning (The model runner (Model Runner V2)).

max_concurrent_batches is pp_size + 1 with async scheduling and Model Runner V2, so 2 on one GPU (vllm/config/vllm.py:586-597). The engine core makes a batch queue only when the value is above 1 (vllm/v1/engine/core.py:205-211).

set_current_vllm_config#

@contextmanager
def set_current_vllm_config(
    vllm_config: VllmConfig, check_compile=False, prefix: str | None = None
):
    """Temporarily set the current vLLM config.
    Used during model initialization.
    We save the current vLLM config in a global variable,
    so that all modules can access it, e.g. custom ops
    can access the vLLM config to determine how to dispatch.
    """
    global _current_vllm_config, _current_prefix
    old_vllm_config = _current_vllm_config
    old_prefix = _current_prefix
    from vllm.compilation.counter import compilation_counter

    num_models_seen = compilation_counter.num_models_seen
    try:
        # Clear the compilation config cache when context changes.
        # This is needed since the old config may have been accessed
        # and cached before the new config is set.
        get_cached_compilation_config.cache_clear()

        _current_vllm_config = vllm_config
        _current_prefix = prefix
        yield

The context manager puts the config into a module global for the time of the block, for example while the worker builds the model (vllm/v1/worker/gpu_worker.py:592-598). get_current_vllm_config() raises an AssertionError outside such a block (lines 3576-3586). A CustomOp made at import time hits this error, as the message says.

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

Worked example: the compilation fields of one start

vllm serve meta-llama/Llama-3.1-8B-Instruct on one H100, with no flags. The table follows the compilation fields through __post_init__. (Derived from the lines named.)

FieldAfter the constructorPhase and lineValue after
compilation_config.modeNone (vllm/config/compilation.py:432)5: vllm/config/vllm.py:1806-1808VLLM_COMPILE
custom_ops[]5: vllm/config/vllm.py:1819-1824["none"]
cudagraph_modeNone (vllm/config/compilation.py:610)5: O2 table, vllm/config/vllm.py:310FULL_AND_PIECEWISE
max_cudagraph_capture_sizeNone6: vllm/config/vllm.py:2439-2449min(1024 × 1 × 2, 512) = 512
scheduler_config.async_schedulingNone3: vllm/config/vllm.py:1697-1698True
parallel_config.worker_cls"auto"7: vllm/platforms/cuda.py:330-331vllm.v1.worker.gpu_worker.Worker

With --enforce-eager, phase 4 sets the mode and the graph mode to NONE first. The level table then leaves both alone, because they are not None any more (vllm/config/vllm.py:1014-1019).

Deep dive

__post_init__ also writes the environment

Two places in __post_init__ write os.environ:

  • VLLM_USE_BREAKABLE_CUDAGRAPH=1 for the default breakable-graph architectures, or for some batch-invariant BF16 models (vllm/config/vllm.py:824-843).
  • NCCL_GRAPH_REGISTER=0 when the CUDA graph pool uses cumem, with a warning if the user set another value (vllm/config/vllm.py:2309-2316).

In vllm serve, this runs in the API server before the engine core starts. A spawned child gets a copy of the parent environment, so the engine core and the workers see the values (derived; see How configuration works, Step 9). So in vLLM some configuration travels in the environment, not in the config object.

Deep dive

Error paths: what raises, what only logs

SituationResultWhere
Explicit --async-scheduling with an unsupported executorValueErrorvllm/config/vllm.py:1660-1663
Async scheduling unset, executor unsupportedwarning; async offvllm/config/vllm.py:1676-1682
--language-model-only with cudagraph_mm_encoderValueErrorvllm/config/vllm.py:1536-1547
mm_tensor_ipc="torch_shm" without spawnValueErrorvllm/config/vllm.py:1722-1731
Inductor turned off while fusion options are setwarning: those options "will be ignored"vllm/config/vllm.py:1774-1785
Pooling model with full CUDA graphswarning; mode becomes PIECEWISEvllm/config/vllm.py:1933-1940
Model Runner V2 and an unsupported feature (forced V2)ValueErrorvllm/config/vllm.py:3283-3292
Explicit hybrid KV cache manager that the setup cannot useValueErrorvllm/config/vllm.py:2260-2269
get_current_vllm_config() outside a config blockAssertionErrorvllm/config/vllm.py:3576-3586
Deep dive

Environment variables read in this file

NameRead atEffect
VLLM_USE_V2_MODEL_RUNNERvllm/config/vllm.py:715, vllm/config/vllm.py:730Forces Model Runner V2 or V1 (with exceptions).
TORCH_COMPILE_DISABLE (read with os.environ)vllm/config/vllm.py:1765Compilation mode NONE.
VLLM_USE_BREAKABLE_CUDAGRAPH (read and written)vllm/config/vllm.py:826Breakable CUDA graphs; turns compilation off.
VLLM_WORKER_MULTIPROC_METHOD (read with os.environ)vllm/config/vllm.py:1726, vllm/config/vllm.py:2017Checks for torch_shm and Whisper.
VLLM_DEBUG_DUMP_PATHvllm/config/vllm.py:2289-2297Overrides the compile debug dump path.

Check yourself#

  1. 1
    What does -O3 change compared to the default at this commit?
    easy
    Show answer

    Nothing. The O3 table has the same values as the O2 table (vllm/config/vllm.py:296-337), and the docstring says "Currently the same as -O2s." (vllm/config/vllm.py:142-143).

  2. 2
    A platform plugin wants a different default CUDA graph mode, but it must not override a user's value. Which hook should it use?
    medium
    Show answer

    apply_config_platform_defaults. It runs before the level defaults (vllm/config/vllm.py:1804, vllm/config/vllm.py:1833-1834). If the hook sets the mode only when it is None, the level table then keeps the value, because it fills only None fields (vllm/config/vllm.py:1014-1019).

  3. 3
    Why does a change of --max-num-seqs make the next start compile again?
    medium
    Show answer

    SchedulerConfig.compute_hash includes max_num_seqs (vllm/config/scheduler.py:282-284). VllmConfig.compute_hash includes that hash (vllm/config/vllm.py:500-501), and the compile cache folder name depends on it (vllm/compilation/backends.py:1091-1097).

  4. 4
    You start vllm serve with --enforce-eager -O2. Which CUDA graph capture sizes does the config end with, and which lines decide it?
    hard
    Show answer

    An empty list, with max_cudagraph_capture_size = 0. Phase 4 sets the graph mode to NONE (vllm/config/vllm.py:1745-1747); the O2 table does not override it, because the field is not None (vllm/config/vllm.py:1014-1019). On a static-graph platform, the eager branch then clears the sizes (vllm/config/vllm.py:1983-1988).