@@ -252,30 +252,155 @@ def _reference_qkv_backward(
252252 return out
253253
254254
255+ class _TorchRoPEBackend :
256+ @staticmethod
257+ def fused_rope_forward (
258+ input ,
259+ freqs ,
260+ start_positions ,
261+ qkv_format ,
262+ interleaved ,
263+ cu_seqlens ,
264+ cp_size ,
265+ cp_rank ,
266+ ):
267+ return _reference_rope (
268+ input ,
269+ freqs ,
270+ qkv_format ,
271+ interleaved ,
272+ cu_seqlens ,
273+ start_positions ,
274+ cp_size ,
275+ cp_rank ,
276+ False ,
277+ )
278+
279+ @staticmethod
280+ def fused_rope_backward (
281+ output_grads ,
282+ freqs ,
283+ start_positions ,
284+ qkv_format ,
285+ interleaved ,
286+ cu_seqlens ,
287+ cp_size ,
288+ cp_rank ,
289+ ):
290+ return _reference_rope (
291+ output_grads ,
292+ freqs ,
293+ qkv_format ,
294+ interleaved ,
295+ cu_seqlens ,
296+ start_positions ,
297+ cp_size ,
298+ cp_rank ,
299+ True ,
300+ )
301+
302+ @staticmethod
303+ def fused_qkv_rope_forward (
304+ qkv_input ,
305+ q_freqs ,
306+ k_freqs ,
307+ start_positions ,
308+ qkv_split_arg_list ,
309+ qkv_format ,
310+ interleaved ,
311+ cp_size ,
312+ cp_rank ,
313+ ):
314+ return _reference_qkv_forward (
315+ qkv_input ,
316+ q_freqs ,
317+ k_freqs ,
318+ start_positions ,
319+ qkv_split_arg_list ,
320+ qkv_format ,
321+ interleaved ,
322+ cp_size ,
323+ cp_rank ,
324+ )
325+
326+ @staticmethod
327+ def fused_qkv_rope_backward (
328+ q_grad_out ,
329+ k_grad_out ,
330+ v_grad_out ,
331+ q_freqs ,
332+ k_freqs ,
333+ qkv_split_arg_list ,
334+ qkv_format ,
335+ interleaved ,
336+ cp_size ,
337+ cp_rank ,
338+ ):
339+ return _reference_qkv_backward (
340+ q_grad_out ,
341+ k_grad_out ,
342+ v_grad_out ,
343+ q_freqs ,
344+ k_freqs ,
345+ qkv_split_arg_list ,
346+ qkv_format ,
347+ interleaved ,
348+ cp_size ,
349+ cp_rank ,
350+ )
351+
352+
255353class FusedRoPETests (TestCase ):
256354 def __init__ (self , device = "cpu" ):
257355 super ().__init__ (
258356 "Fused RoPE" ,
259- "Test FlagOS Triton fused RoPE and fused QKV RoPE implementations " ,
357+ "Test fused RoPE and fused QKV RoPE across CUDA, FlagOS, and torch reference " ,
260358 )
261- self .backends = [backend for backend in get_available_backends () if backend == "flagos" ]
359+ self .backends = get_available_backends ()
360+ if "torch" not in self .backends :
361+ self .backends .append ("torch" )
362+ self .backends = [
363+ backend for backend in self .backends if backend in ("cuda" , "flagos" , "torch" )
364+ ]
262365 self .device = device
263366
264- def _iter_runnable_backends (self ):
367+ def _get_backend (self , backend_name ):
368+ if backend_name == "torch" :
369+ return _TorchRoPEBackend ()
370+ if self .device == "cpu" :
371+ raise NotImplementedError ("fused RoPE requires a GPU device" )
372+ if backend_name == "flagos" and not _triton_available ():
373+ raise NotImplementedError ("Triton is not installed" )
374+ return get_backend (backend_name )
375+
376+ def _iter_backends (self ):
265377 if not self .backends :
266378 self .skipped += 1
267- print (" skip: flagos backend is not registered" )
379+ print (" ⊘ no tested backend is registered" )
268380 return
269381 for backend_name in self .backends :
270- if self .device == "cpu" :
271- self .skipped += 1
272- print (f" skip { backend_name } : fused RoPE requires a GPU device" )
273- continue
274- if not _triton_available ():
382+ try :
383+ yield backend_name , self ._get_backend (backend_name )
384+ except NotImplementedError as exc :
275385 self .skipped += 1
276- print (f" skip { backend_name } : Triton is not installed" )
277- continue
278- yield backend_name , get_backend (backend_name )
386+ print (f" ⊘ { backend_name } ({ exc } )" )
387+
388+ def _compare_to_cuda (self , outputs , backend_name , labels , msg ):
389+ if "cuda" not in outputs or backend_name not in outputs :
390+ return
391+
392+ try :
393+ for actual , expected , label in zip (outputs [backend_name ], outputs ["cuda" ], labels ):
394+ self .assert_close (
395+ actual .float (),
396+ expected .float (),
397+ rtol = 1e-4 ,
398+ atol = 1e-4 ,
399+ msg = f"{ msg } { label } mismatch between { backend_name } and cuda" ,
400+ )
401+ print (f" ✓ { backend_name } matches cuda" )
402+ except AssertionError as exc :
403+ print (f" ✗ { backend_name } vs cuda: { exc } " )
279404
280405 def test_rope_sbhd_bshd_forward_backward (self ):
281406 print ("\n Testing fused_rope_forward/backward for SBHD and BSHD" )
@@ -286,6 +411,11 @@ def test_rope_sbhd_bshd_forward_backward(self):
286411 ]
287412
288413 for qkv_format , shape , interleaved , cp_size , cp_rank , use_start in cases :
414+ print (
415+ f"\n Testing fused_rope_forward/backward with { qkv_format .name } , "
416+ f"interleaved={ interleaved } , cp_size={ cp_size } , "
417+ f"start_positions={ use_start } "
418+ )
289419 d2 = 6
290420 freq_len = shape [0 ] if qkv_format == NVTE_QKV_Format .NVTE_SBHD else shape [1 ]
291421 freq_len = max (freq_len * cp_size + 3 , 12 )
@@ -313,7 +443,8 @@ def test_rope_sbhd_bshd_forward_backward(self):
313443 grad , freqs , qkv_format , interleaved , None , start_positions , cp_size , cp_rank , True
314444 )
315445
316- for backend_name , backend in self ._iter_runnable_backends ():
446+ outputs = {}
447+ for backend_name , backend in self ._iter_backends ():
317448 try :
318449 out = backend .fused_rope_forward (
319450 tensor ,
@@ -349,10 +480,28 @@ def test_rope_sbhd_bshd_forward_backward(self):
349480 atol = 1e-4 ,
350481 msg = f"fused_rope_backward mismatch for { backend_name } " ,
351482 )
352- print (f" ok { backend_name } : { qkv_format .name } , interleaved={ interleaved } " )
483+ outputs [backend_name ] = (out , dx )
484+ print (f" ✓ { backend_name } " )
485+ except NotImplementedError as exc :
486+ self .skipped += 1
487+ print (f" ⊘ { backend_name } ({ exc } )" )
488+ except RuntimeError as exc :
489+ if "is not available" in str (exc ):
490+ self .skipped += 1
491+ print (f" ⊘ { backend_name } ({ exc } )" )
492+ else :
493+ self .failed += 1
494+ print (f" ✗ { backend_name } : { exc } " )
353495 except Exception as exc :
354496 self .failed += 1
355- print (f" fail { backend_name } : { exc } " )
497+ print (f" ✗ { backend_name } : { exc } " )
498+
499+ self ._compare_to_cuda (
500+ outputs ,
501+ "flagos" ,
502+ ("forward" , "backward" ),
503+ f"{ qkv_format .name } fused_rope" ,
504+ )
356505
357506 def test_rope_thd_forward_backward (self ):
358507 print ("\n Testing fused_rope_forward/backward for THD" )
@@ -362,6 +511,11 @@ def test_rope_thd_forward_backward(self):
362511 ]
363512
364513 for cu_cpu , interleaved , cp_size , cp_rank , use_start in cases :
514+ print (
515+ "\n Testing fused_rope_forward/backward with NVTE_THD, "
516+ f"interleaved={ interleaved } , cp_size={ cp_size } , "
517+ f"start_positions={ use_start } "
518+ )
365519 cu_seqlens = cu_cpu .to (self .device )
366520 local_cu = cu_cpu // cp_size
367521 total_t = int (local_cu [- 1 ].item ())
@@ -397,7 +551,8 @@ def test_rope_thd_forward_backward(self):
397551 True ,
398552 )
399553
400- for backend_name , backend in self ._iter_runnable_backends ():
554+ outputs = {}
555+ for backend_name , backend in self ._iter_backends ():
401556 try :
402557 out = backend .fused_rope_forward (
403558 tensor ,
@@ -433,10 +588,28 @@ def test_rope_thd_forward_backward(self):
433588 atol = 1e-4 ,
434589 msg = f"THD fused_rope_backward mismatch for { backend_name } " ,
435590 )
436- print (f" ok { backend_name } : THD, interleaved={ interleaved } " )
591+ outputs [backend_name ] = (out , dx )
592+ print (f" ✓ { backend_name } " )
593+ except NotImplementedError as exc :
594+ self .skipped += 1
595+ print (f" ⊘ { backend_name } ({ exc } )" )
596+ except RuntimeError as exc :
597+ if "is not available" in str (exc ):
598+ self .skipped += 1
599+ print (f" ⊘ { backend_name } ({ exc } )" )
600+ else :
601+ self .failed += 1
602+ print (f" ✗ { backend_name } : { exc } " )
437603 except Exception as exc :
438604 self .failed += 1
439- print (f" fail { backend_name } : { exc } " )
605+ print (f" ✗ { backend_name } : { exc } " )
606+
607+ self ._compare_to_cuda (
608+ outputs ,
609+ "flagos" ,
610+ ("forward" , "backward" ),
611+ "THD fused_rope" ,
612+ )
440613
441614 def test_qkv_rope_forward_backward (self ):
442615 print ("\n Testing fused_qkv_rope_forward/backward" )
@@ -447,6 +620,11 @@ def test_qkv_rope_forward_backward(self):
447620 qkv_split_arg_list = [16 , 8 , 8 ]
448621
449622 for qkv_format , shape , interleaved , cp_size , cp_rank , use_start in cases :
623+ print (
624+ f"\n Testing fused_qkv_rope_forward/backward with { qkv_format .name } , "
625+ f"interleaved={ interleaved } , cp_size={ cp_size } , "
626+ f"start_positions={ use_start } "
627+ )
450628 d2 = 6
451629 seq_len = shape [0 ] if qkv_format == NVTE_QKV_Format .NVTE_SBHD else shape [1 ]
452630 freq_len = max (seq_len * cp_size + 3 , 12 )
@@ -486,7 +664,8 @@ def test_qkv_rope_forward_backward(self):
486664 cp_rank ,
487665 )
488666
489- for backend_name , backend in self ._iter_runnable_backends ():
667+ outputs = {}
668+ for backend_name , backend in self ._iter_backends ():
490669 try :
491670 q_out , k_out , v_out = backend .fused_qkv_rope_forward (
492671 qkv ,
@@ -539,16 +718,34 @@ def test_qkv_rope_forward_backward(self):
539718 atol = 1e-4 ,
540719 msg = f"fused_qkv_rope_backward mismatch for { backend_name } " ,
541720 )
542- print (f" ok { backend_name } : { qkv_format .name } , interleaved={ interleaved } " )
721+ outputs [backend_name ] = (q_out , k_out , v_out , dqkv )
722+ print (f" ✓ { backend_name } " )
723+ except NotImplementedError as exc :
724+ self .skipped += 1
725+ print (f" ⊘ { backend_name } ({ exc } )" )
726+ except RuntimeError as exc :
727+ if "is not available" in str (exc ):
728+ self .skipped += 1
729+ print (f" ⊘ { backend_name } ({ exc } )" )
730+ else :
731+ self .failed += 1
732+ print (f" ✗ { backend_name } : { exc } " )
543733 except Exception as exc :
544734 self .failed += 1
545- print (f" fail { backend_name } : { exc } " )
735+ print (f" ✗ { backend_name } : { exc } " )
736+
737+ self ._compare_to_cuda (
738+ outputs ,
739+ "flagos" ,
740+ ("Q forward" , "K forward" , "V forward" , "backward" ),
741+ f"{ qkv_format .name } fused_qkv_rope" ,
742+ )
546743
547744 def run_all_tests (self ):
548745 print ("\n " + "=" * 60 )
549746 print ("Testing Fused RoPE" )
550747 print ("=" * 60 )
551- print (f"Available tested backends: { ', ' .join (self .backends ) or 'none' } " )
748+ print (f"Available backends: { ', ' .join (self .backends ) or 'none' } " )
552749
553750 self .test_rope_sbhd_bshd_forward_backward ()
554751 self .test_rope_thd_forward_backward ()
0 commit comments