Download scripts/export_hf.py from CodeSoft/MetaDiffusion-150M-ChatBase: direct link, hf CLI and curl.
- Browser
- Download file 7.06 kB
-
https://huggingface.co/CodeSoft/MetaDiffusion-150M-ChatBase/resolve/main/scripts/export_hf.py
- Command line
-
hf download hf://CodeSoft/MetaDiffusion-150M-ChatBase/scripts/export_hf.py
-
curl -L -o export_hf.py https://huggingface.co/CodeSoft/MetaDiffusion-150M-ChatBase/resolve/main/scripts/export_hf.py
7.06 kB
| #!/usr/bin/env python3 | |
| """ | |
| export_hf.py: Export a chat-SFT checkpoint to a HuggingFace-style dir. | |
| Output dir contains: | |
| model.safetensors (weights, "model."-prefixed keys) | |
| config.json (architecture, mask_vocab_size = 32010) | |
| generation_config.json (sampling defaults for chat) | |
| tokenizer.json / tokenizer_config.json (with ChatML + rainbow tokens) | |
| README.md (minimal) | |
| Usage: | |
| python3 export_hf.py \ | |
| --checkpoint checkpoints_chat/step_3000.pt \ | |
| --tokenizer data/no_robots_chatml/tokenizer \ | |
| --output MetaDiffusion-150M-Chat/ | |
| """ | |
| import argparse | |
| import json | |
| import shutil | |
| import sys | |
| from dataclasses import asdict | |
| from pathlib import Path | |
| import torch | |
| from safetensors.torch import save_file | |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) | |
| from model import MetaDiffusionConfig # noqa: E402 | |
| KEY_MAP = { | |
| "embed_tokens.weight": "model.embed_tokens.weight", | |
| "norm.weight": "model.norm.weight", | |
| "lm_head.weight": "model.lm_head.weight", | |
| } | |
| LAYER_KEY_MAP = { | |
| "input_layernorm.weight": "input_layernorm.weight", | |
| "self_attn.q_proj.weight": "self_attn.q_proj.weight", | |
| "self_attn.k_proj.weight": "self_attn.k_proj.weight", | |
| "self_attn.v_proj.weight": "self_attn.v_proj.weight", | |
| "self_attn.o_proj.weight": "self_attn.o_proj.weight", | |
| "post_attention_layernorm.weight": "post_attention_layernorm.weight", | |
| "mlp.gate_proj.weight": "mlp.gate_proj.weight", | |
| "mlp.up_proj.weight": "mlp.up_proj.weight", | |
| "mlp.down_proj.weight": "mlp.down_proj.weight", | |
| "timestep_residual.proj.weight": "timestep_residual.proj.weight", | |
| "timestep_residual.proj.bias": "timestep_residual.proj.bias", | |
| } | |
| TIMESTEP_KEY_MAP = { | |
| "timestep_emb.mlp.0.weight": "model.timestep_emb.mlp.0.weight", | |
| "timestep_emb.mlp.0.bias": "model.timestep_emb.mlp.0.bias", | |
| "timestep_emb.mlp.2.weight": "model.timestep_emb.mlp.2.weight", | |
| "timestep_emb.mlp.2.bias": "model.timestep_emb.mlp.2.bias", | |
| } | |
| GENERATION_CONFIG = { | |
| "bos_token_id": 0, | |
| "eos_token_id": 2, | |
| "pad_token_id": 1, | |
| "mask_token_id": 32000, | |
| "temperature": 0.7, | |
| "repetition_penalty": 1.5, | |
| "re_mask": 0.1, | |
| "num_steps": 128, | |
| "max_new_tokens": 96, | |
| "use_cache": False, | |
| "transformers_version": "4.40.0", | |
| } | |
| def remap_state_dict(state_dict, half=False): | |
| # Strip torch.compile's _orig_mod. prefix (old checkpoints saved from a | |
| # compiled model have it; training now saves clean keys) | |
| state_dict = {k.replace("_orig_mod.", "", 1) if k.startswith("_orig_mod.") else k: v | |
| for k, v in state_dict.items()} | |
| new_dict = {} | |
| for key, tensor in state_dict.items(): | |
| if key in KEY_MAP: | |
| new_key = KEY_MAP[key] | |
| elif key in TIMESTEP_KEY_MAP: | |
| new_key = TIMESTEP_KEY_MAP[key] | |
| elif key.startswith("layers."): | |
| parts = key.split(".") | |
| # layers.N.<component>.<rest> | |
| layer_idx, component = parts[1], parts[2] | |
| rest = ".".join(parts[3:]) | |
| comp_key = f"{component}.{rest}" if rest else component | |
| new_key = f"model.layers.{layer_idx}.{comp_key}" | |
| else: | |
| new_key = key | |
| new_dict[new_key] = tensor.half() if half else tensor.float() | |
| return new_dict | |
| def package_scripts(out): | |
| """Copy the runnable pipeline into out/scripts/ so the release is | |
| self-contained: chat, finetune, and re-export work from the artifact.""" | |
| src = Path(__file__).resolve().parent | |
| root = src.parent # model.py lives in the project root | |
| scripts_dir = out / "scripts" | |
| scripts_dir.mkdir(parents=True, exist_ok=True) | |
| for name in ["model.py", "chat.py", "prepare_data.py", "train_chat.py", | |
| "export_hf.py", "convert_data.py"]: | |
| cand = src / name if (src / name).exists() else root / name | |
| if cand.exists(): | |
| shutil.copy2(cand, scripts_dir / name) | |
| req = scripts_dir / "requirements.txt" | |
| if not req.exists(): | |
| req.write_text("torch>=2.2\ntransformers>=4.40\nsafetensors>=0.4\n" | |
| "datasets>=2.18\nnumpy>=1.26\n" | |
| "pandas>=2.0\npyarrow>=14.0\n") | |
| print(f"[*] Packaged scripts -> {scripts_dir}") | |
| def export(checkpoint_path, tokenizer_dir, output_dir, half=False): | |
| out = Path(output_dir) | |
| out.mkdir(parents=True, exist_ok=True) | |
| print(f"[*] Loading checkpoint {checkpoint_path}") | |
| ckpt = torch.load(checkpoint_path, map_location="cpu", weights_only=False) | |
| config = MetaDiffusionConfig( | |
| **{k: v for k, v in ckpt["config"].items() | |
| if k in MetaDiffusionConfig.__dataclass_fields__} | |
| ) | |
| config.tie_word_embeddings = False | |
| print("[*] Remapping state dict...") | |
| state_dict = remap_state_dict(ckpt["model_state_dict"], half=half) | |
| save_file(state_dict, out / "model.safetensors") | |
| print(f"[*] Saved {len(state_dict)} tensors -> {out / 'model.safetensors'} " | |
| f"({'fp16' if half else 'fp32'})") | |
| # Keep vocab fields consistent with the actual weights: the chat pipeline | |
| # resizes embed_tokens/lm_head to 32010 (Supra 32000 + chat tokens), and | |
| # the ckpt config still carries vocab_size=32000 from the base. Any loader | |
| # using vocab_size to size embeddings would fail with a size mismatch. | |
| n_vocab = state_dict["model.lm_head.weight"].shape[0] | |
| config.vocab_size = n_vocab | |
| config.mask_vocab_size = n_vocab | |
| print(f"[*] Vocab in config: {n_vocab} (matches weights)") | |
| config_dict = asdict(config) | |
| config_dict.pop("dtype", None) # transformers chokes on "torch.float32" strings | |
| config_dict["model_type"] = "metadiffusion" | |
| config_dict["architectures"] = ["MetaDiffusionForCausalLM"] | |
| with open(out / "config.json", "w") as f: | |
| json.dump(config_dict, f, indent=2) | |
| with open(out / "generation_config.json", "w") as f: | |
| json.dump(GENERATION_CONFIG, f, indent=2) | |
| # Copy tokenizer (has ChatML + rainbow tokens) | |
| tok_dir = Path(tokenizer_dir) | |
| for name in ["tokenizer.json", "tokenizer_config.json", "special_tokens_map.json"]: | |
| src = tok_dir / name | |
| if src.exists(): | |
| shutil.copy2(src, out / name) | |
| package_scripts(out) | |
| print(f"[*] Exported to {out}") | |
| print(" Vocab:", len(state_dict.get("model.lm_head.weight", [])), | |
| "| step:", ckpt.get("step")) | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Export chat-SFT checkpoint to HF-style dir") | |
| parser.add_argument("--checkpoint", required=True, help="step_*.pt or best.pt") | |
| parser.add_argument("--tokenizer", default="data/no_robots_chatml/tokenizer") | |
| parser.add_argument("--output", required=True, help="Output dir") | |
| parser.add_argument("--fp16", action="store_true", | |
| help="Save weights as fp16 (half the size; matches the " | |
| "base release format)") | |
| args = parser.parse_args() | |
| export(args.checkpoint, args.tokenizer, args.output, half=args.fp16) | |
| if __name__ == "__main__": | |
| main() | |