| # |
| # Copyright (c) 2024 Apple Inc. All rights reserved. |
| # Provided subject to the LICENSE file in the top level directory. |
| # |
| |
| import logging |
| import time |
| from typing import Tuple |
| |
| import torch |
| from executorch.backends.apple.mps.test.test_mps_utils import TestMPS |
| from torch.export.exported_program import ExportedProgram |
| |
| |
| def bench_forward(func, *args): |
| # warmup |
| for _ in range(10): |
| func(*args) |
| |
| start = time.time() |
| for _ in range(100): |
| func(*args) |
| end = time.time() |
| return end - start |
| |
| |
| def executorch_forward_pass(model, inputs): |
| for _ in range(10): |
| model.forward(inputs) |
| |
| |
| def synchronize(): |
| torch.mps.synchronize() |
| |
| |
| def pytorch_forward_pass(model, inputs): |
| for _ in range(10): |
| model(*inputs) |
| synchronize() |
| |
| |
| def get_mps_inputs(inputs): |
| inputs_mps = [] |
| for tensor in inputs: |
| inputs_mps.append(tensor.to("mps")) |
| inputs_mps = tuple(inputs_mps) |
| return inputs_mps |
| |
| |
| def get_executorch_model(executorch_program: ExportedProgram): |
| try: |
| from executorch.extension.pybindings.portable_lib import ( # @manual |
| _load_for_executorch_from_buffer, |
| ) |
| |
| return _load_for_executorch_from_buffer(executorch_program.buffer) |
| except ImportError: |
| logging.info( |
| "ExecuTorch MPS delegate was built without pybind support (not possible to run forward pass within python)" |
| ) |
| return None |
| |
| |
| def bench_torch(executorch_program: ExportedProgram, model, inputs, model_name): |
| model = model.to("mps") |
| inputs_mps = get_mps_inputs(inputs) |
| |
| executorch_model = get_executorch_model(executorch_program) |
| if executorch_model is not None: |
| t_pytorch = bench_forward(pytorch_forward_pass, model, inputs_mps) |
| t_executorch = bench_forward(executorch_forward_pass, executorch_model, inputs) |
| |
| logging.info(f"Model name: {model_name}") |
| logging.info(f"Pytorch MPS forward pass: {t_pytorch} seconds") |
| logging.info(f"ExecuTorch MPS forward pass: {t_executorch} seconds") |
| logging.info( |
| f"ExecuTorch speedup: {((t_pytorch - t_executorch) / t_pytorch) * 100}%" |
| ) |
| |
| |
| def compare_outputs( |
| executorch_program: ExportedProgram, |
| model: torch.nn.Module, |
| inputs: Tuple[torch.tensor], |
| model_name: str, |
| use_fp16: bool, |
| ): |
| test_module = TestMPS() |
| inputs_copy = [] |
| if use_fp16: |
| model = model.to(torch.float16) |
| model = model |
| for t in inputs: |
| tensor = t.detach().clone() |
| if use_fp16 and tensor.dtype == torch.float32: |
| tensor = tensor.to(torch.float16) |
| inputs_copy.append(tensor) |
| inputs_copy = tuple(inputs_copy) |
| |
| pytorch_results = model(*inputs_copy) |
| |
| executorch_model = get_executorch_model(executorch_program) |
| if executorch_model is not None: |
| executorch_results = executorch_model.forward(inputs) |
| test_module.assert_outputs_equal(executorch_results, pytorch_results, use_fp16) |
| logging.info( |
| f"Results between ExecuTorch forward pass with MPS backend and PyTorch forward pass for {model_name} are matching!" |
| ) |