vcruz305 commited on
Commit
d0e9fdf
·
verified ·
1 Parent(s): 216bb8a

Add the KV-only repair for files converted before the fix

Browse files
llama.cpp/patches/fix_gguf_engram_kv.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Repair the engram metadata of a DeepSeek-V4.1 GGUF that was written before the converter fix.
3
+
4
+ Two things went wrong in those files and both live in the header of the first shard:
5
+
6
+ 1. the four engram keys carry a hardcoded `deepseek4.` prefix, so a `deepseek41` model looks for
7
+ `deepseek41.engram.head_count` and finds nothing
8
+ 2. the five constants the hash actually needs are absent, because gguf-py's add_array() maps every
9
+ Python int to INT32, the 47 bit multipliers raised struct.error, and a broad except downgraded
10
+ that to a warning
11
+
12
+ Tensor data is untouched. Existing key/value pairs are re-emitted byte for byte, apart from the
13
+ four that get renamed, so nothing this script does not understand can be corrupted by it.
14
+
15
+ python fix_gguf_engram_kv.py shard1.gguf out.gguf --model-dir /path/to/DeepSeek-V4.1-Flash
16
+ """
17
+ import argparse
18
+ import os
19
+ import struct
20
+ import sys
21
+
22
+ GGUF_MAGIC = b"GGUF"
23
+
24
+ # value type tags
25
+ T_UINT32 = 4
26
+ T_INT32 = 5
27
+ T_STRING = 8
28
+ T_ARRAY = 9
29
+ T_UINT64 = 10
30
+
31
+ FIXED = {0: 1, 1: 1, 2: 2, 3: 2, 4: 4, 5: 4, 6: 4, 7: 1, 10: 8, 11: 8, 12: 8}
32
+
33
+
34
+ def _is_prime(n: int) -> bool:
35
+ if n < 2:
36
+ return False
37
+ for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
38
+ if n % p == 0:
39
+ return n == p
40
+ i = 41
41
+ while i * i <= n:
42
+ if n % i == 0 or n % (i + 2) == 0:
43
+ return False
44
+ i += 6
45
+ return True
46
+
47
+
48
+ def _next_prime(start: int, seen: set) -> int:
49
+ c = start + 1
50
+ while not _is_prime(c) or c in seen:
51
+ c += 1
52
+ return c
53
+
54
+
55
+ def build_token_map(model_dir):
56
+ """Case folded, accent stripped vocabulary, exactly as the reference builds it."""
57
+ from tokenizers import Regex, normalizers
58
+ from transformers import AutoTokenizer
59
+
60
+ tok = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
61
+ sentinel = "" # private use char, so a lone space survives Strip()
62
+ norm = normalizers.Sequence([
63
+ normalizers.NFKC(),
64
+ normalizers.NFD(),
65
+ normalizers.StripAccents(),
66
+ normalizers.Lowercase(),
67
+ normalizers.Replace(Regex(r"[ \t\r\n]+"), " "),
68
+ normalizers.Replace(Regex(r"^ $"), sentinel),
69
+ normalizers.Strip(),
70
+ normalizers.Replace(sentinel, " "),
71
+ ])
72
+ backend = tok.backend_tokenizer
73
+ key_to_new, lookup = {}, [0] * len(tok)
74
+ for tid in range(len(tok)):
75
+ text = backend.decode([tid], skip_special_tokens=False)
76
+ if "�" in text:
77
+ key = backend.id_to_token(tid)
78
+ else:
79
+ normalized = norm.normalize_str(text)
80
+ key = normalized if normalized else text
81
+ new = key_to_new.get(key)
82
+ if new is None:
83
+ new = len(key_to_new)
84
+ key_to_new[key] = new
85
+ lookup[tid] = new
86
+ return lookup, len(key_to_new)
87
+
88
+
89
+ def build_constants(model_dir, layer_ids, max_ngram, n_heads, vocab_size, pad_raw):
90
+ import numpy as np
91
+
92
+ token_map, compressed = build_token_map(model_dir)
93
+
94
+ max_long = np.iinfo(np.int64).max
95
+ bound = max(1, (max_long // compressed) // 2)
96
+ mults = []
97
+ for lid in layer_ids:
98
+ rng = np.random.default_rng(10007 * lid)
99
+ mults.extend(int(v) * 2 + 1 for v in rng.integers(0, bound, size=(max_ngram,), dtype=np.int64))
100
+
101
+ primes, seen = [], set()
102
+ for _ in layer_ids:
103
+ for _ in range(max_ngram - 1):
104
+ cur = vocab_size - 1
105
+ for _ in range(n_heads):
106
+ cur = _next_prime(cur, seen)
107
+ seen.add(cur)
108
+ primes.append(cur)
109
+
110
+ per_layer = (max_ngram - 1) * n_heads
111
+ offsets = []
112
+ for l in range(len(layer_ids)):
113
+ acc = 0
114
+ for b in range(per_layer):
115
+ offsets.append(acc)
116
+ acc += primes[l * per_layer + b]
117
+
118
+ return {
119
+ "multipliers": mults,
120
+ "primes": primes,
121
+ "offsets": offsets,
122
+ "token_map": token_map,
123
+ "pad_id": token_map[pad_raw],
124
+ "compressed_vocab": compressed,
125
+ }
126
+
127
+
128
+ def kv_uint32(v):
129
+ return struct.pack("<I", T_UINT32) + struct.pack("<I", v)
130
+
131
+
132
+ def kv_array(elem_type, values):
133
+ fmt = {T_INT32: "<i", T_UINT64: "<Q"}[elem_type]
134
+ out = [struct.pack("<I", T_ARRAY), struct.pack("<I", elem_type), struct.pack("<Q", len(values))]
135
+ out.extend(struct.pack(fmt, int(v)) for v in values)
136
+ return b"".join(out)
137
+
138
+
139
+ def kv_entry(key, value_bytes):
140
+ k = key.encode("utf-8")
141
+ return struct.pack("<Q", len(k)) + k + value_bytes
142
+
143
+
144
+ def main():
145
+ ap = argparse.ArgumentParser()
146
+ ap.add_argument("src")
147
+ ap.add_argument("dst")
148
+ ap.add_argument("--model-dir", required=True, help="the original checkpoint, for its tokenizer")
149
+ ap.add_argument("--arch", default="deepseek41")
150
+ ap.add_argument("--engram-vocab", type=int, default=16_000_000)
151
+ ap.add_argument("--engram-pad-id", type=int, default=2)
152
+ args = ap.parse_args()
153
+
154
+ f = open(args.src, "rb")
155
+ assert f.read(4) == GGUF_MAGIC, "not a gguf"
156
+ version, = struct.unpack("<I", f.read(4))
157
+ n_tensors, = struct.unpack("<Q", f.read(8))
158
+ n_kv, = struct.unpack("<Q", f.read(8))
159
+
160
+ def rstr():
161
+ n, = struct.unpack("<Q", f.read(8))
162
+ return f.read(n).decode("utf-8")
163
+
164
+ def skip_value(t):
165
+ if t == T_STRING:
166
+ n, = struct.unpack("<Q", f.read(8))
167
+ f.seek(n, os.SEEK_CUR)
168
+ elif t == T_ARRAY:
169
+ et, = struct.unpack("<I", f.read(4))
170
+ cnt, = struct.unpack("<Q", f.read(8))
171
+ if et == T_STRING:
172
+ for _ in range(cnt):
173
+ n, = struct.unpack("<Q", f.read(8))
174
+ f.seek(n, os.SEEK_CUR)
175
+ else:
176
+ f.seek(FIXED[et] * cnt, os.SEEK_CUR)
177
+ else:
178
+ f.seek(FIXED[t], os.SEEK_CUR)
179
+
180
+ kvs = [] # (key, raw value bytes including the type tag)
181
+ seen_keys = set()
182
+ for _ in range(n_kv):
183
+ key = rstr()
184
+ vstart = f.tell()
185
+ t, = struct.unpack("<I", f.read(4))
186
+ skip_value(t)
187
+ vend = f.tell()
188
+ f.seek(vstart)
189
+ raw = f.read(vend - vstart)
190
+ kvs.append([key, raw])
191
+ seen_keys.add(key)
192
+
193
+ tensor_info_start = f.tell()
194
+ for _ in range(n_tensors):
195
+ rstr()
196
+ ndim, = struct.unpack("<I", f.read(4))
197
+ f.seek(8 * ndim, os.SEEK_CUR)
198
+ f.seek(4, os.SEEK_CUR) # ggml type
199
+ f.seek(8, os.SEEK_CUR) # offset
200
+ tensor_info_end = f.tell()
201
+ f.seek(tensor_info_start)
202
+ tensor_info_raw = f.read(tensor_info_end - tensor_info_start)
203
+
204
+ alignment = 32
205
+ for key, raw in kvs:
206
+ if key == "general.alignment":
207
+ alignment, = struct.unpack("<I", raw[4:8])
208
+
209
+ data_start = (tensor_info_end + alignment - 1) // alignment * alignment
210
+
211
+ # --- rename the mis-prefixed keys -------------------------------------------------
212
+ renamed = 0
213
+ for kv in kvs:
214
+ if kv[0].startswith("deepseek4.engram."):
215
+ kv[0] = args.arch + "." + kv[0][len("deepseek4."):]
216
+ renamed += 1
217
+
218
+ def get_scalar(name):
219
+ for key, raw in kvs:
220
+ if key == name:
221
+ t, = struct.unpack("<I", raw[:4])
222
+ return struct.unpack("<I" if t in (T_UINT32,) else "<i", raw[4:8])[0]
223
+ return None
224
+
225
+ layer_ids = None
226
+ for key, raw in kvs:
227
+ if key == f"{args.arch}.engram.layer_ids":
228
+ et, = struct.unpack("<I", raw[4:8])
229
+ cnt, = struct.unpack("<Q", raw[8:16])
230
+ fmt = {T_INT32: "<i", T_UINT32: "<I", T_UINT64: "<Q"}[et]
231
+ sz = FIXED[et]
232
+ layer_ids = [struct.unpack(fmt, raw[16 + i * sz: 16 + (i + 1) * sz])[0] for i in range(cnt)]
233
+
234
+ n_heads = get_scalar(f"{args.arch}.engram.head_count")
235
+ max_ngram = get_scalar(f"{args.arch}.engram.max_ngram_size")
236
+ if layer_ids is None or n_heads is None or max_ngram is None:
237
+ sys.exit("could not read the engram layer ids, head count or ngram size from the header")
238
+
239
+ print(f" arch={args.arch} layer_ids={layer_ids} heads={n_heads} max_ngram={max_ngram}")
240
+ print(f" renamed {renamed} mis-prefixed keys")
241
+
242
+ const = build_constants(args.model_dir, layer_ids, max_ngram, n_heads,
243
+ args.engram_vocab, args.engram_pad_id)
244
+ print(f" compressed vocab {const['compressed_vocab']}, token map {len(const['token_map'])}, "
245
+ f"{len(const['primes'])} primes, pad_id {const['pad_id']}")
246
+ print(f" first multipliers {const['multipliers'][:3]} (max bits "
247
+ f"{max(const['multipliers']).bit_length()})")
248
+
249
+ additions = [
250
+ (f"{args.arch}.engram.multipliers", kv_array(T_UINT64, const["multipliers"])),
251
+ (f"{args.arch}.engram.primes", kv_array(T_UINT64, const["primes"])),
252
+ (f"{args.arch}.engram.offsets", kv_array(T_UINT64, const["offsets"])),
253
+ (f"{args.arch}.engram.token_map", kv_array(T_INT32, const["token_map"])),
254
+ (f"{args.arch}.engram.pad_id", kv_uint32(const["pad_id"])),
255
+ ]
256
+ additions = [(k, v) for k, v in additions if k not in {kv[0] for kv in kvs}]
257
+ print(f" adding {len(additions)} keys")
258
+
259
+ header = bytearray()
260
+ header += GGUF_MAGIC
261
+ header += struct.pack("<I", version)
262
+ header += struct.pack("<Q", n_tensors)
263
+ header += struct.pack("<Q", len(kvs) + len(additions))
264
+ for key, raw in kvs:
265
+ header += kv_entry(key, raw)
266
+ for key, raw in additions:
267
+ header += kv_entry(key, raw)
268
+ header += tensor_info_raw
269
+
270
+ pad = (-len(header)) % alignment
271
+ header += b"\x00" * pad
272
+
273
+ src_size = os.path.getsize(args.src)
274
+ print(f" header {tensor_info_end} -> {len(header)} bytes, copying "
275
+ f"{(src_size - data_start)/1e9:.1f} GB of tensor data")
276
+
277
+ f.seek(data_start)
278
+ with open(args.dst, "wb") as out:
279
+ out.write(header)
280
+ while True:
281
+ chunk = f.read(64 << 20)
282
+ if not chunk:
283
+ break
284
+ out.write(chunk)
285
+
286
+ print(f" wrote {args.dst} ({os.path.getsize(args.dst)/1e9:.1f} GB)")
287
+
288
+
289
+ if __name__ == "__main__":
290
+ main()