mirror of
https://github.com/deepseek-ai/DeepInfer.git
synced 2026-10-31 00:36:20 +08:00
`requant_expert_fp4` expanded the UE8M0 scale with `_expand_blocks`, which repeats *both*
axes. That is right for dense weights, whose scales are `(n_out/32, k/32)`, but expert scales
are already per-output-channel: `(n_out, k/32)`. Expanding both axes therefore treated
`(2304, 160)` as `(72, 5)` and handed every one of the 2304 output channels the scales of the
first 72 rows, scrambling each expert's weights.
This is what the layer comparison was pointing at: `shared_expert` matched the reference
(cos 0.9987) because it goes through `requant_dense_fp8`, which uses `_expand_blocks`
correctly, while `combined` -- routed experts plus shared -- was off by 3.6e-1.
Verified against the authoritative implementation, `inference/convert.py`'s
`cast_e2m1fn_to_e4m3fn`, on layer 0 / expert 370:
w1 cos 0.8936 -> 1.0000 rel_err 5.35e-1 -> 2.84e-2
w3 cos 0.8960 -> 0.9997 rel_err 5.26e-1 -> 2.69e-2
w2 cos 0.8964 -> 0.9996 rel_err 5.20e-1 -> 2.65e-2
The residual ~2.7% is the per-32 -> per-128 requantization loss, not a defect; the reference
is lossless because it folds the relative per-32 offset into the FP8 values.
End to end on the 11-token chat prompt:
before: [17607, 41175, 389, 666] "中国的首都是**"
after: [17607, 2793, 3139, 666, 6127, 666, 320, 1] "中国的首都是**北京**。"
ref: [17607, 2793, 3139, 6127, 320] "中国的首都是北京。"
The answer is now correct and the first three tokens match the reference exactly. The remaining
difference is markdown emphasis (`**`), so some numerical drift still moves the top-1 on the
occasional position.
Also adds two checks that would have caught this immediately:
`scripts/h20_expert_weight_check.py` diffs our requantization against DeepSeek's own converter,
and `scripts/h20_quant_check.py` pins the E2M1 table, nibble order and UE8M0 bias. The nibble
order and table turn out to match the reference; only the scale broadcast was wrong.
113 lines
4.7 KiB
Python
113 lines
4.7 KiB
Python
"""Diff per-layer hidden states between DeepInfer and the reference implementation.
|
|
|
|
Both dumps keep only the final position of the prompt, reduced over the `hc_mult` copies, so
|
|
the comparison is one vector per layer. Reports cosine similarity, relative L2 error and the
|
|
ratio of norms, which together distinguish "slightly off" from "different direction".
|
|
|
|
python scripts/compare_layers.py --ref ref_layers.npz --mine deepinfer_layers.npz
|
|
"""
|
|
|
|
import argparse
|
|
|
|
import numpy as np
|
|
|
|
|
|
def rel_err(a: np.ndarray, b: np.ndarray) -> float:
|
|
return float(np.linalg.norm(a - b) / (np.linalg.norm(b) + 1e-12))
|
|
|
|
|
|
def cosine(a: np.ndarray, b: np.ndarray) -> float:
|
|
na, nb = np.linalg.norm(a), np.linalg.norm(b)
|
|
if na == 0 or nb == 0:
|
|
return float("nan")
|
|
return float(np.dot(a, b) / (na * nb))
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--ref", required=True)
|
|
parser.add_argument("--mine", required=True)
|
|
parser.add_argument("--tol", type=float, default=1e-2, help="first layer above this rel_err is the culprit")
|
|
args = parser.parse_args()
|
|
|
|
ref = np.load(args.ref)
|
|
mine = np.load(args.mine)
|
|
|
|
layers = sorted(int(k[5:]) for k in ref.files if k.startswith("layer") and k[5:].isdigit())
|
|
mine_layers = {int(k[5:]) for k in mine.files if k.startswith("layer") and k[5:].isdigit()}
|
|
missing = [i for i in layers if i not in mine_layers]
|
|
if missing:
|
|
print(f"WARNING: no dump from DeepInfer for layers {missing}")
|
|
|
|
# Routing first: expert ids are discrete, so a set mismatch is a definitive verdict and the
|
|
# weights/branch comparisons below only make sense once routing is known to agree.
|
|
if args.ref and args.mine:
|
|
for layer in range(4):
|
|
key = f"layer{layer}_topk_idx"
|
|
if key not in ref.files or key not in mine.files:
|
|
continue
|
|
r_idx, m_idx = ref[key].astype(np.int64), mine[key].astype(np.int64)
|
|
r_set = set(r_idx.reshape(-1).tolist())
|
|
m_set = set(m_idx.reshape(-1).tolist())
|
|
inter = r_set & m_set
|
|
print(
|
|
f"{key:>18} ref={sorted(r_set)}\n{'':>18} mine={sorted(m_set)}"
|
|
f"\n{'':>18} overlap={len(inter)}/{len(r_set)}"
|
|
f" {'IDENTICAL' if r_idx.shape == m_idx.shape and (r_idx == m_idx).all() else 'DIFFERS'}"
|
|
)
|
|
print()
|
|
|
|
for key in ("embed", "layer0_attn", "layer1_attn"):
|
|
if key in ref.files and key in mine.files:
|
|
r, m = ref[key].astype(np.float64), mine[key].astype(np.float64)
|
|
if r.shape == m.shape:
|
|
print(
|
|
f"{key:>12} cos={cosine(m, r):9.5f} rel_err={rel_err(m, r):9.2e}"
|
|
f" |mine|/|ref|={np.linalg.norm(m) / (np.linalg.norm(r) + 1e-12):8.4f}"
|
|
)
|
|
print()
|
|
print(f"{'layer':>5} {'cos':>9} {'rel_err':>9} {'|mine|/|ref|':>12}")
|
|
first_bad = None
|
|
for i in layers:
|
|
if i not in mine_layers:
|
|
continue
|
|
r, m = ref[f"layer{i}"].astype(np.float64), mine[f"layer{i}"].astype(np.float64)
|
|
if r.shape != m.shape:
|
|
print(f"{i:>5} shape mismatch ref={r.shape} mine={m.shape}")
|
|
continue
|
|
err, cos = rel_err(m, r), cosine(m, r)
|
|
ratio = float(np.linalg.norm(m) / (np.linalg.norm(r) + 1e-12))
|
|
flag = ""
|
|
if first_bad is None and err > args.tol:
|
|
first_bad = i
|
|
flag = " <-- first > tol"
|
|
print(f"{i:>5} {cos:9.5f} {err:9.2e} {ratio:12.4f}{flag}")
|
|
|
|
ref_tok = int(ref["next_token"][0]) if "next_token" in ref.files else None
|
|
print(f"\nreference next token: {ref_tok}")
|
|
|
|
if "final_row_logits" in ref.files:
|
|
rl = ref["final_row_logits"].astype(np.float64)
|
|
top = np.argsort(-rl)[:5]
|
|
print("reference top-5 tokens:", [int(x) for x in top], "logits:", [round(float(rl[x]), 3) for x in top])
|
|
# DeepInfer's logits are not dumped; derive its choice from the harness output instead.
|
|
|
|
print()
|
|
for key in sorted(k for k in ref.files if k.endswith(("_shared_out", "_combined", "_moe_out"))):
|
|
if key in mine.files and ref[key].shape == mine[key].shape:
|
|
r, m = ref[key].astype(np.float64), mine[key].astype(np.float64)
|
|
print(
|
|
f"{key:>18} cos={cosine(m, r):9.5f} rel_err={rel_err(m, r):9.2e}"
|
|
f" |mine|/|ref|={np.linalg.norm(m) / (np.linalg.norm(r) + 1e-12):8.4f}"
|
|
)
|
|
|
|
print()
|
|
if first_bad is None:
|
|
print(f"RESULT: every dumped layer is within rel_err <= {args.tol}; divergence is downstream")
|
|
else:
|
|
print(f"RESULT: first layer above rel_err {args.tol} is layer {first_bad}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|