@@ -520,6 +520,8 @@ def test_replace_aten_conv_with_cadence_conv(
520520 groups ,
521521 ),
522522 )
523+ if not bias_enabled :
524+ convolution .node .meta ["debug_handle" ] = 1234
523525 builder .output ([convolution ])
524526 original_gm = builder .get_graph_module ()
525527
@@ -550,13 +552,171 @@ def test_replace_aten_conv_with_cadence_conv(
550552 count_node (graph_after_passes , exir_ops .edge .cadence .conv1d .default ),
551553 1 ,
552554 )
555+ for node in graph_after_passes .graph .nodes :
556+ if node .target == exir_ops .edge .cadence .conv1d .default :
557+ self .assertIsNotNone (node .args [2 ])
558+ if not bias_enabled :
559+ bias_node = node .args [2 ]
560+ self .assertIsInstance (bias_node , torch .fx .Node )
561+ self .assertEqual (
562+ exir_ops .edge .aten .full .default ,
563+ cast (torch .fx .Node , bias_node ).target ,
564+ )
565+ self .assertEqual (
566+ 1234 , cast (torch .fx .Node , bias_node ).meta ["debug_handle" ]
567+ )
568+ self .assertEqual (
569+ (out_channels ,),
570+ tuple (cast (torch .fx .Node , bias_node ).meta ["val" ].shape ),
571+ )
553572 self .assertEqual (
554573 count_node (
555574 graph_after_passes , exir_ops .edge .cadence .transposed_convolution .default
556575 ),
557576 0 ,
558577 )
559578
579+ @expand ([[2 ], [3 ]])
580+ @torch .no_grad ()
581+ def test_replace_aten_multidimensional_conv_materializes_zero_bias (
582+ self ,
583+ spatial_dims : int ,
584+ ) -> None :
585+ in_channels = 4
586+ out_channels = 6
587+ builder = GraphBuilder ()
588+ x_tensor = torch .randn (
589+ 1 ,
590+ in_channels ,
591+ * ([8 ] * spatial_dims ),
592+ dtype = torch .float32 ,
593+ )
594+ weights_tensor = torch .randn (
595+ out_channels ,
596+ in_channels ,
597+ * ([3 ] * spatial_dims ),
598+ dtype = torch .float32 ,
599+ )
600+ x = builder .placeholder ("x" , x_tensor )
601+ weights = builder .placeholder ("weights" , weights_tensor )
602+ convolution = builder .call_operator (
603+ op = exir_ops .edge .aten .convolution .default ,
604+ args = (
605+ x ,
606+ weights ,
607+ None ,
608+ [1 ] * spatial_dims ,
609+ [0 ] * spatial_dims ,
610+ [1 ] * spatial_dims ,
611+ False ,
612+ [0 ] * spatial_dims ,
613+ 1 ,
614+ ),
615+ )
616+ convolution .node .meta ["debug_handle" ] = 1234
617+ convolution .node .meta ["stack_trace" ] = "convolution-only provenance"
618+ builder .output ([convolution ])
619+ original_gm = builder .get_graph_module ()
620+ gm_before = copy .deepcopy (original_gm )
621+
622+ result = cast (
623+ PassResult ,
624+ ReplaceAtenConvolutionWithCadenceConvolutionPass ()(original_gm ),
625+ )
626+ self .assertTrue (result .modified )
627+ graph_after_passes = result .graph_module
628+ validate (
629+ gm_before ,
630+ graph_after_passes ,
631+ (x_tensor , weights_tensor ),
632+ "ReplaceAtenConvolutionWithCadenceConvolutionPass" ,
633+ )
634+
635+ target = (
636+ exir_ops .edge .cadence .conv2d .default
637+ if spatial_dims == 2
638+ else exir_ops .edge .cadence .conv3d .default
639+ )
640+ replacement = next (
641+ node for node in graph_after_passes .graph .nodes if node .target == target
642+ )
643+ bias_node = replacement .args [2 ]
644+ self .assertIsInstance (bias_node , torch .fx .Node )
645+ assert isinstance (bias_node , torch .fx .Node )
646+ self .assertEqual (exir_ops .edge .aten .full .default , bias_node .target )
647+ self .assertEqual ((out_channels ,), tuple (bias_node .meta ["val" ].shape ))
648+ self .assertEqual (1234 , bias_node .meta ["debug_handle" ])
649+ self .assertNotIn ("stack_trace" , bias_node .meta )
650+
651+ @torch .no_grad ()
652+ def test_materialized_zero_bias_metadata_matches_runtime_value (self ) -> None :
653+ for weight_dtype , bias_dtype in (
654+ (torch .float32 , torch .float32 ),
655+ (torch .float64 , torch .float64 ),
656+ ):
657+ with self .subTest (weight_dtype = weight_dtype ):
658+ graph = torch .fx .Graph ()
659+ input_node = graph .placeholder ("x" )
660+ weight = graph .placeholder ("weight" )
661+ weight .meta ["val" ] = torch .ones (6 , 2 , 3 , dtype = weight_dtype )
662+ source = graph .call_function (
663+ exir_ops .edge .aten .relu .default ,
664+ args = (input_node ,),
665+ )
666+ source .meta ["val" ] = torch .ones (2 , 6 , 4 , dtype = torch .float32 )
667+ source .meta ["debug_handle" ] = 1234
668+
669+ zero_bias = ReplaceAtenConvolutionWithCadenceConvolutionPass ().materialize_zero_bias (
670+ source ,
671+ weight ,
672+ )
673+
674+ self .assertEqual (exir_ops .edge .aten .full .default , zero_bias .target )
675+ self .assertEqual (([6 ], 0.0 ), zero_bias .args )
676+ self .assertEqual ({"dtype" : bias_dtype }, zero_bias .kwargs )
677+ torch .testing .assert_close (
678+ zero_bias .meta ["val" ],
679+ weight .meta ["val" ].new_zeros (6 , dtype = bias_dtype ),
680+ )
681+ self .assertEqual (bias_dtype , zero_bias .meta ["val" ].dtype )
682+ self .assertEqual (
683+ weight .meta ["val" ].device ,
684+ zero_bias .meta ["val" ].device ,
685+ )
686+ self .assertEqual (1234 , zero_bias .meta ["debug_handle" ])
687+ self .assertLess (
688+ list (graph .nodes ).index (zero_bias ),
689+ list (graph .nodes ).index (source ),
690+ )
691+
692+ @torch .no_grad ()
693+ def test_rejected_convolution_does_not_materialize_zero_bias (self ) -> None :
694+ builder = GraphBuilder ()
695+ x = builder .placeholder ("x" , torch .randn (1 , 4 , 8 ))
696+ weights = builder .placeholder ("weights" , torch .randn (6 , 4 , 3 ))
697+ convolution = builder .call_operator (
698+ op = exir_ops .edge .aten .convolution .default ,
699+ args = (x , weights , None , [1 ], [0 ], [1 ], False , [1 ], 1 ),
700+ )
701+ builder .output ([convolution ])
702+
703+ result = cast (
704+ PassResult ,
705+ ReplaceAtenConvolutionWithCadenceConvolutionPass ()(
706+ builder .get_graph_module ()
707+ ),
708+ )
709+
710+ self .assertFalse (result .modified )
711+ self .assertEqual (
712+ 1 ,
713+ count_node (result .graph_module , exir_ops .edge .aten .convolution .default ),
714+ )
715+ self .assertEqual (
716+ 0 ,
717+ count_node (result .graph_module , exir_ops .edge .aten .full .default ),
718+ )
719+
560720 @expand (
561721 [
562722 [(1 , 8 , 16 ), 8 , 16 , 3 ],
@@ -649,6 +809,16 @@ def test_replace_aten_transposed_conv_with_cadence_transposed_conv(
649809 ),
650810 1 ,
651811 )
812+ for node in graph_after_passes .graph .nodes :
813+ if node .target == exir_ops .edge .cadence .transposed_convolution .default :
814+ self .assertIsNotNone (node .args [2 ])
815+ if not bias_enabled :
816+ bias_node = node .args [2 ]
817+ self .assertIsInstance (bias_node , torch .fx .Node )
818+ self .assertEqual (
819+ exir_ops .edge .aten .full .default ,
820+ cast (torch .fx .Node , bias_node ).target ,
821+ )
652822
653823 @expand (
654824 [
@@ -947,7 +1117,7 @@ def test_replace_repeat_with_cat(self) -> None:
9471117 def test_replace_masked_scalar_tensor_with_full (
9481118 self ,
9491119 shape : Tuple [int ],
950- mask_shape : Union [Tuple [int , ...], None ] = None ,
1120+ mask_shape : Optional [Tuple [int , ...]] = None ,
9511121 ) -> None :
9521122 builder = GraphBuilder ()
9531123 x = builder .placeholder ("x" , torch .randn (* shape , dtype = torch .float32 ))
0 commit comments