-
Notifications
You must be signed in to change notification settings - Fork 497
Expand file tree
/
Copy pathrepack_experts.py
More file actions
319 lines (259 loc) · 12.4 KB
/
Copy pathrepack_experts.py
File metadata and controls
319 lines (259 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#!/usr/bin/env python3
"""Repack expert weights from scattered safetensors into contiguous per-layer binary files.
Creates one binary file per layer: packed_experts/layer_XX.bin
Each file = 512 experts x 7,077,888 bytes = ~3.63 GB
Expert E starts at byte offset E * 7,077,888
Within each expert block, 9 components packed in fixed order:
gate_proj.weight, gate_proj.scales, gate_proj.biases,
up_proj.weight, up_proj.scales, up_proj.biases,
down_proj.weight, down_proj.scales, down_proj.biases
Usage:
python repack_experts.py # repack all 60 layers
python repack_experts.py --layers 0-4 # repack layers 0-4
python repack_experts.py --layers 0,5,10 # repack specific layers
python repack_experts.py --dry-run # verify without writing
python repack_experts.py --verify-only 0 # verify layer 0 against originals
"""
import argparse
import json
import os
import time
import sys
# Component order and expected sizes
COMPONENTS = [
{"name": "gate_proj.weight", "offset": 0, "size": 2097152, "dtype": "U32", "shape": [1024, 512]},
{"name": "gate_proj.scales", "offset": 2097152, "size": 131072, "dtype": "BF16", "shape": [1024, 64]},
{"name": "gate_proj.biases", "offset": 2228224, "size": 131072, "dtype": "BF16", "shape": [1024, 64]},
{"name": "up_proj.weight", "offset": 2359296, "size": 2097152, "dtype": "U32", "shape": [1024, 512]},
{"name": "up_proj.scales", "offset": 4456448, "size": 131072, "dtype": "BF16", "shape": [1024, 64]},
{"name": "up_proj.biases", "offset": 4587520, "size": 131072, "dtype": "BF16", "shape": [1024, 64]},
{"name": "down_proj.weight", "offset": 4718592, "size": 2097152, "dtype": "U32", "shape": [4096, 128]},
{"name": "down_proj.scales", "offset": 6815744, "size": 131072, "dtype": "BF16", "shape": [4096, 16]},
{"name": "down_proj.biases", "offset": 6946816, "size": 131072, "dtype": "BF16", "shape": [4096, 16]},
]
EXPERT_SIZE = 7077888 # bytes per expert
NUM_EXPERTS = 512
NUM_LAYERS = 60
LAYER_SIZE = NUM_EXPERTS * EXPERT_SIZE # 3,623,878,656 bytes (~3.63 GB)
def parse_layers(spec):
"""Parse layer specification like '0-4' or '0,5,10' or 'all'."""
if spec is None or spec == 'all':
return list(range(NUM_LAYERS))
layers = []
for part in spec.split(','):
part = part.strip()
if '-' in part:
a, b = part.split('-', 1)
layers.extend(range(int(a), int(b) + 1))
else:
layers.append(int(part))
return sorted(set(layers))
def load_index(index_path):
"""Load expert_index.json and return expert_reads dict + model_path."""
with open(index_path) as f:
idx = json.load(f)
return idx['expert_reads'], idx['model_path']
def verify_component_sizes(expert_reads):
"""Verify that component sizes in the index match expected sizes."""
expected = {c['name']: c['size'] for c in COMPONENTS}
for layer_key, comps in expert_reads.items():
for comp_name, info in comps.items():
if comp_name not in expected:
print(f"WARNING: unknown component {comp_name} in layer {layer_key}")
continue
if info['expert_size'] != expected[comp_name]:
print(f"MISMATCH: layer {layer_key}, {comp_name}: "
f"index says {info['expert_size']}, expected {expected[comp_name]}")
return False
print("Component sizes verified: all match expected layout")
return True
def open_source_files(expert_reads, model_path, layers):
"""Open all needed safetensors files, return {filename: fd}."""
needed_files = set()
for layer_idx in layers:
layer_key = str(layer_idx)
if layer_key not in expert_reads:
print(f"WARNING: layer {layer_idx} not found in expert_reads")
continue
for info in expert_reads[layer_key].values():
needed_files.add(info['file'])
fds = {}
for fname in sorted(needed_files):
path = os.path.join(model_path, fname)
fds[fname] = os.open(path, os.O_RDONLY)
print(f"Opened {len(fds)} source safetensors files")
return fds
def repack_layer(layer_idx, expert_reads, model_path, fds, output_dir, dry_run=False):
"""Repack all 512 experts for one layer into a contiguous binary file.
Returns (bytes_written, elapsed_seconds).
"""
layer_key = str(layer_idx)
if layer_key not in expert_reads:
print(f" Layer {layer_idx}: NOT FOUND in index, skipping")
return 0, 0.0
layer_info = expert_reads[layer_key]
out_path = os.path.join(output_dir, f"layer_{layer_idx:02d}.bin")
if dry_run:
# Just verify we can compute all offsets
for expert_idx in range(NUM_EXPERTS):
for comp in COMPONENTS:
info = layer_info[comp['name']]
src_offset = info['abs_offset'] + expert_idx * info['expert_stride']
dst_offset = expert_idx * EXPERT_SIZE + comp['offset']
print(f" Layer {layer_idx:2d}: DRY RUN OK — would write {LAYER_SIZE:,} bytes to {out_path}")
return LAYER_SIZE, 0.0
t0 = time.monotonic()
# Pre-allocate output file with zeros
fd_out = os.open(out_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0o644)
os.ftruncate(fd_out, LAYER_SIZE)
bytes_written = 0
# Build read plan: group reads by source file for better locality
# Each entry: (src_fd, src_offset, dst_offset, size)
read_plan = []
for expert_idx in range(NUM_EXPERTS):
for comp in COMPONENTS:
info = layer_info[comp['name']]
src_fd = fds[info['file']]
src_offset = info['abs_offset'] + expert_idx * info['expert_stride']
dst_offset = expert_idx * EXPERT_SIZE + comp['offset']
read_plan.append((src_fd, src_offset, dst_offset, comp['size']))
# Sort by (src_fd, src_offset) for sequential read locality
read_plan.sort(key=lambda x: (x[0], x[1]))
# Execute reads and writes
for src_fd, src_offset, dst_offset, size in read_plan:
data = os.pread(src_fd, size, src_offset)
if len(data) != size:
raise IOError(f"Short read: expected {size}, got {len(data)} "
f"at offset {src_offset}")
os.pwrite(fd_out, data, dst_offset)
bytes_written += size
os.close(fd_out)
elapsed = time.monotonic() - t0
return bytes_written, elapsed
def verify_layer(layer_idx, expert_reads, model_path, fds, output_dir):
"""Read back expert 0 from packed file and compare to originals."""
layer_key = str(layer_idx)
layer_info = expert_reads[layer_key]
out_path = os.path.join(output_dir, f"layer_{layer_idx:02d}.bin")
if not os.path.exists(out_path):
print(f" Layer {layer_idx}: packed file not found")
return False
fd_packed = os.open(out_path, os.O_RDONLY)
mismatches = 0
for expert_idx in [0, 1, 255, 511]: # spot check several experts
for comp in COMPONENTS:
info = layer_info[comp['name']]
src_fd = fds[info['file']]
src_offset = info['abs_offset'] + expert_idx * info['expert_stride']
dst_offset = expert_idx * EXPERT_SIZE + comp['offset']
original = os.pread(src_fd, comp['size'], src_offset)
packed = os.pread(fd_packed, comp['size'], dst_offset)
if original != packed:
print(f" MISMATCH: layer {layer_idx}, expert {expert_idx}, {comp['name']}")
mismatches += 1
os.close(fd_packed)
if mismatches == 0:
print(f" Layer {layer_idx}: verification PASSED (experts 0, 1, 255, 511)")
else:
print(f" Layer {layer_idx}: verification FAILED ({mismatches} mismatches)")
return mismatches == 0
def write_layout(output_dir):
"""Write layout.json describing the packed format."""
layout = {
"expert_size": EXPERT_SIZE,
"num_layers": NUM_LAYERS,
"num_experts": NUM_EXPERTS,
"components": COMPONENTS,
}
path = os.path.join(output_dir, "layout.json")
with open(path, 'w') as f:
json.dump(layout, f, indent=2)
print(f"Wrote {path}")
def main():
parser = argparse.ArgumentParser(description="Repack expert weights into contiguous per-layer binary files")
parser.add_argument('--index', default='/Users/danielwoods/Workspace/ane-research/expert_index.json',
help='Path to expert_index.json')
parser.add_argument('--layers', default=None,
help='Layer spec: "all", "0-4", "0,5,10" (default: all)')
parser.add_argument('--dry-run', action='store_true',
help='Verify offsets without writing')
parser.add_argument('--verify-only', type=int, default=None, metavar='LAYER',
help='Verify a specific layer against originals')
args = parser.parse_args()
print("Loading expert index...")
expert_reads, model_path = load_index(args.index)
print(f"Model path: {model_path}")
print(f"Layers in index: {len(expert_reads)}")
# Verify component sizes
if not verify_component_sizes(expert_reads):
print("ABORTING: component size mismatch")
sys.exit(1)
output_dir = os.path.join(model_path, "packed_experts")
os.makedirs(output_dir, exist_ok=True)
print(f"Output directory: {output_dir}")
# Determine which layers to process
if args.verify_only is not None:
layers = [args.verify_only]
else:
layers = parse_layers(args.layers)
print(f"Layers to process: {layers[0]}-{layers[-1]} ({len(layers)} layers)")
if not args.dry_run and args.verify_only is None:
total_bytes = len(layers) * LAYER_SIZE
print(f"Total data to write: {total_bytes / (1024**3):.1f} GB")
# Check free disk space
stat = os.statvfs(output_dir)
free_bytes = stat.f_bavail * stat.f_frsize
free_gb = free_bytes / (1024**3)
needed_gb = total_bytes / (1024**3)
print(f"Free disk space: {free_gb:.1f} GB, needed: {needed_gb:.1f} GB")
if free_bytes < total_bytes:
print(f"WARNING: Not enough free space! Need {needed_gb:.1f} GB but only {free_gb:.1f} GB free.")
print(f"Hint: use --layers to process a subset, e.g. --layers 0-{int(free_gb / 3.63) - 1}")
sys.exit(1)
# Open source files
fds = open_source_files(expert_reads, model_path, layers)
if args.verify_only is not None:
verify_layer(args.verify_only, expert_reads, model_path, fds, output_dir)
for fd in fds.values():
os.close(fd)
return
# Write layout.json
write_layout(output_dir)
# Repack each layer
t_start = time.monotonic()
total_written = 0
for i, layer_idx in enumerate(layers):
t_layer = time.monotonic()
bytes_written, elapsed = repack_layer(
layer_idx, expert_reads, model_path, fds, output_dir, dry_run=args.dry_run
)
total_written += bytes_written
if not args.dry_run and bytes_written > 0:
throughput = bytes_written / elapsed / (1024**3) if elapsed > 0 else float('inf')
overall_elapsed = time.monotonic() - t_start
overall_throughput = total_written / overall_elapsed / (1024**3) if overall_elapsed > 0 else 0
eta = (len(layers) - i - 1) * (overall_elapsed / (i + 1))
print(f" Layer {layer_idx:2d}: {bytes_written/1024**3:.2f} GB in {elapsed:.1f}s "
f"({throughput:.1f} GB/s) | "
f"Total: {total_written/1024**3:.1f}/{len(layers)*LAYER_SIZE/1024**3:.1f} GB "
f"({overall_throughput:.1f} GB/s avg) | "
f"ETA: {eta:.0f}s")
# Verify this layer immediately
if not verify_layer(layer_idx, expert_reads, model_path, fds, output_dir):
print(f"ABORTING: verification failed for layer {layer_idx}")
sys.exit(1)
# Close source files
for fd in fds.values():
os.close(fd)
# Final summary
total_elapsed = time.monotonic() - t_start
if not args.dry_run and total_written > 0:
print(f"\n{'='*60}")
print(f"DONE: {total_written:,} bytes ({total_written/1024**3:.1f} GB) written")
print(f"Time: {total_elapsed:.1f}s")
print(f"Throughput: {total_written/total_elapsed/1024**3:.1f} GB/s")
print(f"Output: {output_dir}")
elif args.dry_run:
print(f"\nDRY RUN complete: {len(layers)} layers validated")
if __name__ == '__main__':
main()