|
| 1 | +import os |
| 2 | + |
1 | 3 | # SPDX-License-Identifier: Apache-2.0 |
2 | 4 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
3 | 5 |
|
| 6 | +from vllm.logger import init_logger |
| 7 | + |
| 8 | +logger = init_logger(__name__) |
| 9 | + |
4 | 10 | # Adapted from https://github.com/vllm-project/vllm/blob/94d8ec8d2bcb4ec55e33022b313c7e978edf05e1/vllm/model_executor/models/bamba.py |
5 | 11 | # Copyright 2024 HuggingFace Inc. team. All rights reserved. |
6 | 12 | # Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. |
|
64 | 70 | maybe_remap_kv_scale_name, |
65 | 71 | ) |
66 | 72 | from vllm.model_executor.models.interfaces import ( |
| 73 | + EagleModelMixin, |
67 | 74 | HasInnerState, |
68 | 75 | IsHybrid, |
69 | 76 | MixtureOfExperts, |
| 77 | + SupportsEagle3, |
70 | 78 | SupportsLoRA, |
71 | 79 | SupportsMambaPrefixCaching, |
72 | 80 | SupportsPP, |
@@ -539,7 +547,7 @@ def forward( |
539 | 547 |
|
540 | 548 |
|
541 | 549 | @support_torch_compile |
542 | | -class NemotronHModel(nn.Module): |
| 550 | +class NemotronHModel(nn.Module, EagleModelMixin): |
543 | 551 | def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""): |
544 | 552 | super().__init__() |
545 | 553 |
|
@@ -578,6 +586,14 @@ def get_layer(prefix: str): |
578 | 586 | self.start_layer, self.end_layer, self.layers = make_layers( |
579 | 587 | len(config.hybrid_override_pattern), get_layer, prefix=f"{prefix}.layers" |
580 | 588 | ) |
| 589 | + |
| 590 | + # DFlash offline extraction: auto-set aux_hidden_state_layers from env var |
| 591 | + # This bypasses the need for apply_model() which has serialization issues |
| 592 | + _dflash_layers = os.environ.get("DFLASH_AUX_HIDDEN_STATE_LAYERS", "") |
| 593 | + if _dflash_layers: |
| 594 | + _layers = tuple(int(x) for x in _dflash_layers.split(",")) |
| 595 | + self._set_aux_hidden_state_layers(_layers) |
| 596 | + logger.info("DFlash: auto-set aux_hidden_state_layers=%s from env", _layers) |
581 | 597 | self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory( |
582 | 598 | ["hidden_states", "residual"], config.hidden_size |
583 | 599 | ) |
@@ -605,18 +621,27 @@ def forward( |
605 | 621 | hidden_states = intermediate_tensors["hidden_states"] |
606 | 622 | residual = intermediate_tensors["residual"] |
607 | 623 |
|
608 | | - for layer in islice(self.layers, self.start_layer, self.end_layer): |
| 624 | + # EAGLE3 auxiliary hidden state collection (for DFlash spec decoding) |
| 625 | + aux_hidden_states = self._maybe_add_hidden_state([], 0, hidden_states, residual) |
| 626 | + for idx, layer in enumerate( |
| 627 | + islice(self.layers, self.start_layer, self.end_layer) |
| 628 | + ): |
609 | 629 | hidden_states, residual = layer( |
610 | 630 | positions=positions, |
611 | 631 | hidden_states=hidden_states, |
612 | 632 | residual=residual, |
613 | 633 | ) |
| 634 | + self._maybe_add_hidden_state( |
| 635 | + aux_hidden_states, idx + 1, hidden_states, residual |
| 636 | + ) |
614 | 637 |
|
615 | 638 | if not get_pp_group().is_last_rank: |
616 | 639 | return IntermediateTensors( |
617 | 640 | {"hidden_states": hidden_states, "residual": residual} |
618 | 641 | ) |
619 | 642 | hidden_states, _ = self.norm_f(hidden_states, residual) |
| 643 | + if len(aux_hidden_states) > 0: |
| 644 | + return hidden_states, aux_hidden_states |
620 | 645 | return hidden_states |
621 | 646 |
|
622 | 647 | def is_spec_layer(self, config: NemotronHConfig, weight_name: str) -> bool: |
@@ -771,6 +796,7 @@ class NemotronHForCausalLM( |
771 | 796 | SupportsQuant, |
772 | 797 | MixtureOfExperts, |
773 | 798 | SupportsMambaPrefixCaching, |
| 799 | + SupportsEagle3, |
774 | 800 | ): |
775 | 801 | # Relevant only if self.has_moe is True |
776 | 802 | is_non_gated_moe: bool = True |
@@ -919,11 +945,42 @@ def forward( |
919 | 945 | intermediate_tensors: IntermediateTensors | None = None, |
920 | 946 | inputs_embeds: torch.Tensor | None = None, |
921 | 947 | **kwargs, |
922 | | - ): |
923 | | - hidden_states = self.model( |
| 948 | + ) -> torch.Tensor | IntermediateTensors: |
| 949 | + model_output = self.model( |
924 | 950 | input_ids, positions, intermediate_tensors, inputs_embeds |
925 | 951 | ) |
926 | 952 |
|
| 953 | + # Handle EAGLE3 aux_hidden_states (for offline extraction and DFlash spec decode) |
| 954 | + if isinstance(model_output, tuple): |
| 955 | + hidden_states, aux_hidden_states = model_output |
| 956 | + |
| 957 | + # Extraction mode: save to disk (rank 0 only) |
| 958 | + _extract_path = os.environ.get("DFLASH_EXTRACT_PATH", "") |
| 959 | + if _extract_path: |
| 960 | + import torch.distributed as dist |
| 961 | + if not dist.is_initialized() or dist.get_rank() == 0: |
| 962 | + import safetensors.torch |
| 963 | + _num_tokens = hidden_states.shape[0] |
| 964 | + stacked = torch.stack( |
| 965 | + [t[:_num_tokens].detach().cpu() for t in aux_hidden_states], dim=1 |
| 966 | + ) |
| 967 | + _batch_id = id(input_ids) if input_ids is not None else id(hidden_states) |
| 968 | + _filename = os.path.join(_extract_path, f"batch_{_batch_id}.safetensors") |
| 969 | + if input_ids is not None: |
| 970 | + _token_ids = input_ids[:_num_tokens].detach().cpu().reshape(-1) |
| 971 | + else: |
| 972 | + _token_ids = torch.tensor([], dtype=torch.long) |
| 973 | + safetensors.torch.save_file({ |
| 974 | + "hidden_states": stacked, "token_ids": _token_ids, |
| 975 | + }, _filename) |
| 976 | + # Extraction mode: return just hidden_states |
| 977 | + return hidden_states |
| 978 | + |
| 979 | + # DFlash spec decode mode: return the tuple so model runner can extract aux_hidden_states |
| 980 | + return model_output |
| 981 | + else: |
| 982 | + hidden_states = model_output |
| 983 | + |
927 | 984 | return hidden_states |
928 | 985 |
|
929 | 986 | def compute_logits( |
|
0 commit comments