166 lines
5.6 KiB
Python
Executable File
166 lines
5.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Kernel-level Lean vs Standard (SplitK) decode-attention sweep, both GQA head
|
|
configs, full batch x context grid, at the shipped 1xCU persistent grid.
|
|
|
|
Generalizes benchmark/lean_kernel_qwen_gqa.py (which is Qwen batch=1 only) to
|
|
sweep Qwen2.5-7B (28Q/4KV) and Llama-3.1-8B (32Q/8KV) over
|
|
batch in {1,2,4,8,16,32} x context in {8K,16K,32K,64K,128K}, reporting per-call
|
|
kernel latency, speedup (std / lean), cosine parity vs SplitK, and whether the
|
|
eager auto-gate would enable Lean. Writes a CSV for the PR tables.
|
|
|
|
Grid is whatever SGLANG_FORCE_LEAN_GRID_CU_MULT resolves to (default 1.0 = one
|
|
CTA per CU); set it to A/B other grids without a rebuild.
|
|
|
|
Usage: python3 benchmark/lean_kernel_sweep.py [out.csv]
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "python"))
|
|
|
|
import torch
|
|
|
|
from sglang.kernels.ops.attention.decode_attention import (
|
|
_LEAN_BLOCK_M,
|
|
_lean_decode_launch_params,
|
|
decode_attention_fwd,
|
|
decode_attention_fwd_grouped,
|
|
lean_decode_seqlen_gate,
|
|
)
|
|
|
|
MODELS = [
|
|
("qwen2.5-7b", 28, 4),
|
|
("llama3.1-8b", 32, 8),
|
|
]
|
|
D = D_V = 128
|
|
MAX_KV_SPLITS = 8
|
|
BATCHES = [1, 2, 4, 8, 16, 32]
|
|
CONTEXTS = [8192, 16384, 32768, 65536, 131072]
|
|
|
|
|
|
def bench(fn, warmup=15, iters=100):
|
|
for _ in range(warmup):
|
|
fn()
|
|
torch.cuda.synchronize()
|
|
t0 = time.perf_counter()
|
|
for _ in range(iters):
|
|
fn()
|
|
torch.cuda.synchronize()
|
|
return (time.perf_counter() - t0) / iters * 1000.0 # ms
|
|
|
|
|
|
def run(H_Q, H_KV, B, S):
|
|
dev, dt = "cuda", torch.float16
|
|
kvg = H_Q // H_KV
|
|
sm = 1.0 / (D**0.5)
|
|
tot = B * S
|
|
|
|
total_programs, _, _ = _lean_decode_launch_params(H_KV, kvg)
|
|
lean_Mp = torch.empty(
|
|
(total_programs, _LEAN_BLOCK_M), dtype=torch.float32, device=dev
|
|
)
|
|
lean_Lp = torch.empty(
|
|
(total_programs, _LEAN_BLOCK_M), dtype=torch.float32, device=dev
|
|
)
|
|
lean_Op = torch.empty(
|
|
(total_programs, _LEAN_BLOCK_M, D_V), dtype=torch.float32, device=dev
|
|
)
|
|
lean_locks = torch.zeros((total_programs,), dtype=torch.int32, device=dev)
|
|
|
|
kvi = torch.arange(0, (B + 1) * S, step=S, device=dev, dtype=torch.int32)
|
|
kvx = torch.arange(0, tot, device=dev, dtype=torch.int32)
|
|
q = torch.randn(B, H_Q, D, dtype=dt, device=dev)
|
|
k = torch.randn(tot, H_KV, D, dtype=dt, device=dev)
|
|
v = torch.randn(tot, H_KV, D_V, dtype=dt, device=dev)
|
|
attn_logits = torch.empty(
|
|
(B, H_Q, MAX_KV_SPLITS, D_V), dtype=torch.float32, device=dev
|
|
)
|
|
attn_lse = torch.empty((B, H_Q, MAX_KV_SPLITS), dtype=torch.float32, device=dev)
|
|
nks = torch.full((B,), MAX_KV_SPLITS, dtype=torch.int32, device=dev)
|
|
|
|
o_std = torch.zeros(B, H_Q, D_V, dtype=dt, device=dev)
|
|
std_ms = bench(
|
|
lambda: decode_attention_fwd_grouped(
|
|
q, k, v, o_std, kvi, kvx, attn_logits, attn_lse, nks, MAX_KV_SPLITS, sm, 1.0
|
|
)
|
|
)
|
|
|
|
attn_logits2 = torch.empty_like(attn_logits)
|
|
attn_lse2 = torch.empty_like(attn_lse)
|
|
o_lean = torch.zeros(B, H_Q, D_V, dtype=dt, device=dev)
|
|
lean_ms = bench(
|
|
lambda: decode_attention_fwd(
|
|
q,
|
|
k,
|
|
v,
|
|
o_lean,
|
|
kvi,
|
|
kvx,
|
|
attn_logits2,
|
|
attn_lse2,
|
|
nks,
|
|
MAX_KV_SPLITS,
|
|
sm,
|
|
1.0,
|
|
1.0,
|
|
enable_lean=True,
|
|
lean_Mp=lean_Mp,
|
|
lean_Lp=lean_Lp,
|
|
lean_Op=lean_Op,
|
|
lean_locks=lean_locks,
|
|
)
|
|
)
|
|
|
|
cos = torch.nn.functional.cosine_similarity(
|
|
o_lean.flatten().float(), o_std.flatten().float(), dim=0
|
|
).item()
|
|
gate = lean_decode_seqlen_gate(H_Q, kvg, B, B * S, is_mla=False)
|
|
return std_ms, lean_ms, cos, gate
|
|
|
|
|
|
def main():
|
|
out = sys.argv[1] if len(sys.argv) > 1 else "grid_out/kernel_sweep_1xcu.csv"
|
|
mult = float(os.environ.get("SGLANG_FORCE_LEAN_GRID_CU_MULT", "1.0"))
|
|
print(f"\nKernel sweep GPU={torch.cuda.get_device_name(0)} grid_mult={mult}")
|
|
print("=" * 78)
|
|
rows = ["model,H_Q,H_KV,batch,context,std_ms,lean_ms,speedup,cos,gate"]
|
|
for name, H_Q, H_KV in MODELS:
|
|
print(f"\n{name} ({H_Q}Q/{H_KV}KV)")
|
|
print(
|
|
f"{'batch':>5} {'ctx':>6} {'std_ms':>9} {'lean_ms':>9} {'speedup':>8} {'gate':>5} {'cos':>7}"
|
|
)
|
|
for B in BATCHES:
|
|
for S in CONTEXTS:
|
|
# b32 x 128K on the 8-KV-head config exceeds the microbench's single
|
|
# contiguous KV tensor (faults the GPU); real serving uses a paged pool.
|
|
if H_KV == 8 and B == 32 and S == 131072:
|
|
print(
|
|
f"{B:>5} {S // 1024:>5}K {'skipped (contiguous-KV limit)':>30}"
|
|
)
|
|
rows.append(f"{name},{H_Q},{H_KV},{B},{S},,,,skip,")
|
|
continue
|
|
try:
|
|
std, lean, cos, gate = run(H_Q, H_KV, B, S)
|
|
except torch.cuda.OutOfMemoryError:
|
|
torch.cuda.empty_cache()
|
|
print(f"{B:>5} {S // 1024:>5}K {'OOM':>9}")
|
|
rows.append(f"{name},{H_Q},{H_KV},{B},{S},,,,OOM,")
|
|
continue
|
|
sp = std / lean
|
|
print(
|
|
f"{B:>5} {S // 1024:>5}K {std:>9.3f} {lean:>9.3f} {sp:>7.2f}x {('ON' if gate else 'OFF'):>5} {cos:>7.4f}"
|
|
)
|
|
rows.append(
|
|
f"{name},{H_Q},{H_KV},{B},{S},{std:.4f},{lean:.4f},{sp:.4f},{cos:.4f},{int(gate)}"
|
|
)
|
|
os.makedirs(os.path.dirname(out), exist_ok=True)
|
|
open(out, "w").write("\n".join(rows) + "\n")
|
|
print(f"\nwrote {out}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|