Source code for ecal.calculators.training
from typing import Dict, Tuple, Optional
from torchvision.models import resnet18
from ecal.calculators.model_flops import FLOPCalculator, FlopsCalculatorFactory
[docs]
class Training:
"""
This class is used to estimate the flops of the model training, which is then used to estimate
the energy consumption of the model training.
"""
[docs]
def __init__(self, model_name: str,
batch_size: int, num_epochs: int, num_samples: int,
processor_flops_per_second: float, processor_max_power: int, input_size: Tuple,
evaluation_strategy: str, k_folds: int, split_ratio: float,
calculator: Optional[FLOPCalculator] = None):
"""
Initialize Training class with optional custom FLOP calculator
Args:
calculator: Optional custom FLOPCalculator implementation
input_size: Tuple of input size
batch_size: int of batch size
num_epochs: int of number of epochs
num_samples: int of number of samples
processor_flops_per_second: float of processor flops per second
processor_max_power: int of processor max power in watts
evaluation_strategy: str of evaluation strategy
k_folds: int of number of folds for cross-validation
split_ratio: float of split ratio for train-test split
"""
if model_name == 'resnet18':
self.model = resnet18()
else:
self.model = model_name
if calculator is not None:
self.calculator = calculator
else:
self.calculator = FlopsCalculatorFactory.create_calculator(self.model)
if evaluation_strategy == 'train_test_split':
self.evaluation_strategy = 'train_test_split'
self.split_ratio = split_ratio
elif evaluation_strategy == 'cross_validation':
self.evaluation_strategy = 'cross_validation'
self.k_folds = k_folds
else:
raise ValueError(f"Unsupported evaluation strategy: {evaluation_strategy}")
self.input_size = input_size
self.batch_size = batch_size
self.num_epochs = num_epochs
self.num_samples = num_samples
# hardware parameters
self.processor_flops_per_second = processor_flops_per_second
self.processor_max_power = processor_max_power
[docs]
def calculate_flops_training(self) -> float:
"""
Calculate total FLOPs required for training
Approximates one training pass (forward + backward + weight update)
as three times the forward-pass FLOPs, then scales by the number of
training samples (per the configured evaluation strategy) and the
number of epochs.
Returns:
Total training FLOPs across all epochs
"""
forward_flops = self.calculator.calculate(self.model, self.input_size)['total_flops']
# 1 training pass takes roughly 3x a single forward pass
training_flops = forward_flops * 3
# Calculate the number of batches
if self.evaluation_strategy == 'train_test_split':
training_samples = self.num_samples * self.split_ratio
elif self.evaluation_strategy == 'cross_validation':
percentage_of_samples = 1 - (1 / self.k_folds) # percentage of samples used for training
number_of_folds = self.k_folds
training_samples = self.num_samples * percentage_of_samples * number_of_folds
else:
raise ValueError(f"Unsupported evaluation strategy: {self.evaluation_strategy}")
# Calculate the total number of flops
total_flops = training_flops * training_samples * self.num_epochs
return total_flops
[docs]
def calculate_flops_evaluation(self) -> float:
"""
Calculate total FLOPs required for evaluation
Scales a single forward pass's FLOPs by the number of samples held
out for evaluation, based on the configured evaluation strategy
(train/test split or k-fold cross-validation).
Returns:
Total evaluation FLOPs
"""
# Calculate the total number of flops
forward_flops = self.calculator.calculate(self.model, self.input_size)['total_flops']
if self.evaluation_strategy == 'train_test_split':
evaluation_samples = self.num_samples * (1 - self.split_ratio)
elif self.evaluation_strategy == 'cross_validation':
percentage_of_samples = 1 / self.k_folds # percentage of samples used for evaluation
number_of_folds = self.k_folds
evaluation_samples = self.num_samples * percentage_of_samples * number_of_folds
else:
raise ValueError(f"Unsupported evaluation strategy: {self.evaluation_strategy}")
total_flops = forward_flops * evaluation_samples
return total_flops
[docs]
def calculate_energy(self) -> Dict[str, float]:
"""
Calculate the total energy usage of training and evaluation
Returns:
Dictionary with "total_energy" (training + evaluation, in Joules),
"training_energy", "evaluation_energy", "training_flops",
"evaluation_flops", "train_time" (seconds), and "eval_time" (seconds)
"""
# Calculate the total number of flops
training_flops = self.calculate_flops_training()
evaluation_flops = self.calculate_flops_evaluation()
training_energy = training_flops / self.processor_flops_per_second * self.processor_max_power
evaluation_energy = evaluation_flops / self.processor_flops_per_second * self.processor_max_power
# Calculate the total energy usage
total_energy = training_energy + evaluation_energy
return {
"total_energy": total_energy,
"training_energy": training_energy,
"evaluation_energy": evaluation_energy,
"training_flops": training_flops,
"evaluation_flops": evaluation_flops,
"train_time": training_flops / self.processor_flops_per_second,
"eval_time": evaluation_flops / self.processor_flops_per_second
}