1use crate::ptx_kernels::correlate_ptx;
30
31fn ptx_header(sm: u32) -> String {
37 let ptx_ver = if sm >= 100 {
38 "8.7"
39 } else if sm >= 90 {
40 "8.4"
41 } else if sm >= 80 {
42 "8.0"
43 } else {
44 "7.5"
45 };
46 format!(".version {ptx_ver}\n.target sm_{sm}\n.address_size 64\n\n")
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct TileConfig {
68 pub block_x: u32,
70 pub block_y: u32,
72 pub stages: u32,
74 pub smem_bytes_per_stage: u32,
76 pub cp_async: bool,
78 pub tma: bool,
80}
81
82impl TileConfig {
83 #[must_use]
86 pub const fn portable_default() -> Self {
87 Self {
88 block_x: 256,
89 block_y: 1,
90 stages: 1,
91 smem_bytes_per_stage: 0,
92 cp_async: false,
93 tma: false,
94 }
95 }
96
97 #[must_use]
102 pub fn for_sm(sm: u32) -> Self {
103 let (block_x, stages) = if sm >= 90 {
104 (512, 3)
105 } else if sm >= 80 {
106 (256, 2)
107 } else {
108 (128, 1)
109 };
110 Self {
111 block_x,
112 block_y: 1,
113 stages,
114 smem_bytes_per_stage: block_x * 4,
115 cp_async: sm >= 80,
116 tma: sm >= 90,
117 }
118 }
119
120 #[must_use]
123 pub fn grid_x(self, n: usize) -> u32 {
124 let bx = self.block_x.max(1) as usize;
125 (n.div_ceil(bx)).max(1) as u32
126 }
127
128 #[must_use]
130 pub fn threads_per_block(self) -> u32 {
131 self.block_x.saturating_mul(self.block_y)
132 }
133
134 #[must_use]
137 pub fn total_smem_bytes(self) -> u32 {
138 self.stages.saturating_mul(self.smem_bytes_per_stage)
139 }
140}
141
142#[must_use]
161pub fn correlate_tma_ptx(sm: u32) -> String {
162 if sm < 90 {
163 return correlate_ptx(sm);
166 }
167 let hdr = ptx_header(sm);
168 let cfg = TileConfig::for_sm(sm);
169 let prologue = format!(
171 ".visible .entry correlate_tma_kernel(\n\
172 .param .u64 p_c,\n\
173 .param .u64 p_phi,\n\
174 .param .u64 p_r,\n\
175 .param .u32 p_m,\n\
176 .param .u32 p_n\n\
177 )\n\
178 {{\n\
179 // CTA-shared staging tile for the residual block ({tile} bytes).\n\
180 .shared .align 16 .b8 r_tile[{tile}];\n\
181 // mbarrier object for the bulk async copy completion.\n\
182 .shared .align 8 .b64 tma_bar[1];\n",
183 tile = cfg.smem_bytes_per_stage,
184 );
185 let body = "\
186 .reg .u64 %rd<20>;\n\
187 .reg .u32 %r<24>;\n\
188 .reg .f32 %f<8>;\n\
189 .reg .pred %p0;\n\
190 \n\
191 ld.param.u64 %rd0, [p_c];\n\
192 ld.param.u64 %rd1, [p_phi];\n\
193 ld.param.u64 %rd2, [p_r];\n\
194 ld.param.u32 %r0, [p_m];\n\
195 ld.param.u32 %r1, [p_n];\n\
196 \n\
197 mov.u32 %r2, %ntid.x;\n\
198 mov.u32 %r3, %ctaid.x;\n\
199 mov.u32 %r4, %tid.x;\n\
200 mad.lo.u32 %r5, %r2, %r3, %r4;\n\
201 \n\
202 // Thread 0 initialises the mbarrier and issues the bulk copy of r[0..m).\n\
203 mov.u64 %rd3, r_tile;\n\
204 mov.u64 %rd4, tma_bar;\n\
205 setp.ne.u32 %p0, %r4, 0;\n\
206 @%p0 bra $TMA_WAIT;\n\
207 \n\
208 // bytes = min(m, block_x) * 4 ; here we stage the leading block.\n\
209 mul.lo.u32 %r6, %r2, 4;\n\
210 mbarrier.init.shared.b64 [%rd4], 1;\n\
211 cp.async.bulk.shared::cluster.global.mbarrier::complete_tx::bytes \
212[%rd3], [%rd2], %r6, [%rd4];\n\
213 mbarrier.arrive.expect_tx.shared.b64 _, [%rd4], %r6;\n\
214 \n\
215 $TMA_WAIT:\n\
216 bar.sync 0;\n\
217 // All threads wait on the mbarrier phase before reading r_tile.\n\
218 mbarrier.try_wait.parity.shared.b64 %p0, [%rd4], 0;\n\
219 @!%p0 bra $TMA_WAIT;\n\
220 \n\
221 setp.ge.u32 %p0, %r5, %r1;\n\
222 @%p0 bra $TMA_DONE;\n\
223 \n\
224 mov.f32 %f0, 0f00000000;\n\
225 mov.u32 %r7, 0;\n\
226 \n\
227 $TMA_LOOP:\n\
228 setp.ge.u32 %p0, %r7, %r0;\n\
229 @%p0 bra $TMA_WRITE;\n\
230 \n\
231 // phi[i, j] = i * n + j\n\
232 mul.lo.u32 %r8, %r7, %r1;\n\
233 add.u32 %r8, %r8, %r5;\n\
234 mul.wide.u32 %rd5, %r8, 4;\n\
235 add.u64 %rd6, %rd1, %rd5;\n\
236 ld.global.f32 %f1, [%rd6];\n\
237 \n\
238 // r[i] from the shared staging tile when i < block_x, else global.\n\
239 setp.ge.u32 %p0, %r7, %r2;\n\
240 @%p0 bra $TMA_RGLOBAL;\n\
241 mul.wide.u32 %rd7, %r7, 4;\n\
242 add.u64 %rd8, %rd3, %rd7;\n\
243 ld.shared.f32 %f2, [%rd8];\n\
244 bra $TMA_HAVE_R;\n\
245 $TMA_RGLOBAL:\n\
246 mul.wide.u32 %rd7, %r7, 4;\n\
247 add.u64 %rd8, %rd2, %rd7;\n\
248 ld.global.f32 %f2, [%rd8];\n\
249 $TMA_HAVE_R:\n\
250 fma.rn.f32 %f0, %f1, %f2, %f0;\n\
251 add.u32 %r7, %r7, 1;\n\
252 bra $TMA_LOOP;\n\
253 \n\
254 $TMA_WRITE:\n\
255 mul.wide.u32 %rd9, %r5, 4;\n\
256 add.u64 %rd10, %rd0, %rd9;\n\
257 st.global.f32 [%rd10], %f0;\n\
258 \n\
259 $TMA_DONE:\n\
260 ret;\n\
261 }\n";
262 hdr + &prologue + body
263}
264
265#[must_use]
283pub fn iht_step_cp_async_ptx(sm: u32) -> String {
284 if sm < 80 {
285 return crate::ptx_kernels::iht_step_ptx(sm);
286 }
287 let hdr = ptx_header(sm);
288 let cfg = TileConfig::for_sm(sm);
289 let prologue = format!(
290 ".visible .entry iht_step_cp_async_kernel(\n\
291 .param .u64 p_x,\n\
292 .param .u64 p_grad,\n\
293 .param .f32 p_mu,\n\
294 .param .u32 p_n\n\
295 )\n\
296 {{\n\
297 // {stages}-stage shared staging buffer for grad ({tile} bytes/stage).\n\
298 .shared .align 16 .b8 grad_tile[{tile}];\n",
299 stages = cfg.stages,
300 tile = cfg.smem_bytes_per_stage,
301 );
302 let body = "\
303 .reg .u64 %rd<16>;\n\
304 .reg .u32 %r<16>;\n\
305 .reg .f32 %f<8>;\n\
306 .reg .pred %p0;\n\
307 \n\
308 ld.param.u64 %rd0, [p_x];\n\
309 ld.param.u64 %rd1, [p_grad];\n\
310 ld.param.f32 %f0, [p_mu];\n\
311 ld.param.u32 %r0, [p_n];\n\
312 \n\
313 mov.u32 %r1, %ntid.x;\n\
314 mov.u32 %r2, %ctaid.x;\n\
315 mov.u32 %r3, %tid.x;\n\
316 mad.lo.u32 %r4, %r1, %r2, %r3;\n\
317 \n\
318 setp.ge.u32 %p0, %r4, %r0;\n\
319 @%p0 bra $CA_DONE;\n\
320 \n\
321 // Stage this thread's grad element global->shared via cp.async (4 bytes).\n\
322 mov.u64 %rd2, grad_tile;\n\
323 mul.wide.u32 %rd3, %r3, 4;\n\
324 add.u64 %rd4, %rd2, %rd3;\n\
325 mul.wide.u32 %rd5, %r4, 4;\n\
326 add.u64 %rd6, %rd1, %rd5;\n\
327 cp.async.cg.shared.global [%rd4], [%rd6], 4;\n\
328 cp.async.commit_group;\n\
329 cp.async.wait_group 0;\n\
330 bar.sync 0;\n\
331 \n\
332 // x[i] += mu * grad_tile[tid]\n\
333 ld.shared.f32 %f2, [%rd4];\n\
334 add.u64 %rd7, %rd0, %rd5;\n\
335 ld.global.f32 %f1, [%rd7];\n\
336 fma.rn.f32 %f3, %f0, %f2, %f1;\n\
337 st.global.f32 [%rd7], %f3;\n\
338 \n\
339 $CA_DONE:\n\
340 ret;\n\
341 }\n";
342 hdr + &prologue + body
343}
344
345#[must_use]
366pub fn correlate_fp8_ptx(sm: u32) -> String {
367 if sm < 89 {
368 return correlate_ptx(sm);
369 }
370 let hdr = ptx_header(sm);
371 let body = ".visible .entry correlate_fp8_kernel(\n\
372 .param .u64 p_c,\n\
373 .param .u64 p_phi,\n\
374 .param .u64 p_r,\n\
375 .param .u32 p_m,\n\
376 .param .u32 p_n\n\
377 )\n\
378 {\n\
379 .reg .u64 %rd<12>;\n\
380 .reg .u32 %r<20>;\n\
381 .reg .f32 %f<12>;\n\
382 .reg .b32 %rb<6>;\n\
383 .reg .b16 %rh<6>;\n\
384 .reg .pred %p0;\n\
385 .reg .pred %p1;\n\
386 \n\
387 ld.param.u64 %rd0, [p_c];\n\
388 ld.param.u64 %rd1, [p_phi];\n\
389 ld.param.u64 %rd2, [p_r];\n\
390 ld.param.u32 %r0, [p_m];\n\
391 ld.param.u32 %r1, [p_n];\n\
392 \n\
393 mov.u32 %r2, %ntid.x;\n\
394 mov.u32 %r3, %ctaid.x;\n\
395 mov.u32 %r4, %tid.x;\n\
396 mad.lo.u32 %r5, %r2, %r3, %r4;\n\
397 \n\
398 setp.ge.u32 %p0, %r5, %r1;\n\
399 @%p0 bra $F8_DONE;\n\
400 \n\
401 mov.f32 %f0, 0f00000000;\n\
402 mov.u32 %r6, 0;\n\
403 \n\
404 $F8_LOOP:\n\
405 // process rows i and i+1 together when both in range.\n\
406 add.u32 %r7, %r6, 1;\n\
407 setp.ge.u32 %p0, %r6, %r0;\n\
408 @%p0 bra $F8_WRITE;\n\
409 setp.ge.u32 %p1, %r7, %r0;\n\
410 @%p1 bra $F8_TAIL;\n\
411 \n\
412 // phi byte offset for (i, j): i*n + j ; pair stride along i is n.\n\
413 mul.lo.u32 %r8, %r6, %r1;\n\
414 add.u32 %r8, %r8, %r5;\n\
415 cvt.u64.u32 %rd3, %r8;\n\
416 add.u64 %rd4, %rd1, %rd3;\n\
417 ld.global.u8 %rh0, [%rd4];\n\
418 mul.lo.u32 %r9, %r7, %r1;\n\
419 add.u32 %r9, %r9, %r5;\n\
420 cvt.u64.u32 %rd5, %r9;\n\
421 add.u64 %rd6, %rd1, %rd5;\n\
422 ld.global.u8 %rh1, [%rd6];\n\
423 // pack the two e4m3 phi bytes into a b16, unpack to two f32.\n\
424 shl.b16 %rh2, %rh1, 8;\n\
425 or.b16 %rh2, %rh2, %rh0;\n\
426 cvt.rn.f32.e4m3x2 %rb0, %rh2;\n\
427 mov.b32 {%rh3, %rh4}, %rb0;\n\
428 cvt.f32.f16 %f1, %rh3;\n\
429 cvt.f32.f16 %f2, %rh4;\n\
430 \n\
431 // r bytes for rows i, i+1.\n\
432 cvt.u64.u32 %rd7, %r6;\n\
433 add.u64 %rd8, %rd2, %rd7;\n\
434 ld.global.u8 %rh0, [%rd8];\n\
435 cvt.u64.u32 %rd9, %r7;\n\
436 add.u64 %rd10, %rd2, %rd9;\n\
437 ld.global.u8 %rh1, [%rd10];\n\
438 shl.b16 %rh2, %rh1, 8;\n\
439 or.b16 %rh2, %rh2, %rh0;\n\
440 cvt.rn.f32.e4m3x2 %rb1, %rh2;\n\
441 mov.b32 {%rh3, %rh4}, %rb1;\n\
442 cvt.f32.f16 %f3, %rh3;\n\
443 cvt.f32.f16 %f4, %rh4;\n\
444 \n\
445 fma.rn.f32 %f0, %f1, %f3, %f0;\n\
446 fma.rn.f32 %f0, %f2, %f4, %f0;\n\
447 add.u32 %r6, %r6, 2;\n\
448 bra $F8_LOOP;\n\
449 \n\
450 $F8_TAIL:\n\
451 // single odd row i = %r6.\n\
452 mul.lo.u32 %r8, %r6, %r1;\n\
453 add.u32 %r8, %r8, %r5;\n\
454 cvt.u64.u32 %rd3, %r8;\n\
455 add.u64 %rd4, %rd1, %rd3;\n\
456 ld.global.u8 %rh0, [%rd4];\n\
457 cvt.rn.f32.e4m3x2 %rb0, %rh0;\n\
458 mov.b32 {%rh3, %rh4}, %rb0;\n\
459 cvt.f32.f16 %f1, %rh3;\n\
460 cvt.u64.u32 %rd7, %r6;\n\
461 add.u64 %rd8, %rd2, %rd7;\n\
462 ld.global.u8 %rh0, [%rd8];\n\
463 cvt.rn.f32.e4m3x2 %rb1, %rh0;\n\
464 mov.b32 {%rh3, %rh4}, %rb1;\n\
465 cvt.f32.f16 %f3, %rh3;\n\
466 fma.rn.f32 %f0, %f1, %f3, %f0;\n\
467 add.u32 %r6, %r6, 1;\n\
468 bra $F8_LOOP;\n\
469 \n\
470 $F8_WRITE:\n\
471 mul.wide.u32 %rd3, %r5, 4;\n\
472 add.u64 %rd4, %rd0, %rd3;\n\
473 st.global.f32 [%rd4], %f0;\n\
474 \n\
475 $F8_DONE:\n\
476 ret;\n\
477 }\n";
478 hdr + body
479}
480
481#[must_use]
498pub fn svt_threshold_warpshuffle_ptx(sm: u32) -> String {
499 let hdr = ptx_header(sm);
500 let body = ".visible .entry svt_threshold_ws_kernel(\n\
501 .param .u64 p_sigma_out,\n\
502 .param .u64 p_nucnorm_partial,\n\
503 .param .u64 p_sigma_in,\n\
504 .param .f32 p_tau,\n\
505 .param .u32 p_n\n\
506 )\n\
507 {\n\
508 .reg .u64 %rd<12>;\n\
509 .reg .u32 %r<20>;\n\
510 .reg .f32 %f<8>;\n\
511 .reg .pred %p0;\n\
512 .reg .pred %p1;\n\
513 \n\
514 ld.param.u64 %rd0, [p_sigma_out];\n\
515 ld.param.u64 %rd1, [p_nucnorm_partial];\n\
516 ld.param.u64 %rd2, [p_sigma_in];\n\
517 ld.param.f32 %f0, [p_tau];\n\
518 ld.param.u32 %r0, [p_n];\n\
519 \n\
520 mov.u32 %r1, %ntid.x;\n\
521 mov.u32 %r2, %ctaid.x;\n\
522 mov.u32 %r3, %tid.x;\n\
523 mad.lo.u32 %r4, %r1, %r2, %r3;\n\
524 \n\
525 // Threshold (out-of-range threads contribute 0 to the warp sum).\n\
526 mov.f32 %f3, 0f00000000;\n\
527 setp.ge.u32 %p0, %r4, %r0;\n\
528 @%p0 bra $WS_REDUCE;\n\
529 \n\
530 mul.wide.u32 %rd3, %r4, 4;\n\
531 add.u64 %rd4, %rd2, %rd3;\n\
532 ld.global.f32 %f1, [%rd4];\n\
533 sub.f32 %f2, %f1, %f0;\n\
534 max.f32 %f3, %f2, 0f00000000;\n\
535 add.u64 %rd5, %rd0, %rd3;\n\
536 st.global.f32 [%rd5], %f3;\n\
537 \n\
538 $WS_REDUCE:\n\
539 // Warp-level sum of %f3 via butterfly down-shuffle (full 32-lane mask).\n\
540 shfl.sync.down.b32 %f4, %f3, 16, 31, 0xFFFFFFFF;\n\
541 add.f32 %f3, %f3, %f4;\n\
542 shfl.sync.down.b32 %f4, %f3, 8, 31, 0xFFFFFFFF;\n\
543 add.f32 %f3, %f3, %f4;\n\
544 shfl.sync.down.b32 %f4, %f3, 4, 31, 0xFFFFFFFF;\n\
545 add.f32 %f3, %f3, %f4;\n\
546 shfl.sync.down.b32 %f4, %f3, 2, 31, 0xFFFFFFFF;\n\
547 add.f32 %f3, %f3, %f4;\n\
548 shfl.sync.down.b32 %f4, %f3, 1, 31, 0xFFFFFFFF;\n\
549 add.f32 %f3, %f3, %f4;\n\
550 \n\
551 // Lane 0 of each warp writes its partial nuclear-norm sum.\n\
552 and.b32 %r5, %r3, 31;\n\
553 setp.ne.u32 %p1, %r5, 0;\n\
554 @%p1 bra $WS_DONE;\n\
555 shr.u32 %r6, %r4, 5;\n\
556 mul.wide.u32 %rd6, %r6, 4;\n\
557 add.u64 %rd7, %rd1, %rd6;\n\
558 st.global.f32 [%rd7], %f3;\n\
559 \n\
560 $WS_DONE:\n\
561 ret;\n\
562 }\n";
563 hdr + body
564}
565
566#[cfg(test)]
572mod tests {
573 use super::*;
574
575 const SMS: [u32; 6] = [75, 80, 86, 89, 90, 100];
576
577 #[test]
580 fn tile_config_matches_documented_table() {
581 let t75 = TileConfig::for_sm(75);
583 assert_eq!(t75.block_x, 128);
584 assert_eq!(t75.stages, 1);
585 assert!(!t75.cp_async);
586 assert!(!t75.tma);
587 for sm in [80u32, 86] {
589 let c = TileConfig::for_sm(sm);
590 assert_eq!(c.block_x, 256, "sm {sm}");
591 assert_eq!(c.stages, 2, "sm {sm}");
592 assert!(c.cp_async, "sm {sm}");
593 assert!(!c.tma, "sm {sm}");
594 }
595 let t89 = TileConfig::for_sm(89);
597 assert_eq!(t89.block_x, 256);
598 assert_eq!(t89.stages, 2);
599 assert!(t89.cp_async);
600 assert!(!t89.tma);
601 for sm in [90u32, 100] {
603 let c = TileConfig::for_sm(sm);
604 assert_eq!(c.block_x, 512, "sm {sm}");
605 assert_eq!(c.stages, 3, "sm {sm}");
606 assert!(c.cp_async, "sm {sm}");
607 assert!(c.tma, "sm {sm}");
608 }
609 }
610
611 #[test]
612 fn tile_config_smem_is_block_x_f32() {
613 for sm in SMS {
614 let c = TileConfig::for_sm(sm);
615 assert_eq!(c.smem_bytes_per_stage, c.block_x * 4);
616 assert_eq!(c.total_smem_bytes(), c.stages * c.block_x * 4);
617 }
618 }
619
620 #[test]
621 fn tile_config_grid_ceildiv() {
622 let c = TileConfig::for_sm(80); assert_eq!(c.grid_x(256), 1);
624 assert_eq!(c.grid_x(257), 2);
625 assert_eq!(c.grid_x(512), 2);
626 assert_eq!(c.grid_x(1), 1);
627 assert_eq!(c.grid_x(0), 1);
629 }
630
631 #[test]
632 fn tile_config_threads_per_block() {
633 let c = TileConfig::for_sm(90);
634 assert_eq!(c.threads_per_block(), 512);
635 let d = TileConfig::portable_default();
636 assert_eq!(d.threads_per_block(), 256);
637 assert_eq!(d.stages, 1);
638 assert!(!d.cp_async && !d.tma);
639 }
640
641 #[test]
644 fn tma_correlate_has_bulk_copy_on_hopper() {
645 for sm in [90u32, 100] {
646 let s = correlate_tma_ptx(sm);
647 assert!(
648 s.contains(".visible .entry correlate_tma_kernel"),
649 "sm {sm}"
650 );
651 assert!(s.contains("cp.async.bulk"), "sm {sm} missing TMA bulk copy");
652 assert!(
653 s.contains("mbarrier.init.shared.b64"),
654 "sm {sm} missing mbarrier"
655 );
656 assert!(
657 s.contains(".shared .align 16 .b8 r_tile"),
658 "sm {sm} missing staging tile"
659 );
660 assert!(s.contains("ret"), "sm {sm}");
661 }
662 assert!(correlate_tma_ptx(90).contains(".version 8.4"));
664 assert!(correlate_tma_ptx(100).contains(".version 8.7"));
665 }
666
667 #[test]
668 fn tma_correlate_falls_back_pre_hopper() {
669 for sm in [75u32, 80, 86, 89] {
671 let s = correlate_tma_ptx(sm);
672 assert!(!s.contains("cp.async.bulk"), "sm {sm} should not emit TMA");
673 assert!(
674 s.contains(".visible .entry correlate_kernel"),
675 "sm {sm} fallback"
676 );
677 assert_eq!(
678 s,
679 crate::ptx_kernels::correlate_ptx(sm),
680 "sm {sm} exact fallback"
681 );
682 }
683 }
684
685 #[test]
688 fn cp_async_iht_has_async_copy_on_ampere_plus() {
689 for sm in [80u32, 86, 89, 90, 100] {
690 let s = iht_step_cp_async_ptx(sm);
691 assert!(
692 s.contains(".visible .entry iht_step_cp_async_kernel"),
693 "sm {sm}"
694 );
695 assert!(
696 s.contains("cp.async.cg.shared.global"),
697 "sm {sm} missing cp.async"
698 );
699 assert!(
700 s.contains("cp.async.commit_group"),
701 "sm {sm} missing commit"
702 );
703 assert!(s.contains("cp.async.wait_group"), "sm {sm} missing wait");
704 assert!(
705 s.contains(".shared .align 16 .b8 grad_tile"),
706 "sm {sm} stage buf"
707 );
708 assert!(s.contains("fma.rn.f32"), "sm {sm} missing the IHT FMA");
709 assert!(s.contains("ret"), "sm {sm}");
710 }
711 }
712
713 #[test]
714 fn cp_async_iht_falls_back_pre_ampere() {
715 let s = iht_step_cp_async_ptx(75);
716 assert!(!s.contains("cp.async"), "Turing should not emit cp.async");
717 assert_eq!(s, crate::ptx_kernels::iht_step_ptx(75));
718 }
719
720 #[test]
721 fn cp_async_iht_stage_count_in_comment() {
722 assert!(iht_step_cp_async_ptx(90).contains("3-stage"));
724 assert!(iht_step_cp_async_ptx(80).contains("2-stage"));
725 }
726
727 #[test]
730 fn fp8_correlate_uses_e4m3_on_ada_hopper() {
731 for sm in [89u32, 90, 100] {
732 let s = correlate_fp8_ptx(sm);
733 assert!(
734 s.contains(".visible .entry correlate_fp8_kernel"),
735 "sm {sm}"
736 );
737 assert!(
738 s.contains("cvt.rn.f32.e4m3x2"),
739 "sm {sm} missing e4m3 convert"
740 );
741 assert!(s.contains("fma.rn.f32"), "sm {sm} f32 accumulation");
742 assert!(s.contains("ret"), "sm {sm}");
743 }
744 }
745
746 #[test]
747 fn fp8_correlate_falls_back_pre_ada() {
748 for sm in [75u32, 80, 86] {
749 let s = correlate_fp8_ptx(sm);
750 assert!(!s.contains("e4m3"), "sm {sm} should not emit FP8 path");
751 assert_eq!(s, crate::ptx_kernels::correlate_ptx(sm), "sm {sm} fallback");
752 }
753 }
754
755 #[test]
758 fn warpshuffle_svt_has_full_butterfly() {
759 for sm in SMS {
760 let s = svt_threshold_warpshuffle_ptx(sm);
761 assert!(
762 s.contains(".visible .entry svt_threshold_ws_kernel"),
763 "sm {sm}"
764 );
765 for off in [16u32, 8, 4, 2, 1] {
767 assert!(
768 s.contains(&format!(
769 "shfl.sync.down.b32 %f4, %f3, {off}, 31, 0xFFFFFFFF"
770 )),
771 "sm {sm} missing shuffle offset {off}"
772 );
773 }
774 assert!(s.contains("max.f32"), "sm {sm} missing threshold max");
776 assert!(s.contains("st.global.f32"), "sm {sm}");
777 assert!(s.contains("ret"), "sm {sm}");
778 }
779 }
780
781 #[test]
782 fn warpshuffle_svt_isa_versions() {
783 assert!(svt_threshold_warpshuffle_ptx(75).contains(".version 7.5"));
784 assert!(svt_threshold_warpshuffle_ptx(80).contains(".version 8.0"));
785 assert!(svt_threshold_warpshuffle_ptx(90).contains(".version 8.4"));
786 assert!(svt_threshold_warpshuffle_ptx(100).contains(".version 8.7"));
787 }
788
789 #[test]
792 fn all_advanced_kernels_nonempty_and_balanced_braces() {
793 type KernelFn = fn(u32) -> String;
794 let kernels: [(&str, KernelFn); 4] = [
795 ("correlate_tma", correlate_tma_ptx),
796 ("iht_step_cp_async", iht_step_cp_async_ptx),
797 ("correlate_fp8", correlate_fp8_ptx),
798 ("svt_threshold_ws", svt_threshold_warpshuffle_ptx),
799 ];
800 for (name, f) in kernels {
801 for sm in SMS {
802 let s = f(sm);
803 assert!(!s.is_empty(), "{name} sm {sm} empty");
804 assert!(s.contains(".visible .entry"), "{name} sm {sm} no entry");
805 assert!(s.contains("ret"), "{name} sm {sm} no ret");
806 let opens = s.matches('{').count();
807 let closes = s.matches('}').count();
808 assert_eq!(opens, closes, "{name} sm {sm} unbalanced braces");
809 assert!(s.starts_with(".version"), "{name} sm {sm} no header");
810 }
811 }
812 }
813}