{"record":{"id":"a8ebeb838c4d35b5","repo":"vllm-project/vllm","slug":"a-model-must-have-at-least-one-layer","errorCode":null,"errorMessage":"a model must have at least one layer","messagePattern":"a model must have at least one layer","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"vllm/config/model_arch.py","lineNumber":108,"sourceCode":"        layer = copy(self)\n        object.__setattr__(layer, \"per_layer_overrides\", None)\n        for name, value in self.per_layer_overrides[layer_idx].items():\n            object.__setattr__(layer, name, value)\n        return layer\n\n    @classmethod\n    def from_layers(\n        cls, layers: \"list[ModelArchitectureConfig]\"\n    ) -> \"ModelArchitectureConfig\":\n        \"\"\"Whole-model config for a checkpoint whose layers differ.\n\n        Fields that agree across layers are taken as they are. Fields that differ\n        are collapsed with `max`, so buffers are sized for the largest layer, and\n        the differing values are kept per layer. No field is named here: which\n        ones vary is whatever the checkpoint says.\n        \"\"\"\n        if not layers:\n            raise ValueError(\"a model must have at least one layer\")\n\n        merged: dict[str, Any] = {}\n        overrides: list[dict[str, Any]] = [{} for _ in layers]\n        for f in dataclass_fields(cls):\n            if f.name == \"per_layer_overrides\":\n                continue\n            values = [getattr(layer, f.name) for layer in layers]\n            if all(value == values[0] for value in values):\n                merged[f.name] = values[0]\n                continue\n            # `bool` is an `int`, so an exact type check is what keeps a varying\n            # flag from collapsing to `any`. `is_deepseek_mla` doing that would\n            # make `use_mla` true model wide, and `get_num_kv_heads` then returns\n            # 1 for every layer, discarding the overrides built here.\n            if not all(type(value) in (int, float) for value in values):\n                raise ValueError(\n                    f\"{f.name!r} varies across layers and has no whole-model \"\n                    f\"value: {sorted(set(map(repr, values)))}. Only numeric \"","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/vllm-project/vllm/blob/c794754062d49a8fdb63ab3c5215b488b865030c/vllm/config/model_arch.py#L90-L126","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Debug why zero layers were collected before from_layers is called (inspect the state dict keys / layer filter).","Guard the caller: skip construction or raise a clearer upstream error when the layer list is empty."],"exampleFix":"# before\nmodel_arch_cfg = ModelArchitectureConfig.from_layers(layer_cfgs)  # layer_cfgs == []\n# after\nassert layer_cfgs, 'no decoder layers found in checkpoint'\nmodel_arch_cfg = ModelArchitectureConfig.from_layers(layer_cfgs)","handlingStrategy":"validation","validationCode":"def build_from_layers(layer_cfgs):\n    if not layer_cfgs:\n        raise ValueError('checkpoint yielded zero layer configs; extraction is broken')\n    return ModelArchitectureConfig.from_layers(layer_cfgs)","typeGuard":"def has_layers(layers: list) -> bool:\n    return len(layers) > 0","tryCatchPattern":null,"preventionTips":["Never call from_layers without asserting a non-empty layer list.","Unit-test layer extraction against real checkpoint indexes so filters never match zero layers.","Treat an empty extraction result as a bug in the extractor, not a valid edge case."],"tags":["model-arch","internal-api","validation"],"backgroundTag":null,"analyzedSha":"c794754062d49a8fdb63ab3c5215b488b865030c","analyzedAt":"2026-08-14T21:17:39.825Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}