Skip to content

Commit 640983b

Browse files
fankun.fan范坤
authored andcommitted
Simplify LA MTP/KVBuffer benchmarks with layered compile-cache helpers.
Align bench_la_decode_mtp and bench_la_kvbuffer with bench_la_decode_vs_fla: wrapper for correctness+compile warmup, then get_compiled_*_handle for kernel-only timing. Centralize cache-key dispatch in kernel modules.
1 parent ad8277a commit 640983b

5 files changed

Lines changed: 433 additions & 412 deletions

File tree

benchmarks/bench_la_decode_mtp.py

Lines changed: 44 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,10 @@
5151
except ImportError:
5252
HAS_FLA = False
5353

54-
from benchmarks.utils import benchmark_cuda_fn
55-
from cula.lightning.la_decode_mtp import (
56-
_get_compiled_la_mtp_kernel,
57-
get_mtp_config,
58-
linear_attention_decode_mtp,
59-
)
54+
from benchmarks.utils import benchmark_cuda_fn, relative_rms_error
55+
from cula.lightning.la_decode_mtp import get_compiled_la_mtp_handle, linear_attention_decode_mtp
6056
from cula.ops.la_decode import linear_attention_decode
61-
from cula.utils import USE_FAST_MATH, get_device_sm_version
57+
from cula.utils import USE_FAST_MATH
6258

6359

6460
# ─────────────────────────────────────────────────────────────────────────────
@@ -88,24 +84,38 @@ def run_config(
8884
device = "cuda"
8985
dtype = torch.bfloat16
9086
scale = K**-0.5
87+
pool_size = B
9188

9289
# Per-head log decay (Lightning Attention formula)
9390
g_gamma = -(8 / H * (1 - layer_idx / num_layers)) * torch.arange(H, device=device, dtype=torch.float32)
9491
decay_scales = -g_gamma # la_decode_mtp convention: exp(-decay_scales)
9592

96-
# ── Random inputs ──────────────────────────────────────────────────────
93+
# =========================================================================
94+
# Layer 1 — Inputs & buffers
95+
# =========================================================================
9796
torch.manual_seed(42)
9897
q_4d = torch.randn(B, T, H, K, device=device, dtype=dtype)
9998
k_4d = torch.randn(B, T, H, K, device=device, dtype=dtype)
10099
v_4d = torch.randn(B, T, HV, V, device=device, dtype=dtype)
101100
state_init = torch.randn(B, HV, K, V, device=device, dtype=torch.float32) * 0.01 # K-major
102101

103-
# ── fla reference output ───────────────────────────────────────────────
102+
s_offsets = torch.arange(B, device=device, dtype=torch.int32)
103+
cu_seqlens_dummy = torch.empty(1, device=device, dtype=torch.int32)
104+
inter = (
105+
torch.zeros(B * T * HV, V, K, device=device, dtype=torch.float32)
106+
if cache_intermediate_states
107+
else torch.empty(1, 1, 1, device=device, dtype=torch.float32)
108+
)
109+
110+
# =========================================================================
111+
# Layer 2 — Correctness + compile warmup (wrapper, same config as benchmark)
112+
# =========================================================================
113+
# fla reference
104114
o_fla = None
105115
if HAS_FLA:
106116
state_fla = state_init.clone()
107117
with torch.no_grad():
108-
o_fla_fp32, ht_fla = fused_recurrent_fwd(
118+
o_fla_fp32, _ht_fla = fused_recurrent_fwd(
109119
q_4d,
110120
k_4d,
111121
v_4d,
@@ -114,18 +124,11 @@ def run_config(
114124
initial_state=state_fla,
115125
output_final_state=True,
116126
)
117-
o_fla = o_fla_fp32.to(dtype) # [B, T, H, V] (fla expects HV==H)
127+
o_fla = o_fla_fp32.to(dtype)
118128

119-
# ── cula MTP ───────────────────────────────────────────────────────────
129+
# cuLA MTP — also populates the compile cache for kernel-only timing below
120130
s_cute = state_init.clone().permute(0, 1, 3, 2).contiguous() # [B, HV, V, K]
121131
out_cute = torch.zeros(B, T, HV, V, device=device, dtype=dtype)
122-
s_offsets = torch.arange(B, device=device, dtype=torch.int32)
123-
inter = torch.empty(1, 1, 1, device=device, dtype=torch.float32) # dummy
124-
cu_seqlens_dummy = torch.empty(1, device=device, dtype=torch.int32)
125-
126-
if cache_intermediate_states:
127-
inter = torch.zeros(B * T * HV, V, K, device=device, dtype=torch.float32)
128-
129132
with torch.no_grad():
130133
linear_attention_decode_mtp(
131134
q_4d,
@@ -144,20 +147,18 @@ def run_config(
144147
is_varlen=False,
145148
)
146149

147-
# ── Correctness vs fla ─────────────────────────────────────────────────
148-
rmse, rel_maxdiff = float("nan"), float("nan")
150+
rmse = rel_maxdiff = float("nan")
149151
if o_fla is not None and HV == H:
150-
out_cmp = out_cute.float()
152+
rmse = relative_rms_error(o_fla.float(), out_cute.float())
151153
ref_cmp = o_fla.float()
152-
rmse = torch.sqrt(torch.mean((out_cmp - ref_cmp) ** 2)).item()
154+
out_cmp = out_cute.float()
153155
max_ref = torch.abs(ref_cmp).max().item()
154156
rel_maxdiff = torch.abs(out_cmp - ref_cmp).max().item() / (max_ref + 1e-8)
155157

156-
# ==================================================================
157-
# Mode 1: KERNEL-ONLY — pre-allocated, pre-compiled, pre-built stream
158-
# ==================================================================
159-
pool_size = B
160-
cache_key = (
158+
# =========================================================================
159+
# Layer 3a — Kernel-only timing (compiled handle + pre-built stream)
160+
# =========================================================================
161+
compiled_cute = get_compiled_la_mtp_handle(
161162
B,
162163
T,
163164
H,
@@ -166,19 +167,16 @@ def run_config(
166167
V,
167168
pool_size,
168169
scale,
169-
disable_state_update,
170-
cache_intermediate_states,
171-
False,
172-
*get_mtp_config(B, T, HV, V, disable_state_update),
173-
get_device_sm_version(q_4d.device)[0] >= 10,
170+
q_4d.device,
171+
disable_state_update=disable_state_update,
172+
cache_intermediate_states=cache_intermediate_states,
173+
is_varlen=False,
174174
)
175-
cute_cache = _get_compiled_la_mtp_kernel(*cache_key)
176-
compiled_cute = cute_cache["compiled"]
177175
stream_handle = cuda_drv.CUstream(torch.cuda.current_stream().cuda_stream)
178176

179177
state_kk = state_init.clone().permute(0, 1, 3, 2).contiguous().view(pool_size * HV, V, K)
180178
out_kk = torch.empty(B, T, HV, V, device=device, dtype=dtype)
181-
inter_kk = inter if cache_intermediate_states else torch.empty(1, 1, 1, device=device, dtype=torch.float32)
179+
inter_kk = inter
182180

183181
def kernel_cute_mtp():
184182
compiled_cute(
@@ -194,7 +192,7 @@ def kernel_cute_mtp():
194192
stream_handle,
195193
)
196194

197-
# cula T-sequential baseline: T calls to la_decode (T=1 each)
195+
# cula self-baseline: T sequential la_decode (T=1) wrapper calls
198196
state_seq = state_init.clone().permute(0, 1, 3, 2).contiguous().view(B * HV, V, K)
199197
out_seq_buf = torch.empty(B, HV, V, device=device, dtype=dtype)
200198
q_slices = [q_4d[:, t].contiguous() for t in range(T)]
@@ -222,14 +220,13 @@ def kernel_cute_seq():
222220
V_SPLIT_DIM=V,
223221
)
224222

225-
# fla kernel-only mode would require careful pre-allocation; use wrapper for fla.
226223
with torch.no_grad():
227224
cute_mtp_ms = benchmark_cuda_fn(kernel_cute_mtp)
228225
cute_seq_ms = benchmark_cuda_fn(kernel_cute_seq)
229226

230-
# ==================================================================
231-
# Mode 2: WRAPPER — full Python entry path (cache lookup + CUstream per call)
232-
# ==================================================================
227+
# =========================================================================
228+
# Layer 3b — Wrapper timing (full Python entry path per call)
229+
# =========================================================================
233230
s_wrap = state_init.clone().permute(0, 1, 3, 2).contiguous()
234231
out_wrap = torch.empty(B, T, HV, V, device=device, dtype=dtype)
235232
inter_wrap = (
@@ -259,7 +256,6 @@ def wrapper_cute_mtp():
259256
with torch.no_grad():
260257
wrap_cute_ms = benchmark_cuda_fn(wrapper_cute_mtp)
261258

262-
# fla wrapper
263259
fla_ms = float("nan")
264260
if HAS_FLA:
265261
state_fla_bench = state_init.clone()
@@ -278,7 +274,9 @@ def wrapper_fla():
278274
with torch.no_grad():
279275
fla_ms = benchmark_cuda_fn(wrapper_fla)
280276

281-
# ── Roofline ────────────────────────────────────────────────────────
277+
# =========================================================================
278+
# Layer 4 — Roofline & summary
279+
# =========================================================================
282280
bytes_moved = la_mtp_bytes(
283281
B,
284282
T,
@@ -291,18 +289,15 @@ def wrapper_fla():
291289
)
292290
sol = sol_pct(bytes_moved, cute_mtp_ms, peak_bps)
293291

294-
speedup_seq = cute_seq_ms / cute_mtp_ms
295-
speedup_fla = fla_ms / cute_mtp_ms if HAS_FLA else float("nan")
296-
297292
return {
298293
"B": B,
299294
"T": T,
300295
"cute_mtp_ms": cute_mtp_ms,
301296
"cute_seq_ms": cute_seq_ms,
302297
"fla_ms": fla_ms,
303298
"wrap_cute_ms": wrap_cute_ms,
304-
"speedup_seq": speedup_seq,
305-
"speedup_fla": speedup_fla,
299+
"speedup_seq": cute_seq_ms / cute_mtp_ms,
300+
"speedup_fla": fla_ms / cute_mtp_ms if HAS_FLA else float("nan"),
306301
"rmse": rmse,
307302
"rel_maxdiff": rel_maxdiff,
308303
"sol_pct": sol,
@@ -337,7 +332,7 @@ def main():
337332
print(f" cache_intermediate_states={args.cache_intermediate}, disable_state_update={args.disable_state_update}")
338333
print(f" USE_FAST_MATH={USE_FAST_MATH}, fla available={HAS_FLA}")
339334

340-
fla_avail = HAS_FLA and HV == H # fla expects HV == H
335+
fla_avail = HAS_FLA and HV == H
341336
if HAS_FLA and HV != H:
342337
print(f" [warning] GQA HV={HV} != H={H}; fla baseline disabled (fla assumes HV==H)")
343338

0 commit comments

Comments
 (0)