Download code/training/scripts/merge_lora.py from adhikjoshi/infosec-v1: direct link, hf CLI and curl.
- Browser
- Download file 3.36 kB
-
https://huggingface.co/adhikjoshi/infosec-v1/resolve/main/code/training/scripts/merge_lora.py
- Command line
-
hf download hf://adhikjoshi/infosec-v1/code/training/scripts/merge_lora.py
-
curl -L -o merge_lora.py https://huggingface.co/adhikjoshi/infosec-v1/resolve/main/code/training/scripts/merge_lora.py
3.36 kB
| #!/usr/bin/env python3 | |
| """Merge a selected LoRA adapter into the base model.""" | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| from typing import Any | |
| import torch | |
| import yaml | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--config", required=True) | |
| parser.add_argument("--adapter", help="Adapter checkpoint path. Defaults to run.output_dir.") | |
| parser.add_argument("--output", help="Merged output path. Defaults to run.merged_output_dir.") | |
| parser.add_argument("--dtype", default=None, choices=["bfloat16", "float16", "float32"]) | |
| return parser.parse_args() | |
| def read_yaml(path: str | Path) -> dict[str, Any]: | |
| with Path(path).open("r", encoding="utf-8") as fh: | |
| payload = yaml.safe_load(fh) or {} | |
| if not isinstance(payload, dict): | |
| raise TypeError(f"Expected a YAML mapping in {path}") | |
| return payload | |
| def torch_dtype(name: str): | |
| return { | |
| "bfloat16": torch.bfloat16, | |
| "float16": torch.float16, | |
| "float32": torch.float32, | |
| }[name] | |
| def load_base_model(transformers_module, model_name: str, dtype, trust_remote_code: bool): | |
| errors: list[str] = [] | |
| for class_name in [ | |
| "AutoModelForMultimodalLM", | |
| "AutoModelForImageTextToText", | |
| "AutoModelForVision2Seq", | |
| "AutoModelForCausalLM", | |
| ]: | |
| model_cls = getattr(transformers_module, class_name, None) | |
| if model_cls is None: | |
| continue | |
| try: | |
| return model_cls.from_pretrained( | |
| model_name, | |
| torch_dtype=dtype, | |
| trust_remote_code=trust_remote_code, | |
| device_map="auto", | |
| ) | |
| except Exception as exc: | |
| errors.append(f"{class_name}: {exc!r}") | |
| raise RuntimeError("Could not load base model:\n" + "\n".join(errors)) | |
| def main() -> int: | |
| args = parse_args() | |
| config = read_yaml(args.config) | |
| run_cfg = config["run"] | |
| model_cfg = config["model"] | |
| adapter_path = Path(args.adapter or run_cfg["output_dir"]) | |
| output_path = Path(args.output or run_cfg["merged_output_dir"]) | |
| dtype_name = args.dtype or str(config.get("merge", {}).get("merged_dtype", model_cfg.get("dtype", "bfloat16"))) | |
| import transformers | |
| from peft import PeftModel | |
| from transformers import AutoTokenizer | |
| dtype = torch_dtype(dtype_name) | |
| base = load_base_model( | |
| transformers, | |
| model_cfg["name_or_path"], | |
| dtype=dtype, | |
| trust_remote_code=bool(model_cfg.get("trust_remote_code", True)), | |
| ) | |
| model = PeftModel.from_pretrained(base, adapter_path) | |
| merged = model.merge_and_unload() | |
| output_path.mkdir(parents=True, exist_ok=True) | |
| merged.save_pretrained(output_path, safe_serialization=True) | |
| tokenizer = AutoTokenizer.from_pretrained(model_cfg["name_or_path"], trust_remote_code=True) | |
| tokenizer.save_pretrained(output_path) | |
| manifest = { | |
| "base_model": model_cfg["name_or_path"], | |
| "adapter": str(adapter_path), | |
| "output": str(output_path), | |
| "dtype": dtype_name, | |
| } | |
| (output_path / "merge_manifest.json").write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") | |
| print(json.dumps(manifest, indent=2)) | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |