5151except 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
6056from 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