423 lines
22 KiB
Python
423 lines
22 KiB
Python
#!/usr/bin/env python3
|
|
"""Convert DeepSeek V4.1 Flash to DwarfStar's Q2 or Q4_K GGUF.
|
|
|
|
Uses the project's C quantizers. Engram FP8 rows and their scales are packed
|
|
losslessly at the end of the file, outside the main model's resident extent.
|
|
Vision and DSpark are separate models and are not part of this text GGUF.
|
|
"""
|
|
|
|
import argparse
|
|
import concurrent.futures
|
|
import dataclasses
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import struct
|
|
import sys
|
|
import time
|
|
|
|
from deepseek41_metadata import GGUF_ALIGNMENT, metadata
|
|
from glm53_quantize import (
|
|
SourceDB, TensorPlan, Quantizer, Imatrix, QTYPE_F32, QTYPE_F16,
|
|
QTYPE_Q8_0, QTYPE_Q2_K, QTYPE_Q4_K, QTYPE_IQ2_XXS, QTYPE_I8, QTYPE_MXFP4, align,
|
|
conversion_signature, kv_string, kv_u32, kv_u32_array, load_resume_state, print_plan,
|
|
qtype_nbytes, save_resume_state, tensor_header,
|
|
)
|
|
|
|
QUANTIZATION = {
|
|
"q2": "IQ2_XXS gate/up; Q2_K down; Q8_0 attention/shared/head",
|
|
"q4": "Q4_K gate/up/down; Q8_0 attention/shared/head",
|
|
"mxfp4": "released MXFP4 gate/up/down; Q8_0 attention/shared/head",
|
|
}
|
|
|
|
|
|
def scale_name(name):
|
|
return name.removesuffix(".weight") + ".scale"
|
|
|
|
|
|
def validate_scales(tensors):
|
|
for name, info in tensors.items():
|
|
if not name.startswith(("layers.", "embed.", "head.", "norm.")):
|
|
continue
|
|
dtype, shape = info["dtype"], info["shape"]
|
|
if dtype not in ("F8_E4M3", "I8") or not name.endswith(".weight"):
|
|
continue
|
|
if len(shape) != 2:
|
|
raise ValueError(f"{name}: expected a matrix")
|
|
if dtype == "I8":
|
|
expected = [shape[0], shape[1] // 16]
|
|
elif ".engram.embed." in name:
|
|
expected = [shape[0], shape[1] // 32]
|
|
else:
|
|
expected = [(dim + 31) // 32 for dim in shape]
|
|
scale = tensors.get(scale_name(name))
|
|
if not scale or scale["dtype"] != "F8_E8M0" or scale["shape"] != expected:
|
|
raise ValueError(f"{name}: expected E8M0 scales {expected}")
|
|
|
|
|
|
def build_plan(db, config, quant="q2", dspark="none", text=True):
|
|
"""The main plan, and separately the DSpark stages when dspark is "separate".
|
|
|
|
With text False only the DSpark stages are planned, so the source may hold
|
|
just their shards."""
|
|
if quant not in QUANTIZATION:
|
|
raise ValueError(f"unknown quantization recipe: {quant}")
|
|
if dspark not in ("separate", "none"):
|
|
raise ValueError(f"unknown DSpark layout: {dspark}")
|
|
c = config["text_config"]
|
|
if config["quantization_config"]["weight_block_size"] != [32, 32]:
|
|
raise ValueError("expected native 32x32 FP8 blocks")
|
|
dim, inter = c["hidden_size"], c["moe_intermediate_size"]
|
|
heads, hd = c["num_attention_heads"], c["head_dim"]
|
|
qrank, orank, groups = c["q_lora_rank"], c["o_lora_rank"], c["o_groups"]
|
|
hc, experts, vocab = c["hc_mult"], c["n_routed_experts"], c["vocab_size"]
|
|
ih, idim = c["index_n_heads"], c["index_head_dim"]
|
|
plan, disk, consumed = [], [], set()
|
|
|
|
def claim(name, expected, dtype=None):
|
|
info = db.info(name)
|
|
if info["shape"] != list(expected) or (dtype and info["dtype"] != dtype):
|
|
raise ValueError(f"{name}: unexpected {info['dtype']} {info['shape']}, expected {dtype} {expected}")
|
|
consumed.add(name)
|
|
if info["dtype"] in ("I8", "F8_E4M3"):
|
|
consumed.add(scale_name(name))
|
|
return info
|
|
|
|
def regular(name, source, shape, qtype, role):
|
|
claim(source, shape)
|
|
plan.append(TensorPlan(name, tuple(reversed(shape)), qtype, role, source=source))
|
|
|
|
def expert_qtype(qt):
|
|
return QTYPE_MXFP4 if quant == "mxfp4" else QTYPE_Q4_K if quant == "q4" else qt
|
|
|
|
def block(src, dst, n_exp, layer, middle=None):
|
|
"""One transformer block: HC, attention, `middle` (compressor and indexer), MoE."""
|
|
for site in ("attn", "ffn"):
|
|
for part, shape, qt in (("fn", (hc * (hc + 2), hc * dim), QTYPE_F16),
|
|
("base", (hc * (hc + 2),), QTYPE_F32),
|
|
("scale", (3,), QTYPE_F32)):
|
|
regular(f"{dst}.hc_{site}_{part}.weight", f"{src}.hc_{site}_{part}", shape, qt, "mhc")
|
|
regular(f"{dst}.{site}_norm.weight", f"{src}.{site}_norm.weight", (dim,), QTYPE_F32, "norm")
|
|
for target, source, shape, qt in (
|
|
("attn_sinks.weight", "attn_sink", (heads,), QTYPE_F32),
|
|
("attn_q_a.weight", "wq_a.weight", (qrank, dim), QTYPE_Q8_0),
|
|
("attn_q_b.weight", "wq_b.weight", (heads * hd, qrank), QTYPE_Q8_0),
|
|
("attn_q_a_norm.weight", "q_norm.weight", (qrank,), QTYPE_F32),
|
|
("attn_kv.weight", "wkv.weight", (hd, dim), QTYPE_Q8_0),
|
|
("attn_kv_a_norm.weight", "kv_norm.weight", (hd,), QTYPE_F32),
|
|
("attn_output_a.weight", "wo_a.weight", (groups * orank, heads * hd // groups), QTYPE_Q8_0),
|
|
("attn_output_b.weight", "wo_b.weight", (dim, groups * orank), QTYPE_Q8_0),
|
|
):
|
|
regular(f"{dst}.{target}", f"{src}.attn.{source}", shape, qt, "attention")
|
|
if middle:
|
|
middle()
|
|
regular(f"{dst}.ffn_gate_inp.weight", f"{src}.ffn.gate.weight", (n_exp, dim), QTYPE_F32, "router")
|
|
for suffix, target in (("bias", "exp_probs_b.bias"), ("bias_vl", "exp_probs_b_vl.bias")):
|
|
regular(f"{dst}.{target}", f"{src}.ffn.gate.{suffix}", (n_exp,), QTYPE_F32, "router")
|
|
for part, source, shape, qt in (("gate", "w1", (inter, dim), QTYPE_IQ2_XXS),
|
|
("up", "w3", (inter, dim), QTYPE_IQ2_XXS),
|
|
("down", "w2", (dim, inter), QTYPE_Q2_K)):
|
|
regular(f"{dst}.ffn_{part}_shexp.weight", f"{src}.ffn.shared_experts.{source}.weight",
|
|
shape, QTYPE_Q8_0, "shared")
|
|
pattern = f"{src}.ffn.experts.{{expert}}.{source}.weight"
|
|
for expert in range(n_exp):
|
|
claim(pattern.format(expert=expert), (shape[0], shape[1] // 2), "I8")
|
|
plan.append(TensorPlan(f"{dst}.ffn_{part}_exps.weight", (*reversed(shape), n_exp),
|
|
expert_qtype(qt), "experts", source=pattern, expert_layer=layer,
|
|
expert_part=part, expert_count=n_exp))
|
|
|
|
if text:
|
|
regular("token_embd.weight", "embed.weight", (vocab, dim), QTYPE_F16, "embedding")
|
|
regular("output_norm.weight", "norm.weight", (dim,), QTYPE_F32, "norm")
|
|
regular("output.weight", "head.weight", (vocab, dim), QTYPE_Q8_0, "output")
|
|
for layer in range(c["num_hidden_layers"] if text else 0):
|
|
src, dst = f"layers.{layer}", f"blk.{layer}"
|
|
|
|
def middle(src=src, dst=dst, layer=layer):
|
|
if layer in c["kv_source_layer_ids"]:
|
|
for target, source, shape, qt in (
|
|
("attn_compressor_kv.weight", "compressor.wkv.weight", (hd, dim), QTYPE_F16),
|
|
("attn_compressor_norm.weight", "compressor.norm.weight", (hd,), QTYPE_F32),
|
|
("indexer.attn_k.weight", "indexer.wk.weight", (idim, hd), QTYPE_F16),
|
|
("indexer.k_norm.weight", "indexer.k_norm.weight", (idim,), QTYPE_F32),
|
|
):
|
|
regular(f"{dst}.{target}", f"{src}.attn.{source}", shape, qt, "compressor")
|
|
if c["compress_ratios"][layer] > 1:
|
|
regular(f"{dst}.attn_compressor_gate.weight", f"{src}.attn.compressor.wgate.weight",
|
|
(hd, dim), QTYPE_F16, "compressor")
|
|
if layer in c["index_source_layer_ids"]:
|
|
regular(f"{dst}.indexer.attn_q_b.weight", f"{src}.attn.indexer.wq_b.weight",
|
|
(ih * idim, qrank), QTYPE_F16, "indexer")
|
|
regular(f"{dst}.indexer.proj.weight", f"{src}.attn.indexer.weights_proj.weight",
|
|
(ih, dim), QTYPE_F16, "indexer")
|
|
|
|
block(src, dst, experts, layer, middle)
|
|
if layer in c["engram_layer_ids"]:
|
|
index = c["engram_layer_ids"].index(layer)
|
|
rows = c["engram_num_embeddings"][index]
|
|
engram = f"{src}.engram"
|
|
claim(f"{engram}.embed.weight", (rows, 256), "F8_E4M3")
|
|
claim(f"{engram}.embed.scale", (rows, 8), "F8_E8M0")
|
|
disk.append(TensorPlan(f"{dst}.engram_embd.weight", (264, rows), QTYPE_I8,
|
|
"engram_disk", source=f"{engram}.embed.weight", raw_copy=True))
|
|
for part in ("q", "k"):
|
|
regular(f"{dst}.engram_{part}_norm.weight", f"{engram}.{part}_weight", (hc, dim), QTYPE_F32, "engram")
|
|
regular(f"{dst}.engram_kv.weight", f"{engram}.wkv.weight", ((hc + 1) * dim, 24 * 256), QTYPE_F16, "engram")
|
|
trunk, plan = plan, []
|
|
stages = c["num_nextn_predict_layers"] if dspark != "none" else 0
|
|
for stage in range(stages):
|
|
src, dst, layer = f"mtp.{stage}", f"mtp.{stage}", c["num_hidden_layers"] + stage
|
|
if stage == 0:
|
|
regular(f"{dst}.main_proj.weight", f"{src}.main_proj.weight",
|
|
(dim, len(c["dspark_target_layer_ids"]) * dim), QTYPE_Q8_0, "dspark")
|
|
regular(f"{dst}.main_norm.weight", f"{src}.main_norm.weight", (dim,), QTYPE_F32, "dspark")
|
|
block(src, dst, c["dspark_n_routed_experts"], layer)
|
|
if stage + 1 == stages:
|
|
rank = c["dspark_markov_rank"]
|
|
regular(f"{dst}.norm.weight", f"{src}.norm.weight", (dim,), QTYPE_F32, "dspark")
|
|
regular(f"{dst}.markov_head.embed.weight", f"{src}.markov_head.embed.weight", (vocab, rank), QTYPE_F16, "dspark")
|
|
regular(f"{dst}.markov_head.head.weight", f"{src}.markov_head.head.weight", (vocab, rank), QTYPE_F16, "dspark")
|
|
regular(f"{dst}.confidence_head.proj.weight", f"{src}.confidence_head.proj.weight",
|
|
(1, dim + rank), QTYPE_F32, "dspark")
|
|
draft, plan = plan, trunk
|
|
omitted = {name for name in db.tensors if name.startswith(("vision.", "aligner.", "image_"))}
|
|
if not stages:
|
|
omitted |= {name for name in db.tensors if name.startswith("mtp.")}
|
|
if consumed | omitted != set(db.tensors):
|
|
raise ValueError(f"unclaimed source tensors: {sorted(set(db.tensors) - consumed - omitted)[:10]}")
|
|
main = plan + disk
|
|
for items in (main, draft if dspark == "separate" else []):
|
|
offset = 0
|
|
for item in items:
|
|
item.offset = offset
|
|
item.nbytes = qtype_nbytes(item.qtype, item.shape)
|
|
offset += align(item.nbytes, GGUF_ALIGNMENT)
|
|
return main, draft if dspark == "separate" else []
|
|
|
|
|
|
def dspark_records(config):
|
|
"""The DSpark metadata the engine reads from either file."""
|
|
c = config["text_config"]
|
|
return [kv_u32("dspark.block_size", c["dspark_block_size"]),
|
|
kv_u32("dspark.markov_rank", c["dspark_markov_rank"]),
|
|
kv_u32("dspark.noise_token_id", c["dspark_noise_token_id"]),
|
|
kv_u32_array("dspark.target_layer_ids", c["dspark_target_layer_ids"]),
|
|
kv_u32("dspark.n_routed_experts", c["dspark_n_routed_experts"]),
|
|
kv_u32("dspark.num_experts_per_tok", c["dspark_num_experts_per_tok"])]
|
|
|
|
|
|
class NativeQuantizer(Quantizer):
|
|
def to_f32(self, db, name, row_start=0, row_count=None):
|
|
if ".engram.embed." in name:
|
|
raise ValueError("Engram must never be materialized as a whole float tensor")
|
|
np = self.np
|
|
info = db.info(name)
|
|
if info["dtype"] not in ("I8", "F8_E4M3"):
|
|
result = super().to_f32(db, name, row_start, row_count)
|
|
else:
|
|
shape = info["shape"]
|
|
codes = np.frombuffer(db.read(name), dtype=np.uint8).reshape(shape)
|
|
sinfo = db.info(scale_name(name))
|
|
scales = np.frombuffer(db.read(scale_name(name)), dtype=np.uint8).reshape(sinfo["shape"])
|
|
if np.any(scales == 255):
|
|
raise ValueError(f"{name}: nonfinite scale")
|
|
scales = np.ldexp(np.ones_like(scales, dtype=np.float32), scales.astype(np.int32) - 127)
|
|
if info["dtype"] == "I8":
|
|
lut = np.array([0, .5, 1, 1.5, 2, 3, 4, 6, -0., -.5, -1, -1.5, -2, -3, -4, -6], np.float32)
|
|
result = np.empty((shape[0], shape[1] * 2), dtype=np.float32)
|
|
result[:, 0::2] = lut[codes & 15]
|
|
result[:, 1::2] = lut[codes >> 4]
|
|
result = (result.reshape(shape[0], -1, 32) * scales[:, :, None]).reshape(result.shape)
|
|
else:
|
|
if np.any((codes & 127) == 127):
|
|
raise ValueError(f"{name}: nonfinite FP8 weight")
|
|
result = self.fp8_lut[codes]
|
|
for row in range(0, shape[0], 32):
|
|
for col in range(0, shape[1], 32):
|
|
result[row:row + 32, col:col + 32] *= scales[row // 32, col // 32]
|
|
if row_count is not None:
|
|
result = result[row_start:row_start + row_count]
|
|
if not np.all(np.isfinite(result)):
|
|
raise ValueError(f"{name}: nonfinite dequantized weight")
|
|
return np.ascontiguousarray(result, dtype=np.float32)
|
|
|
|
def to_mxfp4(self, db, name):
|
|
"""Repack released E2M1 pairs into GGUF MXFP4 blocks without touching the values."""
|
|
np = self.np
|
|
info = db.info(name)
|
|
if info["dtype"] != "I8" or info["shape"][1] % 16:
|
|
raise ValueError(f"{name}: not a packed MXFP4 tensor")
|
|
rows, blocks = info["shape"][0], info["shape"][1] // 16
|
|
codes = np.frombuffer(db.read(name), dtype=np.uint8).reshape(rows, blocks, 16)
|
|
scales = np.frombuffer(db.read(scale_name(name)), dtype=np.uint8).reshape(rows, blocks)
|
|
if np.any(scales == 255):
|
|
raise ValueError(f"{name}: nonfinite scale")
|
|
values = np.empty((rows, blocks, 32), dtype=np.uint8)
|
|
values[:, :, 0::2] = codes & 15
|
|
values[:, :, 1::2] = codes >> 4
|
|
packed = np.empty((rows, blocks, 17), dtype=np.uint8)
|
|
packed[:, :, 0] = scales
|
|
packed[:, :, 1:] = values[:, :, :16] | (values[:, :, 16:] << 4)
|
|
return packed.tobytes()
|
|
|
|
def encode(self, array, qtype, imatrix=None):
|
|
if qtype == QTYPE_F16 and self.np.any(self.np.abs(array) > 65504):
|
|
raise ValueError("F16 tensor would overflow; preserve this family in BF16/F32")
|
|
return super().encode(array, qtype, imatrix)
|
|
|
|
|
|
def write_engram(fp, item, db, np):
|
|
# Keep weights and scales together without changing either byte. A chunk
|
|
# holds 16K rows (4 MiB of weights), independent of the 94 GiB table size.
|
|
rows = item.shape[1]
|
|
for start in range(0, rows, 16384):
|
|
count = min(16384, rows - start)
|
|
weights = b"".join(db.iter_read(item.source, start * 256, count * 256))
|
|
scales = b"".join(db.iter_read(scale_name(item.source), start * 8, count * 8))
|
|
w = np.frombuffer(weights, dtype=np.uint8).reshape(count, 256)
|
|
s = np.frombuffer(scales, dtype=np.uint8).reshape(count, 8)
|
|
if np.any((w & 127) == 127) or np.any(s == 255):
|
|
raise ValueError(f"{item.name}: nonfinite Engram row near {start}")
|
|
packed = np.empty((count, 264), dtype=np.uint8)
|
|
packed[:, :256], packed[:, 256:] = w, s
|
|
fp.write(packed.tobytes())
|
|
|
|
|
|
def write_gguf(args, plan, records, db, out):
|
|
quantizer = NativeQuantizer(args.quants_library)
|
|
imatrix = Imatrix(args.imatrix, quantizer.np)
|
|
if args.imatrix:
|
|
for item in plan:
|
|
if item.is_expert and item.name not in imatrix.entries:
|
|
raise ValueError(f"missing imatrix tensor {item.name}")
|
|
if any(quantizer.np.any(values < 0) for values in imatrix.entries.values()):
|
|
raise ValueError("negative importance values")
|
|
data_start, data_bytes = print_plan(plan, records, [], GGUF_ALIGNMENT)
|
|
partial, journal = out + ".partial", out + ".partial.json"
|
|
signature = conversion_signature(plan, records, [], args.imatrix)
|
|
# A metadata/recipe match alone cannot distinguish two source downloads.
|
|
source_identity = [(name, db.info(name)) for name in sorted(db.tensors)]
|
|
signature = hashlib.sha256((signature + json.dumps(source_identity, sort_keys=True)).encode()).hexdigest()
|
|
if os.path.exists(out):
|
|
raise ValueError(f"refusing to overwrite {out}")
|
|
completed = 0
|
|
if os.path.exists(partial) or os.path.exists(journal):
|
|
if not args.resume or not (os.path.exists(partial) and os.path.exists(journal)):
|
|
raise ValueError("partial file and journal require --resume")
|
|
completed = load_resume_state(journal, signature, plan)
|
|
end = data_start + (plan[completed - 1].offset +
|
|
align(plan[completed - 1].nbytes, GGUF_ALIGNMENT) if completed else 0)
|
|
free = shutil.disk_usage(os.path.dirname(os.path.abspath(out))).free
|
|
if free < data_start + data_bytes - end + (32 << 30):
|
|
raise ValueError("insufficient disk space for remaining output plus 32 GiB reserve")
|
|
header = b"GGUF" + struct.pack("<IQQ", 3, len(plan), len(records))
|
|
header += b"".join(records) + b"".join(tensor_header(item) for item in plan)
|
|
header += bytes(data_start - len(header))
|
|
if os.path.exists(partial):
|
|
with open(partial, "rb") as fp:
|
|
if fp.read(data_start) != header or os.fstat(fp.fileno()).st_size < end:
|
|
raise ValueError("partial GGUF is truncated or has a different header")
|
|
else:
|
|
with open(partial, "xb") as fp:
|
|
fp.write(header)
|
|
fp.flush()
|
|
os.fsync(fp.fileno())
|
|
save_resume_state(journal, signature, 0)
|
|
with open(partial, "r+b") as fp, concurrent.futures.ThreadPoolExecutor(max_workers=args.threads) as pool:
|
|
fp.truncate(end)
|
|
fp.seek(end)
|
|
for index in range(completed, len(plan)):
|
|
item = plan[index]
|
|
started = time.monotonic()
|
|
missing = 0
|
|
if fp.tell() != data_start + item.offset:
|
|
raise ValueError(f"incorrect offset for {item.name}")
|
|
if item.role == "engram_disk":
|
|
write_engram(fp, item, db, quantizer.np)
|
|
elif item.is_expert:
|
|
def convert(expert):
|
|
if item.qtype == QTYPE_MXFP4:
|
|
return quantizer.to_mxfp4(db, item.source.format(expert=expert)), False
|
|
values = quantizer.to_f32(db, item.source.format(expert=expert))
|
|
importance = imatrix.expert(item.name, expert, item.shape[0], item.expert_count)
|
|
return quantizer.encode(values, item.qtype, importance), importance is None
|
|
for start in range(0, item.expert_count, args.threads):
|
|
futures = [pool.submit(convert, e) for e in range(start, min(start + args.threads, item.expert_count))]
|
|
for future in futures:
|
|
data, fallback = future.result()
|
|
if len(data) != item.nbytes // item.expert_count:
|
|
raise ValueError("wrong encoded expert size")
|
|
fp.write(data)
|
|
missing += int(fallback)
|
|
else:
|
|
fp.write(quantizer.encode(quantizer.to_f32(db, item.source), item.qtype))
|
|
if fp.tell() != data_start + item.offset + item.nbytes:
|
|
raise ValueError(f"incorrect payload size for {item.name}")
|
|
fp.write(bytes(align(item.nbytes, GGUF_ALIGNMENT) - item.nbytes))
|
|
fp.flush()
|
|
os.fsync(fp.fileno())
|
|
save_resume_state(journal, signature, index + 1)
|
|
print(f"[{index + 1}/{len(plan)}] {item.name}: {item.nbytes / (1 << 30):.3f} GiB, "
|
|
f"{time.monotonic() - started:.1f}s, uncalibrated_experts={missing}", flush=True)
|
|
os.rename(partial, out)
|
|
os.unlink(journal)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--hf", required=True)
|
|
parser.add_argument("--out", help="the model GGUF; omit to write only --dspark-out")
|
|
parser.add_argument("--source-revision", required=True)
|
|
parser.add_argument("--quant", choices=QUANTIZATION, default="q2")
|
|
parser.add_argument("--dspark-out", help="write the DSpark stages to this support GGUF")
|
|
parser.add_argument("--imatrix")
|
|
parser.add_argument("--threads", type=int, default=8)
|
|
parser.add_argument("--resume", action="store_true")
|
|
parser.add_argument("--dry-run", action="store_true")
|
|
suffix = "dylib" if sys.platform == "darwin" else "so"
|
|
parser.add_argument("--quants-library", default=os.path.join(os.path.dirname(__file__), f"libds4quants.{suffix}"))
|
|
args = parser.parse_args()
|
|
if not re.fullmatch(r"[0-9a-f]{40}", args.source_revision):
|
|
parser.error("source revision must be a full commit hash")
|
|
if not 1 <= args.threads <= 32:
|
|
parser.error("threads must be between 1 and 32")
|
|
if not args.out and not args.dspark_out:
|
|
parser.error("--out or --dspark-out is required")
|
|
dspark = "separate" if args.dspark_out else "none"
|
|
config, records = metadata(args.hf, args.source_revision)
|
|
# A drafter-only run reads nothing outside the mtp.* stages, so only their shards are opened.
|
|
stages_only = not args.out
|
|
db = SourceDB(args.hf, index_validator=lambda _: None, scale_validator=validate_scales,
|
|
tensor_filter=(lambda name: name.startswith("mtp.")) if stages_only else None)
|
|
try:
|
|
plan, draft = build_plan(db, config, args.quant, dspark, text=not stages_only)
|
|
quantization = [kv_string("deepseek41.quantization", QUANTIZATION[args.quant]),
|
|
kv_string("deepseek41.calibration", "imatrix" if args.imatrix else "weight-energy bootstrap")]
|
|
if args.imatrix:
|
|
quantization.append(kv_string("quantize.imatrix.file", os.path.basename(args.imatrix)))
|
|
records += quantization
|
|
support = [kv_string("general.architecture", "deepseek41-dspark"),
|
|
kv_string("general.name", "DeepSeek V4.1 Flash DSpark"),
|
|
kv_string("general.source.revision", args.source_revision),
|
|
kv_u32("general.alignment", GGUF_ALIGNMENT)] + dspark_records(config) + quantization
|
|
outputs = ([(args.out, plan, records)] if args.out else []) + \
|
|
([(args.dspark_out, draft, support)] if args.dspark_out else [])
|
|
for out, items, kv in outputs:
|
|
if args.dry_run:
|
|
print_plan(items, kv, [], GGUF_ALIGNMENT)
|
|
for item in items:
|
|
print(json.dumps(dataclasses.asdict(item), sort_keys=True))
|
|
else:
|
|
write_gguf(args, items, kv, db, out)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except (OSError, ValueError) as error:
|
|
sys.exit(f"deepseek41-quantize: {error}")
|