huggingface/transformers · #49544

Add `MtpModel` helpers for frameworks that own the drafting loop

hmellor · merged Oct 14, 20261 files · 76 + / 37 −
src/transformers/modeling_layers.py76 + / 37 −
@@ -44,6 +44,7 @@ if TYPE_CHECKING:     from .cache_utils import MtpCache     from .configuration_utils import PreTrainedConfig+    from .core_model_loading import WeightTransform     from .generation.logits_process import LogitsProcessorList  @@ -502,27 +503,10 @@ def forward(         drafted_logits = []         drafted_tokens = []         loss = None-        for i, mtp_layer in enumerate(self.layers):-            # We need to recompute those every layer since they change-            inputs_embeds = self.embed_tokens(input_ids).to(last_hidden_states.device)-            position_embeddings = (-                self.rotary_emb(inputs_embeds, position_ids=position_ids) if self.rotary_emb is not None else None-            )--            # In full generality, we may need to recompute masks for every layer due to the position offset of each layer-            masks = self.create_masks_for_mtp_layer(i, inputs_embeds, mtp_cache, position_ids)--            last_hidden_states = mtp_layer(-                inputs_embeds,-                last_hidden_states,-                position_embeddings=position_embeddings,-                position_ids=position_ids,-                past_key_values=mtp_cache,-                **masks,-                **kwargs,+        for i in range(len(self.layers)):+            last_hidden_states = self.forward_layer(+                i, last_hidden_states, position_ids, mtp_cache, input_ids=input_ids, **kwargs             )-            if self.use_shared_post_norm:-                last_hidden_states = self.shared_post_norm(last_hidden_states)              # If we are not computing the loss, only compute logits for the next drafted token to save memory             slice_indices = slice(-1, None) if labels is None else slice(None, None)@@ -563,13 +547,53 @@ def forward(         candidate_logits = torch.cat(drafted_logits, dim=1)         return new_candidate_ids, candidate_logits, loss -    @classmethod-    def from_pretrained(cls, main_model: PreTrainedModel, device_map=None, **kwargs) -> MtpModel:-        pretrained_model_name_or_path = main_model.config.name_or_path+    def forward_layer(+        self,+        layer_idx: int,+        last_hidden_states: torch.Tensor,+        position_ids: torch.Tensor | None,+        mtp_cache: MtpCache | None,+        input_ids: torch.Tensor | None = None,+        inputs_embeds: torch.Tensor | None = None,+        **kwargs,+    ) -> torch.Tensor:+        """+        Run the mtp layer `layer_idx` once, i.e. a single step of the drafting loop in `forward`. Exactly one of `input_ids`+        or `inputs_embeds` should be passed. This lets frameworks which own the drafting loop (e.g. vLLM) reuse the per-layer+        logic.+        """+        if (input_ids is None) == (inputs_embeds is None):+            raise ValueError("You must specify exactly one of input_ids or inputs_embeds")+        # We need to recompute those every layer since they change+        if inputs_embeds is None:+            inputs_embeds = self.embed_tokens(input_ids).to(last_hidden_states.device)++        # In full generality, we may need to recompute masks for every layer due to the position offset of each layer+        masks = self.create_masks_for_mtp_layer(layer_idx, inputs_embeds, mtp_cache, position_ids)++        position_embeddings = (+            self.rotary_emb(inputs_embeds, position_ids=position_ids) if self.rotary_emb is not None else None+        )+        last_hidden_states = self.layers[layer_idx](+            inputs_embeds,+            last_hidden_states,+            position_embeddings=position_embeddings,+            position_ids=position_ids,+            past_key_values=mtp_cache,+            **masks,+            **kwargs,+        )+        if self.use_shared_post_norm:+            last_hidden_states = self.shared_post_norm(last_hidden_states)+        return last_hidden_states++    @staticmethod+    def get_mtp_weights_regex(main_model: PreTrainedModel) -> re.Pattern:+        """Regex matching the checkpoint keys of the mtp weights of `main_model`."""         num_hidden_layers = main_model.config.get_text_config().num_hidden_layers         # Heuristic: the main model should have the mtp layer patterns under `_keys_to_ignore_on_load_unexpected` to avoid         # loading them by default, so use it to later load the correct keys from the checkpoints-        mtp_patterns = main_model._keys_to_ignore_on_load_unexpected.copy()  # type: ignore+        mtp_patterns = main_model._keys_to_ignore_on_load_unexpected         # Due to different released checkpoints, only keep the ones with layer number >= num_hidden_layers - otherwise         # mtp layers in a smaller checkpoints could be wrongly added as a 2nd mtp layer of a bigger checkpoint         final_mtp_patterns = []@@ -580,7 +604,33 @@ def from_pretrained(cls, main_model: PreTrainedModel, device_map=None, **kwargs)             final_mtp_patterns.append(pattern)         if len(final_mtp_patterns) == 0:             raise ValueError(f"{main_model.__class__.__name__} does not seem to register any known MTP layer patterns")-        mtp_regex = re.compile("|".join(rf"({pattern})" for pattern in final_mtp_patterns))+        return re.compile("|".join(rf"({pattern})" for pattern in final_mtp_patterns))++    def get_weight_conversions(self, main_model: PreTrainedModel) -> list[WeightTransform]:+        """The weight conversions which map the mtp weights of `main_model`'s checkpoint onto this model."""+        num_hidden_layers = main_model.config.get_text_config().num_hidden_layers+        # For the correct conversions, we need first the mtp-specific renamings, then the main_model conversions+        # Note that since the layer numbers are dynamic, we cannot register those conversions - we also add the `mtp_block`+        # part for all weights since we cannot distinguish easily those that are under the main model's block or not. It will+        # be removed after for the few that should not have it+        weight_conversions = [+            WeightRenaming(+                source_patterns=f"layers.{N}.", target_patterns=f"layers.{N - num_hidden_layers}.mtp_block."+            )+            for N in range(num_hidden_layers, num_hidden_layers + self.num_mtp_layers)+        ]+        weight_conversions.extend(get_model_conversion_mapping(self, add_legacy=False))+        # `_weight_conversions` is only attached to models loaded with `from_pretrained`+        main_conversions = getattr(main_model, "_weight_conversions", None)+        if main_conversions is None:+            main_conversions = get_model_conversion_mapping(main_model)+        weight_conversions.extend(main_conversions)+        return weight_conversions++    @classmethod+    def from_pretrained(cls, main_model: PreTrainedModel, device_map=None, **kwargs) -> MtpModel:+        pretrained_model_name_or_path = main_model.config.name_or_path+        mtp_regex = cls.get_mtp_weights_regex(main_model)          # Get the number of layers in the checkpoint         num_mtp_layers = main_model.config.get_text_config().num_mtp_layers@@ -623,18 +673,7 @@ def from_pretrained(cls, main_model: PreTrainedModel, device_map=None, **kwargs)                 ):                     mtp_state_dict[k] = file_pointer.get_slice(k)  # don't materialize yet -        # For the correct conversions, we need first the mtp-specific renamings, then the main_model conversions-        # Note that since the layer numbers are dynamic, we cannot register those conversions - we also add the `mtp_block`-        # part for all weights since we cannot distinguish easily those that are under the main model's block or not. It will-        # be removed after for the few that should not have it-        weight_conversions = [-            WeightRenaming(-                source_patterns=f"layers.{N}.", target_patterns=f"layers.{N - num_hidden_layers}.mtp_block."-            )-            for N in range(num_hidden_layers, num_hidden_layers + num_mtp_layers)-        ]-        weight_conversions.extend(get_model_conversion_mapping(mtp_model, add_legacy=False))-        weight_conversions.extend(main_model._weight_conversions)+        weight_conversions = mtp_model.get_weight_conversions(main_model)          # Load the weights         loading_info, _ = convert_and_load_state_dict_in_model(