bloomer010 commited on
Commit
6dc5c65
·
verified ·
1 Parent(s): d2a0865

Document SwiGLU metadata correction

Browse files
Files changed (2) hide show
  1. README.md +15 -2
  2. add-ling3-clamp-metadata.py +93 -0
README.md CHANGED
@@ -10,6 +10,18 @@ base_model:
10
 
11
  Stock llama.cpp builds without bailingmoe3 support will not load the model.
12
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  ## Conversion and Quantization
14
 
15
  Taken directly from the released `inclusionAI/Ling-3.0-flash` BF16 safetensors.
@@ -92,7 +104,8 @@ base_model:
92
 
93
  - BF16 architecture load and tensor round-trip
94
  - CPU and CUDA execution on a reduced-size BailingMoE3 fixture
95
- - Target next-token parity against the Hugging Face implementation
 
96
  - First three recursive MTP proposals matched the Hugging Face implementation
97
  - Full MXFP4_MOE target and MTP graph smoke test
98
  - Q8_0 conversion completed successfully with all 938 tensors
@@ -129,4 +142,4 @@ base_model:
129
  Supports up to 256K context. Reasoning is enabled by default.
130
 
131
  Upstream PR:
132
- https://github.com/ggml-org/llama.cpp/pull/26608
 
10
 
11
  Stock llama.cpp builds without bailingmoe3 support will not load the model.
12
 
13
+ ### SwiGLU metadata correction in progress
14
+
15
+ The original GGUF revisions omitted the trained per-layer SwiGLU clamp metadata declared by the released model configuration. This
16
+ can cause rare but severe output corruption in later layers. Corrected files are being uploaded under the same filenames.
17
+
18
+ Files downloaded before this correction should be downloaded again after completion, or repaired locally with
19
+ [`add-ling3-clamp-metadata.py`](./add-ling3-clamp-metadata.py). The repair changes GGUF metadata only. It does not alter or requantize
20
+ tensor data.
21
+
22
+ The corrected llama.cpp converter and loader are available in the linked fork. Commit:
23
+ [`c51308d8`](https://github.com/aetherbird/llama.cpp/commit/c51308d8)
24
+
25
  ## Conversion and Quantization
26
 
27
  Taken directly from the released `inclusionAI/Ling-3.0-flash` BF16 safetensors.
 
104
 
105
  - BF16 architecture load and tensor round-trip
106
  - CPU and CUDA execution on a reduced-size BailingMoE3 fixture
107
+ - Target next-token parity against the released Hugging Face implementation before the missing trained clamps were identified
108
+ - Nonzero SwiGLU clamp execution and GGUF round-trip on the reduced-size BailingMoE3 fixture
109
  - First three recursive MTP proposals matched the Hugging Face implementation
110
  - Full MXFP4_MOE target and MTP graph smoke test
111
  - Q8_0 conversion completed successfully with all 938 tensors
 
142
  Supports up to 256K context. Reasoning is enabled by default.
143
 
144
  Upstream PR:
145
+ https://github.com/ggml-org/llama.cpp/pull/26608
add-ling3-clamp-metadata.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ try:
11
+ import gguf
12
+ from gguf.scripts.gguf_new_metadata import copy_with_new_metadata, get_field_data
13
+ except ModuleNotFoundError as exc:
14
+ raise SystemExit(
15
+ "gguf-py is required. Set PYTHONPATH to the gguf-py directory in llama.cpp."
16
+ ) from exc
17
+
18
+
19
+ DEFAULT_EXP = [0.0] * 35 + [4.0] * 7
20
+ DEFAULT_SHEXP = [0.0] * 34 + [5.0] * 6 + [7.0] * 2
21
+
22
+
23
+ def load_limits(config_path: Path | None, block_count: int) -> tuple[list[float], list[float]]:
24
+ if config_path is None:
25
+ return (
26
+ (DEFAULT_EXP + [0.0] * block_count)[:block_count],
27
+ (DEFAULT_SHEXP + [0.0] * block_count)[:block_count],
28
+ )
29
+
30
+ with config_path.open(encoding="utf-8") as config_file:
31
+ config = json.load(config_file)
32
+
33
+ if config.get("num_hidden_layers") != 42:
34
+ raise ValueError("expected num_hidden_layers=42")
35
+
36
+ def padded(key: str) -> list[float]:
37
+ values = config.get(key)
38
+ if not isinstance(values, list) or len(values) != 42:
39
+ raise ValueError(f"expected {key} to contain 42 entries")
40
+ result = [0.0 if value is None else float(value) for value in values]
41
+ return (result + [0.0] * block_count)[:block_count]
42
+
43
+ return padded("expert_swiglu_limit_list"), padded("share_expert_swiglu_limit_list")
44
+
45
+
46
+ def main() -> None:
47
+ parser = argparse.ArgumentParser(description="Add Ling 3.0 SwiGLU clamp metadata to a GGUF")
48
+ parser.add_argument("input", type=Path)
49
+ parser.add_argument("output", type=Path)
50
+ parser.add_argument("--config", type=Path)
51
+ parser.add_argument("--force", action="store_true")
52
+ args = parser.parse_args()
53
+
54
+ if args.input.resolve() == args.output.resolve():
55
+ raise ValueError("input and output must be different files")
56
+ if args.output.exists() and not args.force:
57
+ raise FileExistsError(f"output already exists: {args.output}")
58
+
59
+ reader = gguf.GGUFReader(args.input, "r")
60
+ arch = get_field_data(reader, gguf.Keys.General.ARCHITECTURE)
61
+ if arch != "bailingmoe3":
62
+ raise ValueError(f"expected bailingmoe3 architecture, found {arch!r}")
63
+
64
+ block_key = gguf.Keys.LLM.BLOCK_COUNT.format(arch=arch)
65
+ block_count = get_field_data(reader, block_key)
66
+ if block_count not in (42, 43):
67
+ raise ValueError(f"expected 42 or 43 GGUF blocks, found {block_count!r}")
68
+
69
+ clamp_exp, clamp_shexp = load_limits(args.config, block_count)
70
+ if clamp_exp[35:42] != [4.0] * 7 or any(clamp_exp[:35]) or any(clamp_exp[42:]):
71
+ raise ValueError("unexpected routed-expert clamp layout")
72
+ if clamp_shexp[34:40] != [5.0] * 6 or clamp_shexp[40:42] != [7.0] * 2:
73
+ raise ValueError("unexpected shared-expert clamp layout")
74
+ if any(clamp_shexp[:34]) or any(clamp_shexp[42:]):
75
+ raise ValueError("unexpected shared-expert clamp padding")
76
+
77
+ args.output.parent.mkdir(parents=True, exist_ok=True)
78
+ writer = gguf.GGUFWriter(args.output, arch=arch, endianess=reader.endianess)
79
+ alignment = get_field_data(reader, gguf.Keys.General.ALIGNMENT)
80
+ if alignment is not None:
81
+ writer.data_alignment = alignment
82
+
83
+ writer.add_swiglu_clamp_exp(clamp_exp)
84
+ writer.add_swiglu_clamp_shexp(clamp_shexp)
85
+ keys = [
86
+ gguf.Keys.LLM.SWIGLU_CLAMP_EXP.format(arch=arch),
87
+ gguf.Keys.LLM.SWIGLU_CLAMP_SHEXP.format(arch=arch),
88
+ ]
89
+ copy_with_new_metadata(reader, writer, {}, keys)
90
+
91
+
92
+ if __name__ == "__main__":
93
+ main()