逐层配置
某些检查点是非同质的(并非各层结构一致)。例如,某一层的 MLP 规模较小,另一层的键值头数量较少,或者特定层采用了不同的布局,这意味着单一的全局配置无法准确描述整个模型堆栈。
在编写或检查配置时,以及当模型代码将使用这些差异时,请使用 PreTrainedConfig 上的 per_layer_config 来记录这些差异。每个条目仅存储与全局配置不同的部分,其余部分则继承自全局配置。
异构配置是一项强大的功能。如果异构布局成为一种常见或主流架构,我们将努力在架构实现中对其进行显式建模,而不是依赖
per_layer_config。当存在显式架构时,请优先选择该架构。
以下模型属于异构检查点。堆栈中的层并非均匀一致。它们采用专用架构,使用 block_configs 和自身的 model_type,而非在标准架构上使用 per_layer_config。
定义逐层覆盖
将 per_layer_config 作为从层索引到属性覆盖的映射传递给 LlamaConfig。层索引从零开始。只需指定与全局配置不同的属性。
per_layer_config 负责记录并解析配置值。它本身并不会改变模型所创建的模块,也不会影响这些模块的运行方式。要应用大小覆盖或 skip,需要模型代码在构建或运行每一层时读取解析后的逐层配置。当前的 LlamaModel 使用全局配置来构建每一个 LlamaDecoderLayer,因此下文中的 skip 条目仍然是配置值,并不会在 from_pretrained 之后移除相应的模块。
以下示例记录了四个层的覆盖设置:第 5 层使用较小的 MLP,第 11 层使用更少的键值头,而第 23 层和第 27 层则记录了支持该架构的 skip 值。
from transformers import LlamaConfig
config = LlamaConfig(
hidden_size=4096,
intermediate_size=14336,
num_hidden_layers=32,
num_attention_heads=32,
num_key_value_heads=8,
per_layer_config={
# Use a smaller MLP in one layer.
5: {"intermediate_size": 8192},
# Use fewer key-value heads in another layer.
11: {"num_key_value_heads": 4},
# Record a request to skip the MLP in architectures that support it.
23: {"skip": ["mlp"]},
# Record a request to skip attention in architectures that support it.
27: {"skip": ["attention"]},
},
)架构可以跳过的子模块(例如 "mlp" 和 "attention")是按架构定义的。skip 接受一个列表,因此一个层可以记录多个子模块覆盖。
访问 config.per_layer_config[layer_idx] 会返回一个已解析的层配置。该已解析配置将全局配置与该层的覆盖项合并在一起。
# Layer 0 does not define overrides, so it inherits the global values.
config.per_layer_config[0].intermediate_size
# 14336
config.per_layer_config[0].num_key_value_heads
# 8
# Layer 5 overrides the MLP intermediate size.
config.per_layer_config[5].intermediate_size
# 8192
# Layer 11 overrides the number of key-value heads.
config.per_layer_config[11].num_key_value_heads
# 4
# Layer 23 records an MLP skip.
config.per_layer_config[23].skip
# ["mlp"]
# Layer 27 records an attention skip.
config.per_layer_config[27].skip
# ["attention"]使用 per_layer_config 的配置与其他配置一样,支持相同的 save_pretrained() 和 from_pretrained() 往返流程。
每种架构都在其代码中定义了它在层级别上会消费哪些属性。per_layer_config 提供了记录这些层级别差异并将其与全局配置进行解析的机制。
全局属性访问
具有逐层覆盖的属性没有单一的模型级值。num_key_value_heads 在大多数层中可能为 8,而在特定层中为 4。在层上下文之外读取 config.num_key_value_heads 会产生歧义。
默认情况下,该访问会引发 AmbiguousGlobalPerLayerAttributeError,并引导你查看 config.per_layer_config[layer_idx]。该属性在全局配置中仍然存在,因此这并非 AttributeError。在没有层索引的情况下读取该全局值仍然是错误的。如果代码基于全局的 num_key_value_heads 来构建键值缓存,则会错误地计算错误层的尺寸。
仅当您确实需要全局回退机制且能够处理异构配置时,才设置 allow_global_per_layer_attribute_access=True。此时将允许全局访问,并会发出一次警告。
config = LlamaConfig(
hidden_size=4096,
intermediate_size=14336,
num_hidden_layers=32,
num_attention_heads=32,
num_key_value_heads=8,
allow_global_per_layer_attribute_access=True,
per_layer_config={
11: {"num_key_value_heads": 4},
},
)
config.num_key_value_heads
# 8
# Emits a one-time warning because num_key_value_heads has a per-layer override.序列化
per_layer_config 默认采用稀疏序列化方式,未进行覆盖的层会被省略。与全局值相同的被覆盖属性同样会被省略。
from transformers import LlamaConfig
config = LlamaConfig(
hidden_size=4096,
intermediate_size=14336,
num_hidden_layers=4,
num_attention_heads=32,
num_key_value_heads=8,
per_layer_config={
0: {"num_key_value_heads": 8},
2: {"num_key_value_heads": 4},
},
)
config.to_dict()["per_layer_config"]
# {"2": {"num_key_value_heads": 4}}设置 serialize_explicit_per_layer_config=True 以在 per_layer_config 中为每个层包含所表示的属性。当某些值仍与全局配置一致时,这使得检查层布局变得更加容易。
explicit_config = LlamaConfig(
hidden_size=4096,
intermediate_size=14336,
num_hidden_layers=4,
num_attention_heads=32,
num_key_value_heads=8,
serialize_explicit_per_layer_config=True,
per_layer_config={
0: {"num_key_value_heads": 8},
2: {"num_key_value_heads": 4},
},
)
serialized_per_layer_config = explicit_config.to_dict()["per_layer_config"]
serialized_per_layer_config
# {
# "0": {"num_key_value_heads": 8},
# "1": {"num_key_value_heads": 8},
# "2": {"num_key_value_heads": 4},
# "3": {"num_key_value_heads": 8},
# }使用稀疏序列化以获得紧凑的配置,并在需要完整的逐层布局以提升可读性或便于工具处理时,使用显式序列化。
Update on GitHub