metrale_model_layers/layers/ops/
ssm_gdn_a3.rs1#![allow(unused_imports)]
9
10use anyhow::Result;
11use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
12use metrale_gpu_runtime::kernel_args::{KernelLaunch, div_ceil};
13
14use crate::layers::moe;
15use crate::weight_map::{DenseWeight, Fp8DenseWeight, Fp8Weight, QuantizedWeight};
16
17use super::*;
18
19pub(crate) const GDN_TC_DIM: u32 = 128;
22pub(crate) const GDN_TC_CHUNK: u32 = 64;
24pub(crate) const GDN_TC_SMEM: u32 = GDN_TC_DIM * 136 * 2
31 + 2 * (GDN_TC_CHUNK * 136 * 2)
32 + GDN_TC_DIM * 72 * 2
33 + (GDN_TC_CHUNK + 1) * 4;
34
35pub(crate) fn gdn_tc_spine_reject(
47 requested: bool,
48 kernel_present: bool,
49 k_dim: u32,
50 v_dim: u32,
51 chunk: u32,
52 qk_stride: u32,
53) -> Option<&'static str> {
54 if !requested {
55 Some("not requested")
56 } else if !kernel_present {
57 Some("kernel absent from this image")
58 } else if k_dim != GDN_TC_DIM || v_dim != GDN_TC_DIM || chunk != GDN_TC_CHUNK {
59 Some("head/chunk differs from the compile-time tile (K_DIM=V_DIM=128, CHUNK=64)")
60 } else if !qk_stride.is_multiple_of(8) {
61 Some("qk_stride is not a multiple of 8 (the K staging uses 16-byte vector loads)")
62 } else {
63 None
64 }
65}
66
67#[cfg(test)]
68#[path = "ssm_gdn_tc_tests.rs"]
69mod ssm_gdn_tc_tests;
70
71#[allow(clippy::too_many_arguments)]
80pub fn gdn_prefill_fla(
81 gpu: &dyn GpuBackend,
82 k_recompute_wu: KernelHandle,
83 k_recompute_wu_hopper: KernelHandle,
86 k_chunk_fwd_o_hopper: KernelHandle,
87 k_chunk_delta_h: KernelHandle,
88 k_chunk_delta_h_tc_vblock: KernelHandle,
92 k_chunk_delta_h_tcfuse: KernelHandle,
97 k_chunk_delta_h_fused: KernelHandle,
98 k_chunk_delta_h_tma: KernelHandle,
99 k_chunk_fwd_o: KernelHandle,
100 h_state: DevicePtr,
101 query: DevicePtr,
102 key: DevicePtr,
103 value: DevicePtr,
104 gate: DevicePtr,
105 beta: DevicePtr,
106 output: DevicePtr,
107 w_out: DevicePtr,
108 u_out: DevicePtr,
109 s_out: DevicePtr,
110 uc_out: DevicePtr,
111 gc_out: DevicePtr,
112 batch_size: u32,
113 seq_len: u32,
114 num_chunks: u32,
115 num_k_heads: u32,
116 num_v_heads: u32,
117 k_dim: u32,
118 v_dim: u32,
119 qk_stride: u32,
120 v_stride: u32,
121 gb_stride: u32,
122 h_state_is_table: bool,
125 cu_seqlens: DevicePtr,
130 cu_chunks: DevicePtr,
131 is_varlen: bool,
132 profile: bool,
133 stream: u64,
134) -> Result<()> {
135 const C: u32 = 64; let (kd, vd) = (k_dim, v_dim);
137 let smem_wu = C * kd * 2 + C * C * 4 + C * 4;
140 let smem_dh = 2 * (C * (2 * kd + vd) * 2) + 2 * C * 4 + 2 * (C + 1) * 4;
141 let smem_fo = C * kd * 2 + C * kd * 2 + C * C * 4 + C * vd * 2 + kd * vd * 2 + 2 * C * 4;
142
143 let mut t0: Option<std::time::Instant> = if profile {
144 gpu.synchronize(stream)?;
145 Some(std::time::Instant::now())
146 } else {
147 None
148 };
149
150 macro_rules! prof {
151 ($label:expr, $t0:expr) => {
152 if let Some(t0) = $t0.take() {
153 gpu.synchronize(stream)?;
154 let elapsed = t0.elapsed().as_micros();
155 tracing::info!(" SSM prefill [{}] N={}: {}µs", $label, seq_len, elapsed);
156 *$t0 = Some(std::time::Instant::now());
157 }
158 };
159 }
160
161 let tc_requested = super::target_defaults::resolved().gdn_prefill_tc.value;
168 let (wu, fo) = gdn_hopper_remnants(
169 tc_requested,
170 k_recompute_wu,
171 smem_wu,
172 k_chunk_fwd_o,
173 smem_fo,
174 k_recompute_wu_hopper,
175 k_chunk_fwd_o_hopper,
176 kd,
177 vd,
178 C,
179 );
180
181 let fo = super::gdn_fwd_o_mma8(gpu, fo, k_chunk_fwd_o, kd, vd)?;
183
184 KernelLaunch::new(gpu, wu.kernel)
186 .grid([num_chunks, num_v_heads, batch_size])
187 .block([wu.block, 1, 1])
188 .shared_mem(wu.smem)
189 .arg_ptr(key)
190 .arg_ptr(value)
191 .arg_ptr(gate)
192 .arg_ptr(beta)
193 .arg_ptr(w_out)
194 .arg_ptr(u_out)
195 .arg_ptr(gc_out)
196 .arg_u32(batch_size)
197 .arg_u32(seq_len)
198 .arg_u32(num_chunks)
199 .arg_u32(num_k_heads)
200 .arg_u32(num_v_heads)
201 .arg_u32(kd)
202 .arg_u32(vd)
203 .arg_u32(qk_stride)
204 .arg_u32(v_stride)
205 .arg_u32(gb_stride)
206 .arg_ptr(cu_seqlens)
207 .arg_ptr(cu_chunks)
208 .arg_u32(is_varlen as u32)
209 .launch(stream)?;
210 prof!("gdn_fla_recompute_wu", &mut t0);
211
212 let use_fused = k_chunk_delta_h_fused.0 != 0
223 && std::env::var("METRALE_GDN_VTILE").ok().as_deref() != Some("0");
224 let use_tcvb = !use_fused
225 && k_chunk_delta_h_tc_vblock.0 != 0
226 && std::env::var("METRALE_GDN_TC_VBLOCK").ok().as_deref() == Some("1");
227 const DV_BLK: u32 = 64; let num_dv_blk = (vd / DV_BLK).max(1);
229 let smem_tcvb = DV_BLK * kd * 2
232 + C * DV_BLK * 4
233 + 2 * (C * kd + C * DV_BLK) * 2
234 + 2 * C * 4
235 + 2 * (C + 1) * 4;
236 let spine = super::gdn_scalar_spine();
243 let pipe = spine == super::GdnScalarSpine::Pipe;
244 let smem_fused = if pipe {
245 smem_dh
246 } else {
247 C * kd * 2 + C * kd * 2 + C * vd * 2 + (C + 1) * 4
248 };
249 let fused_block = if spine == super::GdnScalarSpine::Vtile {
250 512u32
251 } else {
252 256u32
253 };
254 let smem_tcfuse = GDN_TC_SMEM;
261 let tc_reject = gdn_tc_spine_reject(
262 tc_requested,
263 k_chunk_delta_h_tcfuse.0 != 0,
264 kd,
265 vd,
266 C,
267 qk_stride,
268 );
269 if tc_requested && let Some(why) = tc_reject {
270 tracing::warn!("METRALE_GDN_PREFILL_TC set but tensor-core spine is NOT running: {why}");
271 }
272 let tc_ok = tc_reject.is_none();
273 if tc_ok {
274 tracing::info!(
277 "{}",
278 gdn_tc_spine_route_line(num_v_heads, batch_size, smem_tcfuse)
279 );
280 }
281 let tma_requested = std::env::var("METRALE_GDN_TMA").ok().as_deref() == Some("1");
286 let tma_reject: Option<&str> = if !tma_requested {
287 Some("not requested")
288 } else if tc_ok {
289 Some("METRALE_GDN_PREFILL_TC is active and takes precedence")
292 } else if k_chunk_delta_h_tma.0 == 0 {
293 Some("kernel absent from this image")
294 } else if is_varlen {
295 Some(
296 "varlen: choff comes from cu_chunks, so the descriptor's flat row count is unknown host-side",
297 )
298 } else if kd != 128 || vd != 128 || C != 64 {
299 Some("head/chunk differs from the compile-time tile the descriptors encode")
300 } else if !qk_stride.is_multiple_of(8) {
301 Some("qk_stride is not a multiple of 8 (bf16 row pitch must be 16-byte aligned)")
302 } else {
303 None
304 };
305 if tma_requested && let Some(why) = tma_reject {
306 tracing::warn!("METRALE_GDN_TMA=1 but the TMA spine is NOT running: {why}");
307 }
308 let tma_ok = tma_reject.is_none();
309 if tma_ok {
310 tracing::info!("GDN state spine: gated_delta_rule_chunk_delta_h_tma");
311 }
312 #[cfg(feature = "cuda")]
316 if tma_ok {
317 use metrale_gpu_runtime::cuda_backend::tensormap::TensorMap;
318 let blocks = (batch_size as u64) * (num_chunks as u64) * (num_v_heads as u64);
321 let w_map =
322 TensorMap::tiled_2d_bf16(w_out, blocks * C as u64, kd as u64, kd as u64, C, kd)?;
323 let u_map =
324 TensorMap::tiled_2d_bf16(u_out, blocks * C as u64, vd as u64, vd as u64, C, vd)?;
325 let k_map = TensorMap::tiled_2d_bf16(
329 key,
330 (batch_size as u64) * (seq_len as u64),
331 qk_stride as u64,
332 qk_stride as u64,
333 C,
334 kd,
335 )?;
336 KernelLaunch::new(gpu, k_chunk_delta_h_tma)
337 .grid([num_v_heads, batch_size, 1])
338 .block([256, 1, 1])
339 .shared_mem(smem_dh)
340 .arg_ptr(h_state)
341 .arg_tensormap(w_map.bytes())
342 .arg_tensormap(u_map.bytes())
343 .arg_tensormap(k_map.bytes())
344 .arg_ptr(gc_out)
345 .arg_ptr(s_out)
346 .arg_ptr(uc_out)
347 .arg_u32(batch_size)
348 .arg_u32(seq_len)
349 .arg_u32(num_chunks)
350 .arg_u32(num_k_heads)
351 .arg_u32(num_v_heads)
352 .arg_u32(vd)
353 .arg_u32(h_state_is_table as u32)
354 .arg_ptr(cu_seqlens)
355 .arg_ptr(cu_chunks)
356 .arg_u32(is_varlen as u32)
357 .launch(stream)?;
358 prof!("gdn_fla_chunk_delta_h", &mut t0);
359 }
360
361 if !tma_ok {
364 let (k_cdh, cdh_grid_y, cdh_smem, cdh_block) = if tc_ok {
365 (k_chunk_delta_h_tcfuse, batch_size, smem_tcfuse, 256u32)
366 } else if use_fused {
367 (k_chunk_delta_h_fused, batch_size, smem_fused, fused_block)
368 } else if use_tcvb {
369 (
370 k_chunk_delta_h_tc_vblock,
371 batch_size * num_dv_blk,
372 smem_tcvb,
373 256u32,
374 )
375 } else {
376 (k_chunk_delta_h, batch_size, smem_dh, 256u32)
377 };
378 KernelLaunch::new(gpu, k_cdh)
379 .grid([num_v_heads, cdh_grid_y, 1])
380 .block([cdh_block, 1, 1])
381 .shared_mem(cdh_smem)
382 .arg_ptr(h_state)
383 .arg_ptr(w_out)
384 .arg_ptr(u_out)
385 .arg_ptr(key)
386 .arg_ptr(gate)
387 .arg_ptr(gc_out)
388 .arg_ptr(s_out)
389 .arg_ptr(uc_out)
390 .arg_u32(batch_size)
391 .arg_u32(seq_len)
392 .arg_u32(num_chunks)
393 .arg_u32(num_k_heads)
394 .arg_u32(num_v_heads)
395 .arg_u32(kd)
396 .arg_u32(vd)
397 .arg_u32(qk_stride)
398 .arg_u32(gb_stride)
399 .arg_u32(h_state_is_table as u32)
400 .arg_ptr(cu_seqlens)
401 .arg_ptr(cu_chunks)
402 .arg_u32(is_varlen as u32)
403 .launch(stream)?;
404 prof!("gdn_fla_chunk_delta_h", &mut t0);
405 }
406
407 KernelLaunch::new(gpu, fo.kernel)
409 .grid([num_chunks, num_v_heads, batch_size])
410 .block([fo.block, 1, 1])
411 .shared_mem(fo.smem)
412 .arg_ptr(query)
413 .arg_ptr(key)
414 .arg_ptr(gate)
415 .arg_ptr(gc_out)
416 .arg_ptr(s_out)
417 .arg_ptr(uc_out)
418 .arg_ptr(output)
419 .arg_u32(batch_size)
420 .arg_u32(seq_len)
421 .arg_u32(num_chunks)
422 .arg_u32(num_k_heads)
423 .arg_u32(num_v_heads)
424 .arg_u32(kd)
425 .arg_u32(vd)
426 .arg_u32(qk_stride)
427 .arg_u32(gb_stride)
428 .arg_ptr(cu_seqlens)
429 .arg_ptr(cu_chunks)
430 .arg_u32(is_varlen as u32)
431 .launch(stream)?;
432 if let Some(t0) = t0 {
433 gpu.synchronize(stream)?;
434 let elapsed = t0.elapsed().as_micros();
435 tracing::info!(
436 " SSM prefill [gdn_fla_chunk_fwd_o] N={}: {}µs",
437 seq_len,
438 elapsed
439 );
440 }
441 Ok(())
442}