项目文件夹

文件
wehub-resource-sync ec436095dd
Book-CI / test (macos-latest) (push) Has been cancelled
Book-CI / test (ubuntu-latest) (push) Has been cancelled
Book-CI / test (windows-latest) (push) Has been cancelled
Release Fake Tag / publish (push) Has been cancelled
Deploy / deploy (macos-latest) (push) Has been cancelled
Deploy / deploy (ubuntu-latest) (push) Has been cancelled
Deploy / deploy (windows-latest) (push) Has been cancelled
Release to PyPI / Build & publish sglang-kt (push) Has been cancelled
Release to PyPI / Build kt-kernel (Python 3.11) (push) Has been cancelled
Release to PyPI / Build kt-kernel (Python 3.12) (push) Has been cancelled
Release to PyPI / Publish kt-kernel to PyPI (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:30:03 +08:00

314 行
12 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#!/usr/bin/env python
# coding=utf-8
"""Benchmark MXFP4 MoE kernel — V4-Flash shape, mat-vec / mat-mat 双路径覆盖。
Synthesizes V4-Flash-shaped MXFP4 weights (random nibbles + bf16 group scales),
runs the chosen backend over a list of batch sizes M, prints a throughput table.
Routing modes (决定是否触发 mat-mat 路径):
balanced —— 每 token randperm(EXPERT_NUM)[:TOP_K]; 平均 per-expert m ≈
M*top_k/expert_num. V4 真实路由分布. 大 batch (M=1024) 才
平均触发 mat-mat (per-expert m ≥ 4).
concentrated —— 所有 M token 共用同一组 top_k expert; per-expert m = M.
M=4 立即触发 mat-mat —— 用来直观放大 mat-mat 性能优势.
Usage:
python bench/bench_fp4_moe.py --backend v1
python bench/bench_fp4_moe.py --backend v1 --routing concentrated
python bench/bench_fp4_moe.py --all --routing concentrated # 所有可用 backend 对比
`--backend` 是预留扩展点; 当前编译只绑定 v1 (AMXFP4_KGroup_MOE)。要选 v2/v3
需要 ext_bindings 里加新绑定。`--all` 会自动检测哪些 backend 可用。
"""
import argparse
import json
import os
import platform
import subprocess
import sys
import time
import torch
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "build"))
from kt_kernel import kt_kernel_ext # noqa: E402
# ----- V4-Flash MoE shape -----
HIDDEN = 4096
INTER = 2048
EXPERT_NUM = 256
TOP_K = 6
K_GROUP_SIZE = 32
# ----- bench knobs -----
# M=1024 时平均 per-expert m ≈ M*6/256 = 24, balanced 路由也能触发 mat-mat。
DEFAULT_M_LIST = [1, 4, 16, 64, 256, 1024]
WARMUP_ITER = 200
TEST_ITER = 2000
# ----- WorkerPool: 2 NUMA × 40 thread (matches bench_k2_moe_amx.py) -----
WORKER_NUMA = 2
WORKER_THREADS_PER_NUMA = 40
# ----- Backend registry: name → kt_kernel_ext.moe class (None = not bound) -----
BACKENDS = {
"v1": getattr(kt_kernel_ext.moe, "AMXFP4_KGroup_MOE", None),
# 预留扩展点; 加新 backend 时在 ext_bindings 绑定后这里加一行即可。
"v2": getattr(kt_kernel_ext.moe, "AMXFP4_KGroup_MOE_V2", None),
}
# OCP MXFP4 (E2M1) codepoints — same order as the kernel's LUT.
E2M1_VALUES = 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 quantize_mxfp4_tensor(weights: torch.Tensor, group_size: int):
"""[E, N, K] fp32/bf16 → packed nibbles uint8 [E, N, K/2] + bf16 scales [E, N, K/gs]."""
w = weights.to(torch.float32)
e, rows, cols = w.shape
assert cols % group_size == 0 and cols % 2 == 0
reshaped = w.view(e, rows, cols // group_size, group_size)
max_abs = reshaped.abs().amax(dim=-1, keepdim=True).clamp(min=1e-8)
scales = (max_abs / 6.0).squeeze(-1)
normalized = reshaped / scales.unsqueeze(-1)
distances = torch.abs(normalized.unsqueeze(-1) - E2M1_VALUES.view(1, 1, 1, 1, 16))
nibbles = distances.argmin(dim=-1).to(torch.uint8).view(e, rows, cols // 2, 2)
lo = nibbles[..., 0]
hi = nibbles[..., 1]
packed = ((hi << 4) | lo).contiguous() # uint8 [E, N, K/2]
scales = scales.to(torch.bfloat16).contiguous() # bf16 [E, N, K/gs]
return packed, scales
def build_synth_weights():
torch.manual_seed(0)
gate = torch.randn((EXPERT_NUM, INTER, HIDDEN), dtype=torch.float32) / 100
up = torch.randn((EXPERT_NUM, INTER, HIDDEN), dtype=torch.float32) / 100
down = torch.randn((EXPERT_NUM, HIDDEN, INTER), dtype=torch.float32) / 100
gw, gs = quantize_mxfp4_tensor(gate, K_GROUP_SIZE)
uw, us = quantize_mxfp4_tensor(up, K_GROUP_SIZE)
dw, ds = quantize_mxfp4_tensor(down, K_GROUP_SIZE)
return {
"gate_w": gw, "up_w": uw, "down_w": dw,
"gate_s": gs, "up_s": us, "down_s": ds,
}
def build_moe(backend: str, weights, cpu_infer):
cls = BACKENDS.get(backend)
if cls is None:
raise RuntimeError(
f"backend={backend} not bound in this build. Available: "
f"{[k for k, v in BACKENDS.items() if v is not None]}"
)
cfg = kt_kernel_ext.moe.MOEConfig(EXPERT_NUM, TOP_K, HIDDEN, INTER, 0)
cfg.max_len = max(DEFAULT_M_LIST)
cfg.pool = cpu_infer.backend_
cfg.quant_config.bits = 4
cfg.quant_config.group_size = K_GROUP_SIZE
cfg.quant_config.zero_point = False
cfg.gate_projs = [[t.data_ptr() for t in weights["gate_w"]]]
cfg.up_projs = [[t.data_ptr() for t in weights["up_w"]]]
cfg.down_projs = [[t.data_ptr() for t in weights["down_w"]]]
cfg.gate_scales = [[t.data_ptr() for t in weights["gate_s"]]]
cfg.up_scales = [[t.data_ptr() for t in weights["up_s"]]]
cfg.down_scales = [[t.data_ptr() for t in weights["down_s"]]]
moe = cls(cfg)
p2l = torch.arange(EXPERT_NUM, dtype=torch.int64).contiguous()
cpu_infer.submit(moe.load_weights_task(p2l.data_ptr()))
cpu_infer.sync()
return moe
def make_expert_ids(M: int, routing: str) -> torch.Tensor:
"""[M, TOP_K] int64 (kernel forward_binding casts to const int64_t*)."""
if routing == "concentrated":
# 所有 M token 共用同组 top_k expert → per-expert m = M
hot = torch.randperm(EXPERT_NUM)[:TOP_K]
return hot.unsqueeze(0).expand(M, TOP_K).contiguous().to(torch.int64)
# balanced: 每 token 独立 randperm
return torch.stack(
[torch.randperm(EXPERT_NUM)[:TOP_K] for _ in range(M)]
).to(torch.int64).contiguous()
def bench_one_m(moe, cpu_infer, M: int, routing: str):
bsz = torch.tensor([M], dtype=torch.int32)
expert_ids = make_expert_ids(M, routing)
routing_w = torch.rand((M, TOP_K), dtype=torch.float32).contiguous()
x = (torch.randn((M, HIDDEN), dtype=torch.bfloat16) / 100).contiguous()
y = torch.empty((M, HIDDEN), dtype=torch.bfloat16).contiguous()
for _ in range(WARMUP_ITER):
cpu_infer.submit(moe.forward_task(
bsz.data_ptr(), TOP_K, expert_ids.data_ptr(),
routing_w.data_ptr(), x.data_ptr(), y.data_ptr(), False))
cpu_infer.sync()
start = time.perf_counter()
for _ in range(TEST_ITER):
cpu_infer.submit(moe.forward_task(
bsz.data_ptr(), TOP_K, expert_ids.data_ptr(),
routing_w.data_ptr(), x.data_ptr(), y.data_ptr(), False))
cpu_infer.sync()
total = time.perf_counter() - start
per_iter_us = total / TEST_ITER * 1e6
tok_per_s = M * TEST_ITER / total
unique_e = int(torch.unique(expert_ids).numel())
avg_m_per_expert = float(M * TOP_K) / max(unique_e, 1)
return {
"M": M, "iters": TEST_ITER, "total_s": total,
"per_iter_us": per_iter_us, "tokens_per_s": tok_per_s,
"unique_experts": unique_e, "avg_m_per_expert": avg_m_per_expert,
}
def run_backend(backend: str, weights, cpu_infer, m_list, routing):
print(f"\n[bench-fp4] backend={backend} routing={routing}")
moe = build_moe(backend, weights, cpu_infer)
rows = []
for M in m_list:
r = bench_one_m(moe, cpu_infer, M, routing)
rows.append(r)
print(f" M={M:>5} avg_m/expert={r['avg_m_per_expert']:>6.1f} "
f"per-iter={r['per_iter_us']:>9.1f} us tok/s={r['tokens_per_s']:>9.1f}")
return rows
def print_single_table(backend, rows, routing):
print(f"\n=== Summary ({backend}, routing={routing}) ===")
print(f"{'M':>5} {'avg_m':>6} {'per-iter us':>12} {'tok/s':>10}")
for r in rows:
print(f"{r['M']:>5} {r['avg_m_per_expert']:>6.1f} "
f"{r['per_iter_us']:>12.1f} {r['tokens_per_s']:>10.1f}")
def print_compare_table(all_rows: dict, routing: str):
backends = list(all_rows.keys())
if len(backends) < 2:
print_single_table(backends[0], all_rows[backends[0]], routing)
return
base = backends[0]
print(f"\n=== {' vs '.join(backends)} (routing={routing}, base={base}) ===")
header = f"{'M':>5} {'avg_m':>6}"
for be in backends:
header += f" {be + ' us':>10}"
for be in backends[1:]:
header += f" {be + '/' + base:>8}"
print(header)
n_rows = len(all_rows[base])
for i in range(n_rows):
line = f"{all_rows[base][i]['M']:>5} {all_rows[base][i]['avg_m_per_expert']:>6.1f}"
for be in backends:
line += f" {all_rows[be][i]['per_iter_us']:>10.1f}"
for be in backends[1:]:
ratio = all_rows[be][i]['per_iter_us'] / all_rows[base][i]['per_iter_us']
line += f" {ratio:>8.3f}"
print(line)
def get_git_commit():
try:
commit = subprocess.check_output(["git", "rev-parse", "HEAD"]).decode().strip()
dirty = bool(subprocess.check_output(["git", "status", "--porcelain"]).decode().strip())
return {"commit": commit, "dirty": dirty}
except Exception as e:
return {"commit": None, "error": str(e)}
def get_system_info():
info = {"node": platform.node(), "system": platform.system()}
cpu_model = None
try:
with open("/proc/cpuinfo") as f:
for line in f:
if "model name" in line:
cpu_model = line.split(":", 1)[1].strip()
break
except Exception:
pass
info["cpu"] = cpu_model
info["cpu_cores"] = os.cpu_count()
return info
def main():
p = argparse.ArgumentParser()
p.add_argument("--backend", choices=list(BACKENDS.keys()), default="v1",
help="单 backend 模式(被 --all 覆盖)。当前可用: "
+ ",".join(k for k, v in BACKENDS.items() if v is not None))
p.add_argument("--all", action="store_true",
help="跑所有已绑定的 backend 并打印对比表(自动跳过未绑定的)")
p.add_argument("--routing", choices=["balanced", "concentrated"], default="balanced",
help="balanced=每 token randperm (V4 真实); "
"concentrated=所有 token 共用同组 top_k (per-expert m=M, 放大 mat-mat)")
p.add_argument("--m-list", type=str, default=None,
help=f"Comma-separated M values, default: {','.join(map(str, DEFAULT_M_LIST))}")
p.add_argument("--numa", type=int, default=WORKER_NUMA)
p.add_argument("--threads-per-numa", type=int, default=WORKER_THREADS_PER_NUMA)
args = p.parse_args()
m_list = [int(x) for x in args.m_list.split(",")] if args.m_list else DEFAULT_M_LIST
if args.all:
backends = [k for k, v in BACKENDS.items() if v is not None]
if not backends:
raise RuntimeError("No MXFP4 backend bound in this build.")
print(f"[bench-fp4] --all: detected backends = {backends}")
else:
if BACKENDS.get(args.backend) is None:
raise RuntimeError(
f"backend={args.backend} not bound. Available: "
f"{[k for k, v in BACKENDS.items() if v is not None]}"
)
backends = [args.backend]
print(f"[bench-fp4] shape=H{HIDDEN}/I{INTER}/E{EXPERT_NUM}/k{TOP_K}/gs{K_GROUP_SIZE} routing={args.routing}")
print(f"[bench-fp4] WorkerPool: numa={args.numa} threads_per_numa={args.threads_per_numa}")
print(f"[bench-fp4] m_list: {m_list}")
wp = kt_kernel_ext.WorkerPoolConfig()
wp.subpool_count = args.numa
wp.subpool_numa_map = list(range(args.numa))
wp.subpool_thread_count = [args.threads_per_numa] * args.numa
cpu_infer = kt_kernel_ext.CPUInfer(wp)
print("[bench-fp4] synthesizing MXFP4 weights …")
weights = build_synth_weights()
all_rows = {}
for be in backends:
all_rows[be] = run_backend(be, weights, cpu_infer, m_list, args.routing)
if len(backends) > 1:
print_compare_table(all_rows, args.routing)
else:
print_single_table(backends[0], all_rows[backends[0]], args.routing)
# JSONL log
out_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bench_fp4_moe.jsonl")
ts = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
git = get_git_commit()
sys_info = get_system_info()
with open(out_path, "a") as f:
for be, rows in all_rows.items():
record = {
"backend": be,
"routing": args.routing,
"shape": {"hidden": HIDDEN, "inter": INTER, "expert_num": EXPERT_NUM,
"top_k": TOP_K, "k_group_size": K_GROUP_SIZE},
"worker_pool": {"numa": args.numa, "threads_per_numa": args.threads_per_numa},
"rows": rows,
"git": git, "system": sys_info, "timestamp": ts,
}
f.write(json.dumps(record) + "\n")
print(f"\n[bench-fp4] appended {len(backends)} record(s) → {out_path}")
if __name__ == "__main__":
main()