aidevelopementtoolkit.torch_utils.distributed_torch_utils

run_distributed_function(devices: List[str], fn_to_distribute: Callable, fn_kwargs: Dict[str, Any]) -> None

This function must be used to run a function in a distributed way using torch distributed library.

Parameters:
  • devices (List[int]) –

    List of visible device IDs.

  • fn_to_distribute (Callable) –

    Funtion to be distributed.

  • fn_kwargs (Dict[str, Any]) –

    Kwargs to be passed to the function.

Examples:

Define a function to execute on every GPU:

>>> import torch
>>> def train_step(epochs: int):
...     rank = get_process_rank()
...     print(f"Running process {rank}")
...     model = torch.nn.Linear(10, 1).cuda()
...     # Training logic here
...
>>> run_distributed_function(
...     devices=["0", "1"],
...     fn_to_distribute=train_step,
...     fn_kwargs={"epochs": 10},
... )

The command used to launch the script should specify the number of processes per node:

.. code-block:: bash

torchrun --nproc_per_node=2 train.py
Notes

The number of launched processes must match the number of devices provided. For example, --nproc_per_node=4 requires devices=["0", "1", "2", "3"].

Source code in aidevelopementtoolkit/torch_utils/distributed_torch_utils.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def run_distributed_function(
        devices: List[str], 
        fn_to_distribute: Callable, 
        fn_kwargs: Dict[str, Any],
    ) -> None:
    """This function must be used to run a function in a distributed
    way using torch distributed library.

    Parameters
    ----------    
    devices : List[int]
        List of visible device IDs.

    fn_to_distribute : Callable
        Funtion to be distributed.

    fn_kwargs : Dict[str, Any]
        Kwargs to be passed to the function.

    Examples
    --------
    Define a function to execute on every GPU:

    >>> import torch
    >>> def train_step(epochs: int):
    ...     rank = get_process_rank()
    ...     print(f"Running process {rank}")
    ...     model = torch.nn.Linear(10, 1).cuda()
    ...     # Training logic here
    ...
    >>> run_distributed_function(
    ...     devices=["0", "1"],
    ...     fn_to_distribute=train_step,
    ...     fn_kwargs={"epochs": 10},
    ... )

    The command used to launch the script should specify the number of
    processes per node:

    .. code-block:: bash

        torchrun --nproc_per_node=2 train.py

    Notes
    -----
    The number of launched processes must match the number of devices
    provided. For example, `--nproc_per_node=4` requires
    `devices=["0", "1", "2", "3"]`.
    """

    # Get torchrun environment variables
    local_rank = int(environ.get("LOCAL_RANK", 0))
    global_rank = int(environ.get("RANK", 0))
    world_size = int(environ.get("WORLD_SIZE", 1))
    local_world_size = int(environ.get("LOCAL_WORLD_SIZE", len(devices)))

    # Handle mismatched launch
    if local_rank >= len(devices):
        logger.error(f"Configuration error: `LOCAL_RANK`={local_rank} exceeds `devices` ({len(devices)}).")
        raise ValueError()

    elif local_world_size != len(devices):
        logger.error(
            f"Configuration error: Started {local_world_size} processes on this node "
            f"but indicated {len(devices)} GPUs (`devices`). These two must be coherent."
        )
        raise ValueError()

    # Restrict visible GPUs for this process
    environ["CUDA_VISIBLE_DEVICES"] = ",".join(map(str, devices))

    # Launch worker
    _main_worker(
        devices=devices,
        local_rank=local_rank,
        global_rank=global_rank,
        world_size=world_size,
        fn_to_distribute=fn_to_distribute,
        fn_kwargs=fn_kwargs,
    )

get_process_rank() -> int

This function checks if the torch distributed backend is initialized and then eventually returns the process rank.

Returns:
  • int

    Rank of the process if the torch distributed backend is initialized, 0 otherwise

Examples:

>>> rank = get_process_rank()
>>> if rank == 0:
...     print("Only the main process executes this code.")
Source code in aidevelopementtoolkit/torch_utils/distributed_torch_utils.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def get_process_rank() -> int:
    """This function checks if the torch distributed backend is
    initialized and then eventually returns the process rank.

    Returns
    -------
    int
        Rank of the process if the torch distributed backend is
        initialized, 0 otherwise

    Examples
    --------
    >>> rank = get_process_rank()
    >>> if rank == 0:
    ...     print("Only the main process executes this code.")
    """

    if dist.is_initialized():
        return dist.get_rank()
    else:
        return 0

dist_barrier() -> None

This function checks if the torch distributed backend is initialized and then performs the barrier to wait all the processes.

Examples:

Synchronize processes before saving a checkpoint:

>>> train_model()
>>> dist_barrier()
>>> if get_process_rank() == 0:
...     save_checkpoint()
Source code in aidevelopementtoolkit/torch_utils/distributed_torch_utils.py
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def dist_barrier() -> None:
    """This function checks if the torch distributed backend is
    initialized and then performs the barrier to wait all the processes.

    Examples
    --------
    Synchronize processes before saving a checkpoint:

    >>> train_model()
    >>> dist_barrier()
    >>> if get_process_rank() == 0:
    ...     save_checkpoint()
    """

    if dist.is_initialized():
        return dist.barrier()