main
py 141 lines 5.64 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from copy import copy
4 from dataclasses import fields as dataclass_fields
5 from typing import Any
6
7 from pydantic import ConfigDict
8 from pydantic.dataclasses import dataclass
9
10 from vllm.logger import init_logger
11
12 logger = init_logger(__name__)
13
14
15 @dataclass(config=ConfigDict(arbitrary_types_allowed=True))
16 class ModelArchitectureConfig:
17 """Configuration for model architecture that required by vLLM runtime."""
18
19 architectures: list[str]
20 """List of model architecture class names (e.g., ['LlamaForCausalLM']).
21 It can be None upon calling `vllm_config.with_hf_config(config.text_config)`"""
22
23 model_type: str
24 """Model type identifier (e.g., 'llama', 'gpt_oss')."""
25
26 text_model_type: str | None
27 """Text model type identifier (e.g., 'llama4_text')."""
28
29 hidden_size: int
30 """Hidden size of the model."""
31
32 total_num_hidden_layers: int
33 """Number of hidden layers in the model."""
34
35 total_num_attention_heads: int
36 """Number of attention heads in the model."""
37
38 head_size: int
39 """Head dimension of the model."""
40
41 vocab_size: int
42 """Vocabulary size of the model."""
43
44 total_num_kv_heads: int
45 """Number of key value heads in the model."""
46
47 num_experts: int
48 """Number of experts in the model."""
49
50 num_experts_per_token: int
51 """Number of routed experts selected per token."""
52
53 quantization_config: dict[str, Any] | None
54 """Quantization configuration dictionary containing quantization parameters."""
55
56 is_deepseek_mla: bool
57 """Whether the model is a DeepSeek MLA model."""
58
59 is_mm_prefix_lm: bool
60 """Whether the model uses image bidirectional attention."""
61
62 rswa_window: int | None
63 """Reference Sliding Window Attention window size (None disables R-SWA)."""
64
65 derived_max_model_len_and_key: tuple[float, str | None]
66 """Derived maximum model length and key from the hf config."""
67
68 per_layer_overrides: "list[dict[str, Any]] | None" = None
69 """Per-layer values for the fields that vary, `None` unless some field does.
70
71 One dict per layer, holding only the fields whose value differs from the
72 whole-model value above. Everything else is read from the whole-model config,
73 so later edits to it are visible through `self[layer_idx]`."""
74
75 def __getitem__(self, layer_idx: int) -> "ModelArchitectureConfig":
76 """ModelArchitectureConfig for a specific layer.
77
78 Returns `self` when no field varies by layer, so callers never need to
79 branch on heterogeneity. Mirrors `PreTrainedConfig.per_layer_config[i]`.
80 """
81 if layer_idx < 0:
82 # Homogeneous configs would ignore a negative index and heterogeneous
83 # ones would wrap, so an off-by-one would only show up on the models
84 # this exists for. Reject it for both.
85 raise IndexError(f"layer index must not be negative, got {layer_idx}")
86 if self.per_layer_overrides is None:
87 return self
88 layer = copy(self)
89 object.__setattr__(layer, "per_layer_overrides", None)
90 for name, value in self.per_layer_overrides[layer_idx].items():
91 object.__setattr__(layer, name, value)
92 return layer
93
94 @classmethod
95 def from_layers(
96 cls, layers: "list[ModelArchitectureConfig]"
97 ) -> "ModelArchitectureConfig":
98 """Whole-model config for a checkpoint whose layers differ.
99
100 Fields that agree across layers are taken as they are. Fields that differ
101 are collapsed with `max`, so buffers are sized for the largest layer, and
102 the differing values are kept per layer. No field is named here: which
103 ones vary is whatever the checkpoint says.
104 """
105 if not layers:
106 raise ValueError("a model must have at least one layer")
107
108 merged: dict[str, Any] = {}
109 overrides: list[dict[str, Any]] = [{} for _ in layers]
110 for f in dataclass_fields(cls):
111 if f.name == "per_layer_overrides":
112 continue
113 values = [getattr(layer, f.name) for layer in layers]
114 if all(value == values[0] for value in values):
115 merged[f.name] = values[0]
116 continue
117 # `bool` is an `int`, so an exact type check is what keeps a varying
118 # flag from collapsing to `any`. `is_deepseek_mla` doing that would
119 # make `use_mla` true model wide, and `get_num_kv_heads` then returns
120 # 1 for every layer, discarding the overrides built here.
121 if not all(type(value) in (int, float) for value in values):
122 raise ValueError(
123 f"{f.name!r} varies across layers and has no whole-model "
124 f"value: {sorted(set(map(repr, values)))}. Only numeric "
125 f"fields collapse (with `max`, to size buffers for the "
126 f"largest layer); give this one an explicit rule in "
127 f"ModelArchitectureConfig.from_layers."
128 )
129 merged[f.name] = max(values)
130 for override, value in zip(overrides, values):
131 if value != merged[f.name]:
132 override[f.name] = value
133
134 if len(layers) != merged["total_num_hidden_layers"]:
135 raise ValueError(
136 f"got {len(layers)} per-layer configs for a model with "
137 f"{merged['total_num_hidden_layers']} layers"
138 )
139 # A checkpoint can be heterogeneous over attributes vLLM never reads, in
140 # which case there is nothing to keep the layers apart for.
141 return cls(**merged, per_layer_overrides=overrides if any(overrides) else None)