Upload slimshard.py
Browse files- slimshard.py +514 -0
slimshard.py
CHANGED
|
@@ -2681,6 +2681,520 @@ def slimshard_info(path: str | Path) -> dict:
|
|
| 2681 |
}
|
| 2682 |
|
| 2683 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2684 |
def main() -> None:
|
| 2685 |
ap = argparse.ArgumentParser(description="HPC SlimShard / CheckFree single-shard LLM recovery")
|
| 2686 |
sub = ap.add_subparsers(dest="cmd", required=False)
|
|
|
|
| 2681 |
}
|
| 2682 |
|
| 2683 |
|
| 2684 |
+
# ---------------------------------------------------------------------------
|
| 2685 |
+
# Universal SafeTensors -> GGUF Exporter (supports FP4/FP8/BF16/F16/F32 + Q2_K/Q4_0)
|
| 2686 |
+
# ---------------------------------------------------------------------------
|
| 2687 |
+
_GGUF_MAGIC = 0x46554747
|
| 2688 |
+
_GGUF_ALIGNMENT = 32
|
| 2689 |
+
_GGML_F32 = 0
|
| 2690 |
+
_GGML_F16 = 1
|
| 2691 |
+
_GGML_Q4_0 = 2
|
| 2692 |
+
_GGML_Q2_K = 10
|
| 2693 |
+
_GGML_BF16 = 30
|
| 2694 |
+
|
| 2695 |
+
_LAYER_SUFFIX_TO_GGUF = {
|
| 2696 |
+
"input_layernorm.weight": "attn_norm.weight",
|
| 2697 |
+
"attn_norm.weight": "attn_norm.weight",
|
| 2698 |
+
"ln_1.weight": "attn_norm.weight",
|
| 2699 |
+
"post_attention_layernorm.weight": "ffn_norm.weight",
|
| 2700 |
+
"ffn_norm.weight": "ffn_norm.weight",
|
| 2701 |
+
"ln_2.weight": "ffn_norm.weight",
|
| 2702 |
+
"self_attn.q_proj.weight": "attn_q.weight",
|
| 2703 |
+
"attn.q_proj.weight": "attn_q.weight",
|
| 2704 |
+
"attention.wq.weight": "attn_q.weight",
|
| 2705 |
+
"self_attn.k_proj.weight": "attn_k.weight",
|
| 2706 |
+
"attn.k_proj.weight": "attn_k.weight",
|
| 2707 |
+
"attention.wk.weight": "attn_k.weight",
|
| 2708 |
+
"self_attn.v_proj.weight": "attn_v.weight",
|
| 2709 |
+
"attn.v_proj.weight": "attn_v.weight",
|
| 2710 |
+
"attention.wv.weight": "attn_v.weight",
|
| 2711 |
+
"self_attn.o_proj.weight": "attn_output.weight",
|
| 2712 |
+
"attn.o_proj.weight": "attn_output.weight",
|
| 2713 |
+
"attention.wo.weight": "attn_output.weight",
|
| 2714 |
+
"attn.wo_b.weight": "attn_output.weight",
|
| 2715 |
+
"attn.wo_a.weight": "attn_o_a.weight",
|
| 2716 |
+
"self_attn.q_a_proj.weight": "attn_q_a.weight",
|
| 2717 |
+
"attn.wq_a.weight": "attn_q_a.weight",
|
| 2718 |
+
"self_attn.q_a_layernorm.weight": "attn_q_a_norm.weight",
|
| 2719 |
+
"attn.q_norm.weight": "attn_q_a_norm.weight",
|
| 2720 |
+
"self_attn.q_b_proj.weight": "attn_q_b.weight",
|
| 2721 |
+
"attn.wq_b.weight": "attn_q_b.weight",
|
| 2722 |
+
"self_attn.kv_a_proj_with_mqa.weight": "attn_kv_a_mqa.weight",
|
| 2723 |
+
"attn.wkv.weight": "attn_kv_a_mqa.weight",
|
| 2724 |
+
"self_attn.kv_a_layernorm.weight": "attn_kv_a_norm.weight",
|
| 2725 |
+
"attn.kv_norm.weight": "attn_kv_a_norm.weight",
|
| 2726 |
+
"self_attn.kv_b_proj.weight": "attn_kv_b.weight",
|
| 2727 |
+
"self_attn.g_proj.weight": "attn_gate.weight",
|
| 2728 |
+
"mlp.gate_proj.weight": "ffn_gate.weight",
|
| 2729 |
+
"feed_forward.w1.weight": "ffn_gate.weight",
|
| 2730 |
+
"mlp.up_proj.weight": "ffn_up.weight",
|
| 2731 |
+
"feed_forward.w3.weight": "ffn_up.weight",
|
| 2732 |
+
"mlp.down_proj.weight": "ffn_down.weight",
|
| 2733 |
+
"feed_forward.w2.weight": "ffn_down.weight",
|
| 2734 |
+
"moe.gate.weight": "ffn_gate_inp.weight",
|
| 2735 |
+
"mlp.gate.weight": "ffn_gate_inp.weight",
|
| 2736 |
+
"ffn.gate.weight": "ffn_gate_inp.weight",
|
| 2737 |
+
"block_sparse_moe.gate.weight": "ffn_gate_inp.weight",
|
| 2738 |
+
"moe.gate.e_score_correction_bias": "exp_probs_b.bias",
|
| 2739 |
+
"ffn.gate.bias": "exp_probs_b.bias",
|
| 2740 |
+
"moe.shared_experts.gate_proj.weight": "ffn_gate_shexp.weight",
|
| 2741 |
+
"mlp.shared_experts.gate_proj.weight": "ffn_gate_shexp.weight",
|
| 2742 |
+
"ffn.shared_experts.w1.weight": "ffn_gate_shexp.weight",
|
| 2743 |
+
"moe.shared_experts.up_proj.weight": "ffn_up_shexp.weight",
|
| 2744 |
+
"mlp.shared_experts.up_proj.weight": "ffn_up_shexp.weight",
|
| 2745 |
+
"ffn.shared_experts.w3.weight": "ffn_up_shexp.weight",
|
| 2746 |
+
"moe.shared_experts.down_proj.weight": "ffn_down_shexp.weight",
|
| 2747 |
+
"mlp.shared_experts.down_proj.weight": "ffn_down_shexp.weight",
|
| 2748 |
+
"ffn.shared_experts.w2.weight": "ffn_down_shexp.weight",
|
| 2749 |
+
}
|
| 2750 |
+
|
| 2751 |
+
_EXPERT_PROJ_TO_GGUF = {
|
| 2752 |
+
"gate_proj": "ffn_gate_exps.weight",
|
| 2753 |
+
"w1": "ffn_gate_exps.weight",
|
| 2754 |
+
"fc1": "ffn_gate_exps.weight",
|
| 2755 |
+
"up_proj": "ffn_up_exps.weight",
|
| 2756 |
+
"w3": "ffn_up_exps.weight",
|
| 2757 |
+
"down_proj": "ffn_down_exps.weight",
|
| 2758 |
+
"w2": "ffn_down_exps.weight",
|
| 2759 |
+
"fc2": "ffn_down_exps.weight",
|
| 2760 |
+
}
|
| 2761 |
+
|
| 2762 |
+
|
| 2763 |
+
def _gguf_align(val: int, alignment: int = _GGUF_ALIGNMENT) -> int:
|
| 2764 |
+
return (val + alignment - 1) & ~(alignment - 1)
|
| 2765 |
+
|
| 2766 |
+
|
| 2767 |
+
def _pack_gguf_str(s: str) -> bytes:
|
| 2768 |
+
b = s.encode("utf-8")
|
| 2769 |
+
return struct.pack("<Q", len(b)) + b
|
| 2770 |
+
|
| 2771 |
+
|
| 2772 |
+
def _build_gguf_kv_pairs(
|
| 2773 |
+
arch: dict,
|
| 2774 |
+
tokenizer_path: Path | None,
|
| 2775 |
+
chat_template_path: Path | None,
|
| 2776 |
+
quant_format: str,
|
| 2777 |
+
model_name: str,
|
| 2778 |
+
) -> list[tuple[str, int, bytes]]:
|
| 2779 |
+
raw_mtype = str(arch.get("model_type", "llama")).lower()
|
| 2780 |
+
if "deepseek" in raw_mtype:
|
| 2781 |
+
g_arch = "deepseek2"
|
| 2782 |
+
elif "qwen" in raw_mtype:
|
| 2783 |
+
g_arch = "qwen2moe" if int(arch.get("num_experts") or 0) > 0 else "qwen2"
|
| 2784 |
+
else:
|
| 2785 |
+
g_arch = raw_mtype
|
| 2786 |
+
|
| 2787 |
+
ftype_map = {"bf16": 32, "q4_0": 2, "q2_k": 10, "mixed": 10}
|
| 2788 |
+
kvs: list[tuple[str, int, bytes]] = [
|
| 2789 |
+
("general.architecture", 8, _pack_gguf_str(g_arch)),
|
| 2790 |
+
("general.name", 8, _pack_gguf_str(model_name)),
|
| 2791 |
+
("general.file_type", 4, struct.pack("<I", ftype_map.get(quant_format, 10))),
|
| 2792 |
+
("general.quantization_version", 4, struct.pack("<I", 2)),
|
| 2793 |
+
]
|
| 2794 |
+
|
| 2795 |
+
def add_u32(k: str, v: int) -> None:
|
| 2796 |
+
if v is not None and int(v) > 0:
|
| 2797 |
+
kvs.append((k, 4, struct.pack("<I", int(v))))
|
| 2798 |
+
|
| 2799 |
+
def add_f32(k: str, v: float) -> None:
|
| 2800 |
+
if v is not None:
|
| 2801 |
+
kvs.append((k, 6, struct.pack("<f", float(v))))
|
| 2802 |
+
|
| 2803 |
+
add_u32(f"{g_arch}.block_count", int(arch.get("num_hidden_layers", 0)))
|
| 2804 |
+
add_u32(f"{g_arch}.embedding_length", int(arch.get("hidden_size", 0)))
|
| 2805 |
+
add_u32(
|
| 2806 |
+
f"{g_arch}.feed_forward_length",
|
| 2807 |
+
int(arch.get("intermediate_size") or arch.get("moe_intermediate_size") or 0),
|
| 2808 |
+
)
|
| 2809 |
+
add_u32(f"{g_arch}.expert_feed_forward_length", int(arch.get("moe_intermediate_size") or 0))
|
| 2810 |
+
add_u32(f"{g_arch}.attention.head_count", int(arch.get("num_attention_heads", 0)))
|
| 2811 |
+
add_u32(
|
| 2812 |
+
f"{g_arch}.attention.head_count_kv",
|
| 2813 |
+
int(arch.get("num_key_value_heads") or arch.get("num_attention_heads") or 0),
|
| 2814 |
+
)
|
| 2815 |
+
add_f32(
|
| 2816 |
+
f"{g_arch}.attention.layer_norm_rms_epsilon",
|
| 2817 |
+
float(arch.get("rms_norm_eps", 1.0e-5)),
|
| 2818 |
+
)
|
| 2819 |
+
add_u32(f"{g_arch}.expert_count", int(arch.get("num_experts") or 0))
|
| 2820 |
+
add_u32(
|
| 2821 |
+
f"{g_arch}.expert_used_count",
|
| 2822 |
+
int(arch.get("num_experts_per_token") or arch.get("num_experts_per_tok") or 0),
|
| 2823 |
+
)
|
| 2824 |
+
add_u32(f"{g_arch}.context_length", int(arch.get("max_position_embeddings", 32768)))
|
| 2825 |
+
add_u32(f"{g_arch}.vocab_size", int(arch.get("vocab_size", 0)))
|
| 2826 |
+
add_u32(f"{g_arch}.attention.q_lora_rank", int(arch.get("q_lora_rank") or 0))
|
| 2827 |
+
add_u32(f"{g_arch}.attention.kv_lora_rank", int(arch.get("kv_lora_rank") or 0))
|
| 2828 |
+
|
| 2829 |
+
vocab_sz = int(arch.get("vocab_size", 0))
|
| 2830 |
+
if tokenizer_path is not None and tokenizer_path.exists():
|
| 2831 |
+
try:
|
| 2832 |
+
tok_data = json.loads(tokenizer_path.read_text())
|
| 2833 |
+
vmap: dict[str, int] = tok_data.get("model", {}).get("vocab", {})
|
| 2834 |
+
added = tok_data.get("added_tokens", [])
|
| 2835 |
+
max_id = max(
|
| 2836 |
+
[vocab_sz - 1]
|
| 2837 |
+
+ list(vmap.values())
|
| 2838 |
+
+ [int(a.get("id", -1)) for a in added]
|
| 2839 |
+
)
|
| 2840 |
+
n_tok = max(max_id + 1, 1)
|
| 2841 |
+
tokens = [f"<|tok_{i}|>" for i in range(n_tok)]
|
| 2842 |
+
ttypes = np.ones(n_tok, dtype="<i4")
|
| 2843 |
+
for s, tid in vmap.items():
|
| 2844 |
+
if 0 <= tid < n_tok:
|
| 2845 |
+
tokens[tid] = s
|
| 2846 |
+
for a in added:
|
| 2847 |
+
tid = int(a.get("id", -1))
|
| 2848 |
+
if 0 <= tid < n_tok:
|
| 2849 |
+
tokens[tid] = str(a.get("content", tokens[tid]))
|
| 2850 |
+
if a.get("special", False):
|
| 2851 |
+
ttypes[tid] = 3
|
| 2852 |
+
|
| 2853 |
+
kvs.append(("tokenizer.ggml.model", 8, _pack_gguf_str("gpt2")))
|
| 2854 |
+
kvs.append(("tokenizer.ggml.pre", 8, _pack_gguf_str("default")))
|
| 2855 |
+
tok_buf = bytearray(struct.pack("<IQ", 8, len(tokens)))
|
| 2856 |
+
for s in tokens:
|
| 2857 |
+
tok_buf.extend(_pack_gguf_str(s))
|
| 2858 |
+
kvs.append(("tokenizer.ggml.tokens", 9, bytes(tok_buf)))
|
| 2859 |
+
kvs.append(
|
| 2860 |
+
(
|
| 2861 |
+
"tokenizer.ggml.token_type",
|
| 2862 |
+
9,
|
| 2863 |
+
struct.pack("<IQ", 5, len(tokens)) + ttypes.tobytes(),
|
| 2864 |
+
)
|
| 2865 |
+
)
|
| 2866 |
+
merges = tok_data.get("model", {}).get("merges", [])
|
| 2867 |
+
if merges:
|
| 2868 |
+
m_buf = bytearray(struct.pack("<IQ", 8, len(merges)))
|
| 2869 |
+
for m in merges:
|
| 2870 |
+
ms = " ".join(m) if isinstance(m, list) else str(m)
|
| 2871 |
+
m_buf.extend(_pack_gguf_str(ms))
|
| 2872 |
+
kvs.append(("tokenizer.ggml.merges", 9, bytes(m_buf)))
|
| 2873 |
+
except Exception:
|
| 2874 |
+
pass
|
| 2875 |
+
|
| 2876 |
+
if "bos_token_id" in arch and arch["bos_token_id"] is not None:
|
| 2877 |
+
kvs.append(("tokenizer.ggml.bos_token_id", 4, struct.pack("<I", int(arch["bos_token_id"]))))
|
| 2878 |
+
if "eos_token_id" in arch and arch["eos_token_id"] is not None:
|
| 2879 |
+
kvs.append(("tokenizer.ggml.eos_token_id", 4, struct.pack("<I", int(arch["eos_token_id"]))))
|
| 2880 |
+
if "pad_token_id" in arch and arch["pad_token_id"] is not None:
|
| 2881 |
+
kvs.append(("tokenizer.ggml.padding_token_id", 4, struct.pack("<I", int(arch["pad_token_id"]))))
|
| 2882 |
+
if chat_template_path is not None and chat_template_path.exists():
|
| 2883 |
+
kvs.append(("tokenizer.chat_template", 8, _pack_gguf_str(chat_template_path.read_text())))
|
| 2884 |
+
|
| 2885 |
+
return kvs
|
| 2886 |
+
|
| 2887 |
+
|
| 2888 |
+
def export_safetensors_to_gguf(
|
| 2889 |
+
safetensors_path: str | Path,
|
| 2890 |
+
output_gguf: str | Path,
|
| 2891 |
+
config: str | Path | None = None,
|
| 2892 |
+
quant_format: str = "mixed",
|
| 2893 |
+
device: str = "auto",
|
| 2894 |
+
verbose: bool = True,
|
| 2895 |
+
progress_cb=None,
|
| 2896 |
+
stop_event=None,
|
| 2897 |
+
) -> dict:
|
| 2898 |
+
"""Convert a SafeTensors model (including FP4/FP8 DeepSeek-V4.1, Raon, Llama, Qwen, Mixtral) to GGUF.
|
| 2899 |
+
|
| 2900 |
+
* Unpacks FP4 (``I8`` + ``.scale``) and FP8 (``F8_E4M3`` + ``.scale`` / ``.weight_scale_inv``)
|
| 2901 |
+
tensor pairs on the fly.
|
| 2902 |
+
* Stacks routed MoE experts ``0..E-1`` per layer into standard 3D GGUF expert tensors
|
| 2903 |
+
(``blk.{l}.ffn_{gate,up,down}_exps.weight``).
|
| 2904 |
+
* Quantizes eligible weights using HPC-Quantize V2 (``mixed``, ``q2_k``, ``q4_0``, or ``bf16``)
|
| 2905 |
+
with cross-layer payload caching for identical CheckFree anchor layers.
|
| 2906 |
+
"""
|
| 2907 |
+
if safe_open is None or torch is None:
|
| 2908 |
+
raise RuntimeError("safetensors + torch are required for GGUF export")
|
| 2909 |
+
|
| 2910 |
+
st_path = Path(safetensors_path)
|
| 2911 |
+
out_path = Path(output_gguf)
|
| 2912 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
| 2913 |
+
cfg_path = Path(config) if config else st_path.parent / "config.json"
|
| 2914 |
+
raw_cfg = json.loads(cfg_path.read_text()) if cfg_path.exists() else {}
|
| 2915 |
+
arch = _normalize_arch_config(raw_cfg)
|
| 2916 |
+
tok_path = cfg_path.parent / "tokenizer.json"
|
| 2917 |
+
if not tok_path.exists():
|
| 2918 |
+
tok_path = st_path.parent / "tokenizer.json"
|
| 2919 |
+
chat_path = cfg_path.parent / "chat_template.jinja"
|
| 2920 |
+
if not chat_path.exists():
|
| 2921 |
+
chat_path = st_path.parent / "chat_template.jinja"
|
| 2922 |
+
|
| 2923 |
+
use_cuda = (device in ("auto", "cuda")) and torch.cuda.is_available()
|
| 2924 |
+
dev = torch.device("cuda:0" if use_cuda else "cpu")
|
| 2925 |
+
lut = torch.tensor(_FP4_LUT_CPU, dtype=torch.float32, device=dev)
|
| 2926 |
+
|
| 2927 |
+
hpc_cfg = HPCConfig(
|
| 2928 |
+
device="cuda" if use_cuda else "cpu",
|
| 2929 |
+
quant_format=quant_format if quant_format in ("mixed", "q2_k", "q4_0") else "mixed",
|
| 2930 |
+
verbose=False,
|
| 2931 |
+
).validate()
|
| 2932 |
+
quantizer = Q2KQuantizer(hpc_cfg)
|
| 2933 |
+
|
| 2934 |
+
with safe_open(str(st_path), framework="pt", device="cpu") as f:
|
| 2935 |
+
keys = sorted(f.keys())
|
| 2936 |
+
keys_set = set(keys)
|
| 2937 |
+
|
| 2938 |
+
# Group routed experts by (layer_idx, proj) -> sorted [(expert_idx, weight_key, scale_key)]
|
| 2939 |
+
expert_groups: dict[tuple[int, str], list[tuple[int, str, str | None]]] = {}
|
| 2940 |
+
consumed_keys: set[str] = set()
|
| 2941 |
+
|
| 2942 |
+
for k in keys:
|
| 2943 |
+
if k.endswith((".scale", ".weight_scale_inv", ".scales", ".weight_scale")):
|
| 2944 |
+
base_w = k.rsplit(".", 1)[0] + ".weight"
|
| 2945 |
+
if base_w in keys_set:
|
| 2946 |
+
consumed_keys.add(k)
|
| 2947 |
+
continue
|
| 2948 |
+
em = EXPERT_RE.match(k)
|
| 2949 |
+
if em and em.group(6) == "weight":
|
| 2950 |
+
lidx = int(em.group(2))
|
| 2951 |
+
eidx = int(em.group(4))
|
| 2952 |
+
proj = em.group(5)
|
| 2953 |
+
sc_k = None
|
| 2954 |
+
for sfx in (".scale", ".weight_scale_inv", ".scales", ".weight_scale"):
|
| 2955 |
+
cand_s = k[:-7] + sfx
|
| 2956 |
+
if cand_s in keys_set:
|
| 2957 |
+
sc_k = cand_s
|
| 2958 |
+
consumed_keys.add(cand_s)
|
| 2959 |
+
break
|
| 2960 |
+
expert_groups.setdefault((lidx, proj), []).append((eidx, k, sc_k))
|
| 2961 |
+
consumed_keys.add(k)
|
| 2962 |
+
|
| 2963 |
+
for gk in expert_groups:
|
| 2964 |
+
expert_groups[gk].sort(key=lambda x: x[0])
|
| 2965 |
+
|
| 2966 |
+
# Build work items: ("single", src_key, scale_key, gguf_name) or ("experts", (lidx, proj), items, gguf_name)
|
| 2967 |
+
work_items: list[tuple] = []
|
| 2968 |
+
for k in keys:
|
| 2969 |
+
if k in consumed_keys:
|
| 2970 |
+
continue
|
| 2971 |
+
sc_k = None
|
| 2972 |
+
if k.endswith(".weight"):
|
| 2973 |
+
for sfx in (".scale", ".weight_scale_inv", ".scales", ".weight_scale"):
|
| 2974 |
+
cand_s = k[:-7] + sfx
|
| 2975 |
+
if cand_s in keys_set:
|
| 2976 |
+
sc_k = cand_s
|
| 2977 |
+
break
|
| 2978 |
+
if _is_embed_key(k):
|
| 2979 |
+
gname = "token_embd.weight"
|
| 2980 |
+
elif _is_head_key(k):
|
| 2981 |
+
gname = "output.weight"
|
| 2982 |
+
elif k in _NORM_NAMES:
|
| 2983 |
+
gname = "output_norm.weight"
|
| 2984 |
+
else:
|
| 2985 |
+
lm = LAYER_RE.match(k)
|
| 2986 |
+
if lm:
|
| 2987 |
+
lidx = int(lm.group(2))
|
| 2988 |
+
suf = lm.group(3)
|
| 2989 |
+
mapped = _LAYER_SUFFIX_TO_GGUF.get(suf, suf)
|
| 2990 |
+
gname = f"blk.{lidx}.{mapped}"
|
| 2991 |
+
else:
|
| 2992 |
+
gname = k
|
| 2993 |
+
work_items.append(("single", k, sc_k, gname))
|
| 2994 |
+
|
| 2995 |
+
for (lidx, proj), members in sorted(expert_groups.items()):
|
| 2996 |
+
mapped_proj = _EXPERT_PROJ_TO_GGUF.get(proj, f"ffn_{proj}_exps.weight")
|
| 2997 |
+
gname = f"blk.{lidx}.{mapped_proj}"
|
| 2998 |
+
work_items.append(("experts", (lidx, proj), members, gname))
|
| 2999 |
+
|
| 3000 |
+
# Stream quantized/converted tensor payloads into a temporary data file and record GGUF directory
|
| 3001 |
+
tmp_data = tempfile.NamedTemporaryFile(delete=False, dir=str(out_path.parent))
|
| 3002 |
+
gguf_tensors: list[tuple[str, tuple[int, ...], int, int]] = []
|
| 3003 |
+
data_offset = 0
|
| 3004 |
+
payload_cache: dict[tuple, tuple[int, tuple[int, ...], int, int]] = {}
|
| 3005 |
+
stats = {"tensors": 0, "q2_k": 0, "q4_0": 0, "bf16": 0, "f32": 0}
|
| 3006 |
+
total_items = len(work_items)
|
| 3007 |
+
|
| 3008 |
+
def _dequant_single_to_f32_torch(w_key: str, s_key: str | None) -> "torch.Tensor":
|
| 3009 |
+
wt = f.get_tensor(w_key).to(dev)
|
| 3010 |
+
dt_s = _safetensors_dtype(f.get_slice(w_key).get_dtype())
|
| 3011 |
+
if s_key is not None and s_key in keys_set:
|
| 3012 |
+
st = f.get_tensor(s_key).to(dev)
|
| 3013 |
+
if dt_s == "I8":
|
| 3014 |
+
return _ds4_dequant_fp4_1x32(wt, st, lut)
|
| 3015 |
+
if dt_s.startswith("F8"):
|
| 3016 |
+
if st.ndim == 2 and wt.shape[0] // st.shape[0] == 32 and wt.shape[1] // st.shape[1] == 32:
|
| 3017 |
+
return _ds4_dequant_fp8_32x32(wt, st)
|
| 3018 |
+
return _dequant_mxfp8_torch(wt, st)
|
| 3019 |
+
return wt.float()
|
| 3020 |
+
|
| 3021 |
+
def _choose_gguf_target(gname: str, torch_shape: tuple[int, ...]) -> str:
|
| 3022 |
+
if len(torch_shape) < 2 or "norm" in gname or gname.endswith(".bias") or "ffn_gate_inp" in gname or "hc_" in gname or "attn_sink" in gname:
|
| 3023 |
+
return "F32" if len(torch_shape) == 1 else "BF16"
|
| 3024 |
+
if quant_format == "bf16":
|
| 3025 |
+
return "BF16"
|
| 3026 |
+
row_w = int(torch_shape[-1])
|
| 3027 |
+
n_el = int(np.prod(torch_shape))
|
| 3028 |
+
is_emb = gname in ("token_embd.weight", "output.weight")
|
| 3029 |
+
if quant_format == "q4_0":
|
| 3030 |
+
return "Q4_0" if (row_w % QK4_0 == 0 and n_el % QK4_0 == 0) else "BF16"
|
| 3031 |
+
if quant_format == "q2_k":
|
| 3032 |
+
if not is_emb and row_w % QK_K == 0 and n_el % QK_K == 0:
|
| 3033 |
+
return "Q2_K"
|
| 3034 |
+
return "BF16"
|
| 3035 |
+
# mixed
|
| 3036 |
+
if is_emb:
|
| 3037 |
+
return "Q4_0" if (row_w % QK4_0 == 0 and n_el % QK4_0 == 0) else "BF16"
|
| 3038 |
+
if row_w % QK_K == 0 and n_el % QK_K == 0:
|
| 3039 |
+
return "Q2_K"
|
| 3040 |
+
if row_w % QK4_0 == 0 and n_el % QK4_0 == 0:
|
| 3041 |
+
return "Q4_0"
|
| 3042 |
+
return "BF16"
|
| 3043 |
+
|
| 3044 |
+
def _encode_tensor_f32(t_f32: "torch.Tensor", tgt: str) -> tuple[int, bytes]:
|
| 3045 |
+
row_w = int(t_f32.shape[-1])
|
| 3046 |
+
if tgt == "F32":
|
| 3047 |
+
return _GGML_F32, t_f32.detach().cpu().numpy().astype("<f4").tobytes()
|
| 3048 |
+
if tgt == "BF16":
|
| 3049 |
+
b16 = t_f32.to(torch.bfloat16).view(torch.uint16).detach().cpu().numpy().astype("<u2").tobytes()
|
| 3050 |
+
return _GGML_BF16, b16
|
| 3051 |
+
if tgt == "Q4_0":
|
| 3052 |
+
total_r = int(np.prod(t_f32.shape[:-1]))
|
| 3053 |
+
t2d = t_f32.reshape(total_r, row_w)
|
| 3054 |
+
chunk_r = max(1, (8 * 1024 * 1024) // max(row_w, 1))
|
| 3055 |
+
out_chunks = []
|
| 3056 |
+
for r0 in range(0, total_r, chunk_r):
|
| 3057 |
+
sub = t2d[r0 : min(r0 + chunk_r, total_r)].detach().cpu().numpy().astype(np.float32)
|
| 3058 |
+
raw_q4, _, _ = quantize_tensor_q4_0(sub, cfg=hpc_cfg)
|
| 3059 |
+
out_chunks.append(raw_q4)
|
| 3060 |
+
return _GGML_Q4_0, b"".join(out_chunks)
|
| 3061 |
+
if tgt == "Q2_K":
|
| 3062 |
+
flat_np = t_f32.detach().cpu().numpy().astype(np.float32).reshape(-1)
|
| 3063 |
+
res = quantizer.quantize(flat_np, row_width=row_w)
|
| 3064 |
+
return _GGML_Q2_K, blocks_to_bytes(res.scales, res.qs, res.dm)
|
| 3065 |
+
raise ValueError(tgt)
|
| 3066 |
+
|
| 3067 |
+
for idx_w, item in enumerate(work_items, 1):
|
| 3068 |
+
if stop_event is not None and stop_event.is_set():
|
| 3069 |
+
tmp_data.close()
|
| 3070 |
+
os.unlink(tmp_data.name)
|
| 3071 |
+
raise InterruptedError("GGUF export cancelled by user.")
|
| 3072 |
+
|
| 3073 |
+
kind = item[0]
|
| 3074 |
+
if kind == "single":
|
| 3075 |
+
_, w_key, s_key, gname = item
|
| 3076 |
+
sl = f.get_slice(w_key)
|
| 3077 |
+
t_shape = tuple(sl.get_shape())
|
| 3078 |
+
g_dims = tuple(reversed(t_shape)) if t_shape else (1,)
|
| 3079 |
+
tgt = _choose_gguf_target(gname, t_shape or (1,))
|
| 3080 |
+
|
| 3081 |
+
# Fast signature for identical anchor layer reuse
|
| 3082 |
+
lm = LAYER_RE.match(w_key)
|
| 3083 |
+
cache_key = None
|
| 3084 |
+
if lm:
|
| 3085 |
+
suf = lm.group(3)
|
| 3086 |
+
w_Sample = _payload_bytes(f.get_tensor(w_key).flatten()[:256])
|
| 3087 |
+
cache_key = ("single", suf, t_shape, tgt, w_Sample)
|
| 3088 |
+
if cache_key is not None and cache_key in payload_cache:
|
| 3089 |
+
out_type, g_dims, src_off, src_len = payload_cache[cache_key]
|
| 3090 |
+
raw_bytes = os.pread(tmp_data.fileno(), src_len, src_off)
|
| 3091 |
+
else:
|
| 3092 |
+
t_f32 = _dequant_single_to_f32_torch(w_key, s_key)
|
| 3093 |
+
if not t_shape:
|
| 3094 |
+
t_f32 = t_f32.reshape(1)
|
| 3095 |
+
out_type, raw_bytes = _encode_tensor_f32(t_f32, tgt)
|
| 3096 |
+
del t_f32
|
| 3097 |
+
else:
|
| 3098 |
+
_, (lidx, proj), members, gname = item
|
| 3099 |
+
first_w = members[0][1]
|
| 3100 |
+
e_shape = tuple(f.get_slice(first_w).get_shape())
|
| 3101 |
+
# If packed FP4 I8, logical in_dim is 2 * stored in_dim
|
| 3102 |
+
dt_e = _safetensors_dtype(f.get_slice(first_w).get_dtype())
|
| 3103 |
+
if dt_e == "I8" and members[0][2] is not None:
|
| 3104 |
+
e_shape = (e_shape[0], e_shape[1] * 2)
|
| 3105 |
+
full_shape = (len(members), e_shape[0], e_shape[1])
|
| 3106 |
+
g_dims = (e_shape[1], e_shape[0], len(members))
|
| 3107 |
+
tgt = _choose_gguf_target(gname, full_shape)
|
| 3108 |
+
|
| 3109 |
+
w_sample = _payload_bytes(f.get_tensor(first_w).flatten()[:256])
|
| 3110 |
+
cache_key = ("experts", proj, full_shape, tgt, w_sample)
|
| 3111 |
+
if cache_key in payload_cache:
|
| 3112 |
+
out_type, g_dims, src_off, src_len = payload_cache[cache_key]
|
| 3113 |
+
raw_bytes = os.pread(tmp_data.fileno(), src_len, src_off)
|
| 3114 |
+
else:
|
| 3115 |
+
chunks = []
|
| 3116 |
+
for _, wk, sk in members:
|
| 3117 |
+
ef32 = _dequant_single_to_f32_torch(wk, sk)
|
| 3118 |
+
_, eb = _encode_tensor_f32(ef32, tgt)
|
| 3119 |
+
chunks.append(eb)
|
| 3120 |
+
del ef32
|
| 3121 |
+
raw_bytes = b"".join(chunks)
|
| 3122 |
+
out_type = (
|
| 3123 |
+
_GGML_Q2_K
|
| 3124 |
+
if tgt == "Q2_K"
|
| 3125 |
+
else (_GGML_Q4_0 if tgt == "Q4_0" else (_GGML_BF16 if tgt == "BF16" else _GGML_F32))
|
| 3126 |
+
)
|
| 3127 |
+
|
| 3128 |
+
pad = _gguf_align(data_offset) - data_offset
|
| 3129 |
+
if pad > 0:
|
| 3130 |
+
tmp_data.write(b"\x00" * pad)
|
| 3131 |
+
data_offset += pad
|
| 3132 |
+
cur_off = data_offset
|
| 3133 |
+
tmp_data.write(raw_bytes)
|
| 3134 |
+
tmp_data.flush()
|
| 3135 |
+
data_offset += len(raw_bytes)
|
| 3136 |
+
|
| 3137 |
+
if cache_key is not None and cache_key not in payload_cache:
|
| 3138 |
+
payload_cache[cache_key] = (out_type, g_dims, cur_off, len(raw_bytes))
|
| 3139 |
+
|
| 3140 |
+
gguf_tensors.append((gname, g_dims, out_type, cur_off))
|
| 3141 |
+
stats["tensors"] += 1
|
| 3142 |
+
if out_type == _GGML_Q2_K:
|
| 3143 |
+
stats["q2_k"] += 1
|
| 3144 |
+
elif out_type == _GGML_Q4_0:
|
| 3145 |
+
stats["q4_0"] += 1
|
| 3146 |
+
elif out_type == _GGML_BF16:
|
| 3147 |
+
stats["bf16"] += 1
|
| 3148 |
+
else:
|
| 3149 |
+
stats["f32"] += 1
|
| 3150 |
+
|
| 3151 |
+
if progress_cb is not None and (idx_w % 8 == 0 or idx_w == total_items):
|
| 3152 |
+
progress_cb(idx_w, total_items, gname, tgt, stats)
|
| 3153 |
+
|
| 3154 |
+
# Update effective expert count in arch metadata if stacked experts were pruned
|
| 3155 |
+
if expert_groups:
|
| 3156 |
+
first_group = next(iter(expert_groups.values()))
|
| 3157 |
+
arch["num_experts"] = len(first_group)
|
| 3158 |
+
|
| 3159 |
+
kv_pairs = _build_gguf_kv_pairs(
|
| 3160 |
+
arch,
|
| 3161 |
+
tok_path if tok_path.exists() else None,
|
| 3162 |
+
chat_path if chat_path.exists() else None,
|
| 3163 |
+
quant_format,
|
| 3164 |
+
st_path.stem,
|
| 3165 |
+
)
|
| 3166 |
+
|
| 3167 |
+
with out_path.open("wb") as fout:
|
| 3168 |
+
fout.write(struct.pack("<IIQQ", _GGUF_MAGIC, 3, len(gguf_tensors), len(kv_pairs)))
|
| 3169 |
+
for k, vt, raw_v in kv_pairs:
|
| 3170 |
+
fout.write(_pack_gguf_str(k))
|
| 3171 |
+
fout.write(struct.pack("<I", vt))
|
| 3172 |
+
fout.write(raw_v)
|
| 3173 |
+
for gname, g_dims, out_type, off in gguf_tensors:
|
| 3174 |
+
fout.write(_pack_gguf_str(gname))
|
| 3175 |
+
fout.write(struct.pack("<I", len(g_dims)))
|
| 3176 |
+
for d in g_dims:
|
| 3177 |
+
fout.write(struct.pack("<Q", int(d)))
|
| 3178 |
+
fout.write(struct.pack("<I", int(out_type)))
|
| 3179 |
+
fout.write(struct.pack("<Q", int(off)))
|
| 3180 |
+
data_start = _gguf_align(fout.tell())
|
| 3181 |
+
if data_start > fout.tell():
|
| 3182 |
+
fout.write(b"\x00" * (data_start - fout.tell()))
|
| 3183 |
+
tmp_data.seek(0)
|
| 3184 |
+
shutil.copyfileobj(tmp_data, fout, length=16 * 1024 * 1024)
|
| 3185 |
+
|
| 3186 |
+
tmp_data.close()
|
| 3187 |
+
os.unlink(tmp_data.name)
|
| 3188 |
+
stats["output_bytes"] = out_path.stat().st_size
|
| 3189 |
+
if verbose:
|
| 3190 |
+
print(
|
| 3191 |
+
f"Exported GGUF -> {out_path} ({stats['output_bytes'] / 1e9:.2f} GB, "
|
| 3192 |
+
f"{stats['tensors']} tensors: Q2_K={stats['q2_k']}, Q4_0={stats['q4_0']}, "
|
| 3193 |
+
f"BF16={stats['bf16']}, F32={stats['f32']})"
|
| 3194 |
+
)
|
| 3195 |
+
return stats
|
| 3196 |
+
|
| 3197 |
+
|
| 3198 |
def main() -> None:
|
| 3199 |
ap = argparse.ArgumentParser(description="HPC SlimShard / CheckFree single-shard LLM recovery")
|
| 3200 |
sub = ap.add_subparsers(dest="cmd", required=False)
|