1#![allow(non_camel_case_types)]
10
11pub 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
25py_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#[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 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#[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#[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 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#[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#[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#[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 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 for (dtype, scalar_type) in &dtype_map {
385 if obj.eq(dtype)? {
386 return Ok(*scalar_type);
387 }
388 }
389
390 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#[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 !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 tensor: unsafe { self.tensor.clone_unsafe() },
594 }
595 }
596
597 pub fn borrow_mut(&self) -> BorrowGuardMut {
598 BorrowGuardMut {
599 tensor: unsafe { self.tensor.clone_unsafe() },
602 }
603 }
604
605 pub fn aliases(&self, other: &TensorCell) -> bool {
606 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 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
683pub trait CloneUnsafe {
688 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#[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
725pub 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 let result = tensor_obj.call_method0("suggest_memory_format").unwrap();
812
813 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}