Skip to main content

burn_dispatch/ops/
distributed.rs

1use alloc::vec::Vec;
2
3use burn_backend::{
4    DeviceId,
5    distributed::{
6        CollectiveTensor, DistributedConfig, DistributedOps, DistributedParams, ReduceOperation,
7        TensorRef,
8    },
9    tensor::FloatTensor,
10};
11
12use crate::{Dispatch, DispatchDevice};
13
14macro_rules! dispatch_distributed_devices_arms {
15    (
16        $device:expr,
17        $devices:expr,
18        |$inner_devices:ident| $body:expr;
19        $([$Backend:ident, $cfg:meta]),*
20    ) => {
21        match $device {
22            // Autodiff arm first
23            #[cfg(feature = "autodiff")]
24            $crate::DispatchDevice::Autodiff(inner) => {
25                let inner_devices = $devices
26                    .iter()
27                    .map(|d| {
28                         match &d {
29                            #[cfg(feature = "autodiff")]
30                            $crate::DispatchDevice::Autodiff(d_inner) => *d_inner.inner.clone(),
31                            _ => unreachable!("All devices are expected to be of the same variant."),
32                        }
33                    })
34                    .collect::<Vec<_>>();
35                let inner_devices = inner_devices.as_slice();
36                // Recursively dispatch on inner
37                dispatch_distributed_devices_arms!(
38                    @autodiff
39                    &**inner,
40                    &*inner_devices,
41                    |$inner_devices| $body;
42                    $([$Backend, $cfg]),*
43                )
44            },
45            $(
46                #[cfg($cfg)]
47                $crate::DispatchDevice::$Backend(_) => {
48                    type B = $crate::backends::$Backend;
49                    let $inner_devices = $devices
50                        .iter()
51                        .map(|d| {
52                            // In a build with only one dispatch variant compiled in, the
53                            // pattern is irrefutable and the else clause is dead — which is
54                            // the point: it only guards the multi-backend builds.
55                            #[allow(irrefutable_let_patterns)]
56                            let DispatchDevice::$Backend(dev) = d else {
57                                unreachable!("All devices are expected to be of the same variant.")
58                            };
59                            dev.clone()
60                        })
61                        .collect::<Vec<_>>();
62                    $body
63                }
64            )*
65            #[allow(unreachable_patterns)]
66            other => panic!("Distributed operations are not supported for device {other:?}"),
67        }
68    };
69    (
70        @autodiff
71        $device:expr,
72        $devices:expr,
73        |$inner_devices:ident| $body:expr;
74        $([$Backend:ident, $cfg:meta]),*
75    ) => {
76        match $device {
77            $(
78                #[cfg($cfg)]
79                $crate::DispatchDevice::$Backend(_) => {
80                    type B = $crate::backends::Autodiff<$crate::backends::$Backend>;
81                    let $inner_devices = $devices
82                        .iter()
83                        .map(|d| {
84                            // In a build with only one dispatch variant compiled in, the
85                            // pattern is irrefutable and the else clause is dead — which is
86                            // the point: it only guards the multi-backend builds.
87                            #[allow(irrefutable_let_patterns)]
88                            let DispatchDevice::$Backend(dev) = d else {
89                                unreachable!("All devices are expected to be of the same variant.")
90                            };
91                            dev.clone()
92                        })
93                        .collect::<Vec<_>>();
94                    $body
95                }
96            )*
97            $crate::DispatchDevice::Autodiff(_) => panic!("Autodiff should not wrap an autodiff device."),
98            #[allow(unreachable_patterns)]
99            other => panic!("Distributed operations are not supported for device {other:?}"),
100        }
101    };
102}
103
104/// Dispatches an operation body based on the provided devices.
105macro_rules! dispatch_distributed_devices {
106    ($device:expr, $devices:expr, |$inner_devices:ident| $body:expr) => {
107        distributed_backend_list!(
108            dispatch_distributed_devices_arms,
109            $device,
110            $devices,
111            |$inner_devices| $body
112        )
113    };
114}
115
116macro_rules! dispatch_distributed_float_arms {
117    ($tensor:expr, |$inner:ident| $body:expr; $([$Backend:ident, $cfg:meta]),*) => {{
118        let autodiff = $tensor.autodiff;
119        match $tensor.kind {
120            #[cfg(feature = "autodiff")]
121            $crate::DispatchTensorKind::Autodiff(inner) => match *inner {
122                $(
123                    #[cfg($cfg)]
124                    $crate::DispatchTensorKind::$Backend($inner) => {
125                        let $crate::DispatchAutodiffContext::Enabled(checkpointing) = autodiff else {
126                            panic!("an autodiff float primitive must have an enabled autodiff context")
127                        };
128                        with_autodiff_backend!($Backend, checkpointing, |B| {
129                            let $inner = $inner.autodiff();
130                            $crate::DispatchTensor {
131                                kind: $crate::DispatchTensorKind::Autodiff(alloc::boxed::Box::new(
132                                    $crate::DispatchTensorKind::$Backend(
133                                        $crate::BackendTensor::Autodiff($body),
134                                    ),
135                                )),
136                                autodiff,
137                            }
138                        })
139                    }
140                )*
141                #[allow(unreachable_patterns)]
142                other => panic!("Distributed operations are not supported for tensor kind {other:?}"),
143            },
144            $(
145                #[cfg($cfg)]
146                $crate::DispatchTensorKind::$Backend($inner) => {
147                    assert_eq!(
148                        autodiff,
149                        $crate::DispatchAutodiffContext::Disabled,
150                        "an enabled float tensor must use an autodiff primitive",
151                    );
152                    type B = $crate::backends::$Backend;
153                    let $inner = $inner.float();
154                    $crate::DispatchTensor {
155                        kind: $crate::DispatchTensorKind::$Backend(
156                            $crate::BackendTensor::Float($body),
157                        ),
158                        autodiff,
159                    }
160                }
161            )*
162            #[allow(unreachable_patterns)]
163            other => panic!("Distributed operations are not supported for tensor kind {other:?}"),
164        }
165    }};
166}
167
168macro_rules! dispatch_distributed_float {
169    ($tensor:expr, |$inner:ident| $body:expr) => {
170        distributed_backend_list!(dispatch_distributed_float_arms, $tensor, |$inner| $body)
171    };
172}
173
174// In builds without a collective-capable backend (Cuda/Remote), the distributed dispatch arms
175// are all cfg'd out, leaving only a diverging fallback — so the captured arguments and trailing
176// expressions are intentionally unused/unreachable.
177#[allow(unused_variables, unreachable_code)]
178impl DistributedOps<Self> for Dispatch {
179    fn start_communication_server(devices: &[DispatchDevice], config: DistributedConfig) {
180        if !devices.is_empty() {
181            let first = &devices[0];
182            dispatch_distributed_devices!(first, devices, |inner_devices| {
183                B::start_communication_server(&inner_devices, config)
184            });
185        }
186    }
187
188    fn close_communication_server(device: &DispatchDevice) {
189        dispatch_device!(@distributed device, |device| {
190            B::close_communication_server(device)
191        })
192    }
193
194    fn register_sync_parameters(
195        device: &DispatchDevice,
196        sharded_param_ids: Vec<DistributedParams>,
197    ) {
198        dispatch_device!(@distributed device, |device| B::register_sync_parameters(
199            device,
200            sharded_param_ids,
201        ))
202    }
203
204    fn submit_sync_collective(device: &DispatchDevice) {
205        dispatch_device!(@distributed device, |device| B::submit_sync_collective(device))
206    }
207
208    fn submit_gradient_sync(_tensor: TensorRef<Self>, _distributed_params: DistributedParams) {
209        unimplemented!()
210    }
211
212    fn all_reduce(
213        tensor: FloatTensor<Self>,
214        op: ReduceOperation,
215        device_ids: Vec<DeviceId>,
216    ) -> CollectiveTensor<Self> {
217        // Safety: we call `assume_resolved` only to wrap it in a new `CollectiveTensor`.
218        // Explicit type: the distributed dispatch only emits arms for collective-capable
219        // backends (Cuda, Remote), so a build with none of them leaves only the diverging
220        // fallback and the match would otherwise infer `!`.
221        let tensor: FloatTensor<Self> = dispatch_distributed_float!(tensor, |tensor| {
222            let collective_tensor = B::all_reduce(tensor, op, device_ids);
223            unsafe { collective_tensor.assume_resolved() }
224        });
225        CollectiveTensor::new(tensor)
226    }
227
228    fn sync_collective(device: &DispatchDevice) {
229        dispatch_device!(@distributed device, |device| B::sync_collective(device))
230    }
231
232    unsafe fn comm_device(_tensor: &TensorRef<Self>) -> DispatchDevice {
233        unimplemented!()
234    }
235
236    unsafe fn float_from_ref(_tensor: &TensorRef<Self>) -> FloatTensor<Self> {
237        unimplemented!()
238    }
239}