Add heterogeneous config support (per-layer configuration)#45333
Conversation
|
@askliar |
ArthurZucker
left a comment
There was a problem hiding this comment.
Reviewed this first: #45332 (review) !
I think we want stuff tto be explicit and "simple".
We do have 2 choices basically:
-
per_layer_config: List[PreTrainedConfig]
-
per_layer_hidden_size: List[int]
-
Per layer configs would be quite simple, we find a way to have a minimal serialization to on serialize the actual per layer. I like that a bit less? But its the most convenient for modeling changes / inheritance
-
With this, we can have
PreTrainedConfigjust init the per-layer-configs based on the lists. This means nice to understand serialization, and you just parse theper_layer_<key>intoConfig(<key> = value).
WDYT?
|
Thanks @ArthurZucker! I agree with the explicit and simple direction. The code now keeps serialization minimal, and exposes full per-layer configs:
So we keep the config file simple and minimal for both input and serialization, while the instantiated config object provides convenient and intuitive per-layer access. |
ArthurZucker
left a comment
There was a problem hiding this comment.
Nice! Missing a tad but of doc / md example, and appart from that if we have a mixin would make it more isolated / we avoid polluting the main configuration_utils.py with more atomic calls!
Looks nice and simple. I would document the reasoning behind: "its a power feature and we'll thrive to merge arch that have this explicitly, but this allows modularity with little to no cost at tleast in the config side!"
something like this
| for key in self.__dict__: | ||
| if self.is_heterogeneous: | ||
| # Per-layer attributes intentionally raise on direct access and should not be exposed by iteration, | ||
| # unless `allow_global_per_layer_attribute_access` is True | ||
| if not self.allow_global_per_layer_attribute_access and key in self.per_layer_attributes: | ||
| continue |
There was a problem hiding this comment.
arf not a super fan of this!
There was a problem hiding this comment.
Yeah, me neither, but I didn't find a cleaner path here
| @property | ||
| def is_heterogeneous(self) -> bool: | ||
| return hasattr(self, "_heterogeneity_spec") | ||
|
|
||
| @property | ||
| def per_layer_config(self) -> Sequence["PreTrainedConfig"] | None: | ||
| if not self.is_heterogeneous: | ||
| return None | ||
| return get_per_layer_config(self) | ||
|
|
||
| @per_layer_config.setter | ||
| def per_layer_config(self, per_layer_config: dict[int | str, dict[str, Any]] | None) -> None: | ||
| if per_layer_config is None: | ||
| delattr(self, "_heterogeneity_spec") | ||
| return | ||
|
|
||
| apply_heterogeneous_config(self, per_layer_config) | ||
|
|
||
| @property | ||
| def per_layer_attributes(self) -> set[str] | None: | ||
| if not self.is_heterogeneous: | ||
| return None | ||
| return self._heterogeneity_spec.per_layer_attributes |
There was a problem hiding this comment.
motivates me to put a HeterogenousMixin, upon init sets itself to is_heterogenous if we detect per layer config -> then you unlock the different properties etc. If not is_heterogenous then you don't see this?
WDYT I hope its not a version you already had. its easier to be looking at the code like this!
There was a problem hiding this comment.
Done!
To really make these properties invisible for non-heterogeneous configs would be pretty dirty and require dynamic attribute hiding, but I think using the mixin already makes it a lot cleaner
ArthurZucker
left a comment
There was a problem hiding this comment.
IDK if there is a way to disable the function of the inherited class / enable based on has_heterogenous_specs. If possible it would simplify a lot because we could overwrite all
| # HeterogeneousConfigMixin: move heterogeneity-specific kwargs into the constructor config dict. | ||
| cls._update_config_dict_with_heterogeneous_kwargs(config_dict, kwargs) | ||
|
|
There was a problem hiding this comment.
when you init, via MRO can't this be done in init (Maybe order is wrong?)
There was a problem hiding this comment.
You are right, I was able to remove it so from_dict is now untouched.
I did add allow_global_per_layer_attribute_access and serialize_explicit_per_layer_config as properties on the mixin, so behavior stays consistent whether they are passed through kwargs or included in the config_dict.
| # HeterogeneousConfigMixin: keys of `self.__dict__` that are per-layer attributes | ||
| # may require hiding when using a heterogeneous config. | ||
| yield from self._iter_config_keys_with_heterogeneous_adjustment(self.__dict__) |
There was a problem hiding this comment.
ah its hiding if not used?
There was a problem hiding this comment.
Since by default we don't want per-layer attributes to be accessed from the global config, we hide them when enumerating the keys on the config. For a regular config this is a passthrough, identical to main
|
I still have a few concerns about injection of heterogenous checks etc but its not blocking anymore IMO |
|
Thanks for the review @ArthurZucker! Regarding disabling the function of the inherited class / enable based on Overwriting the inherited methods entirely to keep |
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
CI recapDashboard: View test results in Grafana |
What does this PR do?
Adds heterogeneous model support - the ability for individual layers to differ from the global config (e.g., different
intermediate_size,num_key_value_heads) and to skip sub-modules entirely (MLP, attention, etc.). This enables models where layers are not uniform, as in pruned, distilled, or NAS-derived architectures.Examples of such models:
These models previously required
trust_remote_code=Trueto modify the classes of the model they are derived from. With this PR (and the follow-up modeling PR #45332), heterogeneous support requires just a few lines.This PR contains the configuration layer only. The modeling, cache, and masking changes that consume this config are in #45332.
How it works
Configuration (
per_layer_config)A new
per_layer_configparameter onPreTrainedConfigmaps layer indices to attribute overrides:Under the hood,
apply_heterogeneous_configvalidates the overrides, computes fallback values, and stores aHeterogeneitySpecon the config. Serialization keepsper_layer_configas a sparse mapping of layer indices to changed attributes only. At runtime,config.per_layer_config[i]returns a full layer config built from the current global config plus that layer's overrides. When a per-layer override matches the global config value, it is omitted from the serialized per_layer_config so the sparse config only records real differences. The config supports fullsave_pretrained/from_pretrainedround-trips (keys are zero-padded for correct JSON sort order).Accessing a per-layer attribute on the global config raises
AttributeErrorand points callers toconfig.per_layer_config[i], since the global value can be misleading. Callers can setallow_global_per_layer_attribute_access=Trueto allowconfig.<per_layer_attribute>access, but it will output awarning_oncemessage.Key changes
src/transformers/heterogeneity/package - configuration utilities (HeterogeneitySpec, validation, runtime per-layer config view, serialization)configuration_utils.py-per_layer_configproperty/view,is_heterogeneous, serialization hooks, and__getattribute__guard for per-layer attributesTests
Test suite in
tests/heterogeneity/test_configuration_utils.pycovering per-layer overrides and fallback, skip normalization, validation, global per-layer attribute access, and save/load round-trip.Who can review?
@ArthurZucker
@hmellor