| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | SUPPORTED_SPECULATORS_TYPES = {} |
| 5 | |
| 6 | |
| 7 | def register_speculator(name): |
| 8 | def decorator(fn): |
| 9 | SUPPORTED_SPECULATORS_TYPES[name] = fn |
| 10 | return fn |
| 11 | |
| 12 | return decorator |
| 13 | |
| 14 | |
| 15 | @register_speculator("eagle3") |
| 16 | def update_eagle3(config_dict: dict, pre_trained_config: dict) -> None: |
| 17 | """Apply Eagle-3 specific configuration transformations to the `dict` used to |
| 18 | construct the Transformers PreTrainedConfig. |
| 19 | |
| 20 | Eagle-3 specific fields: |
| 21 | - draft_vocab_size: Size of the draft model's vocabulary |
| 22 | - target_hidden_size: Hidden size of the target model |
| 23 | - norm_before_residual: Whether to apply norm before residual connection |
| 24 | - norm_before_fc: Whether to apply RMSNorm before the fc projection |
| 25 | - eagle_aux_hidden_state_layer_ids: List of layer indices from the base |
| 26 | model to use as auxiliary inputs for the Eagle3 drafter. These layers |
| 27 | provide intermediate hidden states that help the drafter make better |
| 28 | predictions. This is the standard field used in Eagle3 checkpoints. |
| 29 | """ |
| 30 | pre_trained_config["draft_vocab_size"] = config_dict.get("draft_vocab_size") |
| 31 | if config_dict.get("target_hidden_size") is not None: |
| 32 | pre_trained_config["target_hidden_size"] = config_dict["target_hidden_size"] |
| 33 | pre_trained_config["norm_before_residual"] = config_dict.get( |
| 34 | "norm_before_residual", True |
| 35 | ) |
| 36 | pre_trained_config["norm_before_fc"] = config_dict.get("norm_before_fc", False) |
| 37 | pre_trained_config["fc_norm"] = config_dict.get("fc_norm", False) |
| 38 | pre_trained_config["norm_output"] = config_dict.get("norm_output", False) |
| 39 | eagle3_arch_map = { |
| 40 | "qwen3": "Eagle3Qwen3ForCausalLM", |
| 41 | "llama": "Eagle3LlamaForCausalLM", |
| 42 | } |
| 43 | model_type = pre_trained_config.get("model_type", "llama") |
| 44 | if model_type not in eagle3_arch_map: |
| 45 | raise ValueError(f"Unsupported model_type {model_type} for Eagle3 speculator") |
| 46 | pre_trained_config["architectures"] = [eagle3_arch_map[model_type]] |
| 47 | if config_dict.get("eagle_aux_hidden_state_layer_ids"): |
| 48 | pre_trained_config["eagle_aux_hidden_state_layer_ids"] = config_dict[ |
| 49 | "eagle_aux_hidden_state_layer_ids" |
| 50 | ] |
| 51 | |
| 52 | |
| 53 | @register_speculator("peagle") |
| 54 | def update_peagle(config_dict: dict, pre_trained_config: dict) -> None: |
| 55 | """Apply PEagle (Parallel Eagle) specific configuration transformations to |
| 56 | the `dict` used to construct the Transformers PreTrainedConfig. |
| 57 | |
| 58 | PEagle specific fields: |
| 59 | - draft_vocab_size: Size of the draft model's vocabulary |
| 60 | - target_hidden_size: Hidden size of the target model |
| 61 | - norm_before_residual: Whether to apply norm before residual connection |
| 62 | - norm_before_fc: Whether to apply RMSNorm before the fc projection |
| 63 | - mask_token_id (required): Token ID used for parallel drafting mask |
| 64 | placeholders, mapped to pard_token for the proposer |
| 65 | - eagle_aux_hidden_state_layer_ids: Layer indices from the target model |
| 66 | whose intermediate hidden states are used as auxiliary inputs |
| 67 | """ |
| 68 | pre_trained_config["draft_vocab_size"] = config_dict.get("draft_vocab_size") |
| 69 | if config_dict.get("target_hidden_size") is not None: |
| 70 | pre_trained_config["target_hidden_size"] = config_dict["target_hidden_size"] |
| 71 | pre_trained_config["norm_before_residual"] = config_dict.get( |
| 72 | "norm_before_residual", False |
| 73 | ) |
| 74 | pre_trained_config["norm_before_fc"] = config_dict.get("norm_before_fc", False) |
| 75 | peagle_arch_map = { |
| 76 | "qwen3": "PeagleQwen3ForCausalLM", |
| 77 | "llama": "PeagleLlamaForCausalLM", |
| 78 | } |
| 79 | model_type = pre_trained_config.get("model_type", "llama") |
| 80 | if model_type not in peagle_arch_map: |
| 81 | raise ValueError(f"Unsupported model_type {model_type} for PEagle speculator") |
| 82 | pre_trained_config["architectures"] = [peagle_arch_map[model_type]] |
| 83 | pre_trained_config["pard_token"] = config_dict["mask_token_id"] |
| 84 | if config_dict.get("eagle_aux_hidden_state_layer_ids"): |
| 85 | pre_trained_config["eagle_aux_hidden_state_layer_ids"] = config_dict[ |
| 86 | "eagle_aux_hidden_state_layer_ids" |
| 87 | ] |
| 88 | |
| 89 | |
| 90 | @register_speculator("dflash") |
| 91 | def update_dflash(config_dict: dict, pre_trained_config: dict) -> None: |
| 92 | """Apply DFlash specific configuration transformations to the `dict` used to |
| 93 | construct the Transformers PreTrainedConfig. |
| 94 | |
| 95 | DFlash specific fields: |
| 96 | - draft_vocab_size: Size of the draft model's vocabulary |
| 97 | - target_hidden_size: Hidden size of the target model |
| 98 | - mask_token_id (required): Token ID used for parallel drafting mask |
| 99 | placeholders |
| 100 | - aux_hidden_state_layer_ids (required): Layer indices from the target |
| 101 | model whose intermediate hidden states are used as context for the |
| 102 | DFlash drafter. Mapped to both eagle_aux_hidden_state_layer_ids |
| 103 | (for gpu_model_runner) and dflash_config.target_layer_ids (for the |
| 104 | DFlash model). |
| 105 | - sample_from_anchor: Whether to sample from the anchor position. Default |
| 106 | False (anchor is a bonus token, only mask tokens predict, yielding |
| 107 | block_size - 1 speculative tokens). |
| 108 | """ |
| 109 | pre_trained_config["architectures"] = ["DFlashDraftModel"] |
| 110 | pre_trained_config["draft_vocab_size"] = config_dict.get("draft_vocab_size") |
| 111 | if config_dict.get("target_hidden_size") is not None: |
| 112 | pre_trained_config["target_hidden_size"] = config_dict["target_hidden_size"] |
| 113 | |
| 114 | aux_layer_ids = config_dict["aux_hidden_state_layer_ids"] |
| 115 | pre_trained_config["eagle_aux_hidden_state_layer_ids"] = aux_layer_ids |
| 116 | |
| 117 | # DFlash configs use different indexing for the target layers, see #40727 |
| 118 | pre_trained_config["dflash_config"] = { |
| 119 | "mask_token_id": config_dict["mask_token_id"], |
| 120 | "target_layer_ids": [i - 1 for i in aux_layer_ids], |
| 121 | "sample_from_anchor": config_dict.get("sample_from_anchor", False), |
| 122 | } |
| 123 | # Enable causal masking in SWA for vllm-project/speculators models |
| 124 | pre_trained_config["dflash_config"]["causal"] = not config_dict.get( |
| 125 | "sliding_window_non_causal", True |
| 126 | ) |
| 127 | |
| 128 | |
| 129 | @register_speculator("dflash2") |
| 130 | def update_dflash2(config_dict: dict, pre_trained_config: dict) -> None: |
| 131 | """Apply DFlash2 specific configuration transformations to the `dict` used |
| 132 | to construct the Transformers PreTrainedConfig. |
| 133 | |
| 134 | DFlash2 extends DFlash with a grouped convolution layer and a |
| 135 | low-rank candidate selector head. It reuses the same DFlash runtime |
| 136 | (method="dflash") but has its own architecture (DFlash2DraftModel) |
| 137 | and additional config fields for the convolution and selector. |
| 138 | """ |
| 139 | update_dflash(config_dict, pre_trained_config) |
| 140 | pre_trained_config["architectures"] = ["DFlash2DraftModel"] |
| 141 | |
| 142 | for key in ( |
| 143 | "conv_kernel_size", |
| 144 | "conv_group_size", |
| 145 | "selector_rank", |
| 146 | "selector_top_k", |
| 147 | ): |
| 148 | if config_dict.get(key) is not None: |
| 149 | pre_trained_config["dflash_config"][key] = config_dict[key] |
| 150 | |
| 151 | |
| 152 | @register_speculator("dspark") |
| 153 | def update_dspark(config_dict: dict, pre_trained_config: dict) -> None: |
| 154 | """Apply DSpark specific configuration transformations to the `dict` used to |
| 155 | construct the Transformers PreTrainedConfig. |
| 156 | |
| 157 | DSpark extends DFlash with a Markov logit-bias head, reusing the same |
| 158 | Qwen3DSparkModel loader and DSparkSpeculator runtime as the dense DSpark |
| 159 | checkpoints (e.g. deepseek-ai/dspark_qwen3_8b_block7). |
| 160 | |
| 161 | DSpark specific fields: |
| 162 | - draft_vocab_size: draft vocab size; when smaller than the target vocab the |
| 163 | checkpoint also ships d2t/t2d remap tables. |
| 164 | - mask_token_id (required): token id for parallel-drafting mask slots. |
| 165 | - markov_rank / markov_head_type: low-rank Markov logit-bias head. |
| 166 | - block_size: semi-autoregressive draft block size. |
| 167 | - enable_confidence_head / confidence_head_with_markov: confidence head. |
| 168 | - aux_hidden_state_layer_ids (required): target layer indices feeding the |
| 169 | drafter. Mapped to both eagle_aux_hidden_state_layer_ids and |
| 170 | target_layer_ids (DSpark's i-1 layer semantics). |
| 171 | - sample_from_anchor: Whether to sample from the anchor position. Default |
| 172 | False (anchor is a bonus token, only mask tokens predict, yielding |
| 173 | block_size - 1 speculative tokens). |
| 174 | """ |
| 175 | architectures = config_dict.get("architectures") or [] |
| 176 | supported_architectures = { |
| 177 | "Qwen3DSparkModel", |
| 178 | "Qwen3OmniDSparkModel", |
| 179 | } |
| 180 | selected_architectures = [ |
| 181 | architecture |
| 182 | for architecture in architectures |
| 183 | if architecture in supported_architectures |
| 184 | ] |
| 185 | # Legacy msModelSpec checkpoints use a training-only architecture name. |
| 186 | pre_trained_config["architectures"] = selected_architectures or ["Qwen3DSparkModel"] |
| 187 | |
| 188 | sample_from_anchor = config_dict.get("sample_from_anchor", False) |
| 189 | pre_trained_config["sample_from_anchor"] = sample_from_anchor |
| 190 | pre_trained_config["dspark_bonus_anchor"] = not sample_from_anchor |
| 191 | |
| 192 | aux_layer_ids = config_dict["aux_hidden_state_layer_ids"] |
| 193 | pre_trained_config["eagle_aux_hidden_state_layer_ids"] = aux_layer_ids |
| 194 | # DSpark indexes target layers as aux_id - 1 (matches the dense configs). |
| 195 | pre_trained_config["target_layer_ids"] = [i - 1 for i in aux_layer_ids] |
| 196 | |
| 197 | for key in ( |
| 198 | "draft_vocab_size", |
| 199 | "target_hidden_size", |
| 200 | "mask_token_id", |
| 201 | "markov_rank", |
| 202 | "markov_head_type", |
| 203 | "block_size", |
| 204 | "enable_confidence_head", |
| 205 | "confidence_head_with_markov", |
| 206 | "use_aux_hidden_state", |
| 207 | ): |
| 208 | if config_dict.get(key) is not None: |
| 209 | pre_trained_config[key] = config_dict[key] |