# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.
from typing import Dict, List, Optional, Union
import torch
from overrides import overrides
from archai.discrete_search.api.archai_model import ArchaiModel
from archai.discrete_search.api.model_evaluator import ModelEvaluator
from archai.discrete_search.evaluators.pt_profiler_utils.pt_profiler_eval import profile
[docs]class TorchNumParameters(ModelEvaluator):
"""Total number of parameters."""
def __init__(
self, exclude_cls: Optional[List[torch.nn.Module]] = None, trainable_only: Optional[bool] = True
) -> None:
"""Initialize the evaluator.
Args:
exclude_cls: List of PyTorch module classes to exclude from parameter counting.
trainable_only: A flag indicating whether only trainable parameters should be counted.
"""
self.exclude_cls = exclude_cls
self.trainable_only = trainable_only
[docs] @overrides
def evaluate(self, model: ArchaiModel, budget: Optional[float] = None) -> float:
total_params = sum(
param.numel() for param in model.arch.parameters() if not self.trainable_only or param.requires_grad
)
exclude_params = (
0
if self.exclude_cls is None
else sum(
sum(param.numel() for param in module.parameters())
for module in model.arch.modules()
if isinstance(module, tuple(self.exclude_cls))
)
)
return total_params - exclude_params
[docs]class TorchFlops(ModelEvaluator):
"""Total number of FLOPs."""
def __init__(
self,
forward_args: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,
forward_kwargs: Optional[Dict[str, torch.Tensor]] = None,
ignore_layers: Optional[List[str]] = None,
) -> None:
"""Initialize the evaluator.
Args:
forward_args: `model.forward()` arguments used for profilling.
forward_kwargs: `model.forward()` keyword arguments used for profilling.
ignore_layers: List of layer names that should be ignored during profiling.
"""
self.forward_args = forward_args
self.forward_kwargs = forward_kwargs
self.ignore_layers = ignore_layers
[docs] @overrides
def evaluate(self, model: ArchaiModel, budget: Optional[float] = None) -> float:
return profile(
model.arch,
self.forward_args,
self.forward_kwargs,
num_warmups=0,
num_samples=1,
ignore_layers=self.ignore_layers,
)["flops"]
[docs]class TorchMacs(ModelEvaluator):
"""Total number of MACs."""
def __init__(
self,
forward_args: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,
forward_kwargs: Optional[Dict[str, torch.Tensor]] = None,
ignore_layers: Optional[List[str]] = None,
) -> None:
"""Initialize the evaluator.
Args:
forward_args: `model.forward()` arguments used for profilling.
forward_kwargs: `model.forward()` keyword arguments used for profilling.
ignore_layers: List of layer names that should be ignored during profiling.
"""
self.forward_args = forward_args
self.forward_kwargs = forward_kwargs
self.ignore_layers = ignore_layers
[docs] @overrides
def evaluate(self, model: ArchaiModel, budget: Optional[float] = None) -> float:
return profile(
model.arch,
self.forward_args,
self.forward_kwargs,
num_warmups=0,
num_samples=1,
ignore_layers=self.ignore_layers,
)["macs"]
[docs]class TorchLatency(ModelEvaluator):
"""Average/median latency (in seconds) of a PyTorch model using a sample input."""
def __init__(
self,
forward_args: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,
forward_kwargs: Optional[Dict[str, torch.Tensor]] = None,
num_warmups: Optional[int] = 1,
num_samples: Optional[int] = 1,
use_cuda: Optional[bool] = False,
use_median: Optional[bool] = False,
ignore_layers: Optional[List[str]] = None,
) -> None:
"""Initialize the evaluator.
Args:
forward_args: `model.forward()` arguments used for profilling.
forward_kwargs: `model.forward()` keyword arguments used for profilling.
num_warmups: Number of warmup runs before profilling.
num_samples: Number of runs after warmup.
use_cuda: Whether to use CUDA instead of CPU.
use_median: Whether to use median instead of mean to average memory and latency.
ignore_layers: List of layer names that should be ignored during profiling.
"""
self.forward_args = forward_args
self.forward_kwargs = forward_kwargs
self.ignore_layers = ignore_layers
self.num_warmups = num_warmups
self.num_samples = num_samples
self.use_cuda = use_cuda
self.use_median = use_median
self.ignore_layers = ignore_layers
[docs] @overrides
def evaluate(self, model: ArchaiModel, budget: Optional[float] = None) -> float:
return profile(
model.arch,
self.forward_args,
self.forward_kwargs,
num_warmups=self.num_warmups,
num_samples=self.num_samples,
use_cuda=self.use_cuda,
use_median=self.use_median,
ignore_layers=self.ignore_layers,
)["latency"]
[docs]class TorchPeakCudaMemory(ModelEvaluator):
"""Measures CUDA peak memory (in bytes) of a PyTorch model using a sample input."""
def __init__(
self,
forward_args: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,
forward_kwargs: Optional[Dict[str, torch.Tensor]] = None,
num_warmups: Optional[int] = 1,
num_samples: Optional[int] = 1,
use_median: Optional[bool] = False,
ignore_layers: Optional[List[str]] = None,
) -> None:
"""Initialize the evaluator.
Args:
forward_args: `model.forward()` arguments used for profilling.
forward_kwargs: `model.forward()` keyword arguments used for profilling.
num_warmups: Number of warmup runs before profilling.
num_samples: Number of runs after warmup.
use_median: Whether to use median instead of mean to average memory and latency.
ignore_layers: List of layer names that should be ignored during profiling.
"""
self.forward_args = forward_args
self.forward_kwargs = forward_kwargs
self.ignore_layers = ignore_layers
self.num_warmups = num_warmups
self.num_samples = num_samples
self.use_median = use_median
self.ignore_layers = ignore_layers
[docs] @overrides
def evaluate(self, model: ArchaiModel, budget: Optional[float] = None) -> float:
return profile(
model.arch,
self.forward_args,
self.forward_kwargs,
num_warmups=self.num_warmups,
num_samples=self.num_samples,
use_cuda=True,
use_median=self.use_median,
ignore_layers=self.ignore_layers,
)["peak_memory"]
[docs]class TorchPeakCpuMemory(ModelEvaluator):
"""Measures CPU peak memory (in bytes) of a PyTorch model using a sample input."""
def __init__(
self,
forward_args: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,
forward_kwargs: Optional[Dict[str, torch.Tensor]] = None,
):
"""Initialize the evaluator.
Args:
forward_args: `model.forward()` arguments used for profilling.
forward_kwargs: `model.forward()` keyword arguments used for profilling.
"""
forward_args = forward_args if forward_args is not None else []
self.forward_args = [forward_args] if isinstance(forward_args, torch.Tensor) else forward_args
self.forward_kwargs = forward_kwargs or {}
[docs] @overrides
def evaluate(self, model: ArchaiModel, budget: Optional[float] = None):
model.arch.to("cpu")
forward_args = tuple([arg.to("cpu") for arg in self.forward_args])
forward_kwargs = {key: value.to("cpu") for key, value in self.forward_kwargs.items()}
is_training = model.arch.training
model.arch.eval()
with torch.profiler.profile(
activities=[torch.profiler.ProfilerActivity.CPU], record_shapes=True, profile_memory=True
) as prof:
with torch.profiler.record_function("model_inference"):
model.arch(*forward_args, **forward_kwargs)
event_list = prof.key_averages()
peak_memory = max(event.cpu_memory_usage for event in event_list)
if is_training:
model.arch.train()
return peak_memory