1# ExecuTorch Benchmark App for Apple Platforms 2 3## Introduction 4 5The **Benchmark App** is a tool designed to help developers measure the performance of PyTorch models on Apple devices using the ExecuTorch runtime. 6It provides a flexible framework for dynamically generating and running performance tests on your models, allowing you to assess metrics such as load times, inference speeds, memory usage, and more. 7 8<p align="center"> 9<img src="https://raw.githubusercontent.com/pytorch/executorch/refs/heads/main/docs/source/_static/img/ios_benchmark_app.png" alt="Benchmark App" style="width:800px"> 10</p> 11 12## Prerequisites 13 14- [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12/) 15.0 or later with command-line tools if not already installed (`xcode-select --install`). 15- [CMake](https://cmake.org/download/) 3.19 or later 16 - Download and open the macOS `.dmg` installer and move the CMake app to `/Applications` folder. 17 - Install CMake command line tools: `sudo /Applications/CMake.app/Contents/bin/cmake-gui --install` 18- A development provisioning profile with the [`increased-memory-limit`](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_kernel_increased-memory-limit) entitlement if targeting iOS devices. 19 20## Setting Up the App 21 22### Get the Code 23 24To get started, clone the ExecuTorch repository and cd into the source code directory: 25 26```bash 27git clone https://github.com/pytorch/executorch.git --depth 1 --recurse-submodules --shallow-submodules 28cd executorch 29``` 30 31This command performs a shallow clone to speed up the process. 32 33### Set Up the Frameworks 34 35The Benchmark App relies on prebuilt ExecuTorch frameworks. 36You have two options: 37 38<details> 39<summary>Option 1: Download Prebuilt Frameworks</summary> 40<br/> 41 42Run the provided script to download the prebuilt frameworks: 43 44```bash 45./extension/benchmark/apple/Benchmark/Frameworks/download_frameworks.sh 46``` 47</details> 48 49<details> 50<summary>Option 2: Build Frameworks Locally</summary> 51<br/> 52 53Alternatively, you can build the frameworks yourself by following the [guide](https://pytorch.org/executorch/main/apple-runtime.html#local-build). 54</details> 55 56Once the frameworks are downloaded or built, verify that the `Frameworks` directory contains the necessary `.xcframework` files: 57 58```bash 59ls extension/benchmark/apple/Benchmark/Frameworks 60``` 61 62You should see: 63 64``` 65backend_coreml.xcframework 66backend_mps.xcframework 67backend_xnnpack.xcframework 68executorch.xcframework 69kernels_custom.xcframework 70kernels_optimized.xcframework 71kernels_portable.xcframework 72kernels_quantized.xcframework 73``` 74 75## Adding Models and Resources 76 77Place your exported model files (`.pte`) and any other resources (e.g., `tokenizer.bin`) into the `extension/benchmark/apple/Benchmark/Resources` directory: 78 79```bash 80cp <path/to/my_model.pte> <path/to/llama3.pte> <path/to/tokenizer.bin> extension/benchmark/apple/Benchmark/Resources 81``` 82 83Optionally, check that the files are there: 84 85```bash 86ls extension/benchmark/apple/Benchmark/Resources 87``` 88 89For this example you should see: 90 91``` 92llama3.pte 93my_model.pte 94tokenizer.bin 95``` 96 97The app automatically bundles these resources and makes them available to the test suite. 98 99## Running the Tests 100 101### Build and Run the Tests 102 103Open the Benchmark Xcode project: 104 105```bash 106open extension/benchmark/apple/Benchmark/Benchmark.xcodeproj 107``` 108 109Select the destination device or simulator and press `Command+U`, or click `Product` > `Test` in the menu to run the test suite. 110 111<p align="center"> 112<img src="https://raw.githubusercontent.com/pytorch/executorch/refs/heads/main/docs/source/_static/img/ios_benchmark_app_tests.png" alt="Benchmark App Tests" style="width:800px"> 113</p> 114 115### Configure Signing (if necessary) 116 117If you plan to run the app on a physical device, you may need to set up code signing: 118 1191. Open the **Project Navigator** by pressing `Command+1` and click on the `Benchmark` root of the file tree. 1202. Under Targets section go to the **Signing & Capabilities** tab of both the `App` and `Tests` targets. 1213. Select your development team. Alternatively, manually pick a provisioning profile that supports the increased memory limit entitlement and modify the bundle identifier if needed. 122 123<p align="center"> 124<img src="https://raw.githubusercontent.com/pytorch/executorch/refs/heads/main/docs/source/_static/img/ios_benchmark_app_signing.png" alt="Benchmark App Signing" style="width:800px"> 125</p> 126 127## Viewing Test Results and Metrics 128 129After running the tests, you can view the results in Xcode: 130 1311. Open the **Test Report Navigator** by pressing `Command+9`. 1322. Select the most recent test run. 1333. You'll see a list of tests that ran, along with their status (passed or failed). 1344. To view metrics for a specific test: 135 - Double-click on the test in the list. 136 - Switch to the **Metrics** tab to see detailed performance data. 137 138**Note**: The tests use `XCTMeasureOptions` to run each test multiple times (usually five) to obtain average performance metrics. 139 140<p align="center"> 141<img src="https://raw.githubusercontent.com/pytorch/executorch/refs/heads/main/docs/source/_static/img/ios_benchmark_app_test_load.png" alt="Benchmark App Test Load" style="width:800px"> 142</p> 143<p align="center"> 144<img src="https://raw.githubusercontent.com/pytorch/executorch/refs/heads/main/docs/source/_static/img/ios_benchmark_app_test_forward.png" alt="Benchmark App Test Forward" style="width:800px"> 145</p> 146<p align="center"> 147<img src="https://raw.githubusercontent.com/pytorch/executorch/refs/heads/main/docs/source/_static/img/ios_benchmark_app_test_generate.png" alt="Benchmark App Test Generate" style="width:800px"> 148</p> 149 150## Understanding the Test Suite 151 152The Benchmark App uses a dynamic test generation framework to create tests based on the resources you provide. 153 154### Dynamic Test Generation 155 156The key components are: 157 158- **`DynamicTestCase`**: A subclass of `XCTestCase` that allows for the dynamic creation of test methods. 159- **`ResourceTestCase`**: Builds upon `DynamicTestCase` to generate tests based on resources that match specified criteria. 160 161### How It Works 162 1631. **Define Directories and Predicates**: Override the `directories` and `predicates` methods to specify where to look for resources and how to match them. 164 1652. **Generate Resource Combinations**: The framework searches the specified `directories` for files matching the `predicates`, generating all possible combinations. 166 1673. **Create Dynamic Tests**: For each combination of resources, it calls `dynamicTestsForResources`, where you define the tests to run. 168 1694. **Test Naming**: Test names are dynamically formed using the format: 170 171 ``` 172 test_<TestName>_<Resource1>_<Resource2>_..._<OS>_<Version>_<DeviceModel> 173 ``` 174 175 This ensures that each test is uniquely identifiable based on the resources and device. 176 177### Example: Generic Model Tests 178 179Here's how you might create a test to measure model load and inference times: 180 181```objective-c 182@interface GenericTests : ResourceTestCase 183@end 184 185@implementation GenericTests 186 187+ (NSArray<NSString *> *)directories { 188 return @[@"Resources"]; 189} 190 191+ (NSDictionary<NSString *, BOOL (^)(NSString *)> *)predicates { 192 return @{ 193 @"model" : ^BOOL(NSString *filename) { 194 return [filename hasSuffix:@".pte"]; 195 }, 196 }; 197} 198 199+ (NSDictionary<NSString *, void (^)(XCTestCase *)> *)dynamicTestsForResources:(NSDictionary<NSString *, NSString *> *)resources { 200 NSString *modelPath = resources[@"model"]; 201 return @{ 202 @"load" : ^(XCTestCase *testCase) { 203 [testCase measureWithMetrics:@[[XCTClockMetric new], [XCTMemoryMetric new]] block:^{ 204 XCTAssertEqual(Module(modelPath.UTF8String).load_forward(), Error::Ok); 205 }]; 206 }, 207 @"forward" : ^(XCTestCase *testCase) { 208 // Set up and measure the forward pass... 209 }, 210 }; 211} 212 213@end 214``` 215 216In this example: 217 218- We look for `.pte` files in the `Resources` directory. 219- For each model found, we create two tests: `load` and `forward`. 220- The tests measure the time and memory usage of loading and running the model. 221 222## Extending the Test Suite 223 224You can create custom tests by subclassing `ResourceTestCase` and overriding the necessary methods. 225 226### Steps to Create Custom Tests 227 2281. **Subclass `ResourceTestCase`**: 229 230 ```objective-c 231 @interface MyCustomTests : ResourceTestCase 232 @end 233 ``` 234 2352. **Override `directories` and `predicates`**: 236 237 Specify where to look for resources and how to match them. 238 239 ```objective-c 240 + (NSArray<NSString *> *)directories { 241 return @[@"Resources"]; 242 } 243 244 + (NSDictionary<NSString *, BOOL (^)(NSString *)> *)predicates { 245 return @{ 246 @"model" : ^BOOL(NSString *filename) { 247 return [filename hasSuffix:@".pte"]; 248 }, 249 @"config" : ^BOOL(NSString *filename) { 250 return [filename isEqualToString:@"config.json"]; 251 }, 252 }; 253 } 254 ``` 255 2563. **Implement `dynamicTestsForResources`**: 257 258 Define the tests to run for each combination of resources. 259 260 ```objective-c 261 + (NSDictionary<NSString *, void (^)(XCTestCase *)> *)dynamicTestsForResources:(NSDictionary<NSString *, NSString *> *)resources { 262 NSString *modelPath = resources[@"model"]; 263 NSString *configPath = resources[@"config"]; 264 return @{ 265 @"customTest" : ^(XCTestCase *testCase) { 266 // Implement your test logic here. 267 }, 268 }; 269 } 270 ``` 271 2724. **Add the Test Class to the Test Target**: 273 274 Ensure your new test class is included in the test target in Xcode. 275 276### Example: LLaMA Token Generation Test 277 278An example of a more advanced test is measuring the tokens per second during text generation with the LLaMA model. 279 280```objective-c 281@interface LLaMATests : ResourceTestCase 282@end 283 284@implementation LLaMATests 285 286+ (NSArray<NSString *> *)directories { 287 return @[@"Resources"]; 288} 289 290+ (NSDictionary<NSString *, BOOL (^)(NSString *)> *)predicates { 291 return @{ 292 @"model" : ^BOOL(NSString *filename) { 293 return [filename hasSuffix:@".pte"] && [filename containsString:@"llama"]; 294 }, 295 @"tokenizer" : ^BOOL(NSString *filename) { 296 return [filename isEqualToString:@"tokenizer.bin"]; 297 }, 298 }; 299} 300 301+ (NSDictionary<NSString *, void (^)(XCTestCase *)> *)dynamicTestsForResources:(NSDictionary<NSString *, NSString *> *)resources { 302 NSString *modelPath = resources[@"model"]; 303 NSString *tokenizerPath = resources[@"tokenizer"]; 304 return @{ 305 @"generate" : ^(XCTestCase *testCase) { 306 // Implement the token generation test... 307 }, 308 }; 309} 310 311@end 312``` 313 314In this test: 315 316- We look for LLaMA model files and a `tokenizer.bin`. 317- We measure tokens per second and memory usage during text generation. 318 319## Measuring Performance 320 321The Benchmark App leverages Apple's performance testing APIs to measure metrics such as execution time and memory usage. 322 323- **Measurement Options**: By default, each test is run five times to calculate average metrics. 324- **Custom Metrics**: You can define custom metrics by implementing the `XCTMetric` protocol. 325- **Available Metrics**: 326 - `XCTClockMetric`: Measures wall-clock time. 327 - `XCTMemoryMetric`: Measures memory usage. 328 - **Custom Metrics**: For example, the LLaMA test includes a `TokensPerSecondMetric`. 329 330## Running Tests from the Command Line 331 332You can also run the tests using `xcodebuild`: 333 334```bash 335# Run on an iOS Simulator 336xcodebuild test -project extension/benchmark/apple/Benchmark/Benchmark.xcodeproj \ 337-scheme Benchmark \ 338-destination 'platform=iOS Simulator,name=<SimulatorName>' \ 339-testPlan Tests 340 341# Run on a physical iOS device 342xcodebuild test -project extension/benchmark/apple/Benchmark/Benchmark.xcodeproj \ 343-scheme Benchmark \ 344-destination 'platform=iOS,name=<DeviceName>' \ 345-testPlan Tests \ 346-allowProvisioningUpdates DEVELOPMENT_TEAM=<YourTeamID> 347``` 348 349Replace `<SimulatorName>`, `<DeviceName>`, and `<YourTeamID>` with your simulator/device name and Apple development team ID. 350 351## macOS 352 353The app can be built and run on macOS, just add it as the destination platform. 354 355<p align="center"> 356<img src="https://raw.githubusercontent.com/pytorch/executorch/refs/heads/main/docs/source/_static/img/ios_benchmark_app_macos.png" alt="Benchmark App macOS" style="width:700px"> 357</p> 358 359Also, set up app signing to run locally. 360 361<p align="center"> 362<img src="https://raw.githubusercontent.com/pytorch/executorch/refs/heads/main/docs/source/_static/img/ios_benchmark_app_macos_signing.png" alt="Benchmark App macOS Signing" style="width:800px"> 363</p> 364 365## Conclusion 366 367The ExecuTorch Benchmark App provides a flexible and powerful framework for testing and measuring the performance of PyTorch models on Apple devices. By leveraging dynamic test generation, you can easily add your models and resources to assess their performance metrics. Whether you're optimizing existing models or developing new ones, this tool can help you gain valuable insights into their runtime behavior. 368