1use alloc::format;
2use alloc::string::String;
3use burn_backend::{
4 DType, Shape, TensorData,
5 backend::{Backend, DeviceId, DeviceOps, ExecutionError},
6 try_read_sync,
7};
8use burn_ir::{BackendIr, OperationIr, TensorHandle, TensorId, TensorIr};
9use burn_std::future::DynFut;
10
11use crate::{
12 ByteBridge, DirectChannel, MultiBackendBridge, RouterTensor, Runner, RunnerChannel,
13 RunnerClient,
14};
15
16macro_rules! impl_multi_backend_types {
18 ($module_name:ident, $DefaultBackend:ident, $($OtherBackend:ident),+) => {
20 pub mod $module_name {
29 use super::*;
30
31 pub enum Handle<$DefaultBackend: BackendIr, $($OtherBackend: BackendIr),+> {
34 #[allow(missing_docs)]
35 $DefaultBackend($DefaultBackend::Handle),
36 $(
37 #[allow(missing_docs)]
38 $OtherBackend($OtherBackend::Handle),
39 )+
40 }
41
42 #[derive(Clone, Debug)]
45 pub enum MultiDevice<$DefaultBackend: Backend, $($OtherBackend: Backend),+> {
46 #[allow(missing_docs)]
47 $DefaultBackend($DefaultBackend::Device),
48 $(
49 #[allow(missing_docs)]
50 $OtherBackend($OtherBackend::Device),
51 )+
52 }
53 impl<$DefaultBackend: Backend, $($OtherBackend: Backend),+> PartialEq for MultiDevice<$DefaultBackend, $($OtherBackend),+> {
54 fn eq(&self, other: &Self) -> bool {
55 match (self, other) {
56 (Self::$DefaultBackend(lhs), Self::$DefaultBackend(rhs)) => lhs == rhs,
57 $(
58 (Self::$OtherBackend(lhs), Self::$OtherBackend(rhs)) => lhs == rhs,
59 )+
60 _ => false,
61 }
62 }
63 }
64
65 impl<$DefaultBackend: Backend, $($OtherBackend: Backend),+> Default for MultiDevice<$DefaultBackend, $($OtherBackend),+> {
67 fn default() -> Self {
68 Self::$DefaultBackend($DefaultBackend::Device::default())
69 }
70 }
71
72 impl<$DefaultBackend: Backend, $($OtherBackend: Backend),+> burn_std::device::Device for MultiDevice<$DefaultBackend, $($OtherBackend),+> {
73 fn from_id(_device_id: DeviceId) -> Self {
74 Default::default()
76 }
77
78 fn to_id(&self) -> DeviceId {
79 match self {
80 Self::$DefaultBackend(device) => device.id(),
81 $(
82 Self::$OtherBackend(device) => device.id(),
83 )+
84 }
85 }
86
87 }
88
89 impl<$DefaultBackend: Backend, $($OtherBackend: Backend),+> DeviceOps for MultiDevice<$DefaultBackend, $($OtherBackend),+> {}
90
91 #[derive(Clone)]
93 pub enum MultiRunnerClient<$DefaultBackend: BackendIr, $($OtherBackend: BackendIr),+> {
94 #[allow(missing_docs)]
95 $DefaultBackend(Runner<$DefaultBackend>),
96 $(
97 #[allow(missing_docs)]
98 $OtherBackend(Runner<$OtherBackend>),
99 )+
100 }
101
102 impl<$DefaultBackend: BackendIr, $($OtherBackend: BackendIr),+> RunnerClient for MultiRunnerClient<$DefaultBackend, $($OtherBackend),+>
103 {
104 type Device = MultiDevice<$DefaultBackend, $($OtherBackend),+>;
105
106 fn register_op(&self, op: OperationIr) {
107 match self {
108 Self::$DefaultBackend(runner) => runner.register_op(op),
109 $(
110 Self::$OtherBackend(runner) => runner.register_op(op),
111 )+
112 }
113 }
114
115 fn read_tensor_async(&self, tensor: TensorIr) -> DynFut<Result<TensorData, ExecutionError>> {
116 match self {
117 Self::$DefaultBackend(runner) => runner.read_tensor_async(tensor),
118 $(
119 Self::$OtherBackend(runner) => runner.read_tensor_async(tensor),
120 )+
121 }
122 }
123
124 fn register_tensor_data(&self, data: TensorData) -> RouterTensor<Self> {
125 match self {
126 Self::$DefaultBackend(runner) => {
127 let desc = runner.register_tensor_data_desc(data);
128 RouterTensor::new(desc.id, desc.shape, desc.dtype, self.clone())
129 }
130 $(
131 Self::$OtherBackend(runner) => {
132 let desc = runner.register_tensor_data_desc(data);
133 RouterTensor::new(desc.id, desc.shape, desc.dtype, self.clone())
134 }
135 )+
136 }
137 }
138
139 fn device(&self) -> Self::Device {
140 match self {
141 Self::$DefaultBackend(runner) => MultiDevice::$DefaultBackend(runner.device()),
142 $(
143 Self::$OtherBackend(runner) => MultiDevice::$OtherBackend(runner.device()),
144 )+
145 }
146 }
147
148 fn sync(&self) -> Result<(), ExecutionError> {
149 match self {
150 Self::$DefaultBackend(runner) => runner.sync(),
151 $(
152 Self::$OtherBackend(runner) => runner.sync(),
153 )+
154 }
155 }
156
157 fn seed(&self, seed: u64) {
158 match self {
159 Self::$DefaultBackend(runner) => runner.seed(seed),
160 $(
161 Self::$OtherBackend(runner) => runner.seed(seed),
162 )+
163 }
164 }
165
166 fn create_empty_handle(&self) -> TensorId {
167 match self {
168 Self::$DefaultBackend(runner) => runner.create_empty_handle(),
169 $(
170 Self::$OtherBackend(runner) => runner.create_empty_handle(),
171 )+
172 }
173 }
174
175 fn dtype_usage(&self, dtype: burn_std::DType) -> burn_backend::DTypeUsageSet {
176 match self {
177 Self::$DefaultBackend(runner) => runner.dtype_usage(dtype),
178 $(
179 Self::$OtherBackend(runner) => runner.dtype_usage(dtype),
180 )+
181 }
182 }
183 }
184
185 impl<$DefaultBackend: BackendIr, $($OtherBackend: BackendIr),+, Br> RunnerChannel for DirectChannel<($DefaultBackend, $($OtherBackend),+), Br>
186 where
187 Br: MultiBackendBridge<TensorHandle = Handle<$DefaultBackend, $($OtherBackend),+>, Device = MultiDevice<$DefaultBackend, $($OtherBackend),+>>,
188 {
189 type Device = Br::Device;
190
191 type Bridge = Br;
192
193 type FloatElem = $DefaultBackend::FloatElem;
194 type IntElem = $DefaultBackend::IntElem;
195 type BoolElem = $DefaultBackend::BoolElem;
196
197 type Client = MultiRunnerClient<$DefaultBackend, $($OtherBackend),+>;
198
199 fn init_client(device: &Self::Device) -> Self::Client {
200 match device {
201 MultiDevice::$DefaultBackend(device) => MultiRunnerClient::$DefaultBackend(Runner::new(device.clone())),
202 $(
203 MultiDevice::$OtherBackend(device) => MultiRunnerClient::$OtherBackend(Runner::new(device.clone())),
204 )+
205 }
206 }
207
208 fn get_tensor_handle(
209 tensor: &TensorIr,
210 client: &Self::Client,
211 ) -> <Self::Bridge as MultiBackendBridge>::TensorHandle {
212 match client {
213 MultiRunnerClient::$DefaultBackend(runner) => Handle::$DefaultBackend(runner.get_tensor_handle(tensor)),
214 $(
215 MultiRunnerClient::$OtherBackend(runner) => Handle::$OtherBackend(runner.get_tensor_handle(tensor)),
216 )+
217 }
218 }
219
220 fn register_tensor(
221 client: &Self::Client,
222 handle: <Self::Bridge as MultiBackendBridge>::TensorHandle,
223 shape: Shape,
224 dtype: DType,
225 ) -> RouterTensor<Self::Client> {
226 match client {
227 MultiRunnerClient::$DefaultBackend(runner) => match handle {
228 Handle::$DefaultBackend(handle) => runner.register_tensor(handle, shape, dtype, client.clone()),
229 _ => unreachable!("Can't register tensor handle for another backend."),
230 },
231 $(
232 MultiRunnerClient::$OtherBackend(runner) => match handle {
233 Handle::$OtherBackend(handle) => runner.register_tensor(handle, shape, dtype, client.clone()),
234 _ => unreachable!("Can't register tensor handle for another backend."),
235 },
236 )+
237 }
238 }
239
240 fn name(_device: &Self::Device) -> String {
241 let mut name = format!("{}", $DefaultBackend::name(&<$DefaultBackend::Device as Default>::default()));
242 $(
243 name.push_str(&format!(", {}", $OtherBackend::name(&<$OtherBackend::Device as Default>::default())));
244 )+
245 format!("direct<({})>", name)
246 }
247 }
248
249 impl<$DefaultBackend: BackendIr, $($OtherBackend: BackendIr),+> MultiBackendBridge for ByteBridge<($DefaultBackend, $($OtherBackend),+)> {
250 type TensorHandle = Handle<$DefaultBackend, $($OtherBackend),+>;
251 type Device = MultiDevice<$DefaultBackend, $($OtherBackend),+>;
252
253 fn change_backend_float(
254 tensor: Self::TensorHandle,
255 shape: Shape,
256 target_device: &Self::Device,
257 ) -> Self::TensorHandle {
258 multi_backend_match!(shape, (tensor, target_device) : $DefaultBackend, $($OtherBackend),+)
259 }
260
261 fn change_backend_int(
262 tensor: Self::TensorHandle,
263 shape: Shape,
264 target_device: &Self::Device,
265 ) -> Self::TensorHandle {
266 multi_backend_match!(shape, (tensor, target_device) : $DefaultBackend, $($OtherBackend),+)
267 }
268
269 fn change_backend_bool(
270 tensor: Self::TensorHandle,
271 shape: Shape,
272 target_device: &Self::Device,
273 ) -> Self::TensorHandle {
274 multi_backend_match!(shape, (tensor, target_device) : $DefaultBackend, $($OtherBackend),+)
275 }
276
277 }
278 }
279 };
280}
281
282macro_rules! bridge {
283 ($Backend:ident, $handle:expr, $device:expr, $shape:expr) => {{
284 let tensor = $Backend::float_tensor(TensorHandle {
286 handle: $handle,
287 shape: $shape,
288 });
289 let tensor = $Backend::float_to_device(tensor, $device);
290 let handle = $Backend::float_tensor_handle(tensor);
291 Handle::$Backend(handle)
292 }};
293 ($BackendA:ident, $BackendB:ident, $handle:expr, $device:expr, $shape:expr) => {{
294 let tensor = $BackendA::float_tensor(TensorHandle { handle: $handle, shape: $shape });
296 let data = try_read_sync($BackendA::float_into_data(tensor)).unwrap().expect(
297 "Failed to read tensor data synchronously. This can happen on platforms that don't support blocking futures like WASM."
298 );
299 let tensor = $BackendB::float_from_data(data, $device);
300 let handle = $BackendB::float_tensor_handle(tensor);
301 Handle::$BackendB(handle)
302 }};
303}
304
305macro_rules! multi_backend_match {
306 ($shape:expr, ($handle:expr, $device:expr) : $DefaultBackend:ident, $($OtherBackend:ident),+) => {
307 multi_backend_match! (
308 @step
309 $shape,
310 ($handle, $device);
311 {
312 (Handle::$DefaultBackend(handle), MultiDevice::$DefaultBackend(device)) => bridge!($DefaultBackend, handle, device, $shape),
313 $(
314 (Handle::$DefaultBackend(handle), MultiDevice::$OtherBackend(device)) => bridge!($DefaultBackend, $OtherBackend, handle, device, $shape),
315 (Handle::$OtherBackend(handle), MultiDevice::$DefaultBackend(device)) => bridge!($OtherBackend, $DefaultBackend, handle, device, $shape),
316 (Handle::$OtherBackend(handle), MultiDevice::$OtherBackend(device)) => bridge!($OtherBackend, handle, device, $shape),
317 )+
318 };
319 $($OtherBackend),+
320 )
321 };
322
323 (@step
324 $shape:expr,
325 $pats:tt;
326 { $($arms:tt)* };
327 $BackendA:ident,
328 $($OtherBackend:ident),+
329 ) => {
330 multi_backend_match! (
331 @step
332 $shape,
333 $pats;
334 {
335 $($arms)*
336 $(
337 (Handle::$BackendA(handle), MultiDevice::$OtherBackend(device)) => bridge!($BackendA, $OtherBackend, handle, device, $shape),
338 (Handle::$OtherBackend(handle), MultiDevice::$BackendA(device)) => bridge!($OtherBackend, $BackendA, handle, device, $shape),
339 )*
340 };
341 $($OtherBackend),*
342 )
343 };
344
345 (@step
346 $shape:expr,
347 ($handle:expr, $device:expr);
348 { $($arms:tt)* };
349 $($BackendA:ident)?
350 ) => {
351 match ($handle, $device) {
352 $($arms)*
353 }
354 };
355}
356
357impl_multi_backend_types!(duo, B1, B2);
359impl_multi_backend_types!(trio, B1, B2, B3);
360impl_multi_backend_types!(quad, B1, B2, B3, B4);
361
362#[cfg(not(target_os = "windows"))] #[cfg(test)]
364mod tests {
365 use burn_tensor::Tensor;
366
367 use super::*;
368 use crate::tests::TestBackend;
369
370 #[test]
371 fn should_support_dual_byte_bridge() {
372 let device1 = duo::MultiDevice::B1(Default::default());
373 let device2 = duo::MultiDevice::B2(Default::default());
374 let tensor1 = Tensor::<TestBackend, 1>::from_floats([1.0, 2.0, 3.0, 4.0], &device1);
375 let tensor2 = Tensor::<TestBackend, 1>::from_floats([5.0, 6.0, 7.0, 8.0], &device2);
376
377 let tensor1_2 = tensor1.clone().to_device(&device2);
378 tensor1.into_data().assert_eq(&tensor1_2.into_data(), true);
379
380 let tensor2_1 = tensor2.clone().to_device(&device1);
381 tensor2.into_data().assert_eq(&tensor2_1.into_data(), true);
382 }
383}