Download tools/moe_layer_check.py from HelloSun/minicpm47woek_EPQ: direct link, hf CLI and curl.
- Browser
- Download file 4.51 kB
-
https://huggingface.co/HelloSun/minicpm47woek_EPQ/resolve/main/tools/moe_layer_check.py
- Command line
-
hf download hf://HelloSun/minicpm47woek_EPQ/tools/moe_layer_check.py
-
curl -L -o moe_layer_check.py https://huggingface.co/HelloSun/minicpm47woek_EPQ/resolve/main/tools/moe_layer_check.py
4.51 kB
| #!/usr/bin/env python3 | |
| """用 epq-chat 的 SDQ_MOE_DUMP 檔,驗證「整層」MoE 的計算(真實路由輸入)。 | |
| 這是最接近模型實際執行的驗證:不是隨機 activation,而是模型自己算出來的 | |
| cur / selected_experts / weights,然後檢查我們的輸出是否等於 NumPy 重算的結果。 | |
| """ | |
| import argparse, json, struct, sys | |
| from pathlib import Path | |
| import numpy as np | |
| sys.path.insert(0, str(Path(__file__).parent)) | |
| from moe_ref import quant_q8_K, read_expert | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--model", required=True) | |
| ap.add_argument("--index", required=True) | |
| ap.add_argument("--dump", required=True) | |
| ap.add_argument("--tol", type=float, default=3e-3) | |
| a = ap.parse_args() | |
| b = open(a.dump, "rb").read() | |
| n_embd, n_tok, n_used, n_expert, _, _ = struct.unpack_from("<6i", b, 0) | |
| off = 24 | |
| cur = np.frombuffer(b, dtype=np.float32, count=n_embd * n_tok, offset=off).reshape(n_tok, n_embd) | |
| off += 4 * n_embd * n_tok | |
| ids = np.frombuffer(b, dtype=np.int32, count=n_used * n_tok, offset=off).reshape(n_tok, n_used) | |
| off += 4 * n_used * n_tok | |
| wts = np.frombuffer(b, dtype=np.float32, count=n_used * n_tok, offset=off).reshape(n_tok, n_used) | |
| off += 4 * n_used * n_tok | |
| got = np.frombuffer(b, dtype=np.float32, count=n_embd * n_tok, offset=off).reshape(n_tok, n_embd) | |
| idx = json.loads(Path(a.index).read_text()) | |
| il = int(Path(a.dump).name.rsplit(".", 1)[1]) | |
| layer = idx["per_layer"][il]["experts"] | |
| n_ff = idx["n_ff_exp"] | |
| out = np.zeros((n_tok, n_embd), dtype=np.float32) | |
| # Worst-case effect on one output element of a single Q8_K LSB flip in h. | |
| # ggml accumulates the gate/up dots in float32 while this reference is exact, | |
| # so the two can disagree by ~1e-6 on h. When that crosses a Q8_K rounding | |
| # boundary one code moves by 1 and the layer output jumps by far more than | |
| # float noise. See docs/CORRECTNESS.md: a 1-LSB flip alone produced 7.9e-4. | |
| lsb = np.zeros(n_ff, dtype=np.float64) | |
| with open(a.model, "rb") as fh: | |
| cache = {} | |
| for t in range(n_tok): | |
| for k in range(n_used): | |
| e = int(ids[t, k]) | |
| if e not in cache: | |
| g, u, d = layer["ffn_gate_exps"], layer["ffn_up_exps"], layer["ffn_down_exps"] | |
| Wg = read_expert(fh, g["file_offset"] + e * g["bytes_per_expert"], n_ff * n_embd, g["type"]).reshape(n_ff, n_embd) | |
| Wu = read_expert(fh, u["file_offset"] + e * u["bytes_per_expert"], n_ff * n_embd, u["type"]).reshape(n_ff, n_embd) | |
| Wd = read_expert(fh, d["file_offset"] + e * d["bytes_per_expert"], n_embd * n_ff, d["type"]).reshape(n_embd, n_ff) | |
| cache[e] = (Wg, Wu, Wd) | |
| Wg, Wu, Wd = cache[e] | |
| xq = quant_q8_K(cur[t]) | |
| # float64 for the gate/up dots: this is the reference, so it should | |
| # be the more accurate of the two. | |
| go = Wg.astype(np.float64) @ xq.astype(np.float64) | |
| uo = Wu.astype(np.float64) @ xq.astype(np.float64) | |
| h = ((go / (1.0 + np.exp(-go))) * uo).astype(np.float32) | |
| if t == 0 and k == 0: | |
| ab = np.abs(h) | |
| for bi in range(h.size // 256): | |
| blk = ab[bi * 256:(bi + 1) * 256] | |
| lsb[bi * 256:(bi + 1) * 256] = blk.max() / 127.0 if blk.max() > 0 else 0.0 | |
| out[t] += (Wd @ quant_q8_K(h)) * wts[t, k] | |
| diff = np.abs(out - got) | |
| scale = np.maximum(np.abs(out), 1e-2) | |
| rel = (diff / scale).max() | |
| # bound[r] = sum_j |Wd[r,j]| * d_h(block(j)) = worst case for one flipped code | |
| bound = np.zeros((n_embd, n_ff), dtype=np.float64) | |
| for t in range(n_tok): | |
| for k in range(n_used): | |
| Wd = cache[int(ids[t, k])][2] | |
| bound += np.abs(Wd.astype(np.float64)) * lsb[None, :] | |
| print(f"layer {il} n_tok={n_tok} n_used={n_used} ids[0]={ids[0].tolist()}") | |
| print(f" NumPy out[0,:5]={out[0,:5]}") | |
| print(f" SDQ out[0,:5]={got[0,:5]}") | |
| print(f" max abs diff={diff.max():.6g} max rel={rel:.6g} 1-LSB-flip bound={bound.max():.6g}") | |
| ok = rel <= a.tol | |
| verdict = "PASS" if ok else ("PASS (quantization-boundary)" if diff.max() <= bound.max() else "FAIL") | |
| print(" 結果:", verdict + (" ✅" if ok else (" ✅" if verdict != "FAIL" else " ❌"))) | |
| sys.exit(0 if ok or verdict != "FAIL" else 1) | |
| main() | |