1# Copyright (c) Meta Platforms, Inc. and affiliates. 2# Copyright 2024 Arm Limited and/or its affiliates. 3# All rights reserved. 4# 5# This source code is licensed under the BSD-style license found in the 6# LICENSE file in the root directory of this source tree. 7 8import logging 9import unittest 10 11from typing import Tuple 12 13import torch 14from executorch.backends.arm.test import common 15 16from executorch.backends.arm.test.tester.arm_tester import ArmTester 17from executorch.exir import EdgeCompileConfig 18from executorch.exir.backend.compile_spec_schema import CompileSpec 19from parameterized import parameterized 20 21logger = logging.getLogger(__name__) 22logger.setLevel(logging.INFO) 23 24 25test_data_suite_rank1 = [ 26 # (test_name, test_data, out_features, has_bias) 27 ( 28 "model_linear_rank1_zeros", 29 torch.zeros(10), 30 15, 31 True, 32 ), 33 ( 34 "model_linear_rank1_ones", 35 torch.ones(10), 36 15, 37 False, 38 ), 39 ( 40 "model_linear_rank1_negative_ones", 41 torch.ones(10) * (-1), 42 20, 43 True, 44 ), 45 ( 46 "model_linear_rank1_rand", 47 torch.rand(10), 48 10, 49 True, 50 ), 51 ( 52 "model_linear_rank1_negative_large_rand", 53 torch.rand(10) * (-100), 54 30, 55 False, 56 ), 57 ( 58 "model_linear_rank1_large_randn", 59 torch.randn(15) * 100, 60 20, 61 True, 62 ), 63] 64 65test_data_suite_rank4 = [ 66 # (test_name, test_data, out_features, has_bias) 67 ( 68 "model_linear_rank4_zeros", 69 torch.zeros(5, 10, 25, 20), 70 30, 71 True, 72 ), 73 ( 74 "model_linear_rank4_ones", 75 torch.ones(5, 10, 25, 20), 76 30, 77 False, 78 ), 79 ( 80 "model_linear_rank4_negative_ones", 81 torch.ones(5, 10, 25, 20) * (-1), 82 30, 83 True, 84 ), 85 ( 86 "model_linear_rank4_rand", 87 torch.rand(5, 10, 25, 20), 88 30, 89 False, 90 ), 91 ( 92 "model_linear_rank4_negative_large_rand", 93 torch.rand(5, 10, 25, 20) * (-100), 94 30, 95 True, 96 ), 97 ( 98 "model_linear_rank4_large_randn", 99 torch.randn(5, 10, 25, 20) * 100, 100 30, 101 False, 102 ), 103] 104 105 106class TestLinear(unittest.TestCase): 107 """tests the linear operation y = Ax + b""" 108 109 _edge_compile_config: EdgeCompileConfig = EdgeCompileConfig( 110 _skip_dim_order=True, # TODO(T182928844): Delegate dim order op to backend. 111 ) 112 113 class Linear(torch.nn.Module): 114 def __init__( 115 self, 116 in_features: int, 117 out_features: int = 3, 118 bias: bool = True, 119 ): 120 super().__init__() 121 self.fc = torch.nn.Linear( 122 in_features=in_features, 123 out_features=out_features, 124 bias=bias, 125 ) 126 127 def forward(self, x): 128 return self.fc(x) 129 130 def _test_linear_tosa_MI_pipeline( 131 self, module: torch.nn.Module, test_data: Tuple[torch.Tensor] 132 ): 133 ( 134 ArmTester( 135 module, 136 example_inputs=test_data, 137 compile_spec=common.get_tosa_compile_spec( 138 "TOSA-0.80.0+MI", permute_memory_to_nhwc=True 139 ), 140 ) 141 .export() 142 .check_count({"torch.ops.aten.linear.default": 1}) 143 .check_not(["torch.ops.quantized_decomposed"]) 144 .to_edge_transform_and_lower(edge_compile_config=self._edge_compile_config) 145 .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) 146 .to_executorch() 147 .run_method_and_compare_outputs(inputs=test_data) 148 ) 149 150 def _test_linear_tosa_BI_pipeline( 151 self, module: torch.nn.Module, test_data: Tuple[torch.Tensor] 152 ): 153 ( 154 ArmTester( 155 module, 156 example_inputs=test_data, 157 compile_spec=common.get_tosa_compile_spec( 158 "TOSA-0.80.0+BI", permute_memory_to_nhwc=True 159 ), 160 ) 161 .quantize() 162 .export() 163 .check_count({"torch.ops.aten.linear.default": 1}) 164 .check(["torch.ops.quantized_decomposed"]) 165 .to_edge_transform_and_lower(edge_compile_config=self._edge_compile_config) 166 .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) 167 .to_executorch() 168 .run_method_and_compare_outputs(inputs=test_data, qtol=1) 169 ) 170 171 def _test_linear_tosa_ethosu_BI_pipeline( 172 self, 173 module: torch.nn.Module, 174 compile_spec: CompileSpec, 175 test_data: Tuple[torch.Tensor], 176 ) -> ArmTester: 177 tester = ( 178 ArmTester( 179 module, 180 example_inputs=test_data, 181 compile_spec=compile_spec, 182 ) 183 .quantize() 184 .export() 185 .check_count({"torch.ops.aten.linear.default": 1}) 186 .check(["torch.ops.quantized_decomposed"]) 187 .to_edge_transform_and_lower(edge_compile_config=self._edge_compile_config) 188 .check_count({"torch.ops.higher_order.executorch_call_delegate": 1}) 189 .to_executorch() 190 .serialize() 191 ) 192 return tester 193 194 @parameterized.expand(test_data_suite_rank1 + test_data_suite_rank4) 195 def test_linear_tosa_MI( 196 self, 197 test_name: str, 198 test_data: torch.Tensor, 199 out_features: int, 200 has_bias: bool, 201 ): 202 in_features = test_data.shape[-1] 203 test_data = (test_data,) 204 self._test_linear_tosa_MI_pipeline( 205 self.Linear( 206 in_features=in_features, 207 out_features=out_features, 208 bias=has_bias, 209 ), 210 test_data, 211 ) 212 213 @parameterized.expand(test_data_suite_rank1 + test_data_suite_rank4) 214 def test_linear_tosa_BI( 215 self, 216 test_name: str, 217 test_data: torch.Tensor, 218 out_features: int, 219 has_bias: bool, 220 ): 221 in_features = test_data.shape[-1] 222 test_data = (test_data,) 223 self._test_linear_tosa_BI_pipeline( 224 self.Linear( 225 in_features=in_features, out_features=out_features, bias=has_bias 226 ), 227 test_data, 228 ) 229 230 @parameterized.expand(test_data_suite_rank1) 231 def test_linear_tosa_u55_BI( 232 self, 233 test_name: str, 234 test_data: torch.Tensor, 235 out_features: int, 236 has_bias: bool, 237 ): 238 in_features = test_data.shape[-1] 239 test_data = (test_data,) 240 tester = self._test_linear_tosa_ethosu_BI_pipeline( 241 self.Linear( 242 in_features=in_features, 243 out_features=out_features, 244 bias=has_bias, 245 ), 246 common.get_u55_compile_spec(), 247 test_data, 248 ) 249 250 if common.is_option_enabled("corstone300"): 251 tester.run_method_and_compare_outputs(qtol=1, inputs=test_data) 252 253 @parameterized.expand(test_data_suite_rank1 + test_data_suite_rank4) 254 def test_linear_tosa_u85_BI( 255 self, 256 test_name: str, 257 test_data: torch.Tensor, 258 out_features: int, 259 has_bias: bool, 260 ): 261 in_features = test_data.shape[-1] 262 test_data = (test_data,) 263 self._test_linear_tosa_ethosu_BI_pipeline( 264 self.Linear( 265 in_features=in_features, 266 out_features=out_features, 267 bias=has_bias, 268 ), 269 common.get_u85_compile_spec(), 270 test_data, 271 ) 272