In distributed evaluation, datasets are often split across multiple GPUs/cards to speed up inference. Because simple averaging of metrics from different subsets is often mathematically incorrect, BaseMetric provides a mechanism to synchronize results across all processes before the final calculation.
BaseMetric follows a two-step lifecycle:
- Data Collection: The user calls
add() to store inference results or intermediate metric values into the BaseMetric._results list. - Synchronization and Computation: The user calls
compute(). This triggers an all-gather operation across all processes to synchronize the _results list, followed by a call to the user-defined compute_metric() method to calculate the final metric.
To ensure correctness when using data samplers like PyTorch's DistributedSampler (which may pad datasets with duplicate samples to ensure even distribution across cards), BaseMetric.compute() can accept a size parameter. This parameter represents the true number of samples in the dataset. BaseMetric will use the dist_collect_mode to remove these duplicate padding samples before calling compute_metric().
Note: To successfully remove padding samples, the values stored in _results must maintain a one-to-one mapping with the original dataset samples.
class MyMetric(BaseMetric):
def add(self, result):
# Store results in the internal list
self._results.append(result)
def compute_metric(self, results):
# Perform the actual math on the synchronized results
return sum(results) / len(results)
# Usage flow:
# 1. metric = MyMetric(...)
# 2. metric.add(inference_result)
# 3. final_score = metric.compute(size=true_dataset_size)