MMEval: Machine Learning Evaluation Library

repository·main·Indexed 42 days ago

https://github.com/open-mmlab/mmeval

A machine learning evaluation library providing efficient, distributed metrics for computer vision tasks. It supports multiple frameworks including PyTorch, TensorFlow, PaddlePaddle, OneFlow, and NumPy, with distributed backends such as MPI4Py, Horovod, and torch.distributed. MMEval includes implementations for metrics like Accuracy, COCODetection, and MeanIoU, and provides a storage abstraction layer via mmeval.fileio.

Tokens
16.8K
Snippets
45
Records
74
Agent score
52%

What's inside MMEval

  1. Overview of MMEval

    main

    MMEval is a machine learning evaluation library designed for efficient and accurate distributed evaluation across various machine learning frameworks.

    Key features include:

    • Comprehensive metrics for computer vision tasks.
    • Efficient distributed evaluation supported by multiple backends.
    • Dynamic input dispatching to support multiple frameworks (PyTorch, TensorFlow, PaddlePaddle, OneFlow, and NumPy).

    Supported Distributed Backends:

    • MPI4PyDist (via MPI4Py)
    • TorchCPUDist and TorchCUDADist (via torch.distributed)
    • TFHorovodDist (via Horovod)
    • PaddleDist (via paddle.distributed)
    • OneFlowDist (via oneflow.comm)
  2. Introduction to MMEval

    main

    MMEval is a machine learning algorithm evaluation library designed for efficient and accurate distributed evaluation. It supports multiple machine learning framework backends and provides a wide range of evaluation metrics, primarily focused on Computer Vision (CV) sub-fields, with Natural Language Processing (NLP) metrics currently under development.

    Key features include:

    • Rich Metrics: Extensive support for various Computer Vision evaluation metrics.
    • Distributed Evaluation: Support for multiple distributed communication libraries to ensure efficient and accurate evaluation across distributed systems.
    • Framework Agnostic: Supports multiple machine learning frameworks, automatically dispatching to the appropriate implementation based on the input.
  3. Explore MMEval evaluation examples

    main

    MMEval provides several reference implementations for different deep learning frameworks, tasks, and metrics. You can use these examples to understand how to implement evaluation pipelines for your specific needs.

    Supported combinations include:

    • Classification (Torchvision): Uses the Accuracy metric and supports distributed evaluation. See the CIFAR-10 example.
    • Object Detection (TensorFlow/TensorPack): Uses the COCODetection metric and supports distributed evaluation. See the TensorPack-FasterRCNN example.
    • Semantic Segmentation (Paddle): Uses the MeanIoU metric and supports distributed evaluation. See the PaddelSeg example.
  4. Use mmeval.utils for miscellaneous helper functions

    main
    The mmeval.utils module provides several utility functions for type checking, sequence validation, and file path verification. These are primarily used for internal validation and ensuring data structures match expected formats.
  5. Use File Backends for storage abstraction

    main

    mmeval.fileio provides a storage abstraction layer through BaseStorageBackend. This allows you to interact with files across different storage systems using a unified interface.

    Supported backends include:

    • LocalBackend: For local file systems.
    • HTTPBackend: For accessing files via HTTP.
    • LmdbBackend: For LMDB databases.
    • MemcachedBackend: For Memcached.
    • PetrelBackend: For Petrel storage.

    You can extend the supported backends using the register_backend function.

  6. Handling padded samples in distributed evaluation with BaseMetric.compute

    main

    When using distributed samplers (like PyTorch's DistributedSampler), processes often receive padded, repeated data samples to ensure all GPUs have an equal number of samples. This can skew metric calculations if not handled.

    To ensure correctness, BaseMetric.compute accepts a size parameter representing the actual number of samples in the evaluation dataset. When compute is called with this size, BaseMetric uses the dist_collect_mode to remove the padded samples from the synchronized _results list before calling compute_metric.

    Important: The intermediate results stored in _results must maintain a one-to-one correspondence with the samples to allow for accurate removal of padded data.

  7. How MMEval integrates with OpenMMLab 2.0 libraries

    main
    MMEval is designed to be compatible with the OpenMMLab 2.0 ecosystem. It implements a BaseMetric that follows the mmengine.evaluator design and includes a distributed communication backend. This allows it to support diverse distributed communication libraries, enabling seamless evaluation of OpenMMLab 2.0 algorithm libraries (such as MMCls) using existing metric configurations.
  8. Implement a custom distributed communication backend

    main

    MMEval abstracts distributed communication requirements through the BaseDistBackend interface. This abstraction allows the library to support multiple communication libraries (like MPI) by providing a unified way to perform all-gather and broadcast operations on Python objects.

    To implement a custom backend, you must inherit from mmeval.core.dist_backends.BaseDistBackend and implement the following interface:

    • is_initialized (bool): Indicates if the distributed environment is ready.
    • rank (int): The rank index of the current process group.
    • world_size (int): The total number of processes in the current group.
    • all_gather_object(obj): Performs an all-gather operation on a Pickle-serializable Python object and returns a list of objects from all processes.
    • broadcast_object(obj, src=0): Broadcasts a Pickle-serializable Python object from a source process (defaulting to rank 0) to all other processes.

    Note that all objects passed to these methods must be serializable by Pickle.

    from mpi4py import MPI
    from typing import Any, List
    import os
    from mmeval.core.dist_backends import BaseDistBackend
    
    class MPI4PyDist(BaseDistBackend):
        """A distributed communication backend for mpi4py."""
    
        @property
        def is_initialized(self) -> bool:
            """Returns True if the distributed environment has been initialized."""
            return 'OMPI_COMM_WORLD_SIZE' in os.environ
    
        @property
        def rank(self) -> int:
            """Returns the rank index of the current process group."""
            comm = MPI.COMM_WORLD
            return comm.Get_rank()
    
        @property
        def world_size(self) -> int:
            """Returns the world size of the current process group."""
            comm = MPI.COMM_WORLD
            return comm.Get_size()
    
        def all_gather_object(self, obj: Any) -> List[Any]:
            """All gather the given object from the current process group and
            returns a list consisting gathered object of each process."""
            comm = MPI.COMM_WORLD
            return comm.allgather(obj)
    
        def broadcast_object(self, obj: Any, src: int = 0) -> Any:
            """Broadcast the given object from source process to the current
            process group."""
            comm = MPI.COMM_WORLD
            return comm.bcast(obj, root=src)
  9. Understand distributed backends in mmeval.core.dist_backends

    main

    The mmeval.core.dist_backends module provides various backend implementations for handling distributed evaluation. These backends allow mmeval to scale evaluation processes across different distributed computing frameworks and hardware setups.

    Available backend types include:

    • Base Classes:
      • BaseDistBackend: The base class for all distributed backends.
      • TensorBaseDistBackend: A base class for backends that operate on tensors.
    • Non-Distributed:
      • NonDist: Used when no distributed evaluation is required.
    • Framework/Hardware Specific Backends:
      • MPI4PyDist: Uses MPI (via mpi4py) for distributed communication.
      • TorchCPUDist: Uses PyTorch for distributed evaluation on CPUs.
      • TorchCUDADist: Uses PyTorch for distributed evaluation on GPUs (CUDA).
      • TFHorovodDist: Uses TensorFlow with Horovod for distributed evaluation.
      • PaddleDist: Uses PaddlePaddle for distributed evaluation.
      • OneFlowDist: Uses OneFlow for distributed evaluation.
  10. Use File Handlers to manage file formats

    main

    File Handlers in mmeval.fileio manage the serialization and deserialization of data for specific file formats. They are used to bridge the gap between raw file data and Python objects.

    Supported handlers include:

    • JsonHandler: For JSON files.
    • PickleHandler: For Python pickle files.
    • YamlHandler: For YAML files.

    Use register_handler to add support for new file formats.

  11. How distributed communication backends work in MMEval

    main

    MMEval requires two primary types of distributed communication during evaluation:

    1. all-gather: Collecting intermediate metric calculation results from all processes.
    2. broadcast: Sending the final metric results calculated by rank 0 to all other processes.

    To support different distributed communication libraries, MMEval uses an abstraction called BaseDistBackend. Developers can implement custom backends by inheriting from this base class and implementing its required interface. This allows MMEval to remain agnostic of the underlying communication library (e.g., MPI, NCCL, etc.).

    class BaseDistBackend:
        is_initialized: bool
        rank: int
        world_size: int
        all_gather_object(obj) -> List[Any]
        broadcast_object(obj, src=0) -> Any
  12. How BaseMetric handles distributed evaluation

    main

    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:

    1. Data Collection: The user calls add() to store inference results or intermediate metric values into the BaseMetric._results list.
    2. 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)