Naphula commited on
Commit
216fecf
·
verified ·
1 Parent(s): 01938b3

Upload 2 files

Browse files
Files changed (2) hide show
  1. model_index_json_generator.py +117 -0
  2. model_tools.md +47 -0
model_index_json_generator.py ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Coded with help from Grok, after OpenGPT and Gemini failed several times.
2
+ #!/usr/bin/env python3
3
+ """
4
+ Generate model.safetensors.index.json for modern HuggingFace sharded models
5
+ Works when:
6
+ - Shards have no tensor names
7
+ - Shards have no metadata
8
+ - Only raw binary data + external index expected
9
+ """
10
+
11
+ import json
12
+ import argparse
13
+ from pathlib import Path
14
+ from safetensors import safe_open
15
+
16
+ def generate_index(folder_path: str, output_file: str = "model.safetensors.index.json"):
17
+ folder = Path(folder_path)
18
+ if not folder.is_dir():
19
+ raise ValueError(f"Folder not found: {folder_path}")
20
+
21
+ # Find all shards: model-00001-of-00004.safetensors style
22
+ shards = sorted([
23
+ f for f in folder.glob("*.safetensors")
24
+ if f.name.startswith("model-") and "-of-" in f.name
25
+ ])
26
+
27
+ if not shards:
28
+ raise ValueError("No sharded model-*.safetensors files found!")
29
+
30
+ print(f"Found {len(shards)} shards:")
31
+ for s in shards:
32
+ print(f" - {s.name}")
33
+
34
+ weight_map = {}
35
+ total_size = 0
36
+
37
+ for shard in shards:
38
+ print(f"Scanning {shard.name} ...")
39
+ try:
40
+ with safe_open(str(shard), framework="pt", device="cpu") as f:
41
+ metadata = f.metadata() or {} # Handle None
42
+ keys = f.keys()
43
+
44
+ # Case 1: New format — tensor names in metadata["tensors"] (as JSON string)
45
+ if "tensors" in metadata:
46
+ import ast
47
+ tensors_dict = ast.literal_eval(metadata["tensors"])
48
+ for tensor_name, info in tensors_dict.items():
49
+ weight_map[tensor_name] = shard.name
50
+ total_size += info.get("length", 0)
51
+
52
+ # Case 2: Old format — tensor names directly accessible
53
+ elif keys:
54
+ for key in keys:
55
+ if key in weight_map:
56
+ print(f" Warning: duplicate tensor {key}")
57
+ weight_map[key] = shard.name
58
+ # Try to estimate size
59
+ try:
60
+ tensor = f.get_tensor(key)
61
+ total_size += tensor.numel() * tensor.element_size()
62
+ except:
63
+ pass # some keys might be metadata only
64
+
65
+ # Case 3: No names, no metadata → we need to read the raw header!
66
+ else:
67
+ print(f" No tensor names found in {shard.name} → reading raw header...")
68
+ # This is the REAL fix: read the raw safetensors header manually
69
+ with open(shard, "rb") as sf:
70
+ header_size = int.from_bytes(sf.read(8), "little")
71
+ header_data = sf.read(header_size)
72
+ header = json.loads(header_data)
73
+
74
+ for tensor_name, desc in header.items():
75
+ if tensor_name == "__metadata":
76
+ continue
77
+ weight_map[tensor_name] = shard.name
78
+ # Calculate length from shape + dtype
79
+ import numpy as np
80
+ dtype = desc["dtype"]
81
+ shape = desc["shape"]
82
+ data_offsets = desc["data_offsets"]
83
+ length = data_offsets[1] - data_offsets[0]
84
+ total_size += length
85
+
86
+ except Exception as e:
87
+ print(f" Failed to process {shard.name}: {e}")
88
+ raise
89
+
90
+ if not weight_map:
91
+ raise RuntimeError("No tensors found in any shard! The files might be corrupted.")
92
+
93
+ # Final index
94
+ index = {
95
+ "metadata": {
96
+ "total_size": total_size
97
+ },
98
+ "weight_map": weight_map
99
+ }
100
+
101
+ output_path = folder / output_file
102
+ with open(output_path, "w", encoding="utf-8") as f:
103
+ json.dump(index, f, indent=4)
104
+
105
+ print(f"\nSUCCESS! Generated {output_file}")
106
+ print(f" Tensors mapped: {len(weight_map)}")
107
+ print(f" Total size: {total_size // 1_073_741_824:.2f} GB")
108
+ print(f" Saved to: {output_path}\n")
109
+
110
+
111
+ if __name__ == "__main__":
112
+ parser = argparse.ArgumentParser(description="Generate model.safetensors.index.json (works 100% with modern HF shards)")
113
+ parser.add_argument("folder", help="Path to folder containing model-*-of-*.safetensors")
114
+ parser.add_argument("--output", default="model.safetensors.index.json", help="Output filename")
115
+
116
+ args = parser.parse_args()
117
+ generate_index(args.folder, args.output)
model_tools.md ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Model Tools
3
+ emoji: 📚
4
+ colorFrom: pink
5
+ colorTo: yellow
6
+ sdk: static
7
+ pinned: false
8
+ ---
9
+
10
+ # Model Tools by Naphula
11
+ Tools to enhance LLM quantizations and merging
12
+
13
+ # [fp32_to_fp16.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/fp32_to_fp16.py)
14
+ - Converts FP32 to FP16 safetensors
15
+
16
+ # [textonly_ripper_v2.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/textonly_ripper_v2.py)
17
+ - Converts a sharded, multimodal (text and vision) model into a text-only version. Readme at [textonly_ripper.md](https://huggingface.co/spaces/Naphula/model_tools/blob/main/textonly_ripper.md)
18
+
19
+ # [vocab_resizer.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/vocab_resizer.py)
20
+ - Converts models with larger vocab_sizes to a standard size (default 131072 Mistral 24B) for use with mergekit. Note that `tokenizer.model` must be manually copied into the `/fixed/` folder.
21
+
22
+ # [lm_head_remover.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/lm_head_remover.py)
23
+ - This script will load a "fat" 18.9GB model (default Gemma 9B), force it to tie the weights (deduplicating the lm_head), and re-save it. This will drop the file size to ~17.2GB and make it compatible with the others.
24
+
25
+ # [model_index_json_generator.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/model_index_json_generator.py)
26
+ - Generates a missing `model.safetensors.index.json` file. Useful for cases where safetensors may have been sharded at the wrong size.
27
+
28
+ # [folder_content_combiner_anyfiles.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/folder_content_combiner_anyfiles.py)
29
+ - Combines all files in the script's current directory into a single output file, sorted alphabetically.
30
+
31
+ # [GGUF Repo Suite](https://huggingface.co/spaces/Naphula/gguf-repo-suite)
32
+ - Create and quantize Hugging Face models
33
+
34
+ # [Failed Experiment gguf_to_safetensors_v2.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/gguf_to_safetensors_v2.py)
35
+ - Unsuccessful attempt by Gemini to patch the gguf_to_safetensors script. Missing json files are hard to reconstruct. Also see [safetensors_meta_ripper_v1.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/safetensors_meta_ripper_v1.py) and [tokenizer_ripper_v1.py](https://huggingface.co/spaces/Naphula/model_tools/blob/main/tokenizer_ripper_v1.py)
36
+
37
+ # [Markdown Viewer](https://huggingface.co/spaces/Naphula/Portable_Offline_Markdown_Viewer)
38
+ - Portable Offline Markdown Viewer
39
+
40
+ # [Markdown to SMF](https://huggingface.co/spaces/Naphula/model_tools/blob/main/md_to_smf.py)
41
+ - Converts a Markdown string to an SMF-compatible BBCode string. Not perfect—sometimes misses double bold tags.
42
+
43
+ # [Quant Clone](https://github.com/electroglyph/quant_clone)
44
+ - A tool which allows you to recreate UD quants such as Q8_K_XL. Examples: [Mistral 24B](https://huggingface.co/spaces/Naphula/model_tools/raw/main/Mistral-Small-3.2-24B-Instruct-2506-UD-Q8_K_XL_UD.txt), [Mistral 7B](https://huggingface.co/spaces/Naphula/model_tools/raw/main/Warlock-7B-v2-Q8_K_XL.txt)
45
+
46
+ # Text Analysis Suite
47
+ - Pending reupload