Skip to main content

rstorch_python/
distributed.rs

1//! Distributed training bindings
2
3use crate::{error::PyResult, tensor::PyTensor};
4use pyo3::prelude::*;
5use pyo3::types::{PyModule, PyModuleMethods, PyTuple};
6
7/// Process group for distributed training
8#[pyclass(name = "ProcessGroup")]
9pub struct PyProcessGroup {
10    rank: u32,
11    world_size: u32,
12}
13
14#[pymethods]
15impl PyProcessGroup {
16    #[new]
17    fn new(rank: u32, world_size: u32) -> Self {
18        Self { rank, world_size }
19    }
20
21    #[getter]
22    fn rank(&self) -> u32 {
23        self.rank
24    }
25
26    #[getter]
27    fn world_size(&self) -> u32 {
28        self.world_size
29    }
30
31    fn all_reduce(&self, _tensor: &PyTensor, _op: Option<String>) -> PyResult<()> {
32        Ok(())
33    }
34
35    fn all_gather(&self, _tensors: Vec<PyTensor>, _tensor: &PyTensor) -> PyResult<()> {
36        Ok(())
37    }
38
39    fn broadcast(&self, _tensor: &PyTensor, _src: u32) -> PyResult<()> {
40        Ok(())
41    }
42
43    fn barrier(&self) -> PyResult<()> {
44        Ok(())
45    }
46}
47
48/// Distributed Data Parallel wrapper
49#[pyclass(name = "DistributedDataParallel")]
50pub struct PyDDP {
51    module: Py<PyAny>,
52    process_group: Option<Py<PyProcessGroup>>,
53}
54
55#[pymethods]
56impl PyDDP {
57    #[new]
58    fn new(
59        module: Py<PyAny>,
60        _device_ids: Option<Vec<u32>>,
61        _output_device: Option<u32>,
62        _broadcast_buffers: Option<bool>,
63        process_group: Option<Py<PyProcessGroup>>,
64        _bucket_cap_mb: Option<f32>,
65        _find_unused_parameters: Option<bool>,
66        _check_reduction: Option<bool>,
67        _gradient_as_bucket_view: Option<bool>,
68    ) -> Self {
69        Self {
70            module,
71            process_group,
72        }
73    }
74
75    fn forward(&self, py: Python<'_>, inputs: Vec<Py<PyAny>>) -> PyResult<Py<PyAny>> {
76        let forward_method = self.module.getattr(py, "forward")?;
77        let tuple = PyTuple::new(py, inputs)?;
78        forward_method.call1(py, tuple)
79    }
80
81    fn __call__(&self, py: Python<'_>, inputs: Vec<Py<PyAny>>) -> PyResult<Py<PyAny>> {
82        self.forward(py, inputs)
83    }
84
85    fn parameters(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
86        let method = self.module.getattr(py, "parameters")?;
87        method.call0(py)
88    }
89
90    fn named_parameters(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
91        let method = self.module.getattr(py, "named_parameters")?;
92        method.call0(py)
93    }
94
95    fn train(&mut self, py: Python<'_>, mode: Option<bool>) -> PyResult<()> {
96        let method = self.module.getattr(py, "train")?;
97        method.call1(py, (mode.unwrap_or(true),))?;
98        Ok(())
99    }
100
101    fn eval(&mut self, py: Python<'_>) -> PyResult<()> {
102        self.train(py, Some(false))
103    }
104
105    #[getter]
106    fn process_group(&self) -> Option<&Py<PyProcessGroup>> {
107        self.process_group.as_ref()
108    }
109}
110
111/// Register distributed module
112pub fn register_distributed_module(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
113    m.add_class::<PyProcessGroup>()?;
114    m.add_class::<PyDDP>()?;
115
116    #[pyfunction]
117    fn init_process_group(
118        _backend: String,
119        _init_method: Option<String>,
120        world_size: Option<u32>,
121        rank: Option<u32>,
122        _store: Option<Py<PyAny>>,
123        _timeout: Option<f64>,
124        _group_name: Option<String>,
125        _pg_options: Option<Py<PyAny>>,
126    ) -> PyProcessGroup {
127        PyProcessGroup::new(rank.unwrap_or(0), world_size.unwrap_or(1))
128    }
129
130    #[pyfunction]
131    fn destroy_process_group(_group: Option<Py<PyAny>>) -> PyResult<()> {
132        Ok(())
133    }
134
135    #[pyfunction]
136    fn get_rank(_group: Option<Py<PyAny>>) -> u32 {
137        0
138    }
139
140    #[pyfunction]
141    fn get_world_size(_group: Option<Py<PyAny>>) -> u32 {
142        1
143    }
144
145    #[pyfunction]
146    fn is_initialized() -> bool {
147        false
148    }
149
150    #[pyfunction]
151    fn is_available() -> bool {
152        true
153    }
154
155    #[pyfunction]
156    fn barrier(_group: Option<Py<PyAny>>) -> PyResult<()> {
157        Ok(())
158    }
159
160    #[pyfunction]
161    fn all_reduce(
162        _tensor: &PyTensor,
163        _op: Option<String>,
164        _group: Option<Py<PyAny>>,
165    ) -> PyResult<()> {
166        Ok(())
167    }
168
169    #[pyfunction]
170    fn all_gather(
171        _tensor_list: Vec<PyTensor>,
172        _tensor: &PyTensor,
173        _group: Option<Py<PyAny>>,
174    ) -> PyResult<()> {
175        Ok(())
176    }
177
178    #[pyfunction]
179    fn broadcast(_tensor: &PyTensor, _src: u32, _group: Option<Py<PyAny>>) -> PyResult<()> {
180        Ok(())
181    }
182
183    #[pyfunction]
184    fn reduce(
185        _tensor: &PyTensor,
186        _dst: u32,
187        _op: Option<String>,
188        _group: Option<Py<PyAny>>,
189    ) -> PyResult<()> {
190        Ok(())
191    }
192
193    #[pyfunction]
194    fn scatter(
195        _tensor: &PyTensor,
196        _scatter_list: Option<Vec<PyTensor>>,
197        _src: u32,
198        _group: Option<Py<PyAny>>,
199    ) -> PyResult<()> {
200        Ok(())
201    }
202
203    #[pyfunction]
204    fn gather(
205        _tensor: &PyTensor,
206        _gather_list: Option<Vec<PyTensor>>,
207        _dst: u32,
208        _group: Option<Py<PyAny>>,
209    ) -> PyResult<()> {
210        Ok(())
211    }
212
213    m.add_function(wrap_pyfunction!(init_process_group, m)?)?;
214    m.add_function(wrap_pyfunction!(destroy_process_group, m)?)?;
215    m.add_function(wrap_pyfunction!(get_rank, m)?)?;
216    m.add_function(wrap_pyfunction!(get_world_size, m)?)?;
217    m.add_function(wrap_pyfunction!(is_initialized, m)?)?;
218    m.add_function(wrap_pyfunction!(is_available, m)?)?;
219    m.add_function(wrap_pyfunction!(barrier, m)?)?;
220    m.add_function(wrap_pyfunction!(all_reduce, m)?)?;
221    m.add_function(wrap_pyfunction!(all_gather, m)?)?;
222    m.add_function(wrap_pyfunction!(broadcast, m)?)?;
223    m.add_function(wrap_pyfunction!(reduce, m)?)?;
224    m.add_function(wrap_pyfunction!(scatter, m)?)?;
225    m.add_function(wrap_pyfunction!(gather, m)?)?;
226
227    Ok(())
228}