rstorch_python/
autograd.rs1use crate::{error::PyResult, py_result, tensor::PyTensor};
4use pyo3::prelude::*;
5use pyo3::types::PyAny;
6use pyo3::wrap_pyfunction;
7use pyo3::PyRefMut;
8use std::cell::RefCell;
9
10pub struct AutogradState {
12 enabled: bool,
13 anomaly_detection: bool,
14}
15
16impl AutogradState {
17 fn new() -> Self {
18 Self {
19 enabled: true,
20 anomaly_detection: false,
21 }
22 }
23
24 fn set_enabled(&mut self, enabled: bool) {
25 self.enabled = enabled;
26 }
27
28 fn is_enabled(&self) -> bool {
29 self.enabled
30 }
31
32 fn set_anomaly_detection(&mut self, enabled: bool) {
33 self.anomaly_detection = enabled;
34 }
35
36 fn is_anomaly_detection_enabled(&self) -> bool {
37 self.anomaly_detection
38 }
39}
40
41thread_local! {
43 static AUTOGRAD_STATE: RefCell<AutogradState> = RefCell::new(AutogradState::new());
44}
45
46#[pyclass(name = "no_grad")]
48pub struct PyNoGrad {
49 prev_state: bool,
50}
51
52#[pymethods]
53impl PyNoGrad {
54 #[new]
55 fn new() -> Self {
56 let prev_state = AUTOGRAD_STATE.with(|state| state.borrow().is_enabled());
57 Self { prev_state }
58 }
59
60 fn __enter__(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
61 AUTOGRAD_STATE.with(|state| {
63 slf.prev_state = state.borrow().is_enabled();
64 state.borrow_mut().set_enabled(false);
65 });
66 slf
67 }
68
69 fn __exit__(
70 slf: PyRefMut<'_, Self>,
71 _exc_type: Option<Py<PyAny>>,
72 _exc_val: Option<Py<PyAny>>,
73 _exc_tb: Option<Py<PyAny>>,
74 ) -> PyResult<bool> {
75 AUTOGRAD_STATE.with(|state| {
77 state.borrow_mut().set_enabled(slf.prev_state);
78 });
79 Ok(false)
80 }
81
82 #[staticmethod]
84 fn is_enabled() -> bool {
85 !AUTOGRAD_STATE.with(|state| state.borrow().is_enabled())
86 }
87}
88
89#[pyclass(name = "enable_grad")]
91pub struct PyEnableGrad {
92 prev_state: bool,
93}
94
95#[pymethods]
96impl PyEnableGrad {
97 #[new]
98 fn new() -> Self {
99 let prev_state = AUTOGRAD_STATE.with(|state| state.borrow().is_enabled());
100 Self { prev_state }
101 }
102
103 fn __enter__(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
104 AUTOGRAD_STATE.with(|state| {
106 slf.prev_state = state.borrow().is_enabled();
107 state.borrow_mut().set_enabled(true);
108 });
109 slf
110 }
111
112 fn __exit__(
113 slf: PyRefMut<'_, Self>,
114 _exc_type: Option<Py<PyAny>>,
115 _exc_val: Option<Py<PyAny>>,
116 _exc_tb: Option<Py<PyAny>>,
117 ) -> PyResult<bool> {
118 AUTOGRAD_STATE.with(|state| {
120 state.borrow_mut().set_enabled(slf.prev_state);
121 });
122 Ok(false)
123 }
124
125 #[staticmethod]
127 fn is_enabled() -> bool {
128 AUTOGRAD_STATE.with(|state| state.borrow().is_enabled())
129 }
130}
131
132#[pyclass(name = "set_grad_enabled")]
134pub struct PySetGradEnabled {
135 mode: bool,
136 prev_state: bool,
137}
138
139#[pymethods]
140impl PySetGradEnabled {
141 #[new]
142 fn new(mode: bool) -> Self {
143 let prev_state = AUTOGRAD_STATE.with(|state| state.borrow().is_enabled());
144 Self { mode, prev_state }
145 }
146
147 fn __enter__(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
148 AUTOGRAD_STATE.with(|state| {
150 slf.prev_state = state.borrow().is_enabled();
151 state.borrow_mut().set_enabled(slf.mode);
152 });
153 slf
154 }
155
156 fn __exit__(
157 slf: PyRefMut<'_, Self>,
158 _exc_type: Option<Py<PyAny>>,
159 _exc_val: Option<Py<PyAny>>,
160 _exc_tb: Option<Py<PyAny>>,
161 ) -> PyResult<bool> {
162 AUTOGRAD_STATE.with(|state| {
164 state.borrow_mut().set_enabled(slf.prev_state);
165 });
166 Ok(false)
167 }
168}
169
170#[pyclass(name = "detect_anomaly")]
172pub struct PyDetectAnomaly {
173 mode: bool,
174 prev_state: bool,
175}
176
177#[pymethods]
178impl PyDetectAnomaly {
179 #[new]
180 fn new(mode: bool) -> Self {
181 let prev_state = AUTOGRAD_STATE.with(|state| state.borrow().is_anomaly_detection_enabled());
182 Self { mode, prev_state }
183 }
184
185 fn __enter__(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
186 AUTOGRAD_STATE.with(|state| {
188 slf.prev_state = state.borrow().is_anomaly_detection_enabled();
189 state.borrow_mut().set_anomaly_detection(slf.mode);
190 });
191 slf
192 }
193
194 fn __exit__(
195 slf: PyRefMut<'_, Self>,
196 _exc_type: Option<Py<PyAny>>,
197 _exc_val: Option<Py<PyAny>>,
198 _exc_tb: Option<Py<PyAny>>,
199 ) -> PyResult<bool> {
200 AUTOGRAD_STATE.with(|state| {
202 state.borrow_mut().set_anomaly_detection(slf.prev_state);
203 });
204 Ok(false)
205 }
206
207 #[staticmethod]
209 fn is_enabled() -> bool {
210 AUTOGRAD_STATE.with(|state| state.borrow().is_anomaly_detection_enabled())
211 }
212}
213
214#[pyclass(name = "Function")]
216pub struct PyFunction;
217
218#[pymethods]
219impl PyFunction {
220 #[staticmethod]
221 fn apply(inputs: Vec<PyTensor>) -> PyResult<PyTensor> {
222 if inputs.is_empty() {
224 return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
225 "Function.apply requires at least one input",
226 ));
227 }
228 Ok(inputs[0].clone())
229 }
230
231 }
244
245pub struct AutogradUtils;
247
248impl AutogradUtils {
249 pub fn grad(
251 outputs: Vec<PyTensor>,
252 inputs: Vec<PyTensor>,
253 _grad_outputs: Option<Vec<Option<PyTensor>>>,
254 _retain_graph: Option<bool>,
255 _create_graph: Option<bool>,
256 _only_inputs: Option<bool>,
257 _allow_unused: Option<bool>,
258 ) -> PyResult<Vec<Option<PyTensor>>> {
259 if outputs.len() != 1 {
261 return Err(PyErr::new::<pyo3::exceptions::PyNotImplementedError, _>(
262 "Multiple outputs not yet supported",
263 ));
264 }
265
266 let output = &outputs[0];
267 py_result!(output.tensor.backward())?;
268
269 let mut grads = Vec::new();
271 for input in inputs {
272 grads.push(input.tensor.grad().map(|g| PyTensor { tensor: g }));
273 }
274
275 Ok(grads)
276 }
277}
278
279use pyo3::types::{PyModule, PyModuleMethods};
280
281pub fn register_autograd_module(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
283 m.add_class::<PyNoGrad>()?;
285 m.add_class::<PyEnableGrad>()?;
286 m.add_class::<PySetGradEnabled>()?;
287 m.add_class::<PyDetectAnomaly>()?;
288
289 m.add_class::<PyFunction>()?;
291
292 #[pyfunction]
294 fn grad(
295 outputs: Vec<PyTensor>,
296 inputs: Vec<PyTensor>,
297 grad_outputs: Option<Vec<Option<PyTensor>>>,
298 retain_graph: Option<bool>,
299 create_graph: Option<bool>,
300 only_inputs: Option<bool>,
301 allow_unused: Option<bool>,
302 ) -> PyResult<Vec<Option<PyTensor>>> {
303 AutogradUtils::grad(
304 outputs,
305 inputs,
306 grad_outputs,
307 retain_graph,
308 create_graph,
309 only_inputs,
310 allow_unused,
311 )
312 }
313
314 m.add_function(wrap_pyfunction!(grad, m)?)?;
315
316 #[pyfunction]
317 fn backward(
318 tensors: Vec<PyTensor>,
319 _grad_tensors: Option<Vec<Option<PyTensor>>>,
320 _retain_graph: Option<bool>,
321 _create_graph: Option<bool>,
322 _inputs: Option<Vec<PyTensor>>,
323 ) -> PyResult<()> {
324 for tensor in tensors {
325 py_result!(tensor.tensor.backward())?;
326 }
327 Ok(())
328 }
329
330 m.add_function(wrap_pyfunction!(backward, m)?)?;
331
332 #[pyfunction]
333 fn is_grad_enabled() -> bool {
334 AUTOGRAD_STATE.with(|state| state.borrow().is_enabled())
335 }
336
337 #[pyfunction]
338 fn set_grad_enabled(mode: bool) {
339 AUTOGRAD_STATE.with(|state| {
340 state.borrow_mut().set_enabled(mode);
341 });
342 }
343
344 #[pyfunction]
345 fn detect_anomaly(mode: Option<bool>) -> PyResult<PyDetectAnomaly> {
346 let mode = mode.unwrap_or(true);
347 Ok(PyDetectAnomaly::new(mode))
348 }
349
350 #[pyfunction]
351 fn is_anomaly_detection_enabled() -> bool {
352 AUTOGRAD_STATE.with(|state| state.borrow().is_anomaly_detection_enabled())
353 }
354
355 #[pyfunction]
356 fn set_anomaly_detection(mode: bool) {
357 AUTOGRAD_STATE.with(|state| {
358 state.borrow_mut().set_anomaly_detection(mode);
359 });
360 }
361
362 m.add_function(wrap_pyfunction!(is_grad_enabled, m)?)?;
363 m.add_function(wrap_pyfunction!(set_grad_enabled, m)?)?;
364 m.add_function(wrap_pyfunction!(detect_anomaly, m)?)?;
365 m.add_function(wrap_pyfunction!(is_anomaly_detection_enabled, m)?)?;
366 m.add_function(wrap_pyfunction!(set_anomaly_detection, m)?)?;
367
368 Ok(())
369}