| |
| import torch, math |
| from torch import nn |
| from transformers import Qwen3ForCausalLM, Qwen3Config, PreTrainedModel, PretrainedConfig |
|
|
|
|
| from transformers.modeling_outputs import CausalLMOutputWithPast |
| from transformers.generation import GenerationMixin |
|
|
| class EnsembleConfig(Qwen3Config): |
| model_type = "ensemble_qwen" |
| def __init__( |
| self, |
| model_a_path="Qwen/Qwen3-4B", |
| model_b_path="Qwen/Qwen3-4B-Base", |
| weight_a=0.9, |
| weight_b=0.1, |
| **kwargs, |
| ): |
| super().__init__(**kwargs) |
| self.model_a_path, self.model_b_path = model_a_path, model_b_path |
| self.weight_a, self.weight_b = weight_a, weight_b |
|
|
|
|
| class EnsembleForCausalLM(PreTrainedModel, GenerationMixin): |
| config_class = EnsembleConfig |
| _supports_flash_attn_2 = True |
| _supports_sdpa = True |
| main_input_name = "input_ids" |
| _tp_plan = {"model_a.lm_head": "colwise_rep", "model_b.lm_head": "colwise_rep"} |
| |
| def __init__(self, config: EnsembleConfig): |
| super().__init__(config) |
| |
| self.weight_a = config.weight_a |
| self.weight_b = config.weight_b |
| |
| |
| qwen3_config_dict = config.to_dict() |
| extra_keys = ["model_a_path", "model_b_path", "weight_a", "weight_b", "auto_map"] |
| for key in extra_keys: |
| del qwen3_config_dict[key] |
| |
| qwen3_config_dict["model_type"] = "qwen3" |
| qwen3_config_dict["architectures"] = ["Qwen3ForCausalLM"] |
| |
| if hasattr(config, "attn_implementation"): |
| qwen3_config_dict["attn_implementation"] = config.attn_implementation |
| |
| qwen3_config = Qwen3Config(**qwen3_config_dict) |
| |
| self.model_a = Qwen3ForCausalLM(qwen3_config) |
| self.model_b = Qwen3ForCausalLM(qwen3_config) |
|
|
| |
| def forward( |
| self, |
| input_ids: torch.LongTensor, |
| attention_mask: torch.LongTensor | None = None, |
| past_key_values: tuple | None = None, |
| use_cache: bool = True, |
| **kwargs, |
| ) -> CausalLMOutputWithPast: |
| """Run both models, sum logits, carry both kv-caches forward.""" |
| past_a, past_b = (None, None) if past_key_values is None else past_key_values |
| |
| out_a = self.model_a( |
| input_ids, |
| attention_mask=attention_mask, |
| past_key_values=past_a, |
| use_cache=use_cache, |
| ) |
| out_b = self.model_b( |
| input_ids, |
| attention_mask=attention_mask, |
| past_key_values=past_b, |
| use_cache=use_cache, |
| ) |
|
|
| |
| logits = self.weight_a * out_a.logits + self.weight_b * out_b.logits |
|
|
| |
| past = (out_a.past_key_values, out_b.past_key_values) if use_cache else None |
|
|
| return CausalLMOutputWithPast( |
| logits=logits, |
| past_key_values=past, |
| hidden_states=None, |
| attentions=None, |
| ) |
|
|
| |
| def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs): |
| |
| if past_key_values is not None: |
| past_a, past_b = past_key_values |
| input_ids = input_ids[:, -1:] |
| else: |
| past_a = past_b = None |
| return { |
| "input_ids": input_ids, |
| "past_key_values": (past_a, past_b), |
| "use_cache": kwargs.get("use_cache", True), |
| } |
|
|
| def _reorder_cache(self, past, beam_idx): |
| |
| past_a, past_b = past |
| past_a = self.model_a._reorder_cache(past_a, beam_idx) |
| past_b = self.model_b._reorder_cache(past_b, beam_idx) |
| return (past_a, past_b) |