Files
2026-07-13 13:24:13 +08:00

173 lines
7.1 KiB
Python

import argparse
import os
from collections import OrderedDict
from glob import glob
import torch
from safetensors import safe_open
from transformers import AutoTokenizer, AutoModelForCausalLM, AutoModel, AutoConfig
from accelerate import init_empty_weights
from glob import glob
# permute for sliced rotary
def permute(w, n_heads, dim1, dim2):
"""
"""
return w.view(n_heads, dim1 // n_heads // 2, 2, dim2).transpose(1, 2).reshape(dim1, dim2)
def split_weights(state_dict: OrderedDict, tp_size: int):
new_state_dicts = [OrderedDict() for _ in range(tp_size)]
split_lists = ["q_proj.weight", "k_proj.weight", "v_proj.weight", "gate_proj.weight", "up_proj.weight",
"q_proj.bias", "k_proj.bias", "v_proj.bias",
"embed_tokens.weight", "lm_head.weight"]
for k, v in state_dict.items():
if any(k.endswith(name) for name in split_lists):
tensor = list(torch.chunk(v, tp_size, dim=0))
for i, t in enumerate(tensor):
new_state_dicts[i][k] = t.detach().clone()
elif k.endswith("o_proj.weight") or k.endswith("down_proj.weight"):
tensor = list(torch.chunk(v, tp_size, dim=1))
for i, t in enumerate(tensor):
new_state_dicts[i][k] = t.detach().clone()
else:
for i in range(tp_size):
new_state_dicts[i][k] = v.detach().clone()
print(new_state_dicts[0].keys())
return new_state_dicts
def merge_weights(state_dicts: list, merge_avg: bool = False):
merged_state_dict = OrderedDict()
col_splits = ["q_proj.weight", "k_proj.weight", "v_proj.weight", "gate_proj.weight", "up_proj.weight",
"q_proj.bias", "k_proj.bias", "v_proj.bias",
"embed_tokens.weight", "lm_head.weight"]
row_splits = ["o_proj.weight", "down_proj.weight"]
for k in state_dicts[0].keys():
if any(k.endswith(name) for name in col_splits):
merged_state_dict[k] = torch.cat([state_dict[k] for state_dict in state_dicts], dim=0)
elif any(k.endswith(name) for name in row_splits):
merged_state_dict[k] = torch.cat([state_dict[k] for state_dict in state_dicts], dim=1)
else:
merged_state_dict[k] = state_dicts[0][k]
if merge_avg:
for i in range(1, len(state_dicts)):
merged_state_dict[k] += state_dicts[i][k]
merged_state_dict[k] /= len(state_dicts)
# print(merged_state_dict.keys())
return merged_state_dict
def write_model(input_base_path, tp_size: int):
# model = LlamaForCausalLM.from_pretrained(input_base_path, torch_dtype="auto", device_map="cpu")
config = AutoConfig.from_pretrained(input_base_path)
with init_empty_weights():
model = AutoModel.from_config(config)
tokenizer = AutoTokenizer.from_pretrained(input_base_path)
weights = OrderedDict()
files = glob(os.path.join(input_base_path, "*.safetensors"))
if len(files):
for weight_path in files:
with safe_open(weight_path, framework="pt", device="cpu") as f:
for key in f.keys():
weights[key] = f.get_tensor(key).clone()
else:
for weight_path in glob(os.path.join(input_base_path, "pytorch_model*.bin")):
weights.update(torch.load(weight_path, map_location="cpu"))
new_state_dicts = split_weights(weights, tp_size)
for i, state_dict in enumerate(new_state_dicts):
output_folder = os.path.join(input_base_path, f"mp_{i}-of-{tp_size}")
assert not os.path.exists(output_folder), f"Folder {output_folder} already exists. Please remove it before splitting."
os.makedirs(output_folder, exist_ok=True)
model.save_pretrained(output_folder, state_dict=state_dict, safe_serialization=False)
tokenizer.save_pretrained(output_folder)
print(f"Model saved to {output_folder}")
def merge_model(input_base_path, tp_size: int, merge_avg: bool = False):
config = AutoConfig.from_pretrained(os.path.join(input_base_path, f"mp_0-of-{tp_size}"))
if any(arch in config.architectures for arch in ["LlamaForCausalLMDPO"]):
config.architectures = ["LlamaForCausalLM"]
with init_empty_weights():
model = AutoModelForCausalLM.from_config(config)
tokenizer = AutoTokenizer.from_pretrained(input_base_path)
state_dicts = []
for i in range(tp_size):
folder = os.path.join(input_base_path, f"mp_{i}-of-{tp_size}")
print(folder)
weights = {}
files = glob(os.path.join(folder, "*.safetensors"))
if len(files):
for weight_path in files:
with safe_open(weight_path, framework="pt", device="cpu") as f:
for key in f.keys():
weights[key] = f.get_tensor(key).clone()
else:
for weight_path in glob(os.path.join(folder, "pytorch_model*.bin")):
weights.update(torch.load(weight_path, map_location="cpu"))
for k in weights.keys():
if weights[k].dtype != config.torch_dtype:
weights[k] = weights[k].to(config.torch_dtype)
state_dicts.append(weights)
merged_state_dict = merge_weights(state_dicts, merge_avg)
if merge_avg:
output_folder = os.path.join(input_base_path, "merged_avg")
model.save_pretrained(output_folder, state_dict=merged_state_dict, safe_serialization=False)
tokenizer.save_pretrained(output_folder)
config.save_pretrained(output_folder)
print(f"Model saved to {output_folder}")
else:
tmp_files = glob(os.path.join(input_base_path, "pytorch_model*.bin"))
assert len(tmp_files) == 0, "Found existing pytorch_model*.bin files. Please remove them before merging."
model.save_pretrained(input_base_path, state_dict=merged_state_dict, safe_serialization=False)
# tokenizer.save_pretrained(input_base_path)
config.save_pretrained(input_base_path)
print(f"Model saved to {input_base_path}")
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--input_dir",
help="Location of LLaMA weights, which contains tokenizer.model and model folders",
)
parser.add_argument("--tp_size", help="Tensor model parallel size.", default=2, type=int)
parser.add_argument("--do_split", help="Split the model into shards.", action="store_true", default=False)
parser.add_argument("--merge_avg", help="Merge the model shards using average.", action="store_true", default=False)
parser.add_argument("--use_ds_weight", default=False, action="store_true")
args = parser.parse_args()
if os.path.exists(args.input_dir):
input_dirs = [args.input_dir]
else:
input_dirs = list(glob(args.input_dir))
print(input_dirs)
for _dir in input_dirs:
if args.do_split:
write_model(
input_base_path=_dir,
tp_size=args.tp_size,
)
else:
merge_model(
input_base_path=_dir,
tp_size=args.tp_size,
merge_avg=args.merge_avg,
)
if __name__ == "__main__":
main()