Repository: huggingface/transformers
Pull request: Timm backbone saves and loads `out_features`

Fixes https://github.com/huggingface/transformers/issues/43878

After the refactor we started saving `out_features` and `stage_names` in timm backbone config, because it now also inherits from `BackboneConfigMixin`. But the modeling code would throw an error if `out_features` is already set

This PR deletes the error and instead verifies that the loaded-back config values align with the values obtained from `timm`. Ideally it should always align unless the config was corrupted before being saved, e.g. manually changing fields to un-aligned values

Changed files:
filename: src/transformers/backbone_utils.py
patch: @@ -186,18 +186,33 @@ def _init_timm_backbone(self, backbone) -> None:
         Initialize the backbone model from timm. The backbone must already be loaded to backbone
         """
 
+        out_features_from_config = getattr(self.config, "out_features", None)
+        stage_names_from_config = getattr(self.config, "stage_names", None)
+
         # These will disagree with the defaults for the transformers models e.g. for resnet50
         # the transformer model has out_features = ['stem', 'stage1', 'stage2', 'stage3', 'stage4']
         # the timm model has out_features = ['act', 'layer1', 'layer2', 'layer3', 'layer4']
         self.stage_names = [stage["module"] for stage in backbone.feature_info.info]
         self.num_features = [stage["num_chs"] for stage in backbone.feature_info.info]
 
-        self.config._out_indices = list(backbone.feature_info.out_indices)
-        self.config._out_features = backbone.feature_info.module_name()
-        self.config.stage_names = self.stage_names
+        out_indices = list(backbone.feature_info.out_indices)
+        out_features = backbone.feature_info.module_name()
 
-        # We verify the out indices and out features are valid
-        self.config.verify_out_features_out_indices()
+        if out_features_from_config is not None and out_features_from_config != out_features:
+            raise ValueError(
+                f"Config has `out_features` set to {out_features_from_config} which doesn't match `out_features` "
+                "from backbone's feature_info. Please check if your checkpoint has correct out features/indices saved."
+            )
+
+        if stage_names_from_config is not None and stage_names_from_config != self.stage_names:
+            raise ValueError(
+                f"Config has `stage_names` set to {stage_names_from_config} which doesn't match `stage_names` "
+                "from backbone's feature_info. Please check if your checkpoint has correct `stage_names` saved."
+            )
+
+        # We set, align and verify out indices, out features and stage names
+        self.config.stage_names = self.stage_names
+        self.config.set_output_features_output_indices(out_features, out_indices)
 
     def _init_transformers_backbone(self) -> None:
         self.stage_names = self.config.stage_names

filename: src/transformers/models/timm_backbone/configuration_timm_backbone.py
patch: @@ -78,7 +78,6 @@ def __init__(
         self.output_stride = output_stride
         self.freeze_batch_norm_2d = freeze_batch_norm_2d
 
-        # self._out_features = kwargs.pop("out_features", None)
         super().__init__(**kwargs)
 
     @property

filename: src/transformers/models/timm_backbone/modeling_timm_backbone.py
patch: @@ -45,9 +45,6 @@ def __init__(self, config, **kwargs):
         if config.backbone is None:
             raise ValueError("backbone is not set in the config. Please set it to a timm model name.")
 
-        if hasattr(config, "out_features") and config.out_features is not None:
-            raise ValueError("out_features is not supported by TimmBackbone. Please use out_indices instead.")
-
         # We just take the final layer by default. This matches the default for the transformers models.
         out_indices = config.out_indices if getattr(config, "out_indices", None) is not None else (-1,)
         pretrained = kwargs.pop("pretrained", False)