Files
2026-07-13 12:37:59 +08:00

174 lines
7.1 KiB
Python

"""
Script to convert GPT2 models from llm.c binary format to Hugging Face
It can optinally upload to your account on Hugging Face if you have the CLI:
pip install -U "huggingface_hub[cli]"
huggingface-cli login
Export to a local HF model:
python export_hf.py --input input_file.bin --output output_dir
Export to a local HF model and also push to your account on Hugging Face:
python export_hf.py --input input_file.bin --output output_dir --push true
"""
import numpy as np
import torch
import argparse, sys
from transformers import GPT2Config, GPT2Tokenizer, GPT2LMHeadModel
# -----------------------------------------------------------------------------
# Tensor functions for both bfloat16 (from int16) and normal float32
# Both return float32 tensors
def tensor_bf16(data_int16, transpose=False):
if transpose:
data_int16 = data_int16.transpose(1,0)
return torch.tensor(data_int16).view(torch.bfloat16).to(torch.float32)
def tensor_fp32(data_float32, transpose=False):
if transpose:
data_float32 = data_float32.transpose(1,0)
return torch.tensor(data_float32).view(torch.float32)
# -----------------------------------------------------------------------------
# Main conversion function
def convert(filepath, output, push_to_hub=False, out_dtype="bfloat16"):
print(f"Converting model {filepath} to {output} in {out_dtype} format and pushing to Hugging Face: {push_to_hub}")
f = open(filepath, 'rb')
# Read in our header, checking the magic number and version
# version 3 = fp32, padded vocab
# version 5 = bf16, padded vocab
model_header = np.frombuffer(f.read(256*4), dtype=np.int32)
if model_header[0] != 20240326:
print("ERROR: magic number mismatch in the data .bin file!")
exit(1)
version = model_header[1]
if not version in [3, 5]:
print("Bad version in model file")
exit(1)
# Load in our model parameters
maxT = model_header[2].item() # max sequence length
V = model_header[3].item() # vocab size
L = model_header[4].item() # num layers
H = model_header[5].item() # num heads
C = model_header[6].item() # channels
Vp = model_header[7].item() # padded vocab size
print(f"{version=}, {maxT=}, {V=}, {Vp=}, {L=}, {H=}, {C=}")
# Define the shapes of our parameters
shapes = {
'wte': (Vp, C),
'wpe': (maxT, C),
'ln1w': (L, C),
'ln1b': (L, C),
'qkvw': (L, 3 * C, C),
'qkvb': (L, 3 * C),
'attprojw': (L, C, C),
'attprojb': (L, C),
'ln2w': (L, C),
'ln2b': (L, C),
'fcw': (L, 4 * C, C),
'fcb': (L, 4 * C),
'fcprojw': (L, C, 4 * C),
'fcprojb': (L, C),
'lnfw': (C,),
'lnfb': (C,),
}
# Load in our weights given our parameter shapes
dtype = np.float32 if version == 3 else np.int16
w = {}
for key, shape in shapes.items():
num_elements = np.prod(shape)
data = np.frombuffer(f.read(num_elements * np.dtype(dtype).itemsize), dtype=dtype)
w[key] = data.reshape(shape)
# The binary file saves the padded vocab - drop the padding back to GPT2 size
if shape[0] == Vp:
w[key] = w[key].reshape(shape)[:(V-Vp), :]
# Ensure the file is fully read and then close
assert f.read() == b''
f.close()
# Map to our model dict, the tensors at this stage are always fp32
mk_tensor = {
3 : tensor_fp32,
5 : tensor_bf16,
}[version]
model_dict = {}
model_dict['transformer.wte.weight'] = mk_tensor(w['wte'])
model_dict['transformer.wpe.weight'] = mk_tensor(w['wpe'])
model_dict['lm_head.weight'] = model_dict['transformer.wte.weight'] # Tie weights
for i in range(L):
model_dict[f'transformer.h.{i}.ln_1.weight'] = mk_tensor(w['ln1w'][i])
model_dict[f'transformer.h.{i}.ln_1.bias'] = mk_tensor(w['ln1b'][i])
model_dict[f'transformer.h.{i}.attn.c_attn.weight'] = mk_tensor(w['qkvw'][i], True)
model_dict[f'transformer.h.{i}.attn.c_attn.bias'] = mk_tensor(w['qkvb'][i])
model_dict[f'transformer.h.{i}.attn.c_proj.weight'] = mk_tensor(w['attprojw'][i], True)
model_dict[f'transformer.h.{i}.attn.c_proj.bias'] = mk_tensor(w['attprojb'][i])
model_dict[f'transformer.h.{i}.ln_2.weight'] = mk_tensor(w['ln2w'][i])
model_dict[f'transformer.h.{i}.ln_2.bias'] = mk_tensor(w['ln2b'][i])
model_dict[f'transformer.h.{i}.mlp.c_fc.weight'] = mk_tensor(w['fcw'][i], True)
model_dict[f'transformer.h.{i}.mlp.c_fc.bias'] = mk_tensor(w['fcb'][i])
model_dict[f'transformer.h.{i}.mlp.c_proj.weight'] = mk_tensor(w['fcprojw'][i], True)
model_dict[f'transformer.h.{i}.mlp.c_proj.bias'] = mk_tensor(w['fcprojb'][i])
model_dict['transformer.ln_f.weight'] = mk_tensor(w['lnfw'])
model_dict['transformer.ln_f.bias'] = mk_tensor(w['lnfb'])
# Create a GPT-2 model instance, in the requested dtype
config = GPT2Config(vocab_size = V,
n_positions = maxT,
n_ctx = maxT,
n_embd = C,
n_layer = L,
n_head = H)
model = GPT2LMHeadModel(config)
if out_dtype == "bfloat16":
model = model.to(torch.bfloat16)
# Set the model dict and save
model.load_state_dict(model_dict)
model.save_pretrained(output, max_shard_size="5GB", safe_serialization=True)
# Copy over a standard gpt2 tokenizer
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
tokenizer.save_pretrained(output)
if push_to_hub:
print(f"Uploading {output} to Hugging Face")
model.push_to_hub(output)
tokenizer.push_to_hub(output)
def spin(output):
print("Taking the exported model for a spin...")
print('-'*80)
from transformers import AutoModelForCausalLM, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(output)
model = AutoModelForCausalLM.from_pretrained(output, attn_implementation="flash_attention_2", torch_dtype=torch.bfloat16, device_map='cuda')
model.eval()
tokens = tokenizer.encode("During photosynthesis in green plants", return_tensors="pt")
tokens = tokens.to('cuda')
output = model.generate(tokens, max_new_tokens=64, repetition_penalty=1.3)
samples = tokenizer.batch_decode(output)
for sample in samples:
print('-'*30)
print(sample)
# -----------------------------------------------------------------------------
if __name__== '__main__':
parser=argparse.ArgumentParser()
parser.add_argument("--input", "-i", help="The name of the llm.c model.bin file", type=str, required=True)
parser.add_argument("--output","-o", help="The Hugging Face output model directory", type=str, required=True)
parser.add_argument("--dtype", "-d", help="Output as either float32 or bfloat16 (default)", type=str, default="bfloat16")
parser.add_argument("--push", "-p", help="Push the model to your Hugging Face account", type=bool, default=False)
parser.add_argument("--spin", "-s", help="Take the model for a spin at the end?", type=bool, default=True)
args = parser.parse_args()
convert(args.input, args.output, args.push, args.dtype)
if args.spin:
spin(args.output)