@@ -3032,10 +3032,12 @@ def forward(
30323032
30333033@pytest .mark .ir
30343034@pytest .mark .flaky (reruns = 3 )
3035- @pytest .mark .skip (reason = (
3036- "transform_with_custom_compression_ops has been deprecated. Consider removing "
3037- "these tests or use an alternative way to generate quantized weights"
3038- ))
3035+ @pytest .mark .skip (
3036+ reason = (
3037+ "transform_with_custom_compression_ops has been deprecated. Consider removing "
3038+ "these tests or use an alternative way to generate quantized weights"
3039+ )
3040+ )
30393041def test_externalize_rms_norm_with_quantized_linears_ir () -> None :
30403042 """IR check: Quantized (int4) weights retain si4 dtype when externalization re-exports the model."""
30413043
@@ -3065,7 +3067,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
30653067 )
30663068 quantizer = PostTrainingQuantizer (model , quantization_config )
30673069 model = cast ("nn.Module" , quantizer .compress ())
3068- transform_with_custom_compression_ops (model )
3070+ transform_with_custom_compression_ops (model ) # noqa: F821
30693071
30703072 sample = (torch .randn (2 , 24 ),)
30713073
@@ -3109,10 +3111,12 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
31093111
31103112
31113113@pytest .mark .flaky (reruns = 3 )
3112- @pytest .mark .skip (reason = (
3113- "transform_with_custom_compression_ops has been deprecated. Consider removing "
3114- "these tests or use an alternative way to generate quantized weights"
3115- ))
3114+ @pytest .mark .skip (
3115+ reason = (
3116+ "transform_with_custom_compression_ops has been deprecated. Consider removing "
3117+ "these tests or use an alternative way to generate quantized weights"
3118+ )
3119+ )
31163120async def test_externalize_rms_norm_with_quantized_linears () -> None :
31173121 """Quantized (int4) weights retain si4 dtype when externalization re-exports the model.
31183122
@@ -3153,7 +3157,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
31533157 )
31543158 quantizer = PostTrainingQuantizer (model , quantization_config )
31553159 model = cast ("nn.Module" , quantizer .compress ())
3156- transform_with_custom_compression_ops (model )
3160+ transform_with_custom_compression_ops (model ) # noqa: F821
31573161
31583162 sample = (torch .randn (2 , 24 ),)
31593163
@@ -3176,10 +3180,12 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
31763180
31773181@pytest .mark .ir
31783182@pytest .mark .flaky (reruns = 3 )
3179- @pytest .mark .skip (reason = (
3180- "transform_with_custom_compression_ops has been deprecated. Consider removing "
3181- "these tests or use an alternative way to generate quantized weights"
3182- ))
3183+ @pytest .mark .skip (
3184+ reason = (
3185+ "transform_with_custom_compression_ops has been deprecated. Consider removing "
3186+ "these tests or use an alternative way to generate quantized weights"
3187+ )
3188+ )
31833189def test_externalize_gather_mm_with_quantized_rhs_ir () -> None :
31843190 """IR check: Quantized expert weight flows as rhs into an externalized GatherMM composite."""
31853191 num_experts = 4
@@ -3217,7 +3223,7 @@ def forward(self, x: torch.Tensor, indices: torch.Tensor) -> torch.Tensor:
32173223 )
32183224 quantizer = PostTrainingQuantizer (model , quantization_config )
32193225 model = cast ("nn.Module" , quantizer .compress ())
3220- transform_with_custom_compression_ops (model )
3226+ transform_with_custom_compression_ops (model ) # noqa: F821
32213227
32223228 x = torch .randn (2 , 1 , 1 , in_dim )
32233229 indices = torch .tensor ([[0 , 2 ], [1 , 3 ]], dtype = torch .int16 )
@@ -3263,10 +3269,12 @@ def forward(self, x: torch.Tensor, indices: torch.Tensor) -> torch.Tensor:
32633269
32643270
32653271@pytest .mark .flaky (reruns = 3 )
3266- @pytest .mark .skip (reason = (
3267- "transform_with_custom_compression_ops has been deprecated. Consider removing "
3268- "these tests or use an alternative way to generate quantized weights"
3269- ))
3272+ @pytest .mark .skip (
3273+ reason = (
3274+ "transform_with_custom_compression_ops has been deprecated. Consider removing "
3275+ "these tests or use an alternative way to generate quantized weights"
3276+ )
3277+ )
32703278async def test_externalize_gather_mm_with_quantized_rhs () -> None :
32713279 """Quantized expert weight flows as rhs into an externalized GatherMM composite.
32723280
@@ -3318,7 +3326,7 @@ def forward(self, x: torch.Tensor, indices: torch.Tensor) -> torch.Tensor:
33183326 )
33193327 quantizer = PostTrainingQuantizer (model , quantization_config )
33203328 model = cast ("nn.Module" , quantizer .compress ())
3321- transform_with_custom_compression_ops (model )
3329+ transform_with_custom_compression_ops (model ) # noqa: F821
33223330
33233331 x = torch .randn (2 , 1 , 1 , in_dim )
33243332 indices = torch .tensor ([[0 , 2 ], [1 , 3 ]], dtype = torch .int16 )
@@ -3345,10 +3353,12 @@ def forward(self, x: torch.Tensor, indices: torch.Tensor) -> torch.Tensor:
33453353
33463354@pytest .mark .ir
33473355@pytest .mark .flaky (reruns = 3 )
3348- @pytest .mark .skip (reason = (
3349- "transform_with_custom_compression_ops has been deprecated. Consider removing "
3350- "these tests or use an alternative way to generate quantized weights"
3351- ))
3356+ @pytest .mark .skip (
3357+ reason = (
3358+ "transform_with_custom_compression_ops has been deprecated. Consider removing "
3359+ "these tests or use an alternative way to generate quantized weights"
3360+ )
3361+ )
33523362def test_externalize_multiple_composites_with_quantized_weights_ir () -> None :
33533363 """IR check: Multiple composite ops (RMSNorm + SDPA) externalized with quantized linears."""
33543364 head_dim = 16
@@ -3390,7 +3400,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
33903400 )
33913401 quantizer = PostTrainingQuantizer (model , quantization_config )
33923402 model = cast ("nn.Module" , quantizer .compress ())
3393- transform_with_custom_compression_ops (model )
3403+ transform_with_custom_compression_ops (model ) # noqa: F821
33943404
33953405 sample = (torch .randn (1 , 4 , embed_dim ),)
33963406
@@ -3433,10 +3443,12 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
34333443
34343444
34353445@pytest .mark .flaky (reruns = 3 )
3436- @pytest .mark .skip (reason = (
3437- "transform_with_custom_compression_ops has been deprecated. Consider removing "
3438- "these tests or use an alternative way to generate quantized weights"
3439- ))
3446+ @pytest .mark .skip (
3447+ reason = (
3448+ "transform_with_custom_compression_ops has been deprecated. Consider removing "
3449+ "these tests or use an alternative way to generate quantized weights"
3450+ )
3451+ )
34403452async def test_externalize_multiple_composites_with_quantized_weights () -> None :
34413453 """Multiple composite ops (RMSNorm + SDPA) externalized with quantized linears.
34423454
@@ -3484,7 +3496,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
34843496 )
34853497 quantizer = PostTrainingQuantizer (model , quantization_config )
34863498 model = cast ("nn.Module" , quantizer .compress ())
3487- transform_with_custom_compression_ops (model )
3499+ transform_with_custom_compression_ops (model ) # noqa: F821
34883500
34893501 sample = (torch .randn (1 , 4 , embed_dim ),)
34903502
@@ -3512,10 +3524,12 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
35123524
35133525
35143526@pytest .mark .ir
3515- @pytest .mark .skip (reason = (
3516- "transform_with_custom_compression_ops has been deprecated. Consider removing "
3517- "these tests or use an alternative way to generate quantized weights"
3518- ))
3527+ @pytest .mark .skip (
3528+ reason = (
3529+ "transform_with_custom_compression_ops has been deprecated. Consider removing "
3530+ "these tests or use an alternative way to generate quantized weights"
3531+ )
3532+ )
35193533def test_externalize_gather_mm_combined_with_rms_norm_ir () -> None :
35203534 """IR check: GatherMM + RMSNorm both externalized alongside quantized weights."""
35213535 num_experts = 4
@@ -3553,7 +3567,7 @@ def forward(self, x: torch.Tensor, indices: torch.Tensor) -> torch.Tensor:
35533567 )
35543568 quantizer = PostTrainingQuantizer (model , quantization_config )
35553569 model = cast ("nn.Module" , quantizer .compress ())
3556- transform_with_custom_compression_ops (model )
3570+ transform_with_custom_compression_ops (model ) # noqa: F821
35573571
35583572 x = torch .randn (2 , in_dim )
35593573 indices = torch .tensor ([[0 , 2 ], [1 , 3 ]], dtype = torch .int16 )
@@ -3590,10 +3604,13 @@ def forward(self, x: torch.Tensor, indices: torch.Tensor) -> torch.Tensor:
35903604 """
35913605 filecheck_pattern (ir , check_file = pattern )
35923606
3593- @pytest .mark .skip (reason = (
3594- "transform_with_custom_compression_ops has been deprecated. Consider removing "
3595- "these tests or use an alternative way to generate quantized weights"
3596- ))
3607+
3608+ @pytest .mark .skip (
3609+ reason = (
3610+ "transform_with_custom_compression_ops has been deprecated. Consider removing "
3611+ "these tests or use an alternative way to generate quantized weights"
3612+ )
3613+ )
35973614async def test_externalize_gather_mm_combined_with_rms_norm () -> None :
35983615 """GatherMM + RMSNorm both externalized alongside quantized weights.
35993616
@@ -3637,7 +3654,7 @@ def forward(self, x: torch.Tensor, indices: torch.Tensor) -> torch.Tensor:
36373654 )
36383655 quantizer = PostTrainingQuantizer (model , quantization_config )
36393656 model = cast ("nn.Module" , quantizer .compress ())
3640- transform_with_custom_compression_ops (model )
3657+ transform_with_custom_compression_ops (model ) # noqa: F821
36413658
36423659 x = torch .randn (2 , in_dim )
36433660 indices = torch .tensor ([[0 , 2 ], [1 , 3 ]], dtype = torch .int16 )
@@ -3666,3 +3683,108 @@ def forward(self, x: torch.Tensor, indices: torch.Tensor) -> torch.Tensor:
36663683 await _validate_numerics (
36673684 coreai_program , model , sample , input_names = ("x" , "indices" )
36683685 )
3686+
3687+
3688+ @pytest .mark .ir
3689+ def test_externalize_unused_submodule_ir () -> None :
3690+ """An externalizable submodule that the model's forward never calls is skipped.
3691+
3692+ Previously, a registered submodule that did not appear in the exported graph
3693+ caused the externalize pipeline to raise ``ValueError: Custom op for ...
3694+ not found in any ancestor program``. The pipeline should instead warn and
3695+ proceed, lowering the rest of the model normally.
3696+ """
3697+
3698+ class InnerModule (nn .Module ):
3699+ def __init__ (self ):
3700+ super ().__init__ ()
3701+ self .fc = nn .Linear (4 , 4 )
3702+
3703+ def forward (self , x : torch .Tensor ) -> torch .Tensor :
3704+ return torch .relu (self .fc (x ))
3705+
3706+ class OuterModel (nn .Module ):
3707+ def __init__ (self ):
3708+ super ().__init__ ()
3709+ self .pre = nn .Linear (4 , 4 )
3710+ # Registered as a submodule but intentionally never invoked.
3711+ self .unused = InnerModule ()
3712+ self .post = nn .Linear (4 , 4 )
3713+
3714+ def forward (self , x : torch .Tensor ) -> torch .Tensor :
3715+ return self .post (self .pre (x ))
3716+
3717+ torch .manual_seed (42 )
3718+ model = OuterModel ().eval ()
3719+ sample = (torch .randn (2 , 4 ),)
3720+
3721+ with pytest .warns (UserWarning , match = "skipping unused submodule" ):
3722+ converter = TorchConverter ().add_pytorch_module (
3723+ model ,
3724+ export_fn = lambda m : torch .export .export (m , args = sample ).run_decompositions (
3725+ get_decomp_table ()
3726+ ),
3727+ externalize_modules = [InnerModule ],
3728+ )
3729+ coreai_program = converter .to_coreai ()
3730+
3731+ check_file = """
3732+ // CHECK-LABEL: module {
3733+ // CHECK-NOT: coreai.graph noinline
3734+ // CHECK: coreai.graph @main(
3735+ // CHECK-SAME: %[[ARG0:[a-zA-Z0-9_]+]]: tensor<2x4xf32> {coreai.name = "x"}
3736+ // CHECK-SAME: ) -> (tensor<2x4xf32>
3737+ // CHECK: %[[MM0:[0-9a-z_]+]] = coreai.decomposable.broadcasting_batch_matmul %[[ARG0]],
3738+ // CHECK-SAME: : (tensor<2x4xf32>, tensor<4x4xf32>) -> tensor<2x4xf32>
3739+ // CHECK: %[[ADD0:[0-9a-z_]+]] = coreai.decomposable.broadcasting_add %[[MM0]],
3740+ // CHECK-SAME: : (tensor<2x4xf32>, tensor<4xf32>) -> tensor<2x4xf32>
3741+ // CHECK: %[[MM1:[0-9a-z_]+]] = coreai.decomposable.broadcasting_batch_matmul %[[ADD0]],
3742+ // CHECK-SAME: : (tensor<2x4xf32>, tensor<4x4xf32>) -> tensor<2x4xf32>
3743+ // CHECK: %[[ADD1:[0-9a-z_]+]] = coreai.decomposable.broadcasting_add %[[MM1]],
3744+ // CHECK-SAME: : (tensor<2x4xf32>, tensor<4xf32>) -> tensor<2x4xf32>
3745+ // CHECK-NOT: coreai.invoke
3746+ // CHECK-NOT: coreai.relu
3747+ // CHECK: coreai.output %[[ADD1]] : tensor<2x4xf32>
3748+ // CHECK: }
3749+ // CHECK-NOT: coreai.graph
3750+ // CHECK: }
3751+ """
3752+ filecheck_pattern (str (coreai_program ), check_file = check_file )
3753+
3754+
3755+ async def test_externalize_unused_submodule_numerics () -> None :
3756+ """Numerics: unused externalizable submodule does not affect output."""
3757+
3758+ class InnerModule (nn .Module ):
3759+ def __init__ (self ):
3760+ super ().__init__ ()
3761+ self .fc = nn .Linear (4 , 4 )
3762+
3763+ def forward (self , x : torch .Tensor ) -> torch .Tensor :
3764+ return torch .relu (self .fc (x ))
3765+
3766+ class OuterModel (nn .Module ):
3767+ def __init__ (self ):
3768+ super ().__init__ ()
3769+ self .pre = nn .Linear (4 , 4 )
3770+ self .unused = InnerModule ()
3771+ self .post = nn .Linear (4 , 4 )
3772+
3773+ def forward (self , x : torch .Tensor ) -> torch .Tensor :
3774+ return self .post (self .pre (x ))
3775+
3776+ torch .manual_seed (42 )
3777+ model = OuterModel ().eval ()
3778+ sample = (torch .randn (2 , 4 ),)
3779+
3780+ with pytest .warns (UserWarning , match = "skipping unused submodule" ):
3781+ converter = TorchConverter ().add_pytorch_module (
3782+ model ,
3783+ export_fn = lambda m : torch .export .export (m , args = sample ).run_decompositions (
3784+ get_decomp_table ()
3785+ ),
3786+ externalize_modules = [InnerModule ],
3787+ )
3788+ coreai_program = converter .to_coreai ()
3789+
3790+ await _validate_numerics (coreai_program , model , sample )
0 commit comments