Files
wehub-resource-sync e06fe8e8c6
Secret Leaks / trufflehog (push) Failing after 1s
Build documentation / build (push) Failing after 1s
Build documentation / build_other_lang (push) Failing after 0s
CodeQL Security Analysis / CodeQL Analysis (push) Failing after 0s
PR CI / pr-ci (push) Failing after 1s
Slow tests on important models (on Push - A10) / Get all modified files (push) Failing after 1s
Slow tests on important models (on Push - A10) / Model CI (push) Has been skipped
Self-hosted runner (benchmark) / Benchmark (aws-g5-4xlarge-cache) (push) Has been cancelled
New model PR merged notification / Notify new model (push) Has been cancelled
Update Transformers metadata / build_and_package (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:57:37 +08:00

363 lines
17 KiB
Python

# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import contextlib
import tempfile
import unittest
from parameterized import parameterized
from transformers import LlamaConfig
from transformers.integrations.heterogeneity import AmbiguousGlobalPerLayerAttributeError
from transformers.utils import logging as transformers_logging
# ──────────────────────────────────────────────────────────────────────
# Tiny config factories
# ──────────────────────────────────────────────────────────────────────
def _tiny_llama_config(per_layer_config=None, **overrides):
defaults = {
"hidden_size": 64,
"intermediate_size": 128,
"num_hidden_layers": 4,
"num_attention_heads": 4,
"num_key_value_heads": 4,
"head_dim": 16,
"vocab_size": 32,
"max_position_embeddings": 64,
**overrides,
}
return LlamaConfig(per_layer_config=per_layer_config, **defaults)
# ──────────────────────────────────────────────────────────────────────
# Tests: Config
# ──────────────────────────────────────────────────────────────────────
class TestHeterogeneousConfig(unittest.TestCase):
def test_per_layer_config_skip_normalization(self):
config = _tiny_llama_config(per_layer_config={1: {"skip": ["mlp", "attention"]}, 2: {"skip": []}})
self.assertEqual(config.to_dict()["per_layer_config"], {"1": {"skip": ["attention", "mlp"]}})
self.assertEqual(config.per_layer_config[0].skip, [])
self.assertEqual(config.per_layer_config[1].skip, ["attention", "mlp"])
self.assertEqual(config.per_layer_config[2].skip, [])
set_config = _tiny_llama_config(per_layer_config={1: {"skip": {"attention"}}})
self.assertEqual(set_config.to_dict()["per_layer_config"], {"1": {"skip": ["attention"]}})
self.assertEqual(set_config.per_layer_config[1].skip, ["attention"])
no_skip_config = _tiny_llama_config(per_layer_config={1: {"intermediate_size": 96}})
self.assertNotIn("skip", no_skip_config.to_dict()["per_layer_config"]["1"])
self.assertEqual(no_skip_config.per_layer_config[1].skip, [])
@parameterized.expand(
[
("string", "attention"),
("non_string_item", ["attention", 1]),
]
)
def test_per_layer_config_invalid_skip_raises(self, _name, invalid_skip):
with self.assertRaises(TypeError):
_tiny_llama_config(per_layer_config={0: {"skip": invalid_skip}})
def test_per_layer_config_string_indices_are_normalized(self):
config = _tiny_llama_config(per_layer_config={"01": {"num_key_value_heads": 2}})
self.assertEqual(config.to_dict()["per_layer_config"], {"1": {"num_key_value_heads": 2}})
self.assertEqual(config.per_layer_config[1].num_key_value_heads, 2)
def test_per_layer_config_and_fallback(self):
"""Per-layer values should override, and non-overridden layers should fall back to global."""
config = _tiny_llama_config(per_layer_config={1: {"num_key_value_heads": 2}, 3: {"num_key_value_heads": 1}})
self.assertTrue(config.is_heterogeneous)
self.assertEqual(config.per_layer_attributes, {"num_key_value_heads"})
# Per-layer configs
self.assertEqual(config.per_layer_config[1].num_key_value_heads, 2)
self.assertEqual(config.per_layer_config[3].num_key_value_heads, 1)
# Fallback to original global value
self.assertEqual(config.per_layer_config[0].num_key_value_heads, 4)
# Other attributes are unaffected
self.assertEqual(config.per_layer_config[0].hidden_size, 64)
def test_per_layer_config_reassignment_uses_existing_global_fallback(self):
config = _tiny_llama_config(per_layer_config={0: {"num_key_value_heads": 2}})
config.per_layer_config = {1: {"num_key_value_heads": 1}}
self.assertEqual(config.to_dict()["per_layer_config"], {"1": {"num_key_value_heads": 1}})
self.assertEqual(config.per_layer_config[0].num_key_value_heads, 4)
self.assertEqual(config.per_layer_config[1].num_key_value_heads, 1)
def test_per_layer_values_matching_global_are_removed_from_sparse_config(self):
config = _tiny_llama_config(
per_layer_config={
0: {"num_key_value_heads": 4},
1: {"num_key_value_heads": 2},
2: {"num_key_value_heads": 4},
}
)
self.assertEqual(config.to_dict()["per_layer_config"], {"1": {"num_key_value_heads": 2}})
self.assertEqual(config.per_layer_attributes, {"num_key_value_heads"})
self.assertEqual(config.per_layer_config[0].num_key_value_heads, 4)
self.assertEqual(config.per_layer_config[1].num_key_value_heads, 2)
self.assertEqual(config.per_layer_config[2].num_key_value_heads, 4)
def test_uniform_per_layer_values_do_not_overwrite_global(self):
per_layer = {layer_idx: {"num_key_value_heads": 2} for layer_idx in range(4)}
config = _tiny_llama_config(per_layer_config=per_layer)
self.assertEqual(object.__getattribute__(config, "num_key_value_heads"), 4)
self.assertEqual(
config.to_dict()["per_layer_config"],
{str(i): {"num_key_value_heads": 2} for i in range(4)},
)
for layer_idx in range(4):
self.assertEqual(config.per_layer_config[layer_idx].num_key_value_heads, 2)
def test_explicit_serialization_restores_pruned_global_values(self):
per_layer = {layer_idx: {"num_key_value_heads": 4} for layer_idx in range(4)}
sparse_config = _tiny_llama_config(per_layer_config=per_layer)
explicit_config = _tiny_llama_config(
per_layer_config=per_layer,
serialize_explicit_per_layer_config=True,
)
self.assertEqual(sparse_config.to_dict()["per_layer_config"], {})
self.assertEqual(sparse_config.per_layer_attributes, set())
self.assertEqual(
explicit_config.to_dict()["per_layer_config"],
{str(i): {"num_key_value_heads": 4} for i in range(4)},
)
def test_per_layer_config_reflects_current_global_config_state(self):
config = _tiny_llama_config(per_layer_config={0: {"intermediate_size": 64}})
# PreTrainedModel.__init__ updates this after config construction.
config._attn_implementation_internal = "sdpa"
config.hidden_size = 96
config.intermediate_size = 192
self.assertIs(type(config.per_layer_config[0]), type(config))
self.assertFalse(config.per_layer_config[0].is_heterogeneous)
self.assertIsNone(config.per_layer_config[0].per_layer_config)
self.assertEqual(config.per_layer_config[0]._attn_implementation, "sdpa")
self.assertEqual(config.per_layer_config[1]._attn_implementation, "sdpa")
self.assertEqual(config.per_layer_config[0].hidden_size, 96)
self.assertEqual(config.per_layer_config[1].hidden_size, 96)
self.assertEqual(config.per_layer_config[0].intermediate_size, 64)
self.assertEqual(config.per_layer_config[1].intermediate_size, 192)
layer_dict = config.per_layer_config[0].to_dict()
self.assertNotIn("per_layer_config", layer_dict)
self.assertEqual(layer_dict["hidden_size"], 96)
self.assertEqual(layer_dict["intermediate_size"], 64)
def test_accessing_per_layer_attr_raises(self):
config = _tiny_llama_config(per_layer_config={0: {"num_key_value_heads": 2}, 1: {"num_key_value_heads": 1}})
with self.assertRaisesRegex(
AmbiguousGlobalPerLayerAttributeError, "allow_global_per_layer_attribute_access.*global value incorrectly"
):
_ = config.num_key_value_heads
def test_ambiguous_global_attr_access_is_not_treated_as_missing(self):
config = _tiny_llama_config(per_layer_config={0: {"num_key_value_heads": 2}, 1: {"num_key_value_heads": 1}})
self.assertIn("num_key_value_heads", config.__dict__)
with self.assertRaises(AmbiguousGlobalPerLayerAttributeError):
getattr(config, "num_key_value_heads", "default")
with self.assertRaises(AmbiguousGlobalPerLayerAttributeError):
hasattr(config, "num_key_value_heads")
def test_allow_global_per_layer_attribute_access(self):
config = _tiny_llama_config(
per_layer_config={0: {"num_key_value_heads": 2}, 1: {"num_key_value_heads": 1}},
allow_global_per_layer_attribute_access=True,
)
logger = transformers_logging.get_logger("transformers.integrations.heterogeneity.configuration_utils")
logger.warning_once.cache_clear()
with self.assertLogs(logger=logger, level="WARNING") as logs:
self.assertEqual(config.num_key_value_heads, 4)
self.assertIn("Reading global config value for per-layer attribute `num_key_value_heads`", logs.output[0])
self.assertEqual(config.per_layer_config[0].num_key_value_heads, 2)
self.assertEqual(config.per_layer_config[1].num_key_value_heads, 1)
self.assertEqual(config.per_layer_config[2].num_key_value_heads, 4)
def test_flags_are_applied_from_pretrained_kwargs(self):
"""The flags are properties, so `from_dict`'s kwargs handling applies them like `per_layer_config`."""
with tempfile.TemporaryDirectory() as tmpdir:
_tiny_llama_config().save_pretrained(tmpdir)
config = LlamaConfig.from_pretrained(
tmpdir,
per_layer_config={1: {"num_key_value_heads": 2}},
allow_global_per_layer_attribute_access=True,
serialize_explicit_per_layer_config=True,
)
self.assertEqual(config.num_key_value_heads, 4)
self.assertEqual(
config.to_dict()["per_layer_config"],
{str(i): {"num_key_value_heads": 2 if i == 1 else 4} for i in range(4)},
)
# The property defaults must not leak into the serialization of configs that never set the flags.
self.assertNotIn("allow_global_per_layer_attribute_access", _tiny_llama_config().to_dict())
def test_non_per_layer_attributes_do_not_warn(self):
config = _tiny_llama_config(per_layer_config={0: {"num_key_value_heads": 2}, 1: {"num_key_value_heads": 1}})
logger = transformers_logging.get_logger("transformers.integrations.heterogeneity.configuration_utils")
logger.warning_once.cache_clear()
with self.assertNoLogs(logger=logger, level="WARNING"):
self.assertEqual(config.hidden_size, 64)
def test_iter_skips_per_layer_attributes_by_default(self):
config = _tiny_llama_config(per_layer_config={0: {"num_key_value_heads": 2}, 1: {"num_key_value_heads": 1}})
keys = list(config)
self.assertNotIn("num_key_value_heads", keys)
self.assertIn("hidden_size", keys)
def test_iter_includes_per_layer_attributes_when_global_access_allowed(self):
config = _tiny_llama_config(
per_layer_config={0: {"num_key_value_heads": 2}, 1: {"num_key_value_heads": 1}},
allow_global_per_layer_attribute_access=True,
)
self.assertIn("num_key_value_heads", list(config))
def test_validation_missing_global_attr(self):
# "fake_attr" in layer 0 but not in layer 1, and not global → should fail
with self.assertRaises(ValueError):
_tiny_llama_config(
per_layer_config={
0: {"fake_attr": 42, "intermediate_size": 64},
1: {"intermediate_size": 96},
}
)
@parameterized.expand(
[
("negative", -1),
("too_large", 4),
]
)
def test_validation_layer_idx_out_of_range(self, _name, layer_idx):
with self.assertRaises(ValueError):
_tiny_llama_config(per_layer_config={layer_idx: {"num_key_value_heads": 2}})
def test_save_pretrained_config_round_trip(self):
"""Config should survive save_pretrained → from_pretrained on disk."""
per_layer = {i: {"intermediate_size": 64 + i} for i in range(0, 12, 2)}
config = _tiny_llama_config(per_layer_config=per_layer, num_hidden_layers=12)
# Keys are zero-padded so they sort numerically in JSON (0,1,...,10 not 0,1,10,2,...)
d = config.to_dict()
self.assertEqual(list(d["per_layer_config"].keys()), sorted(d["per_layer_config"].keys()))
with tempfile.TemporaryDirectory() as tmpdir:
config.save_pretrained(tmpdir)
loaded = LlamaConfig.from_pretrained(tmpdir)
self.assertTrue(loaded.is_heterogeneous)
for i in range(config.num_hidden_layers):
self.assertEqual(
config.per_layer_config[i].intermediate_size,
loaded.per_layer_config[i].intermediate_size,
)
@parameterized.expand(
[
(
"global_sw_global_acs",
{"sliding_window": 4096, "attention_chunk_size": 2048},
{0: {"intermediate_size": 64}},
True,
),
("global_sw_per_layer_acs", {"sliding_window": 4096}, {0: {"attention_chunk_size": 2048}}, True),
(
"per_layer_sw_per_layer_acs_same_layer",
{},
{0: {"sliding_window": 4096, "attention_chunk_size": 2048}},
True,
),
(
"per_layer_sw_per_layer_acs_different_layers",
{"sliding_window": None, "attention_chunk_size": None},
{0: {"sliding_window": 4096}, 1: {"attention_chunk_size": 2048}},
False,
),
(
"global_conflict_resolved_by_per_layer_override",
{"sliding_window": 4096, "attention_chunk_size": 2048},
{
0: {"sliding_window": None},
1: {"sliding_window": None},
2: {"attention_chunk_size": None},
3: {"attention_chunk_size": None},
},
False,
),
],
)
def test_validation_sliding_window_and_attention_chunk_size(
self, _name, overrides, per_layer_config, should_raise
):
ctx = self.assertRaises(ValueError) if should_raise else contextlib.nullcontext()
with ctx:
_tiny_llama_config(per_layer_config=per_layer_config, **overrides)
def test_all_layers_overridden_no_global_default(self):
"""Custom attribute on every layer without a global default should be accessible per layer."""
config = _tiny_llama_config(
per_layer_config={
0: {"custom_attr": 10},
1: {"custom_attr": 20},
2: {"custom_attr": 30},
3: {"custom_attr": 40},
},
)
self.assertTrue(config.is_heterogeneous)
self.assertEqual(config.per_layer_config[0].custom_attr, 10)
self.assertEqual(config.per_layer_config[1].custom_attr, 20)
self.assertEqual(config.per_layer_config[2].custom_attr, 30)
self.assertEqual(config.per_layer_config[3].custom_attr, 40)
def test_per_layer_config_can_serialize_explicit_layer_overrides(self):
sparse_config = _tiny_llama_config(per_layer_config={0: {"num_key_value_heads": 1}})
explicit_config = _tiny_llama_config(
per_layer_config={0: {"num_key_value_heads": 1}},
serialize_explicit_per_layer_config=True,
)
explicit_config.num_key_value_heads = 8
explicit_config_dict = explicit_config.to_dict()
self.assertEqual(sparse_config.to_dict()["per_layer_config"], {"0": {"num_key_value_heads": 1}})
self.assertEqual(
explicit_config_dict["per_layer_config"],
{
"0": {"num_key_value_heads": 1},
"1": {"num_key_value_heads": 8},
"2": {"num_key_value_heads": 8},
"3": {"num_key_value_heads": 8},
},
)