@@ -692,17 +692,9 @@ async def test_nan_propagation(self) -> None:
692692 await validate_numerical_output (model = model , y = y , x = x )
693693
694694
695- @pytest .mark .parametrize (
696- "x" ,
697- [
698- torch .rand (2 , 3 , 8 , 8 ),
699- torch .rand (2 , 3 , 8 , 8 , dtype = torch .float16 ), # fp16
700- ],
701- )
702- @pytest .mark .parametrize (
703- "dynamic_dims" , [tuple (), (0 ,), (2 ,), (3 ,), (0 , 2 ), (0 , 3 ), (0 , 2 , 3 )]
704- )
705- async def test_batchnorm (x : Tensor , dynamic_dims : tuple [int ]) -> None :
695+ class TestBatchNorm :
696+ """Tests for native_batch_norm → coreai batch_norm composite."""
697+
706698 class BatchNormModel (nn .Module ):
707699 def __init__ (self ) -> None :
708700 super ().__init__ ()
@@ -711,10 +703,47 @@ def __init__(self) -> None:
711703 def forward (self , x : Tensor ) -> Tensor :
712704 return self .bn (x )
713705
714- model = BatchNormModel ().eval ()
715- dim_names = {0 : "batch" , 1 : "channels" , 2 : "height" , 3 : "width" }
716- dynamic_shapes = make_dynamic_shapes (x = {d : dim_names [d ] for d in dynamic_dims })
717- await validate_numerical_output (model = model , x = x , dynamic_shapes = dynamic_shapes )
706+ @pytest .mark .parametrize (
707+ "x" ,
708+ [
709+ torch .rand (2 , 3 , 8 , 8 ),
710+ torch .rand (2 , 3 , 8 , 8 , dtype = torch .float16 ), # fp16
711+ ],
712+ )
713+ @pytest .mark .parametrize (
714+ "dynamic_dims" , [tuple (), (0 ,), (2 ,), (3 ,), (0 , 2 ), (0 , 3 ), (0 , 2 , 3 )]
715+ )
716+ async def test_basic (self , x : Tensor , dynamic_dims : tuple [int ]) -> None :
717+ model = self .BatchNormModel ().eval ()
718+ dim_names = {0 : "batch" , 1 : "channels" , 2 : "height" , 3 : "width" }
719+ dynamic_shapes = make_dynamic_shapes (x = {d : dim_names [d ] for d in dynamic_dims })
720+ await validate_numerical_output (model = model , x = x , dynamic_shapes = dynamic_shapes )
721+
722+ @pytest .mark .parametrize (
723+ "running_var, running_mean, weight" ,
724+ [
725+ # running_var past the fp16 max (65504) becomes inf when the params are
726+ # downcast before the sqrt, which silently zeroes the whole output.
727+ ([1e6 , 2e5 , 9e4 ], [0.0 , 1.0 , - 2.0 ], [2000.0 , 1000.0 , 500.0 ]),
728+ # Tiny variances make eps and the param mantissas precision-critical.
729+ ([1e-6 , 4e-5 , 2.0 ], [0.5 , - 0.25 , 100.0 ], [1.0 , 2.0 , 0.5 ]),
730+ ],
731+ )
732+ async def test_fp16_input_fp32_params (
733+ self ,
734+ running_var : list [float ],
735+ running_mean : list [float ],
736+ weight : list [float ],
737+ ) -> None :
738+ """An fp16 activation with fp32 params must be computed in fp32."""
739+ model = self .BatchNormModel ().eval ()
740+ model .bn .running_var .data = torch .tensor (running_var )
741+ model .bn .running_mean .data = torch .tensor (running_mean )
742+ model .bn .weight .data = torch .tensor (weight )
743+ model .bn .bias .data = torch .tensor ([0.0 , 1.0 , - 1.0 ])
744+
745+ x = (torch .rand (2 , 3 , 4 , 4 ) * 2 - 1 ).half ()
746+ await validate_numerical_output (model = model , x = x )
718747
719748
720749@pytest .mark .parametrize ("dynamic" , [False , True ])
0 commit comments