vllm-project/vllm · error · ValueError

a model must have at least one layer

Error message

a model must have at least one layer

What it means

ModelArchitectureConfig.from_layers merges per-layer configs into a whole-model config; an empty layer list has nothing to merge, so it fails fast with ValueError instead of producing a meaningless config.

Source

Thrown at vllm/config/model_arch.py:108

        layer = copy(self)
        object.__setattr__(layer, "per_layer_overrides", None)
        for name, value in self.per_layer_overrides[layer_idx].items():
            object.__setattr__(layer, name, value)
        return layer

    @classmethod
    def from_layers(
        cls, layers: "list[ModelArchitectureConfig]"
    ) -> "ModelArchitectureConfig":
        """Whole-model config for a checkpoint whose layers differ.

        Fields that agree across layers are taken as they are. Fields that differ
        are collapsed with `max`, so buffers are sized for the largest layer, and
        the differing values are kept per layer. No field is named here: which
        ones vary is whatever the checkpoint says.
        """
        if not layers:
            raise ValueError("a model must have at least one layer")

        merged: dict[str, Any] = {}
        overrides: list[dict[str, Any]] = [{} for _ in layers]
        for f in dataclass_fields(cls):
            if f.name == "per_layer_overrides":
                continue
            values = [getattr(layer, f.name) for layer in layers]
            if all(value == values[0] for value in values):
                merged[f.name] = values[0]
                continue
            # `bool` is an `int`, so an exact type check is what keeps a varying
            # flag from collapsing to `any`. `is_deepseek_mla` doing that would
            # make `use_mla` true model wide, and `get_num_kv_heads` then returns
            # 1 for every layer, discarding the overrides built here.
            if not all(type(value) in (int, float) for value in values):
                raise ValueError(
                    f"{f.name!r} varies across layers and has no whole-model "
                    f"value: {sorted(set(map(repr, values)))}. Only numeric "

View on GitHub (pinned to c794754062)

Solutions

  1. Debug why zero layers were collected before from_layers is called (inspect the state dict keys / layer filter).
  2. Guard the caller: skip construction or raise a clearer upstream error when the layer list is empty.

Example fix

# before
model_arch_cfg = ModelArchitectureConfig.from_layers(layer_cfgs)  # layer_cfgs == []
# after
assert layer_cfgs, 'no decoder layers found in checkpoint'
model_arch_cfg = ModelArchitectureConfig.from_layers(layer_cfgs)
Defensive patterns

Strategy: validation

Validate before calling

def build_from_layers(layer_cfgs):
    if not layer_cfgs:
        raise ValueError('checkpoint yielded zero layer configs; extraction is broken')
    return ModelArchitectureConfig.from_layers(layer_cfgs)

Type guard

def has_layers(layers: list) -> bool:
    return len(layers) > 0

Prevention

When it happens

Trigger: Calling ModelArchitectureConfig.from_layers([]) — typically from layer-splitting logic that filtered out every layer (bad layer-type predicate, empty checkpoint state dict, or a parsing step that yielded zero layer configs).

Common situations: Custom heterogeneous-layer checkpoints where the layer extraction regex/filter matches nothing after a config schema change; tooling that builds ModelArchitectureConfig from safetensors indexes getting an empty index.

Related errors


AI-assisted analysis of vllm-project/vllm@c794754062 (2026-08-14). Data as JSON: /api/errors/a8ebeb838c4d35b5. Report an issue: GitHub.