patdev/k3-a40-bootstrap
01.3k
1#!/usr/bin/env python32"""Rewrite a Kimi-K3 DSpark GGUF so llama.cpp will load it.3 4Lucebox/Kimi-K3-DSpark-Q8_0-GGUF was produced by convert_dspark_to_gguf.py v1,5which stamps `general.architecture = "dflash-draft"`. llama.cpp registers the6architecture as plain `"dflash"` (LLM_ARCH_DFLASH), so loading fails with:7 8 error loading model: unknown model architecture: 'dflash-draft'9 10Everything else in the file is already correct -- the tensors are named11`dflash.dspark.markov.w1`, `dflash.fc.weight`, `blk.N.attn_q.weight`, exactly12what LLM_TENSOR_DSPARK_* expects. Only the architecture string and the KV keys13namespaced under it need renaming.14 15Usage: fix_dspark_arch.py IN.gguf OUT.gguf [--from dflash-draft] [--to dflash]16Exit codes: 0 rewritten, 3 nothing to do, 1 failure.17"""18from __future__ import annotations19 20import argparse21import sys22 23from gguf import GGUFReader, GGUFWriter24from gguf.constants import GGUFValueType25 26 27def field_value(field):28 """Return a plain Python value for a GGUFReader field."""29 # Newer gguf exposes .contents(); fall back to manual decoding.30 contents = getattr(field, "contents", None)31 if callable(contents):32 return contents()33 if field.types and field.types[0] == GGUFValueType.STRING:34 return str(bytes(field.parts[field.data[0]]), encoding="utf-8")35 return field.parts[field.data[0]].tolist()[0]36 37 38def main() -> int:39 ap = argparse.ArgumentParser()40 ap.add_argument("src")41 ap.add_argument("dst")42 ap.add_argument("--from", dest="old", default="dflash-draft")43 ap.add_argument("--to", dest="new", default="dflash")44 a = ap.parse_args()45 46 reader = GGUFReader(a.src)47 arch_field = reader.get_field("general.architecture")48 if arch_field is None:49 print("FAIL: no general.architecture in source", file=sys.stderr)50 return 151 arch = field_value(arch_field)52 print(f"source architecture: {arch!r}")53 if arch == a.new:54 print("already correct, nothing to do")55 return 356 if arch != a.old:57 print(f"FAIL: unexpected architecture {arch!r} (expected {a.old!r})", file=sys.stderr)58 return 159 60 writer = GGUFWriter(a.dst, a.new)61 62 renamed = 063 for key, field in reader.fields.items():64 # GGUFReader surfaces the file header as pseudo-fields (GGUF.version,65 # GGUF.tensor_count, GGUF.kv_count). Copying them as real KV pairs66 # makes GGUFWriter emit a second copy of the header, and the result67 # fails to reopen with "Duplicate GGUF.version already in list".68 if key.startswith("GGUF."):69 continue70 if key == "general.architecture":71 continue # GGUFWriter already wrote it as a.new72 new_key = key73 if key.startswith(a.old + "."):74 new_key = a.new + key[len(a.old):]75 renamed += 176 if not field.types:77 continue78 vtype = field.types[0]79 if vtype == GGUFValueType.ARRAY:80 # Arrays carry their element type as the second entry.81 sub = field.types[1]82 if sub == GGUFValueType.STRING:83 vals = [str(bytes(field.parts[i]), encoding="utf-8") for i in field.data]84 else:85 vals = [field.parts[i].tolist()[0] for i in field.data]86 writer.add_key_value(new_key, vals, GGUFValueType.ARRAY, sub_type=sub)87 else:88 writer.add_key_value(new_key, field_value(field), vtype)89 90 for t in reader.tensors:91 # No raw_shape. For a quantized raw_dtype, add_tensor_info treats the92 # shape it is given as a BYTE shape and runs quant_shape_from_byte_shape93 # on it; handing it the logical shape makes it try to divide 163840 by94 # the Q8_0 type size of 34. reader tensors already carry the byte shape95 # in .data, which is exactly what it wants.96 writer.add_tensor(t.name, t.data, raw_dtype=t.tensor_type)97 98 writer.write_header_to_file()99 writer.write_kv_data_to_file()100 writer.write_tensors_to_file()101 writer.close()102 print(f"renamed {renamed} '{a.old}.*' keys, copied {len(reader.tensors)} tensors")103 104 # Verify rather than assume: a silently truncated or mis-shaped rewrite105 # would only surface much later, as a confusing runtime failure.106 check = GGUFReader(a.dst)107 got = field_value(check.get_field("general.architecture"))108 if got != a.new:109 print(f"FAIL: output architecture is {got!r}", file=sys.stderr)110 return 1111 if len(check.tensors) != len(reader.tensors):112 print(f"FAIL: {len(check.tensors)} tensors written, {len(reader.tensors)} expected",113 file=sys.stderr)114 return 1115 # Compare shape AND dtype AND byte size. Byte size alone would accept a116 # tensor that was reshaped into something the model cannot use.117 def sig(ts):118 return {t.name: (tuple(t.shape.tolist()), int(t.tensor_type), int(t.n_bytes)) for t in ts}119 120 src_sig, dst_sig = sig(reader.tensors), sig(check.tensors)121 if src_sig != dst_sig:122 only = set(src_sig) ^ set(dst_sig)123 changed = [k for k in set(src_sig) & set(dst_sig) if src_sig[k] != dst_sig[k]]124 print(f"FAIL: tensor mismatch; name-diff={sorted(only)[:5]}", file=sys.stderr)125 for k in changed[:5]:126 print(f" {k}: {src_sig[k]} -> {dst_sig[k]}", file=sys.stderr)127 return 1128 print(f"verified: architecture={got}, {len(check.tensors)} tensors, shapes/dtypes/sizes identical")129 return 0130 131 132if __name__ == "__main__":133 raise SystemExit(main())134 