gemma-4-31B-it-sharded / shard_safetensors.py
TusharGoel's picture
Upload folder using huggingface_hub
f8907eb verified
Raw
History Blame
5.17 kB
#!/usr/bin/env python3
import argparse
import json
import re
import shutil
from pathlib import Path
from typing import Dict, List, Tuple
from safetensors import safe_open
from safetensors.torch import save_file
TARGET_PATTERN = re.compile(r"^model-\d{5}-of-\d{5}\.safetensors$")
def tensor_nbytes(tensor) -> int:
return tensor.numel() * tensor.element_size()
def get_source_files(model_dir: Path) -> Tuple[List[Path], Path | None]:
index_path = model_dir / "model.safetensors.index.json"
if index_path.exists():
with index_path.open("r", encoding="utf-8") as f:
index_data = json.load(f)
weight_map = index_data.get("weight_map", {})
files = sorted({model_dir / fname for fname in weight_map.values()})
files = [p for p in files if p.exists()]
if files:
return files, index_path
files = sorted(
p for p in model_dir.glob("*.safetensors") if p.is_file() and not TARGET_PATTERN.match(p.name)
)
if files:
return files, index_path if index_path.exists() else None
files = sorted(p for p in model_dir.glob("*.safetensors") if p.is_file())
return files, index_path if index_path.exists() else None
def shard_model(model_dir: Path, max_shard_size_gb: float, prefix: str = "model") -> None:
source_files, old_index_path = get_source_files(model_dir)
if not source_files:
raise RuntimeError(f"No .safetensors files found in {model_dir}")
max_shard_bytes = int(max_shard_size_gb * 1024**3)
if max_shard_bytes <= 0:
raise ValueError("max_shard_size_gb must be > 0")
tmp_dir = model_dir / "_reshard_tmp"
if tmp_dir.exists():
shutil.rmtree(tmp_dir)
tmp_dir.mkdir(parents=True, exist_ok=False)
weight_map_tmp: Dict[str, str] = {}
shard_files_tmp: List[Path] = []
current_tensors = {}
current_size = 0
total_size = 0
shard_idx = 0
def flush_current_shard() -> None:
nonlocal shard_idx, current_tensors, current_size
if not current_tensors:
return
shard_idx += 1
tmp_name = f"tmp-{shard_idx:05d}.safetensors"
tmp_path = tmp_dir / tmp_name
save_file(current_tensors, str(tmp_path))
shard_files_tmp.append(tmp_path)
for key in current_tensors:
weight_map_tmp[key] = tmp_name
current_tensors = {}
current_size = 0
for src in source_files:
with safe_open(str(src), framework="pt", device="cpu") as f:
for key in f.keys():
tensor = f.get_tensor(key)
t_size = tensor_nbytes(tensor)
total_size += t_size
if current_tensors and current_size + t_size > max_shard_bytes:
flush_current_shard()
current_tensors[key] = tensor
current_size += t_size
flush_current_shard()
if not shard_files_tmp:
shutil.rmtree(tmp_dir, ignore_errors=True)
raise RuntimeError("No shards were produced")
total_shards = len(shard_files_tmp)
final_files: List[Path] = []
tmp_to_final: Dict[str, str] = {}
for i, tmp_path in enumerate(shard_files_tmp, start=1):
final_name = f"{prefix}-{i:05d}-of-{total_shards:05d}.safetensors"
final_files.append(model_dir / final_name)
tmp_to_final[tmp_path.name] = final_name
# Remove old index first (if present) so a failed file move can't leave stale mapping.
if old_index_path and old_index_path.exists():
old_index_path.unlink()
for src in source_files:
if src.exists():
src.unlink()
for tmp_path in shard_files_tmp:
final_name = tmp_to_final[tmp_path.name]
shutil.move(str(tmp_path), str(model_dir / final_name))
new_weight_map = {k: tmp_to_final[v] for k, v in weight_map_tmp.items()}
index_data = {"metadata": {"total_size": total_size}, "weight_map": new_weight_map}
with (model_dir / "model.safetensors.index.json").open("w", encoding="utf-8") as f:
json.dump(index_data, f, indent=2, sort_keys=True)
f.write("\n")
shutil.rmtree(tmp_dir, ignore_errors=True)
print(f"Sharded {len(new_weight_map)} tensors into {total_shards} files.")
print(f"Max shard size: {max_shard_size_gb} GB")
print("Old .safetensors files deleted and replaced.")
def main() -> None:
parser = argparse.ArgumentParser(
description="Shard a safetensors model into max-size shards and replace old safetensors."
)
parser.add_argument(
"--model-dir",
type=Path,
default=Path("."),
help="Directory containing model safetensors (default: current directory).",
)
parser.add_argument(
"--max-shard-size-gb",
type=float,
default=5.0,
help="Maximum shard size in GB (default: 5.0).",
)
parser.add_argument(
"--prefix",
type=str,
default="model",
help="Shard filename prefix (default: model).",
)
args = parser.parse_args()
shard_model(args.model_dir.resolve(), args.max_shard_size_gb, args.prefix)
if __name__ == "__main__":
main()