Download code/llmc_training/dev/eval/import_hf.py from SciCode/MileTone_2: direct link, hf CLI and curl.
- Browser
- Download file 5.75 kB
-
https://huggingface.co/SciCode/MileTone_2/resolve/main/code/llmc_training/dev/eval/import_hf.py
- Command line
-
hf download hf://SciCode/MileTone_2/code/llmc_training/dev/eval/import_hf.py
-
curl -L -o import_hf.py https://huggingface.co/SciCode/MileTone_2/resolve/main/code/llmc_training/dev/eval/import_hf.py
5.75 kB
| """ | |
| Convert a HuggingFace GPT-2-architecture model to llm.c binary format (version 5, bf16). | |
| Usage: | |
| python import_hf.py --input <hf_model_dir> --output <output.bin> | |
| The output is compatible with train_gpt2cu's -e flag. | |
| """ | |
| import argparse | |
| import math | |
| import struct | |
| import numpy as np | |
| import torch | |
| from transformers import GPT2LMHeadModel, AutoConfig, AutoModelForCausalLM | |
| def fp32_to_bf16_int16(tensor: torch.Tensor) -> np.ndarray: | |
| """Convert float32 tensor to bfloat16 stored as int16 (same byte layout).""" | |
| bf16 = tensor.to(torch.bfloat16) | |
| return bf16.view(torch.int16).cpu().numpy() | |
| def write_model(model: GPT2LMHeadModel, output_path: str) -> None: | |
| cfg = model.config | |
| V = cfg.vocab_size | |
| maxT = cfg.n_positions | |
| L = cfg.n_layer | |
| H = cfg.n_head | |
| C = cfg.n_embd | |
| # Pad vocab to next multiple of 128 | |
| Vp = math.ceil(V / 128) * 128 | |
| print(f"V={V}, Vp={Vp}, maxT={maxT}, L={L}, H={H}, C={C}") | |
| # Build header: 256 int32 values | |
| header = np.zeros(256, dtype=np.int32) | |
| header[0] = 20240326 # magic | |
| header[1] = 5 # version 5 = bf16 + padded vocab | |
| header[2] = maxT | |
| header[3] = V | |
| header[4] = L | |
| header[5] = H | |
| header[6] = C | |
| header[7] = Vp | |
| sd = model.state_dict() | |
| def get(key): | |
| return sd[key].float() | |
| with open(output_path, "wb") as f: | |
| f.write(header.tobytes()) | |
| # wte: (Vp, C) — pad vocab rows with zeros | |
| wte = get("transformer.wte.weight") # (V, C) | |
| pad = torch.zeros(Vp - V, C, dtype=torch.float32) | |
| wte_padded = torch.cat([wte, pad], dim=0) # (Vp, C) | |
| f.write(fp32_to_bf16_int16(wte_padded).tobytes()) | |
| # wpe: (maxT, C) | |
| f.write(fp32_to_bf16_int16(get("transformer.wpe.weight")).tobytes()) | |
| # Per-layer weights | |
| for i in range(L): | |
| # ln1w, ln1b: (C,) | |
| f.write(fp32_to_bf16_int16(get(f"transformer.h.{i}.ln_1.weight")).tobytes()) | |
| for i in range(L): | |
| f.write(fp32_to_bf16_int16(get(f"transformer.h.{i}.ln_1.bias")).tobytes()) | |
| # qkvw: (L, 3C, C) — HF stores c_attn.weight as (C, 3C), transpose to (3C, C) | |
| for i in range(L): | |
| w = get(f"transformer.h.{i}.attn.c_attn.weight") # (C, 3C) | |
| f.write(fp32_to_bf16_int16(w.T.contiguous()).tobytes()) # (3C, C) | |
| # qkvb: (L, 3C) | |
| for i in range(L): | |
| f.write(fp32_to_bf16_int16(get(f"transformer.h.{i}.attn.c_attn.bias")).tobytes()) | |
| # attprojw: (L, C, C) — HF stores c_proj.weight as (C, C), transpose | |
| for i in range(L): | |
| w = get(f"transformer.h.{i}.attn.c_proj.weight") # (C, C) | |
| f.write(fp32_to_bf16_int16(w.T.contiguous()).tobytes()) | |
| # attprojb: (L, C) | |
| for i in range(L): | |
| f.write(fp32_to_bf16_int16(get(f"transformer.h.{i}.attn.c_proj.bias")).tobytes()) | |
| # ln2w, ln2b | |
| for i in range(L): | |
| f.write(fp32_to_bf16_int16(get(f"transformer.h.{i}.ln_2.weight")).tobytes()) | |
| for i in range(L): | |
| f.write(fp32_to_bf16_int16(get(f"transformer.h.{i}.ln_2.bias")).tobytes()) | |
| # fcw: (L, 4C, C) — HF c_fc.weight is (C, 4C), transpose to (4C, C) | |
| for i in range(L): | |
| w = get(f"transformer.h.{i}.mlp.c_fc.weight") # (C, 4C) | |
| f.write(fp32_to_bf16_int16(w.T.contiguous()).tobytes()) | |
| # fcb: (L, 4C) | |
| for i in range(L): | |
| f.write(fp32_to_bf16_int16(get(f"transformer.h.{i}.mlp.c_fc.bias")).tobytes()) | |
| # fcprojw: (L, C, 4C) — HF c_proj.weight is (4C, C), transpose to (C, 4C) | |
| for i in range(L): | |
| w = get(f"transformer.h.{i}.mlp.c_proj.weight") # (4C, C) | |
| f.write(fp32_to_bf16_int16(w.T.contiguous()).tobytes()) | |
| # fcprojb: (L, C) | |
| for i in range(L): | |
| f.write(fp32_to_bf16_int16(get(f"transformer.h.{i}.mlp.c_proj.bias")).tobytes()) | |
| # lnfw, lnfb | |
| f.write(fp32_to_bf16_int16(get("transformer.ln_f.weight")).tobytes()) | |
| f.write(fp32_to_bf16_int16(get("transformer.ln_f.bias")).tobytes()) | |
| size_mb = __import__("os").path.getsize(output_path) / 1e6 | |
| print(f"Saved {output_path} ({size_mb:.1f} MB)") | |
| def main(): | |
| parser = argparse.ArgumentParser("HF GPT-2 → llm.c bf16 binary") | |
| parser.add_argument("--input", "-i", required=True, help="HF model directory") | |
| parser.add_argument("--output", "-o", required=True, help="Output .bin path") | |
| args = parser.parse_args() | |
| print(f"Loading model from {args.input} ...") | |
| # Support loading when config and weights are in different snapshot dirs. | |
| # If args.input has no config.json, look for one in sibling snapshot dirs. | |
| import os, glob | |
| cfg_path = os.path.join(args.input, "config.json") | |
| if not os.path.exists(cfg_path): | |
| snapshots_dir = os.path.dirname(args.input) | |
| candidates = sorted(glob.glob(os.path.join(snapshots_dir, "*/config.json"))) | |
| if not candidates: | |
| raise FileNotFoundError(f"No config.json found near {args.input}") | |
| cfg_path = candidates[0] | |
| print(f"Using config from: {cfg_path}") | |
| from transformers import GPT2Config | |
| config = GPT2Config.from_pretrained(os.path.dirname(cfg_path)) | |
| model = GPT2LMHeadModel(config) | |
| from safetensors.torch import load_file | |
| weights_file = os.path.join(args.input, "model.safetensors") | |
| if os.path.exists(weights_file): | |
| sd = load_file(weights_file) | |
| model.load_state_dict(sd, strict=False) | |
| print(f"Loaded safetensors weights from {weights_file}") | |
| else: | |
| model = GPT2LMHeadModel.from_pretrained(args.input, config=config) | |
| model.eval() | |
| write_model(model, args.output) | |
| if __name__ == "__main__": | |
| main() | |