Skip to main content

torch_sys2/
lib.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9#![allow(non_camel_case_types)]
10
11//! Simplified Rust bindings for libtorch C++ APIs.
12//!
13//! This is a streamlined version that only includes the functionality
14//! actually used by the monarch codebase.
15
16pub mod testing;
17
18use monarch_types::py_global;
19use pyo3::prelude::*;
20use pyo3::types::PyAny;
21use serde::Deserialize;
22use serde::Serialize;
23use thiserror::Error;
24
25// Cached imports for torch APIs
26py_global!(torch_device, "torch", "device");
27py_global!(torch_strided, "torch", "strided");
28py_global!(torch_sparse_coo, "torch", "sparse_coo");
29py_global!(torch_contiguous_format, "torch", "contiguous_format");
30py_global!(torch_preserve_format, "torch", "preserve_format");
31py_global!(torch_channels_last, "torch", "channels_last");
32py_global!(torch_channels_last_3d, "torch", "channels_last_3d");
33py_global!(torch_uint8, "torch", "uint8");
34py_global!(torch_int8, "torch", "int8");
35py_global!(torch_int16, "torch", "int16");
36py_global!(torch_int32, "torch", "int32");
37py_global!(torch_int64, "torch", "int64");
38py_global!(torch_float16, "torch", "float16");
39py_global!(torch_float32, "torch", "float32");
40py_global!(torch_float64, "torch", "float64");
41py_global!(torch_complex32, "torch", "complex32");
42py_global!(torch_complex64, "torch", "complex64");
43py_global!(torch_complex128, "torch", "complex128");
44py_global!(torch_bool, "torch", "bool");
45py_global!(torch_bfloat16, "torch", "bfloat16");
46py_global!(torch_float8_e5m2, "torch", "float8_e5m2");
47py_global!(torch_float8_e4m3fn, "torch", "float8_e4m3fn");
48py_global!(torch_zeros, "torch", "zeros");
49py_global!(torch_empty, "torch", "empty");
50py_global!(torch_tensor, "torch", "tensor");
51py_global!(torch_allclose, "torch", "allclose");
52py_global!(torch_full, "torch", "full");
53py_global!(torch_stack, "torch", "stack");
54
55// ============================================================================
56// Device Types
57// ============================================================================
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
60pub enum DeviceType {
61    CPU,
62    CUDA,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
66pub struct DeviceIndex(pub i8);
67
68impl From<DeviceIndex> for i8 {
69    fn from(idx: DeviceIndex) -> i8 {
70        idx.0
71    }
72}
73
74impl From<i8> for DeviceIndex {
75    fn from(idx: i8) -> DeviceIndex {
76        DeviceIndex(idx)
77    }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
81pub struct Device {
82    device_type: DeviceType,
83    index: Option<DeviceIndex>,
84}
85
86impl Device {
87    pub fn device_type(&self) -> DeviceType {
88        self.device_type
89    }
90}
91
92impl FromPyObject<'_, '_> for Device {
93    type Error = PyErr;
94
95    fn extract(obj: pyo3::Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
96        let obj = &*obj;
97        let device_str: String = obj.str()?.extract()?;
98        // Parse the device string
99        device_str.parse().map_err(|e: DeviceParseError| {
100            PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string())
101        })
102    }
103}
104
105impl<'py> IntoPyObject<'py> for Device {
106    type Target = PyAny;
107    type Output = Bound<'py, Self::Target>;
108    type Error = PyErr;
109
110    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
111        let device_str = self.to_string();
112        let device = torch_device(py).call1((device_str,))?;
113        Ok(device)
114    }
115}
116
117impl std::fmt::Display for Device {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self.device_type {
120            DeviceType::CPU => write!(f, "cpu"),
121            DeviceType::CUDA => {
122                if let Some(index) = self.index {
123                    write!(f, "cuda:{}", index.0)
124                } else {
125                    write!(f, "cuda")
126                }
127            }
128        }
129    }
130}
131
132impl std::str::FromStr for Device {
133    type Err = DeviceParseError;
134
135    fn from_str(s: &str) -> Result<Self, Self::Err> {
136        if s == "cpu" {
137            Ok(Device {
138                device_type: DeviceType::CPU,
139                index: None,
140            })
141        } else if s == "cuda" {
142            Ok(Device {
143                device_type: DeviceType::CUDA,
144                index: None,
145            })
146        } else if let Some(cuda_idx) = s.strip_prefix("cuda:") {
147            let index = cuda_idx
148                .parse::<i8>()
149                .map_err(|_| DeviceParseError::InvalidDevice)?;
150            Ok(Device {
151                device_type: DeviceType::CUDA,
152                index: Some(DeviceIndex(index)),
153            })
154        } else {
155            Err(DeviceParseError::InvalidDevice)
156        }
157    }
158}
159
160impl From<CudaDevice> for Device {
161    fn from(cuda_device: CudaDevice) -> Self {
162        Device {
163            device_type: DeviceType::CUDA,
164            index: Some(cuda_device.index),
165        }
166    }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
170pub struct CudaDevice {
171    index: DeviceIndex,
172}
173
174impl CudaDevice {
175    pub fn new(index: DeviceIndex) -> Self {
176        CudaDevice { index }
177    }
178
179    pub fn index(&self) -> DeviceIndex {
180        self.index
181    }
182}
183
184#[derive(Debug, Error)]
185pub enum DeviceParseError {
186    #[error("invalid device string")]
187    InvalidDevice,
188}
189
190// ============================================================================
191// Layout and Memory Format
192// ============================================================================
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
195#[repr(i32)]
196pub enum Layout {
197    Strided = 0,
198    Sparse = 1,
199    Mkldnn = 2,
200}
201
202/// Remote serde implementation for Layout
203#[derive(Serialize, Deserialize)]
204#[serde(remote = "Layout")]
205pub enum LayoutDef {
206    Strided,
207    Sparse,
208    Mkldnn,
209}
210
211impl FromPyObject<'_, '_> for Layout {
212    type Error = PyErr;
213
214    fn extract(obj: pyo3::Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
215        let obj = &*obj;
216        Python::attach(|py| {
217            let strided = torch_strided(py);
218            let sparse_coo = torch_sparse_coo(py);
219
220            if obj.eq(strided)? {
221                Ok(Layout::Strided)
222            } else if obj.eq(sparse_coo)? {
223                Ok(Layout::Sparse)
224            } else {
225                // Try to match by string representation
226                let obj_str: String = obj.str()?.extract()?;
227                match obj_str.as_str() {
228                    "torch.strided" => Ok(Layout::Strided),
229                    "torch.sparse_coo" => Ok(Layout::Sparse),
230                    _ => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
231                        "Unknown layout type",
232                    )),
233                }
234            }
235        })
236    }
237}
238
239impl<'py> IntoPyObject<'py> for Layout {
240    type Target = PyAny;
241    type Output = Bound<'py, Self::Target>;
242    type Error = PyErr;
243
244    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
245        match self {
246            Layout::Strided => Ok(torch_strided(py)),
247            Layout::Sparse => Ok(torch_sparse_coo(py)),
248            Layout::Mkldnn => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
249                "MKLDNN layout not supported in PyTorch",
250            )),
251        }
252    }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
256#[repr(i32)]
257pub enum MemoryFormat {
258    Contiguous = 0,
259    Preserve = 1,
260    ChannelsLast = 2,
261    ChannelsLast3d = 3,
262}
263
264/// Remote serde implementation for MemoryFormat
265#[derive(Serialize, Deserialize)]
266#[serde(remote = "MemoryFormat")]
267pub enum MemoryFormatDef {
268    Contiguous,
269    Preserve,
270    ChannelsLast,
271    ChannelsLast3d,
272}
273
274impl<'py> IntoPyObject<'py> for MemoryFormat {
275    type Target = PyAny;
276    type Output = Bound<'py, Self::Target>;
277    type Error = PyErr;
278
279    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
280        match self {
281            MemoryFormat::Contiguous => Ok(torch_contiguous_format(py)),
282            MemoryFormat::Preserve => Ok(torch_preserve_format(py)),
283            MemoryFormat::ChannelsLast => Ok(torch_channels_last(py)),
284            MemoryFormat::ChannelsLast3d => Ok(torch_channels_last_3d(py)),
285        }
286    }
287}
288
289// ============================================================================
290// ScalarType
291// ============================================================================
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
294#[repr(i32)]
295pub enum ScalarType {
296    Byte = 0,
297    Char = 1,
298    Short = 2,
299    Int = 3,
300    Long = 4,
301    Half = 5,
302    Float = 6,
303    Double = 7,
304    ComplexHalf = 8,
305    ComplexFloat = 9,
306    ComplexDouble = 10,
307    Bool = 11,
308    QInt8 = 12,
309    QUInt8 = 13,
310    QInt32 = 14,
311    BFloat16 = 15,
312    QUInt4x2 = 16,
313    QUInt2x4 = 17,
314    Bits1x8 = 18,
315    Bits2x4 = 19,
316    Bits4x2 = 20,
317    Bits8 = 21,
318    Bits16 = 22,
319    Float8_e5m2 = 23,
320    Float8_e4m3fn = 24,
321    Float8_e5m2fnuz = 25,
322    Float8_e4m3fnuz = 26,
323}
324
325/// Remote serde implementation for ScalarType
326#[derive(Serialize, Deserialize)]
327#[serde(remote = "ScalarType")]
328pub enum ScalarTypeDef {
329    Byte,
330    Char,
331    Short,
332    Int,
333    Long,
334    Half,
335    Float,
336    Double,
337    ComplexHalf,
338    ComplexFloat,
339    ComplexDouble,
340    Bool,
341    QInt8,
342    QUInt8,
343    QInt32,
344    BFloat16,
345    QUInt4x2,
346    QUInt2x4,
347    Bits1x8,
348    Bits2x4,
349    Bits4x2,
350    Bits8,
351    Bits16,
352    Float8_e5m2,
353    Float8_e4m3fn,
354    Float8_e5m2fnuz,
355    Float8_e4m3fnuz,
356}
357
358impl FromPyObject<'_, '_> for ScalarType {
359    type Error = PyErr;
360
361    fn extract(obj: pyo3::Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
362        let obj = &*obj;
363        Python::attach(|py| {
364            // Map of PyTorch dtype getters to ScalarType
365            let dtype_map = [
366                (torch_uint8(py), ScalarType::Byte),
367                (torch_int8(py), ScalarType::Char),
368                (torch_int16(py), ScalarType::Short),
369                (torch_int32(py), ScalarType::Int),
370                (torch_int64(py), ScalarType::Long),
371                (torch_float16(py), ScalarType::Half),
372                (torch_float32(py), ScalarType::Float),
373                (torch_float64(py), ScalarType::Double),
374                (torch_complex32(py), ScalarType::ComplexHalf),
375                (torch_complex64(py), ScalarType::ComplexFloat),
376                (torch_complex128(py), ScalarType::ComplexDouble),
377                (torch_bool(py), ScalarType::Bool),
378                (torch_bfloat16(py), ScalarType::BFloat16),
379                (torch_float8_e5m2(py), ScalarType::Float8_e5m2),
380                (torch_float8_e4m3fn(py), ScalarType::Float8_e4m3fn),
381            ];
382
383            // Try matching by equality with torch dtypes
384            for (dtype, scalar_type) in &dtype_map {
385                if obj.eq(dtype)? {
386                    return Ok(*scalar_type);
387                }
388            }
389
390            // Try matching by string representation
391            let obj_str: String = obj.str()?.extract()?;
392            let str_map = [
393                ("uint8", ScalarType::Byte),
394                ("int8", ScalarType::Char),
395                ("int16", ScalarType::Short),
396                ("int32", ScalarType::Int),
397                ("int64", ScalarType::Long),
398                ("float16", ScalarType::Half),
399                ("float32", ScalarType::Float),
400                ("float64", ScalarType::Double),
401                ("complex32", ScalarType::ComplexHalf),
402                ("complex64", ScalarType::ComplexFloat),
403                ("complex128", ScalarType::ComplexDouble),
404                ("bool", ScalarType::Bool),
405                ("bfloat16", ScalarType::BFloat16),
406                ("float8_e5m2", ScalarType::Float8_e5m2),
407                ("float8_e4m3fn", ScalarType::Float8_e4m3fn),
408            ];
409
410            for (name, scalar_type) in &str_map {
411                if obj_str.contains(name) {
412                    return Ok(*scalar_type);
413                }
414            }
415
416            Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
417                "Unknown scalar type: {}",
418                obj_str
419            )))
420        })
421    }
422}
423
424impl<'py> IntoPyObject<'py> for ScalarType {
425    type Target = PyAny;
426    type Output = Bound<'py, Self::Target>;
427    type Error = PyErr;
428
429    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
430        match self {
431            ScalarType::Byte => Ok(torch_uint8(py)),
432            ScalarType::Char => Ok(torch_int8(py)),
433            ScalarType::Short => Ok(torch_int16(py)),
434            ScalarType::Int => Ok(torch_int32(py)),
435            ScalarType::Long => Ok(torch_int64(py)),
436            ScalarType::Half => Ok(torch_float16(py)),
437            ScalarType::Float => Ok(torch_float32(py)),
438            ScalarType::Double => Ok(torch_float64(py)),
439            ScalarType::ComplexHalf => Ok(torch_complex32(py)),
440            ScalarType::ComplexFloat => Ok(torch_complex64(py)),
441            ScalarType::ComplexDouble => Ok(torch_complex128(py)),
442            ScalarType::Bool => Ok(torch_bool(py)),
443            ScalarType::BFloat16 => Ok(torch_bfloat16(py)),
444            ScalarType::Float8_e5m2 => Ok(torch_float8_e5m2(py)),
445            ScalarType::Float8_e4m3fn => Ok(torch_float8_e4m3fn(py)),
446            _ => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
447                "Unsupported scalar type: {:?}",
448                self
449            ))),
450        }
451    }
452}
453
454// ============================================================================
455// Tensor and TensorCell
456// ============================================================================
457
458#[derive(Debug)]
459pub struct Tensor {
460    inner: Py<PyAny>,
461}
462
463impl Clone for Tensor {
464    fn clone(&self) -> Self {
465        Python::attach(|py| Tensor {
466            inner: self.inner.clone_ref(py),
467        })
468    }
469}
470
471impl Tensor {
472    pub fn scalar_type(&self) -> ScalarType {
473        Python::attach(|py| {
474            let tensor = self.inner.bind(py);
475            let dtype = tensor.getattr("dtype").unwrap();
476            dtype.extract::<ScalarType>().unwrap()
477        })
478    }
479
480    pub fn device(&self) -> Device {
481        Python::attach(|py| {
482            let tensor = self.inner.bind(py);
483            let device = tensor.getattr("device").unwrap();
484            device.extract::<Device>().unwrap()
485        })
486    }
487
488    pub fn numel(&self) -> i64 {
489        Python::attach(|py| {
490            let tensor = self.inner.bind(py);
491            tensor.call_method0("numel").unwrap().extract().unwrap()
492        })
493    }
494
495    pub fn data_ptr(&self) -> *const std::ffi::c_void {
496        Python::attach(|py| {
497            let tensor = self.inner.bind(py);
498            let ptr: usize = tensor.call_method0("data_ptr").unwrap().extract().unwrap();
499            ptr as *const std::ffi::c_void
500        })
501    }
502
503    pub fn mut_data_ptr(&self) -> *mut std::ffi::c_void {
504        self.data_ptr() as *mut std::ffi::c_void
505    }
506
507    pub fn defined(&self) -> bool {
508        Python::attach(|py| {
509            let tensor = self.inner.bind(py);
510            // A tensor is defined if it's not None and has storage
511            !tensor.is_none()
512        })
513    }
514
515    pub fn is_cuda(&self) -> bool {
516        Python::attach(|py| {
517            let tensor = self.inner.bind(py);
518            tensor.getattr("is_cuda").unwrap().extract().unwrap()
519        })
520    }
521
522    pub fn is_sparse(&self) -> bool {
523        Python::attach(|py| {
524            let tensor = self.inner.bind(py);
525            tensor.getattr("is_sparse").unwrap().extract().unwrap()
526        })
527    }
528
529    pub fn is_contiguous(&self) -> bool {
530        Python::attach(|py| {
531            let tensor = self.inner.bind(py);
532            tensor
533                .call_method0("is_contiguous")
534                .unwrap()
535                .extract()
536                .unwrap()
537        })
538    }
539
540    pub fn nbytes(&self) -> i64 {
541        Python::attach(|py| {
542            let tensor = self.inner.bind(py);
543            tensor.getattr("nbytes").unwrap().extract().unwrap()
544        })
545    }
546
547    pub fn sizes(&self) -> Vec<i64> {
548        Python::attach(|py| {
549            let tensor = self.inner.bind(py);
550            let size = tensor.call_method0("size").unwrap();
551            size.try_iter()
552                .unwrap()
553                .map(|x| x.unwrap().extract().unwrap())
554                .collect()
555        })
556    }
557}
558
559impl pyo3::FromPyObject<'_, '_> for Tensor {
560    type Error = pyo3::PyErr;
561
562    fn extract(obj: pyo3::Borrowed<'_, '_, pyo3::PyAny>) -> pyo3::PyResult<Self> {
563        Ok(Tensor {
564            inner: pyo3::Bound::clone(&obj).unbind(),
565        })
566    }
567}
568
569impl<'py> IntoPyObject<'py> for Tensor {
570    type Target = PyAny;
571    type Output = Bound<'py, Self::Target>;
572    type Error = PyErr;
573
574    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
575        Ok(self.inner.bind(py).clone())
576    }
577}
578
579#[derive(Debug, Clone)]
580pub struct TensorCell {
581    tensor: Tensor,
582}
583
584impl TensorCell {
585    pub fn new(tensor: Tensor) -> Self {
586        TensorCell { tensor }
587    }
588
589    pub fn borrow(&self) -> BorrowGuard {
590        BorrowGuard {
591            // SAFETY: TensorCell owns the tensor and the returned BorrowGuard
592            // maintains proper ownership semantics by holding a clone.
593            tensor: unsafe { self.tensor.clone_unsafe() },
594        }
595    }
596
597    pub fn borrow_mut(&self) -> BorrowGuardMut {
598        BorrowGuardMut {
599            // SAFETY: TensorCell owns the tensor and the returned BorrowGuardMut
600            // maintains proper ownership semantics by holding a clone.
601            tensor: unsafe { self.tensor.clone_unsafe() },
602        }
603    }
604
605    pub fn aliases(&self, other: &TensorCell) -> bool {
606        // Check if two tensors share the same underlying storage
607        Python::attach(|_py| {
608            let self_ptr = self.tensor.data_ptr();
609            let other_ptr = other.tensor.data_ptr();
610            self_ptr == other_ptr && !self_ptr.is_null()
611        })
612    }
613
614    /// # Safety
615    /// Caller must ensure that the TensorCell is borrowed appropriately
616    pub unsafe fn get_unchecked(&self) -> &Tensor {
617        &self.tensor
618    }
619
620    pub fn try_borrow(&self) -> Result<BorrowGuard, BorrowError> {
621        Ok(self.borrow())
622    }
623
624    pub fn try_borrow_mut(&self) -> Result<BorrowGuardMut, BorrowError> {
625        Ok(self.borrow_mut())
626    }
627
628    pub fn try_cpu(&self) -> Result<TensorCell, BorrowError> {
629        Python::attach(|py| {
630            let tensor = self.tensor.inner.bind(py);
631            let cpu_tensor = tensor
632                .call_method0("cpu")
633                .map_err(|_| BorrowError::BorrowError)?;
634            Ok(TensorCell::new(Tensor {
635                inner: cpu_tensor.clone().unbind(),
636            }))
637        })
638    }
639}
640
641#[derive(Debug, Clone)]
642pub struct BorrowGuard {
643    tensor: Tensor,
644}
645
646impl std::ops::Deref for BorrowGuard {
647    type Target = Tensor;
648
649    fn deref(&self) -> &Self::Target {
650        &self.tensor
651    }
652}
653
654#[derive(Debug, Clone)]
655pub struct BorrowGuardMut {
656    tensor: Tensor,
657}
658
659impl std::ops::Deref for BorrowGuardMut {
660    type Target = Tensor;
661
662    fn deref(&self) -> &Self::Target {
663        &self.tensor
664    }
665}
666
667impl std::ops::DerefMut for BorrowGuardMut {
668    fn deref_mut(&mut self) -> &mut Self::Target {
669        &mut self.tensor
670    }
671}
672
673impl BorrowGuardMut {
674    pub fn copy_(&mut self, src: &Tensor) {
675        Python::attach(|py| {
676            let dst_tensor = self.tensor.inner.bind(py);
677            let src_tensor = src.inner.bind(py);
678            dst_tensor.call_method1("copy_", (src_tensor,)).unwrap();
679        })
680    }
681}
682
683// ============================================================================
684// CloneUnsafe trait
685// ============================================================================
686
687pub trait CloneUnsafe {
688    /// # Safety
689    /// Caller must ensure proper ownership semantics
690    unsafe fn clone_unsafe(&self) -> Self;
691}
692
693impl CloneUnsafe for Tensor {
694    unsafe fn clone_unsafe(&self) -> Self {
695        self.clone()
696    }
697}
698
699// ============================================================================
700// Borrow errors
701// ============================================================================
702
703#[derive(Debug, Error)]
704pub enum BorrowError {
705    #[error("borrow error")]
706    BorrowError,
707}
708
709#[derive(Debug, Clone, Copy)]
710pub enum BorrowType {
711    Shared,
712    Exclusive,
713}
714
715#[derive(Debug)]
716pub struct Borrow {
717    _private: (),
718}
719
720#[derive(Debug)]
721pub struct MultiBorrow {
722    _private: (),
723}
724
725// ============================================================================
726// Factory functions
727// ============================================================================
728
729pub fn factory_zeros(size: &[i64], dtype: ScalarType, layout: Layout, device: Device) -> Tensor {
730    Python::attach(|py| {
731        let size_tuple = pyo3::types::PyTuple::new(py, size).unwrap();
732        let dtype_obj = dtype.into_pyobject(py).unwrap();
733        let device_obj = device.into_pyobject(py).unwrap();
734        let layout_obj = layout.into_pyobject(py).unwrap();
735
736        let kwargs = pyo3::types::PyDict::new(py);
737        kwargs.set_item("dtype", dtype_obj).unwrap();
738        kwargs.set_item("device", device_obj).unwrap();
739        kwargs.set_item("layout", layout_obj).unwrap();
740
741        let result = torch_zeros(py).call((size_tuple,), Some(&kwargs)).unwrap();
742
743        Tensor {
744            inner: result.clone().unbind(),
745        }
746    })
747}
748
749pub fn factory_empty(size: &[i64], dtype: ScalarType, layout: Layout, device: Device) -> Tensor {
750    Python::attach(|py| {
751        let size_tuple = pyo3::types::PyTuple::new(py, size).unwrap();
752        let dtype_obj = dtype.into_pyobject(py).unwrap();
753        let device_obj = device.into_pyobject(py).unwrap();
754        let layout_obj = layout.into_pyobject(py).unwrap();
755
756        let kwargs = pyo3::types::PyDict::new(py);
757        kwargs.set_item("dtype", dtype_obj).unwrap();
758        kwargs.set_item("device", device_obj).unwrap();
759        kwargs.set_item("layout", layout_obj).unwrap();
760
761        let result = torch_empty(py).call((size_tuple,), Some(&kwargs)).unwrap();
762
763        Tensor {
764            inner: result.clone().unbind(),
765        }
766    })
767}
768
769pub fn factory_float_tensor(data: &[f32], device: Device) -> Tensor {
770    Python::attach(|py| {
771        let data_list = pyo3::types::PyList::new(py, data).unwrap();
772        let device_obj = device.into_pyobject(py).unwrap();
773
774        let kwargs = pyo3::types::PyDict::new(py);
775        kwargs.set_item("device", device_obj).unwrap();
776        kwargs.set_item("dtype", torch_float32(py)).unwrap();
777
778        let result = torch_tensor(py).call((data_list,), Some(&kwargs)).unwrap();
779
780        Tensor {
781            inner: result.clone().unbind(),
782        }
783    })
784}
785
786pub fn deep_clone(tensor: &Tensor) -> Tensor {
787    Python::attach(|py| {
788        let tensor_obj = tensor.inner.bind(py);
789        let cloned = tensor_obj.call_method0("clone").unwrap();
790        Tensor {
791            inner: cloned.clone().unbind(),
792        }
793    })
794}
795
796pub fn is_float8_type(scalar_type: ScalarType) -> bool {
797    matches!(
798        scalar_type,
799        ScalarType::Float8_e5m2
800            | ScalarType::Float8_e4m3fn
801            | ScalarType::Float8_e5m2fnuz
802            | ScalarType::Float8_e4m3fnuz
803    )
804}
805
806pub fn suggest_memory_format(tensor: &Tensor) -> MemoryFormat {
807    Python::attach(|py| {
808        let tensor_obj = tensor.inner.bind(py);
809
810        // Call suggest_memory_format method on the tensor
811        let result = tensor_obj.call_method0("suggest_memory_format").unwrap();
812
813        // Convert the result back to our enum
814        let result_str: String = result.str().unwrap().extract().unwrap();
815
816        if result_str.contains("channels_last_3d") {
817            MemoryFormat::ChannelsLast3d
818        } else if result_str.contains("channels_last") {
819            MemoryFormat::ChannelsLast
820        } else if result_str.contains("preserve") {
821            MemoryFormat::Preserve
822        } else {
823            MemoryFormat::Contiguous
824        }
825    })
826}