109 lines
3.5 KiB
Python
109 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
bench_w8a16_splitlinear.py — W8A16 vs SplitLinear prefill benchmark.
|
|
|
|
Usage:
|
|
python3 bench_w8a16_splitlinear.py [seq_len] # default 512
|
|
|
|
Requires:
|
|
- mlx_vlm with Qwen3-VL-2B-Instruct-8bit model
|
|
- split_linear.py (V4) + libane_bridge_v6.dylib in same directory
|
|
"""
|
|
import sys, time, os
|
|
import numpy as np
|
|
import mlx.core as mx
|
|
from mlx_vlm.utils import load as vlm_load
|
|
from mlx_vlm.models.cache import KVCache
|
|
|
|
# ─── Config ───
|
|
SEQ = int(sys.argv[1]) if len(sys.argv) > 1 else 512
|
|
W8A16_MODEL = '~/Downloads/sft_baseline_v2_w8a16'
|
|
N_WARMUP = 3
|
|
N_BENCH = 8
|
|
|
|
def bench_forward(lang, ids, pos, n_layers, reps):
|
|
ts = []
|
|
for _ in range(reps):
|
|
cache = [KVCache() for _ in range(n_layers)]
|
|
t0 = time.perf_counter()
|
|
mx.eval(lang(ids, cache=cache, position_ids=pos).logits)
|
|
ts.append((time.perf_counter() - t0) * 1000)
|
|
return ts
|
|
|
|
def main():
|
|
print(f"\n{'='*60}")
|
|
print(f" W8A16 vs SplitLinear Prefill Benchmark (seq={SEQ})")
|
|
print(f"{'='*60}")
|
|
print(f" Model: {W8A16_MODEL}")
|
|
print(f" Warmup: {N_WARMUP}, Bench: {N_BENCH}\n")
|
|
|
|
# Load model
|
|
print("[1/4] Loading W8A16 model...")
|
|
model, _ = vlm_load(W8A16_MODEL)
|
|
lang = model.language_model
|
|
N = lang.args.num_hidden_layers
|
|
print(f" {N} layers loaded\n")
|
|
|
|
# Prepare inputs
|
|
ids = mx.ones((1, SEQ), dtype=mx.int32)
|
|
pos = mx.broadcast_to(
|
|
mx.arange(SEQ).reshape(1, SEQ)[None, :, :],
|
|
(3, 1, SEQ)
|
|
)
|
|
mx.eval(ids, pos)
|
|
|
|
# ─── W8A16 Baseline ───
|
|
print("[2/4] W8A16 GPU baseline")
|
|
bench_forward(lang, ids, pos, N, N_WARMUP) # warmup
|
|
ts_w8 = bench_forward(lang, ids, pos, N, N_BENCH)
|
|
for i, t in enumerate(ts_w8):
|
|
print(f" Run {i+1}: {t:.1f}ms")
|
|
med_w8 = float(np.median(ts_w8))
|
|
print(f" Median: {med_w8:.1f}ms\n")
|
|
|
|
# ─── Reference logits ───
|
|
print("[3/4] Computing reference logits for accuracy check...")
|
|
c_ref = [KVCache() for _ in range(N)]
|
|
ref = np.array(lang(ids, cache=c_ref, position_ids=pos).logits.astype(mx.float32))
|
|
|
|
# ─── SplitLinear ───
|
|
print("[4/4] Patch with SplitLinear + benchmark")
|
|
from split_linear import patch_model, SplitLinear
|
|
bridge = patch_model(model, SEQ)
|
|
SplitLinear.set_prefill(True)
|
|
|
|
# Warmup
|
|
bench_forward(lang, ids, pos, N, N_WARMUP)
|
|
|
|
# Accuracy
|
|
c_hyb = [KVCache() for _ in range(N)]
|
|
hyb = np.array(lang(ids, cache=c_hyb, position_ids=pos).logits.astype(mx.float32))
|
|
cos = float(np.dot(ref.flatten(), hyb.flatten()) /
|
|
(np.linalg.norm(ref.flatten()) * np.linalg.norm(hyb.flatten()) + 1e-12))
|
|
top1 = float((ref.argmax(-1) == hyb.argmax(-1)).mean() * 100)
|
|
print(f" Accuracy: cos={cos:.6f}, top1={top1:.1f}%")
|
|
|
|
# Benchmark
|
|
ts_sp = bench_forward(lang, ids, pos, N, N_BENCH)
|
|
for i, t in enumerate(ts_sp):
|
|
print(f" Run {i+1}: {t:.1f}ms")
|
|
med_sp = float(np.median(ts_sp))
|
|
|
|
# ─── Summary ───
|
|
speedup = med_w8 / med_sp
|
|
delta = med_sp - med_w8
|
|
print(f"\n{'='*60}")
|
|
print(f" W8A16 GPU: {med_w8:.1f}ms")
|
|
print(f" SplitLinear: {med_sp:.1f}ms ({speedup:.3f}x)")
|
|
print(f" Delta: {delta:+.1f}ms")
|
|
print(f" Accuracy: cos={cos:.6f} top1={top1:.1f}%")
|
|
print(f"{'='*60}")
|
|
|
|
if speedup >= 1.0:
|
|
print(f" ✅ SplitLinear faster by {(speedup-1)*100:.1f}%")
|
|
else:
|
|
print(f" ⚠️ SplitLinear slower by {(1-speedup)*100:.1f}%")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|