mirror of
https://github.com/deepseek-ai/DeepInfer.git
synced 2026-10-31 08:46:21 +08:00
No behaviour change. `CONTRIBUTING.md` requires `ruff check --no-cache` and `ruff format --check --no-cache` to pass, and the SM90 branch predates that requirement: 72 findings, all in the files added here. Most of it is mechanical (`ruff format`), plus: - annotations for the helper signatures (`torch.Tensor` / `int` / `Generator` / return types); hooks are typed as `Callable[[nn.Module, tuple, object], None]` rather than left bare; - two `if`/`else` assignments become ternaries (SIM108), and the index read in `h20_real_requant_check.py` uses a context manager (SIM115). Also corrects the `sparse_mla_fwd` docstring, which still described the old `0 <= idx <= q_position` masking contract. Passing `is_causal=True` there is what produced the all-NaN layer 0: indices address the assembled `[swa cache | current swa | compressed]` buffer, so the row number is not a valid causal bound and the engine must pass False.
118 lines
5.2 KiB
Python
118 lines
5.2 KiB
Python
"""Compare our FP4->FP8 expert requantization against DeepSeek's own reference conversion.
|
|
|
|
`inference/convert.py::cast_e2m1fn_to_e4m3fn` is the authoritative implementation: it is what
|
|
produced the TP8 checkpoint that the reference model actually runs on. Two things are worth
|
|
checking there, and both matter because a wrong answer scrambles every routed expert while
|
|
leaving the (already-FP8) shared expert intact -- exactly the asymmetry the layer comparison
|
|
showed.
|
|
|
|
1. Equality of the *decoded* weights. Both paths should represent the same real values; any
|
|
difference is accuracy we are throwing away.
|
|
2. The scale grid. The reference keeps a per-32 e8m0 scale (relative offsets folded into the FP8
|
|
values), while the SM90 path re-quantizes on a per-128 fp32 grid. That is a legitimate design
|
|
difference only if the magnitude error stays small.
|
|
|
|
python scripts/h20_expert_weight_check.py --layer 0 --expert 370
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import torch
|
|
from safetensors import safe_open
|
|
|
|
from deepinfer.h20_ops.quant import requant_expert_fp4, ue8m0_to_fp32
|
|
|
|
# Copied verbatim from inference/convert.py so the comparison uses the reference algorithm
|
|
# rather than a paraphrase of it.
|
|
FP4_TABLE = torch.tensor(
|
|
[0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 0.0, -0.5, -1.0, -1.5, -2.0, -3.0, -4.0, -6.0],
|
|
dtype=torch.float32,
|
|
)
|
|
|
|
|
|
def cast_e2m1fn_to_e4m3fn(x: torch.Tensor, scale: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
|
|
"""DeepSeek's lossless FP4 -> FP8 cast (inference/convert.py)."""
|
|
assert x.dtype == torch.int8 and x.ndim == 2
|
|
out_dim, in_dim = x.size()
|
|
in_dim *= 2
|
|
fp8_block_size = 32
|
|
fp4_block_size = 32
|
|
assert in_dim % fp8_block_size == 0 and out_dim % fp8_block_size == 0
|
|
assert scale.size(0) == out_dim and scale.size(1) == in_dim // fp4_block_size
|
|
|
|
x = x.view(torch.uint8)
|
|
low = x & 0x0F
|
|
high = (x >> 4) & 0x0F
|
|
x = torch.stack([FP4_TABLE[low.long()], FP4_TABLE[high.long()]], dim=-1).flatten(2)
|
|
|
|
MAX_OFFSET_BITS = 6
|
|
b_out = out_dim // fp8_block_size
|
|
b_in = in_dim // fp8_block_size
|
|
x = x.view(b_out, fp8_block_size, b_in, fp8_block_size).transpose(1, 2)
|
|
scale = scale.float().view(b_out, fp8_block_size, b_in, -1).transpose(1, 2).flatten(2)
|
|
scale_max_offset_bits = scale.amax(dim=-1, keepdim=True) / (2**MAX_OFFSET_BITS)
|
|
offset = scale / scale_max_offset_bits
|
|
offset = offset.unflatten(-1, (fp8_block_size, -1)).repeat_interleave(fp4_block_size, dim=-1)
|
|
x = (x * offset).transpose(1, 2).reshape(out_dim, in_dim)
|
|
return x.to(torch.float8_e4m3fn), scale_max_offset_bits.squeeze(-1).to(torch.float8_e8m0fnu)
|
|
|
|
|
|
def read_tensor(hf_dir: Path, index: dict, name: str) -> torch.Tensor:
|
|
shard = index["weight_map"][name]
|
|
with safe_open(str(hf_dir / shard), framework="pt", device="cpu") as f:
|
|
return f.get_tensor(name)
|
|
|
|
|
|
def dequant_ref(fp8: torch.Tensor, sf_e8m0: torch.Tensor, block: int = 32) -> torch.Tensor:
|
|
"""Reference layout: (out, in) FP8, scale (out/32, in/32) e8m0 on a 32x32 grid."""
|
|
s = ue8m0_to_fp32(sf_e8m0)
|
|
s = s.repeat_interleave(block, 0).repeat_interleave(block, 1)[: fp8.shape[0], : fp8.shape[1]]
|
|
return fp8.float() * s
|
|
|
|
|
|
def dequant_ours(fp8: torch.Tensor, sf_fp32: torch.Tensor, block: int = 128) -> torch.Tensor:
|
|
"""SM90 layout: (out, in) FP8, scales (in/block, out/block) fp32, contraction-major."""
|
|
s = sf_fp32.t().repeat_interleave(block, 0).repeat_interleave(block, 1)[: fp8.shape[0], : fp8.shape[1]]
|
|
return fp8.float() * s
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--hf-dir", default="/data/models/DeepSeek-V4.1-Flash-1017")
|
|
parser.add_argument("--layer", type=int, default=0)
|
|
parser.add_argument("--expert", type=int, default=370)
|
|
args = parser.parse_args()
|
|
|
|
hf_dir = Path(args.hf_dir)
|
|
index = json.loads((hf_dir / "model.safetensors.index.json").read_text())
|
|
prefix = f"layers.{args.layer}.ffn.experts.{args.expert}."
|
|
|
|
for proj in ("w1", "w3", "w2"):
|
|
packed = read_tensor(hf_dir, index, prefix + proj + ".weight")
|
|
scale = read_tensor(hf_dir, index, prefix + proj + ".scale")
|
|
print(f"\n{proj}: packed {tuple(packed.shape)} {packed.dtype}, scale {tuple(scale.shape)} {scale.dtype}")
|
|
|
|
ref_fp8, ref_sf = cast_e2m1fn_to_e4m3fn(packed, scale)
|
|
ours_fp8, ours_sf = requant_expert_fp4(packed, scale, out_block=128)
|
|
|
|
# `requant_expert_fp4` returns (n_out, k) FP8 and a contraction-major scale; the reference
|
|
# returns (out, in) too, so the value grids line up directly.
|
|
ref_vals = dequant_ref(ref_fp8, ref_sf)
|
|
ours_vals = dequant_ours(ours_fp8, ours_sf)
|
|
|
|
assert ref_vals.shape == ours_vals.shape, f"{ref_vals.shape} vs {ours_vals.shape}"
|
|
num = (ours_vals - ref_vals).norm().item()
|
|
den = ref_vals.norm().item()
|
|
cos = torch.nn.functional.cosine_similarity(ours_vals.reshape(-1), ref_vals.reshape(-1), dim=0).item()
|
|
print(f" ref scale: e8m0 {tuple(ref_sf.shape)} ours: fp32 {tuple(ours_sf.shape)}")
|
|
print(f" |ref|={den:.4f} |ours|={ours_vals.norm().item():.4f} ratio={ours_vals.norm().item() / den:.5f}")
|
|
print(f" rel_err={num / den:.5f} cos={cos:.7f}")
|
|
|
|
print("\nEXPERT_WEIGHT_CHECK_DONE")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|