|
--- |
|
license: apache-2.0 |
|
tags: |
|
- moe |
|
- frankenmoe |
|
- merge |
|
- mergekit |
|
- lazymergekit |
|
- cognitivecomputations/dolphin-2_6-phi-2 |
|
- rhysjones/phi-2-orange |
|
base_model: |
|
- cognitivecomputations/dolphin-2_6-phi-2 |
|
- rhysjones/phi-2-orange |
|
--- |
|
|
|
# PhiMiX-2x2B |
|
|
|
|
|
|
|
<p align="center"> |
|
<img src="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11201acc-4089-416d-921b-cbd71fbf8ddb_1024x1024.jpeg" width="500" class="center"/> |
|
</p> |
|
|
|
|
|
PhiMiX-2x2B is a Mixure of Experts (MoE) made with the following models using mergekit: |
|
* [cognitivecomputations/dolphin-2_6-phi-2](https://huggingface.co/cognitivecomputations/dolphin-2_6-phi-2) |
|
* [rhysjones/phi-2-orange](https://huggingface.co/rhysjones/phi-2-orange) |
|
|
|
|
|
## ©️ Credits |
|
* [mlabonne's phixtral](https://huggingface.co/mlabonne/phixtral-4x2_8) for the PhiConfig and inference code. |
|
* [mergekit](https://github.com/cg123/mergekit) code which I tweaked (you can find the PhiConfig [here](https://github.com/cg123/mergekit/blob/508348ae34be17ea0a95d0a288a6e34491a2558a/mergekit/architecture.py#L289)) |
|
by mainly adding the config in the `moe_mixtral.py` script from `mixtral` branch. |
|
|
|
## ⏱️ Benchmarks |
|
|
|
| Model |AGIEval|GPT4All|TruthfulQA|Bigbench|Average| |
|
|----------------------------------------------------------------|------:|------:|---------:|-------:|------:| |
|
|[**PhiMiX-2x2B**](https://huggingface.co/paulilioaica/PhiMiX-2x2B) | **33.34** | **71.75** | 49.25 | **37.62** | **47.99** | |
|
|[phixtral-4x2_8](https://huggingface.co/mlabonne/phixtral-4x2_8)| 33.91| 70.44| 48.78| 37.68| 47.7| |
|
|[phixtral-2x2_8](https://huggingface.co/mlabonne/phixtral-2x2_8)| 34.1| 70.44| 48.78| 37.82| 47.78| |
|
|[_phi-2-orange_](https://huggingface.co/rhysjones/phi-2-orange)| _33.37_| _71.33_| _49.87_ | _37.3_ | _47.97_ | |
|
|[_dolphin-2_6-phi-2_](https://huggingface.co/cognitivecomputations/dolphin-2_6-phi-2)| _33.12_| _69.85_| _47.39_| _37.2_| _46.89_| |
|
|
|
I have used __bold__ to highlight this merge from the list, and _italics_ to highlight it's base modes used in the merge, |
|
and then __bold__ in the cells where it exceeds the performance of either. |
|
|
|
## 🧩 Configuration |
|
|
|
```yaml |
|
base_model: rhysjones/phi-2-orange |
|
gate_mode: cheap_embed |
|
dtype: float16 |
|
experts: |
|
- source_model: cognitivecomputations/dolphin-2_6-phi-2 |
|
positive_prompts: ["research, logic, math, science"] |
|
- source_model: rhysjones/phi-2-orange |
|
positive_prompts: ["programming, reasoning"] |
|
``` |
|
|
|
## 💻 Usage |
|
|
|
```python |
|
!pip install -qU transformers bitsandbytes accelerate |
|
|
|
from transformers import AutoTokenizer |
|
import transformers |
|
import torch |
|
|
|
model = "paulilioaica/PhiMiX-2x2B" |
|
|
|
tokenizer = AutoTokenizer.from_pretrained(model) |
|
pipeline = transformers.pipeline( |
|
"text-generation", |
|
model=model, |
|
trust_remote_code=True, |
|
model_kwargs={"torch_dtype": torch.float16, "load_in_4bit": True,}, |
|
) |
|
|
|
prompt="How many continents are there?" |
|
input = f"Instruct: {prompt}\nOutput:" |
|
outputs = pipeline(input, max_new_tokens=256, do_sample=True, temperature=0.7, top_k=50, top_p=0.95) |
|
print(outputs[0]["generated_text"]) |
|
|
|
``` |
|
|
|
```terminal |
|
Instruct: How many continents are there? |
|
Output: There are seven continents: Africa, Antarctica, Asia, Europe, North America, Australia, and South America. The total number of continents on Earth is seven, including Antarctica, which is sometimes considered part of the continent of Antarctica or as its own continent. |
|
|
|
It's important to note that the number of continents in popular education and geography is seven, but some sources may include Antarctica as its own continent, while others include it as part of the continent of Antarctica. Regardless of the exact categorization, there are seven continents that collectively make up the Earth's landmass. |
|
|
|
The continents can be divided into several subregions, such as islands, archipelagos, and microcontinents, which are smaller land masses surrounded by water. These subregions can be considered part of the continents or their own unique entities, depending on the context. |
|
|
|
Each continent has its own unique geography, climate, flora, fauna, and human cultures. The continents are interconnected through various landforms, bodies of water, and global trade routes. |
|
|
|
In summary, there are seven continents on Earth, each with its own distinct characteristics and unique contributions to the world's diversity. While the number may vary depending on the categorization of Antarctica, all seven continents together make |
|
``` |
|
|
|
|
|
## ♻️ Replicate this repo |
|
|
|
**beware** this will only work with 2 phis, you might have to tinker in the naming thing for more layers |
|
|
|
**AFTER** all the file modifications and run, you need to replace `configs.json` with the one from **this repo** |
|
**AFTER** that you need to add `modeling_phi.py` and `configurations.phi` from **this repo** to your repo |
|
|
|
Steps |
|
|
|
1. Modify moe_mixtral.py from `/content/mergekit/mergekit/scripts/mixtral_moe.py` to your hf repo |
|
|
|
***mixtral_moe.py*** |
|
|
|
``` |
|
# Copyright (C) 2024 Charles O. Goddard |
|
# |
|
# This software is free software: you can redistribute it and/or |
|
# modify it under the terms of the GNU Lesser General Public License as |
|
# published by the Free Software Foundation, either version 3 of the |
|
# License, or (at your option) any later version. |
|
# |
|
# This software is distributed in the hope that it will be useful, but |
|
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
# Lesser General Public License for more details. |
|
# |
|
# You should have received a copy of the GNU Lesser General Public License |
|
# along with this program. If not, see http://www.gnu.org/licenses/. |
|
|
|
import logging |
|
import os |
|
import sys |
|
from typing import Dict, List, Optional, Union |
|
|
|
import click |
|
import torch |
|
import tqdm |
|
import transformers |
|
import yaml |
|
from pydantic import BaseModel |
|
from transformers import ( |
|
AutoModelForCausalLM, |
|
LlamaForCausalLM, |
|
MistralConfig, |
|
MistralForCausalLM, |
|
MixtralConfig, |
|
) |
|
from transformers.modeling_outputs import CausalLMOutputWithPast |
|
|
|
import mergekit.architecture |
|
from mergekit.common import ModelReference, dtype_from_name |
|
from mergekit.io import LazyTensorLoader, TensorWriter |
|
from mergekit.merge import MergeOptions |
|
from mergekit.options import add_merge_options |
|
|
|
# Create a Mixtral MoE from a set of equally-sized Mistral (or Llama) models. |
|
# Takes the path to a yml config and an output path. |
|
# Config schema is the two classes below. |
|
|
|
|
|
class Expert(BaseModel): |
|
source_model: str |
|
|
|
positive_prompts: List[str] |
|
negative_prompts: Optional[List[str]] = None |
|
noise_scale: Optional[float] = None |
|
|
|
@property |
|
def model_ref(self): |
|
return ModelReference.parse(self.source_model) |
|
|
|
|
|
class MistralMOEConfig(BaseModel): |
|
base_model: str |
|
experts: List[Expert] |
|
gate_mode: str = "hidden" # possible values: "hidden", "cheap_embed", "random" |
|
# "hidden" uses hidden state vectors for the given prompts for each layer |
|
# "cheap_embed" uses the average of token embeddings for the prompts, same for each layer |
|
# "random" is random |
|
dtype: Optional[str] = None |
|
experts_per_token: int = 2 |
|
|
|
|
|
def get_hidden_states( |
|
model: Union[MistralForCausalLM, LlamaForCausalLM], |
|
tokenized: transformers.BatchEncoding, |
|
average: bool = True, |
|
) -> List[torch.Tensor]: |
|
with torch.no_grad(): |
|
output: CausalLMOutputWithPast = model( |
|
**tokenized.to(model.device), output_hidden_states=True, return_dict=True |
|
) |
|
hidden_states = torch.stack( |
|
output.hidden_states[:-1] |
|
) # (num_layers, batch_size, seq_len, hidden_size) |
|
if average: |
|
# use average over sequence |
|
hidden_states = hidden_states.sum(dim=2) / hidden_states.shape[2] |
|
else: |
|
# take last value |
|
hidden_states = hidden_states[:, :, -1, :] |
|
return hidden_states.sum(dim=1) / hidden_states.shape[1] |
|
|
|
|
|
def get_cheap_embedding( |
|
embed: torch.Tensor, |
|
tokenized: Dict[str, torch.Tensor], |
|
num_layers: int, |
|
vocab_size: int, |
|
) -> torch.Tensor: |
|
onehot = torch.nn.functional.one_hot( |
|
tokenized["input_ids"], num_classes=vocab_size |
|
) # (batch_size, seq_len, 32000) |
|
h = onehot.float() @ embed.float() # (batch_size, seq_len, hidden_size) |
|
embedded = ( |
|
(h * tokenized["attention_mask"].unsqueeze(-1)) |
|
.sum(dim=1) |
|
.sum(dim=0, keepdim=True) |
|
) # (1, hidden_size) |
|
res = embedded / embedded.norm(dim=-1, keepdim=True).clamp( |
|
min=1e-8 |
|
) # (1, hidden_size) |
|
return res.repeat(num_layers, 1) |
|
|
|
|
|
def tokenize_prompts( |
|
prompts: List[str], tokenizer: transformers.PreTrainedTokenizerBase |
|
): |
|
return tokenizer( |
|
[tokenizer.bos_token + p for p in prompts], |
|
return_tensors="pt", |
|
padding=True, |
|
add_special_tokens=False, |
|
) |
|
|
|
|
|
def get_gate_params( |
|
model_ref: ModelReference, |
|
tokenizer: transformers.PreTrainedTokenizerBase, |
|
experts: List[Expert], |
|
mode: str = "hidden", |
|
load_in_4bit: bool = False, |
|
load_in_8bit: bool = False, |
|
lazy_unpickle: bool = False, |
|
trust_remote_code: bool = False, |
|
device: str = "auto", |
|
): |
|
gate_vecs = [] |
|
_do_it = None |
|
|
|
model_cfg = model_ref.config(trust_remote_code=trust_remote_code) |
|
|
|
if mode == "random": |
|
return torch.randn( |
|
(model_cfg.num_hidden_layers, len(experts), model_cfg.hidden_size) |
|
) |
|
elif mode == "cheap_embed": |
|
embed = LazyTensorLoader( |
|
model_ref.tensor_index(), lazy_unpickle=lazy_unpickle |
|
).get_tensor("transformer.embd.wte.weight") |
|
|
|
def _do_it(tokenized): |
|
return get_cheap_embedding( |
|
embed, |
|
tokenized, |
|
num_layers=model_cfg.num_hidden_layers, |
|
vocab_size=model_cfg.vocab_size, |
|
) |
|
|
|
elif mode in ("hidden", "hidden_avg", "hidden_last"): |
|
model = AutoModelForCausalLM.from_pretrained( |
|
model_ref.model.path, |
|
revision=model_ref.model.revision, |
|
torch_dtype=torch.bfloat16, |
|
device_map=device, |
|
low_cpu_mem_usage=True, |
|
load_in_4bit=load_in_4bit, |
|
load_in_8bit=load_in_8bit, |
|
trust_remote_code=trust_remote_code, |
|
) |
|
|
|
def _do_it(tokenized): |
|
return get_hidden_states( |
|
model, tokenized=tokenized, average=mode == "hidden_avg" |
|
) |
|
|
|
|
|
gate_vecs = [] |
|
print(experts) |
|
for expert in tqdm.tqdm(experts, desc="expert prompts"): |
|
print(_do_it) |
|
hidden_states = _do_it(tokenize_prompts(expert.positive_prompts, tokenizer)) |
|
if expert.negative_prompts: |
|
hidden_states -= _do_it( |
|
tokenize_prompts(expert.negative_prompts, tokenizer) |
|
) |
|
|
|
hidden_states /= hidden_states.norm(p=2, dim=-1, keepdim=True).clamp(min=1e-8) |
|
gate_vecs.append(hidden_states) |
|
gate_vecs = torch.stack(gate_vecs, dim=0) # (num_expert, num_layer, hidden_size) |
|
return gate_vecs.permute(1, 0, 2) |
|
|
|
|
|
def warn_degenerate_gates(gate_vecs: torch.Tensor, threshold: float = 5.0): |
|
degen_indices = [] |
|
num_layers, _num_experts, _hidden_size = gate_vecs.shape |
|
for idx in range(num_layers): |
|
c = torch.linalg.cond(gate_vecs[idx, :, :].float()) |
|
if c > threshold: |
|
degen_indices.append(idx) |
|
|
|
if degen_indices: |
|
if len(degen_indices) == 1: |
|
layer_str = f"layer {degen_indices[0]}" |
|
verb = "has" |
|
elif len(degen_indices) == 2: |
|
layer_str = f"layers {' and '.join(map(str, degen_indices))}" |
|
verb = "have" |
|
elif len(degen_indices) >= num_layers: |
|
layer_str = "ALL layers" |
|
verb = "have" |
|
else: |
|
layer_str = ( |
|
"layers " |
|
+ ", ".join(map(str, degen_indices[:-1])) |
|
+ ", and " |
|
+ str(degen_indices[-1]) |
|
) |
|
verb = "have" |
|
|
|
logging.warning( |
|
f"{layer_str} {verb} degenerate routing parameters " |
|
"- your prompts may be too similar." |
|
) |
|
logging.warning("One or more experts will be underutilized in your model.") |
|
|
|
|
|
def is_bad_config(config: MistralMOEConfig, allow_all_same: bool = False) -> bool: |
|
if len(config.experts) < 2: |
|
logging.error("Must include at least two experts.") |
|
return True |
|
|
|
if config.gate_mode == "random": |
|
return False # eh we're good |
|
|
|
def prompt_tup(e: Expert): |
|
return (tuple(e.positive_prompts), tuple(e.negative_prompts or [])) |
|
|
|
# let's just nip this trend in the bud |
|
p_first = prompt_tup(config.experts[0]) |
|
if all(prompt_tup(e) == p_first for e in config.experts[1:]): |
|
logging.error( |
|
"Your positive and negative prompts are identical for all experts. This will not produce a functioning MoE." |
|
) |
|
logging.error( |
|
"For each expert, `positive_prompts` must contain one or more example prompt reflecting what should be routed to that expert." |
|
) |
|
return True |
|
|
|
if not allow_all_same: |
|
if all( |
|
e.source_model == config.experts[0].source_model for e in config.experts[1:] |
|
): |
|
logging.error( |
|
"All of your expert models are the same. This will produce " |
|
"a model that uses more resources but gives the exact same output. " |
|
"If you plan to train the model after merging, proceed with the " |
|
"--i-understand-this-is-not-useful-without-training flag." |
|
) |
|
return True |
|
|
|
|
|
def build( |
|
config: MistralMOEConfig, |
|
out_path: str, |
|
merge_options: MergeOptions, |
|
load_in_4bit: bool = False, |
|
load_in_8bit: bool = False, |
|
device: str = "auto", |
|
allow_all_same: bool = False, |
|
): |
|
if is_bad_config(config, allow_all_same=allow_all_same): |
|
sys.exit(1) |
|
|
|
if config.experts_per_token < 1: |
|
logging.error("Experts per token must be >= 1") |
|
sys.exit(1) |
|
if config.experts_per_token > len(config.experts): |
|
logging.error("Experts per token must be <= number of experts") |
|
sys.exit(1) |
|
|
|
base_model = ModelReference.parse(config.base_model) |
|
base_cfg = base_model.config(trust_remote_code=merge_options.trust_remote_code) |
|
if not isinstance(base_cfg, MistralConfig): |
|
base_cfg_mistral = MistralConfig(**base_cfg.to_dict()) |
|
base_cfg_mistral.sliding_window = None |
|
base_cfg_mistral.max_position_embeddings = base_cfg.max_position_embeddings |
|
base_cfg = base_cfg_mistral |
|
|
|
out_cfg = MixtralConfig(**base_cfg.to_dict()) |
|
out_cfg.architectures = ["PhiForCausalLM"] |
|
out_cfg.num_local_experts = len(config.experts) |
|
out_cfg.num_experts_per_tok = config.experts_per_token |
|
out_cfg.sliding_window = None |
|
if config.dtype: |
|
out_cfg.torch_dtype = config.dtype |
|
out_cfg.save_pretrained(out_path) |
|
|
|
if (out_cfg.num_local_experts & (out_cfg.num_local_experts - 1)) != 0: |
|
logging.warning( |
|
f"Your model has {out_cfg.num_local_experts} experts, which is " |
|
"not a power of two. The model will not be usable in llama.cpp." |
|
) |
|
|
|
loaders: Dict[ModelReference, LazyTensorLoader] = {} |
|
for model in tqdm.tqdm( |
|
[base_model] + [e.model_ref for e in config.experts], desc="Warm up loaders" |
|
): |
|
loaders[model] = LazyTensorLoader( |
|
model.tensor_index(cache_dir=merge_options.transformers_cache), |
|
lazy_unpickle=merge_options.lazy_unpickle, |
|
) |
|
|
|
base_loader = loaders.get(base_model) |
|
writer = TensorWriter( |
|
out_path=out_path, |
|
max_shard_size=merge_options.out_shard_size, |
|
safe_serialization=merge_options.safe_serialization, |
|
) |
|
|
|
if config.dtype: |
|
out_dtype = dtype_from_name(config.dtype) |
|
elif base_cfg.torch_dtype: |
|
out_dtype = base_cfg.torch_dtype |
|
if isinstance(out_dtype, str): |
|
out_dtype = dtype_from_name(out_dtype) |
|
else: |
|
out_dtype = None |
|
|
|
logging.info("Copying parameters...") |
|
MISTRAL_INFO = mergekit.architecture.PHI2_INFO |
|
for tensor_name in MISTRAL_INFO.pre_weight_names + MISTRAL_INFO.post_weight_names: |
|
tensor = base_loader.get_tensor(tensor_name) |
|
if not out_dtype: |
|
# All else has failed, take the first dtype we see |
|
out_dtype = tensor.dtype |
|
writer.save_tensor( |
|
tensor_name, tensor.to(dtype=out_dtype), clone=merge_options.clone_tensors |
|
) |
|
set_of_seen_tensors = set() |
|
|
|
for name_format in tqdm.tqdm(MISTRAL_INFO.layer_weight_formats()): |
|
for layer_idx in range(base_cfg.num_hidden_layers): |
|
tensor_name = name_format.format(idx=layer_idx) |
|
if ".mlp.fc" in name_format: |
|
for moe_index, expert in enumerate(config.experts): |
|
if tensor_name in set_of_seen_tensors: |
|
expert_name = tensor_name.replace( |
|
".mlp.fc", f".moe.mlp.1.fc" |
|
) |
|
else: |
|
expert_name = tensor_name.replace( |
|
".mlp.fc", f".moe.mlp.0.fc" |
|
) |
|
set_of_seen_tensors.add(tensor_name) |
|
|
|
expert_loader = loaders.get(expert.model_ref) |
|
tensor = expert_loader.get_tensor(tensor_name) |
|
if expert.noise_scale: |
|
tensor += torch.randn_like(tensor) * expert.noise_scale |
|
writer.save_tensor( |
|
expert_name, tensor.to(dtype=out_dtype), clone=True |
|
) |
|
print(expert_name, tensor_name) |
|
continue |
|
writer.save_tensor( |
|
tensor_name, base_loader.get_tensor(tensor_name).to(dtype=out_dtype) |
|
) |
|
|
|
tokenizer = transformers.AutoTokenizer.from_pretrained( |
|
base_model.model.path, revision=base_model.model.revision |
|
) |
|
tokenizer.padding_side = "left" |
|
tokenizer.pad_token_id = tokenizer.bos_token_id |
|
|
|
logging.info("Getting gate parameters...") |
|
gate_vecs = get_gate_params( |
|
base_model, |
|
tokenizer, |
|
config.experts, |
|
mode=config.gate_mode, |
|
load_in_4bit=load_in_4bit, |
|
load_in_8bit=load_in_8bit, |
|
lazy_unpickle=merge_options.lazy_unpickle, |
|
trust_remote_code=merge_options.trust_remote_code, |
|
device=device, |
|
) |
|
# gate_vecs: (num_layers, num_experts, hidden_size) |
|
|
|
warn_degenerate_gates(gate_vecs) |
|
|
|
for layer_idx in range(base_cfg.num_hidden_layers): |
|
writer.save_tensor( |
|
f"transformer.h.{layer_idx}.moe.gate.weight", |
|
gate_vecs[layer_idx, :, :].contiguous().to(dtype=out_dtype), |
|
) |
|
writer.finalize() |
|
|
|
if merge_options.copy_tokenizer: |
|
logging.info("Saving tokenizer...") |
|
tokenizer.save_pretrained(out_path, safe_serialization=True) |
|
|
|
logging.info("Done.") |
|
|
|
|
|
@click.command("mergekit-moe") |
|
@click.argument("config_path", type=click.Path(exists=True, dir_okay=False)) |
|
@click.argument("out_path", type=click.Path()) |
|
@click.option( |
|
"--load-in-4bit", |
|
is_flag=True, |
|
type=bool, |
|
default=False, |
|
help="Load model in 4bit for computing hidden states", |
|
) |
|
@click.option( |
|
"--load-in-8bit", |
|
is_flag=True, |
|
type=bool, |
|
default=False, |
|
help="Load model in 8bit for computing hidden states", |
|
) |
|
@click.option( |
|
"--device", |
|
type=str, |
|
default="auto", |
|
help="Device to use to compute embeddings", |
|
show_default=True, |
|
) |
|
@click.option( |
|
"--verbose", "-v", type=bool, default=False, is_flag=True, help="Verbose logging" |
|
) |
|
@click.option( |
|
"--i-understand-this-is-not-useful-without-training", |
|
type=bool, |
|
default=False, |
|
is_flag=True, |
|
help="Really make the questionable model you want.", |
|
) |
|
@add_merge_options |
|
def main( |
|
config_path: str, |
|
out_path: str, |
|
load_in_4bit: bool, |
|
load_in_8bit: bool, |
|
device: str, |
|
merge_options: MergeOptions, |
|
verbose: bool, |
|
i_understand_this_is_not_useful_without_training: bool, |
|
): |
|
logging.basicConfig(level=logging.INFO if verbose else logging.WARNING) |
|
|
|
if merge_options.cuda: |
|
logging.warning( |
|
'--cuda is a no-op for mergekit-moe, use "--device cuda" instead' |
|
) |
|
|
|
with open(config_path, "r", encoding="utf-8") as file: |
|
config_source = file.read() |
|
|
|
config = MistralMOEConfig.model_validate(yaml.safe_load(config_source)) |
|
build( |
|
config, |
|
out_path=out_path, |
|
merge_options=merge_options, |
|
load_in_4bit=load_in_4bit, |
|
load_in_8bit=load_in_8bit, |
|
device=device, |
|
allow_all_same=i_understand_this_is_not_useful_without_training, |
|
) |
|
|
|
if merge_options.write_model_card: |
|
# TODO: generate a README.md as well |
|
with open( |
|
os.path.join(out_path, "mergekit_moe_config.yml"), "w", encoding="utf-8" |
|
) as fp: |
|
fp.write(config_source) |
|
|
|
|
|
if __name__ == "__main__": |
|
main() |
|
``` |
|
|
|
2. Modify architecture.py `/content/mergekit/mergekit/architecture.py` |
|
(this you can take from the link to the commit i have in description) |
|
|
|
***architecture.py*** |
|
|
|
``` |
|
# Copyright (C) 2024 Charles O. Goddard |
|
# |
|
# This software is free software: you can redistribute it and/or |
|
# modify it under the terms of the GNU Lesser General Public License as |
|
# published by the Free Software Foundation, either version 3 of the |
|
# License, or (at your option) any later version. |
|
# |
|
# This software is distributed in the hope that it will be useful, but |
|
# WITHOUT ANY WARRANTY; without even the implied warranty of |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
# Lesser General Public License for more details. |
|
# |
|
# You should have received a copy of the GNU Lesser General Public License |
|
# along with this program. If not, see http://www.gnu.org/licenses/. |
|
|
|
from abc import ABC, abstractmethod |
|
from typing import List, Optional |
|
|
|
from pydantic import BaseModel |
|
from transformers import PretrainedConfig |
|
|
|
|
|
class ArchitectureInfo(ABC): |
|
@abstractmethod |
|
def pre_weights(self) -> List[str]: |
|
"""Return a list of all weights preceding the first layer.""" |
|
... |
|
|
|
@abstractmethod |
|
def post_weights(self) -> List[str]: |
|
"""Return a list of all weights following the final layer.""" |
|
... |
|
|
|
@abstractmethod |
|
def layer_weight_formats(self) -> List[str]: |
|
"""Return a list of format strings all weights associated with a layer.""" |
|
... |
|
|
|
@abstractmethod |
|
def embed_weights(self) -> List[str]: |
|
... |
|
|
|
def num_layers(self, config: PretrainedConfig) -> int: |
|
return config.num_hidden_layers |
|
|
|
def num_layers_config_key(self) -> str: |
|
"""Key in config that represents number of layers""" |
|
return "num_hidden_layers" |
|
|
|
|
|
class StaticTensorNames(ArchitectureInfo, BaseModel, frozen=True): |
|
name: str |
|
|
|
pre_weight_names: List[str] # weights applied before first layer |
|
post_weight_names: List[str] # weights applied after last layer |
|
embed_weight_names: List[str] # weights for embed/lm_head |
|
layer_prefix_format: str |
|
layer_weight_suffixes: List[str] |
|
num_layers_key: Optional[str] = None |
|
|
|
def pre_weights(self) -> List[str]: |
|
return self.pre_weight_names |
|
|
|
def post_weights(self) -> List[str]: |
|
return self.post_weight_names |
|
|
|
def embed_weights(self) -> List[str]: |
|
return self.embed_weight_names |
|
|
|
def layer_weight_formats(self) -> List[str]: |
|
res = [] |
|
for suffix in self.layer_weight_suffixes: |
|
res.append(self.layer_prefix_format + "." + suffix) |
|
return res |
|
|
|
def num_layers_config_key(self) -> str: |
|
if self.num_layers_key: |
|
return self.num_layers_key |
|
return super().num_layers_config_key() |
|
|
|
def num_layers(self, config: PretrainedConfig) -> int: |
|
return getattr(config, self.num_layers_config_key()) |
|
|
|
def all_weights(self, config: PretrainedConfig) -> List[str]: |
|
num_layers = self.num_layers(config) |
|
tensor_names = list(self.pre_weights()) |
|
for layer_idx in range(num_layers): |
|
for f in self.layer_weight_formats(): |
|
tensor_names.append(f.format(idx=layer_idx)) |
|
tensor_names.extend(self.post_weights()) |
|
return tensor_names |
|
|
|
|
|
LLAMA_INFO = StaticTensorNames( |
|
name="LlamaForCausalLM", |
|
pre_weight_names=["model.embed_tokens.weight"], |
|
post_weight_names=["model.norm.weight", "lm_head.weight"], |
|
embed_weight_names=["model.embed_tokens.weight", "lm_head.weight"], |
|
layer_prefix_format="model.layers.{idx}", |
|
layer_weight_suffixes=[ |
|
"input_layernorm.weight", |
|
"mlp.up_proj.weight", |
|
"mlp.down_proj.weight", |
|
"mlp.gate_proj.weight", |
|
"post_attention_layernorm.weight", |
|
"self_attn.q_proj.weight", |
|
"self_attn.k_proj.weight", |
|
"self_attn.v_proj.weight", |
|
"self_attn.o_proj.weight", |
|
], |
|
) |
|
|
|
MISTRAL_INFO = StaticTensorNames( |
|
name="MistralForCausalLM", |
|
# lol |
|
**LLAMA_INFO.model_dump(exclude=["name"]), |
|
) |
|
|
|
|
|
STABLELM_INFO = StaticTensorNames( |
|
name="StableLMEpochForCausalLM", |
|
post_weight_names=LLAMA_INFO.post_weight_names + ["model.norm.bias"], |
|
layer_weight_suffixes=LLAMA_INFO.layer_weight_suffixes |
|
+ [ |
|
"input_layernorm.bias", |
|
"post_attention_layernorm.bias", |
|
], |
|
**LLAMA_INFO.model_dump( |
|
exclude=["name", "layer_weight_suffixes", "post_weight_names"] |
|
), |
|
) |
|
|
|
GPT_NEOX_INFO = StaticTensorNames( |
|
name="GPTNeoXForCausalLM", |
|
pre_weight_names=["gpt_neox.embed_in.weight"], |
|
post_weight_names=[ |
|
"gpt_neox.final_layer_norm.bias", |
|
"gpt_neox.final_layer_norm.weight", |
|
"embed_out.weight", |
|
], |
|
embed_weight_names=["gpt_neox.embed_in.weight", "embed_out.weight"], |
|
layer_prefix_format="gpt_neox.layers.{idx}", |
|
layer_weight_suffixes=sum( |
|
( |
|
[f"{prefix}.weight", f"{prefix}.bias"] |
|
for prefix in [ |
|
"attention.dense", |
|
"attention.query_key_value", |
|
"input_layernorm", |
|
"mlp.dense_4h_to_h", |
|
"mlp.dense_h_to_4h", |
|
"post_attention_layernorm", |
|
] |
|
), |
|
start=[], |
|
) |
|
+ ["attention.bias", "attention.masked_bias", "attention.rotary_emb.inv_freq"], |
|
) |
|
|
|
GPT2_INFO = StaticTensorNames( |
|
name="GPT2LMHeadModel", |
|
pre_weight_names=["wte.weight", "wpe.weight"], |
|
post_weight_names=["ln_f.weight", "ln_f.bias"], |
|
embed_weight_names=["wte.weight"], |
|
layer_prefix_format="h.{idx}", |
|
layer_weight_suffixes=[ |
|
"attn.c_attn.weight", |
|
"attn.c_attn.bias", |
|
"attn.c_proj.weight", |
|
"attn.c_proj.bias", |
|
"ln_1.weight", |
|
"ln_1.bias", |
|
"ln_2.weight", |
|
"ln_2.bias", |
|
"mlp.c_proj.weight", |
|
"mlp.c_proj.bias", |
|
"mlp.c_fc.weight", |
|
"mlp.c_fc.bias", |
|
"mlp.c_proj.weight", |
|
"mlp.c_proj.bias", |
|
], |
|
num_layers_key="n_layer", |
|
) |
|
|
|
JAIS_INFO = StaticTensorNames( |
|
name="JAISLMHeadModel", |
|
pre_weight_names=["transformer.wte.weight", "transformer.relative_pe.slopes"], |
|
post_weight_names=["transformer.ln_f.weight", "transformer.ln_f.bias"], |
|
embed_weight_names=["transformer.wte.weight"], |
|
layer_prefix_format="transformer.h.{idx}", |
|
layer_weight_suffixes=[ |
|
"attn.c_attn.weight", |
|
"attn.c_attn.bias", |
|
"attn.c_proj.weight", |
|
"attn.c_proj.bias", |
|
"ln_1.weight", |
|
"ln_1.bias", |
|
"ln_2.weight", |
|
"ln_2.bias", |
|
"mlp.c_fc.weight", |
|
"mlp.c_fc.bias", |
|
"mlp.c_fc2.weight", |
|
"mlp.c_fc2.bias", |
|
"mlp.c_proj.weight", |
|
"mlp.c_proj.bias", |
|
], |
|
num_layers_key="n_layer", |
|
) |
|
|
|
GPT2_SEQCLASS_INFO = StaticTensorNames( |
|
name="GPT2ForSequenceClassification", |
|
pre_weight_names=["transformer.wte.weight", "transformer.wpe.weight"], |
|
post_weight_names=[ |
|
"transformer.ln_f.weight", |
|
"transformer.ln_f.bias", |
|
"score.weight", |
|
], |
|
layer_prefix_format="transformer.h.{idx}", |
|
embed_weight_names=GPT2_INFO.embed_weight_names, |
|
layer_weight_suffixes=GPT2_INFO.layer_weight_suffixes, |
|
num_layers_key=GPT2_INFO.num_layers_key, |
|
) |
|
|
|
|
|
QWEN_INFO = StaticTensorNames( |
|
name="QWenLMHeadModel", |
|
pre_weight_names=["transformer.wte.weight"], |
|
post_weight_names=["transformer.ln_f.weight", "lm_head.weight"], |
|
embed_weight_names=["transformer.wte.weight", "lm_head.weight"], |
|
layer_prefix_format="transformer.h.{idx}", |
|
layer_weight_suffixes=[ |
|
"attn.c_attn.bias", |
|
"attn.c_attn.weight", |
|
"attn.c_proj.weight", |
|
"ln_1.weight", |
|
"ln_2.weight", |
|
"mlp.c_proj.weight", |
|
"mlp.w1.weight", |
|
"mlp.w2.weight", |
|
], |
|
) |
|
|
|
CHATGLM_INFO = StaticTensorNames( |
|
name="ChatGLMModel", |
|
pre_weight_names=[ |
|
"transformer.embedding.word_embeddings.weight", |
|
"transformer.rotary_pos_emb.inv_freq", |
|
], |
|
post_weight_names=[ |
|
"transformer.encoder.final_layernorm.weight", |
|
"transformer.output_layer.weight", |
|
], |
|
embed_weight_names=[ |
|
"transformer.embedding.word_embeddings.weight", |
|
"transformer.output_layer.weight", |
|
], |
|
layer_prefix_format="transformer.encoder.layers.{idx}", |
|
layer_weight_suffixes=[ |
|
"input_layernorm.weight", |
|
"mlp.dense_4h_to_h.weight", |
|
"mlp.dense_h_to_4h.weight", |
|
"post_attention_layernorm.weight", |
|
"self_attention.dense.weight", |
|
"self_attention.query_key_value.bias", |
|
"self_attention.query_key_value.weight", |
|
], |
|
) |
|
|
|
FALCON_INFO = StaticTensorNames( |
|
name="FalconForCausalLM", |
|
pre_weight_names=["transformer.word_embeddings.weight"], |
|
post_weight_names=[ |
|
"transformer.ln_f.weight", |
|
"transformer.ln_f.bias", |
|
"lm_head.weight", |
|
], |
|
embed_weight_names=["transformer.word_embeddings.weight", "lm_head.weight"], |
|
layer_prefix_format="transformer.h.{idx}", |
|
layer_weight_suffixes=[ |
|
"ln_attn.bias", |
|
"ln_attn.weight", |
|
"ln_mlp.bias", |
|
"ln_mlp.weight", |
|
"mlp.dense_4h_to_h.weight", |
|
"mlp.dense_h_to_4h.weight", |
|
"self_attention.dense.weight", |
|
"self_attention.query_key_value.weight", |
|
], |
|
) |
|
|
|
|
|
class PhiTensorNames(ArchitectureInfo): |
|
architecture_name: str = "MixFormerSequentialForCausalLM" |
|
|
|
def __init__(self, config: PretrainedConfig): |
|
self.config = config |
|
|
|
def __eq__(self, rhs: "PhiTensorNames"): |
|
if not isinstance(rhs, PhiTensorNames): |
|
return False |
|
return self.num_layers() == rhs.num_layers() |
|
|
|
def pre_weights(self) -> List[str]: |
|
return ["layers.0.wte.weight"] |
|
|
|
def post_weights(self) -> List[str]: |
|
fake_layer_idx = self.config.n_layer + 1 |
|
return [ |
|
f"layers.{fake_layer_idx}.{suffix}" |
|
for suffix in ["linear.bias", "linear.weight", "ln.bias", "ln.weight"] |
|
] |
|
|
|
def embed_weights(self) -> List[str]: |
|
fake_layer_idx = self.config.n_layer + 1 |
|
return [ |
|
"layers.0.wte.weight", |
|
f"layers.{fake_layer_idx}.linear.weight", |
|
f"layers.{fake_layer_idx}.linear.bias", |
|
] |
|
|
|
def layer_weight_formats(self) -> List[str]: |
|
return [ |
|
("layers.{idx}." + suffix) |
|
for suffix in [ |
|
"ln.bias", |
|
"ln.weight", |
|
"mixer.Wqkv.bias", |
|
"mixer.Wqkv.weight", |
|
"mixer.out_proj.bias", |
|
"mixer.out_proj.weight", |
|
"mixer.rotary_emb.inv_freq", |
|
"mlp.fc1.bias", |
|
"mlp.fc1.weight", |
|
"mlp.fc2.bias", |
|
"mlp.fc2.weight", |
|
] |
|
] |
|
|
|
def num_layers(self, config: PretrainedConfig) -> int: |
|
return config.n_layer |
|
|
|
def num_layers_config_key(self) -> str: |
|
return "n_layer" |
|
|
|
|
|
PHI2_INFO = StaticTensorNames( |
|
name="PhiForCausalLM", |
|
pre_weight_names=["transformer.embd.wte.weight"], |
|
post_weight_names=[ |
|
"lm_head.linear.bias", |
|
"lm_head.linear.weight", |
|
"lm_head.ln.bias", |
|
"lm_head.ln.weight", |
|
], |
|
embed_weight_names=["lm_head.linear.weight", "transformer.embd.wte.weight"], |
|
layer_prefix_format="transformer.h.{idx}", |
|
layer_weight_suffixes=[ |
|
"ln.bias", |
|
"ln.weight", |
|
"mixer.out_proj.bias", |
|
"mixer.out_proj.weight", |
|
"mixer.Wqkv.bias", |
|
"mixer.Wqkv.weight", |
|
"mlp.fc1.bias", |
|
"mlp.fc1.weight", |
|
"mlp.fc2.bias", |
|
"mlp.fc2.weight", |
|
], |
|
num_layers_key="n_layer", |
|
) |
|
|
|
|
|
PHI2_INFO_AGAIN_BUT_DIFFERENT = StaticTensorNames( |
|
name="PhiForCausalLM", |
|
pre_weight_names=["model.embed_tokens.weight"], |
|
post_weight_names=[ |
|
"lm_head.bias", |
|
"lm_head.weight", |
|
"model.final_layernorm.bias", |
|
"model.final_layernorm.weight", |
|
], |
|
embed_weight_names=["lm_head.weight", "model.embed_tokens.weight"], |
|
layer_prefix_format="model.layers.{idx}", |
|
layer_weight_suffixes=[ |
|
"input_layernorm.bias", |
|
"input_layernorm.weight", |
|
"self_attn.dense.bias", |
|
"self_attn.dense.weight", |
|
"self_attn.q_proj.bias", |
|
"self_attn.q_proj.weight", |
|
"self_attn.k_proj.bias", |
|
"self_attn.k_proj.weight", |
|
"self_attn.v_proj.bias", |
|
"self_attn.v_proj.weight", |
|
"mlp.fc1.bias", |
|
"mlp.fc1.weight", |
|
"mlp.fc2.bias", |
|
"mlp.fc2.weight", |
|
], |
|
) |
|
|
|
|
|
BAICHUAN_INFO = StaticTensorNames( |
|
name="BaichuanForCausalLM", |
|
pre_weight_names=["model.embed_tokens.weight"], |
|
post_weight_names=["model.norm.weight", "lm_head.weight"], |
|
embed_weight_names=["model.embed_tokens.weight", "lm_head.weight"], |
|
layer_prefix_format="model.layers.{idx}", |
|
layer_weight_suffixes=[ |
|
"input_layernorm.weight", |
|
"self_attn.W_pack.weight", |
|
"self_attn.o_proj.weight", |
|
"post_attention_layernorm.weight", |
|
"mlp.gate_proj.weight", |
|
"mlp.down_proj.weight", |
|
"mlp.up_proj.weight", |
|
], |
|
) |
|
|
|
|
|
def get_architecture_info(config: PretrainedConfig) -> StaticTensorNames: |
|
if len(config.architectures) != 1: |
|
raise RuntimeError("More than one architecture in config?") |
|
|
|
arch_name = config.architectures[0] |
|
if arch_name == PhiTensorNames.architecture_name: |
|
return PhiTensorNames(config) |
|
|
|
if arch_name == PHI2_INFO.name: |
|
if config.model_type == "phi-msft": |
|
return PHI2_INFO |
|
elif config.model_type == "phi": |
|
return PHI2_INFO_AGAIN_BUT_DIFFERENT |
|
|
|
supported = [ |
|
LLAMA_INFO, |
|
MISTRAL_INFO, |
|
GPT_NEOX_INFO, |
|
QWEN_INFO, |
|
GPT2_INFO, |
|
GPT2_SEQCLASS_INFO, |
|
CHATGLM_INFO, |
|
STABLELM_INFO, |
|
JAIS_INFO, |
|
BAICHUAN_INFO, |
|
FALCON_INFO, |
|
] |
|
for arch in supported: |
|
if arch.name == arch_name: |
|
return arch |
|
|
|
raise RuntimeError(f"Unsupported architecture {arch_name}") |
|
``` |
|
|
|
3) replace `configs.json` with the one from **this repo** |
|
4) you need to add `modeling_phi.py` and `configurations.phi` from **this repo** to your repo |
|
|