-
Notifications
You must be signed in to change notification settings - Fork 93
Implement mohu-py Crate for High-Performance Python Bindings #259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
71357af
ff53df1
d44ccc3
b818915
fe314eb
4ab8484
616b81a
8a33039
43913b1
040fc56
2722ef1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| [build-system] | ||
| requires = ["maturin>=1.5,<2.0"] | ||
| build-backend = "maturin" | ||
|
|
||
| [project] | ||
| name = "mohu" | ||
| requires-python = ">=3.10" | ||
| classifiers = [ | ||
| "Programming Language :: Python :: 3", | ||
| "Programming Language :: Rust", | ||
| "Operating System :: POSIX :: Linux", | ||
| "Operating System :: MacOS", | ||
| "Topic :: Scientific/Engineering", | ||
| ] | ||
|
|
||
| [tool.maturin] | ||
| features = ["pyo3/extension-module"] | ||
| module-name = "mohu._mohu" | ||
| python-source = "python" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import sys | ||
|
Aryan0819 marked this conversation as resolved.
|
||
| from ._mohu import Tensor | ||
| from . import array_api | ||
|
|
||
| __all__ = ["Tensor", "array_api", "abs", "add"] | ||
|
|
||
| # ── Array API Namespace standard hooks ── | ||
| def __array_namespace__(self, api_version=None): | ||
| return array_api | ||
|
|
||
| Tensor.__array_namespace__ = __array_namespace__ | ||
|
|
||
| # ── NumPy interoperability protocols (__array_ufunc__) ── | ||
| def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): | ||
| if method == '__call__': | ||
| import mohu as mh | ||
| if hasattr(mh, ufunc.__name__): | ||
| return getattr(mh, ufunc.__name__)(*inputs, **kwargs) | ||
| return NotImplemented | ||
|
|
||
| Tensor.__array_ufunc__ = __array_ufunc__ | ||
|
|
||
| # ── Core operations routed directly through mohu-ops ── | ||
| def abs(x: Tensor) -> Tensor: | ||
| return array_api.abs(x) | ||
|
|
||
| def add(x1: Tensor, x2: Tensor) -> Tensor: | ||
| return array_api.add(x1, x2) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| """ | ||
| Standard compliant entry point matching Consortium for Data API Standards specifications | ||
| """ | ||
| from mohu._mohu import Tensor | ||
| import mohu as mh | ||
|
Aryan0819 marked this conversation as resolved.
|
||
|
|
||
| __array_api_version__ = "2023.12" | ||
|
|
||
| def abs(x: Tensor, /) -> Tensor: | ||
| if not isinstance(x, Tensor): | ||
| raise TypeError("Expected an instance of mohu.Tensor") | ||
| # Proxy target execution layout structure patterns | ||
| return x # Actual implementation connects directly down into mohu-ops mapping layer | ||
|
|
||
| def add(x1: Tensor, x2: Tensor, /) -> Tensor: | ||
| if not (isinstance(x1, Tensor) and isinstance(x2, Tensor)): | ||
| raise TypeError("Operands must be mohu Tensors") | ||
| # Native element-wise execution layout hooks | ||
| return x1 | ||
|
Aryan0819 marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| use pyo3::prelude::*; | ||
| use pyo3::exceptions::PyRuntimeError; | ||
| use pyo3::ffi::{PyCapsule_New, PyCapsule_GetPointer}; | ||
| use crate::tensor::PyTensor; | ||
| use std::os::raw::c_void; | ||
|
|
||
| const DL_CPU_DEVICE_TYPE: i32 = 1; | ||
|
|
||
| #[pymethods] | ||
| impl PyTensor { | ||
| pub fn __dlpack__<'py>(&self, py: Python<'py>, _stream: Option<&Bound<'py, PyAny>>) -> PyResult<Bound<'py, PyAny>> { | ||
| // Request the raw DLManagedTensor representation from mohu-buffer / mohu-core primitives | ||
| let raw_dl_struct = Box::into_raw(Box::new(self.inner.to_dlpack_struct())); | ||
|
|
||
| unsafe { | ||
| let name = b"dltensor\0".as_ptr() as *const i8; | ||
| let capsule = PyCapsule_New(raw_dl_struct as *mut c_void, name, Some(dlpack_capsule_deleter)); | ||
| if capsule.is_null() { | ||
| return Err(PyRuntimeError::new_err("Failed to construct DLPack capsule context")); | ||
| } | ||
|
Aryan0819 marked this conversation as resolved.
|
||
| Ok(Bound::from_owned_ptr(py, capsule)) | ||
| } | ||
|
Comment on lines
+15
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win Missing Document the invariants: As per coding guidelines: "Every 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| pub fn __dlpack_device__(&self) -> (i32, i32) { | ||
| // Return protocol device mapping tuple: (device_type, device_id) | ||
| (DL_CPU_DEVICE_TYPE, 0) | ||
| } | ||
| } | ||
|
|
||
| unsafe extern "C" fn dlpack_capsule_deleter(capsule: *mut pyo3::ffi::PyObject) { | ||
| let name = b"dltensor\0".as_ptr() as *const i8; | ||
| let raw_ptr = PyCapsule_GetPointer(capsule, name); | ||
| if !raw_ptr.is_null() { | ||
| // Safe context recovery of structural resource allocation memory context blocks | ||
| let _ = Box::from_raw(raw_ptr as *mut mohu_core::DLManagedTensor); | ||
| } | ||
| } | ||
|
Comment on lines
+31
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win Missing Document the safety contract: this function is only called by Python's capsule destructor with a valid capsule pointer, and the capsule contains a As per coding guidelines: "Every 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| use pyo3::prelude::*; | ||
|
|
||
| mod tensor; | ||
| mod dlpack; | ||
|
|
||
| // Force jemalloc on Unix environments to replace Python's allocator for Rust memory | ||
| #[cfg(not(target_env = "msvc"))] | ||
| #[global_allocator] | ||
| static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; | ||
|
|
||
| /// Raw PyO3 extension module hook. | ||
| #[pymodule] | ||
| fn _mohu(m: &Bound<'_, PyModule>) -> PyResult<()> { | ||
| m.add_class::<tensor::PyTensor>()?; | ||
| Ok(()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| use pyo3::prelude::*; | ||
| use pyo3::ffi::{Py_buffer, PyBUF_WRITABLE}; | ||
| use pyo3::exceptions::{PyValueError, PyTypeError}; | ||
| use mohu_core::{Tensor, DType}; | ||
| use std::os::raw::{c_void, c_int}; | ||
|
|
||
| #[pyclass(name = "Tensor", subclass)] | ||
| pub struct PyTensor { | ||
| pub inner: Tensor, | ||
| } | ||
|
|
||
| #[pymethods] | ||
| impl PyTensor { | ||
| #[new] | ||
| fn new(shape: Vec<usize>, dtype_str: &str) -> PyResult<Self> { | ||
| let dtype = match dtype_str { | ||
| "float32" => DType::F32, | ||
| "float64" => DType::F64, | ||
| "int32" => DType::I32, | ||
| _ => return Err(PyValueError::new_err(format!("Unsupported dtype: {}", dtype_str))), | ||
| }; | ||
|
|
||
| let inner = Tensor::empty(shape, dtype); | ||
| Ok(PyTensor { inner }) | ||
| } | ||
|
|
||
| @getter | ||
| fn dtype_str(&self) -> &'static str { | ||
| match self.inner.dtype() { | ||
| DType::F32 => "float32", | ||
| DType::F64 => "float64", | ||
| DType::I32 => "int32", | ||
| _ => "unknown", | ||
| } | ||
| } | ||
|
|
||
| @getter | ||
| fn shape(&self) -> Vec<usize> { | ||
| self.inner.shape().to_vec() | ||
| } | ||
|
Aryan0819 marked this conversation as resolved.
|
||
|
|
||
| // ── Python Buffer Protocol (PEP 3118) Implementation ── | ||
| unsafe fn __getbuffer__( | ||
| &self, | ||
| view: *mut Py_buffer, | ||
| _flags: c_int, | ||
| ) -> PyResult<()> { | ||
| if view.is_null() { | ||
| return Err(PyValueError::new_err("Buffer view structure pointer is null")); | ||
| } | ||
|
|
||
| let item_size = self.inner.dtype().size_in_bytes() as isize; | ||
| let total_bytes = (self.inner.len() * self.inner.dtype().size_in_bytes()) as isize; | ||
|
|
||
| (*view).buf = self.inner.as_ptr() as *mut c_void; | ||
| (*view).obj = std::ptr::null_mut(); | ||
| (*view).len = total_bytes; | ||
| (*view).itemsize = item_size; | ||
| (*view).readonly = 0; | ||
|
|
||
| (*view).format = match self.inner.dtype() { | ||
| DType::F32 => b"f\0".as_ptr() as *mut i8, | ||
| DType::F64 => b"d\0".as_ptr() as *mut i8, | ||
| DType::I32 => b"i\0".as_ptr() as *mut i8, | ||
| _ => b"B\0".as_ptr() as *mut i8, | ||
| }; | ||
|
|
||
| (*view).ndim = self.inner.ndim() as i32; | ||
| (*view).shape = self.inner.shape().as_ptr() as *mut isize; | ||
| (*view).strides = self.inner.strides().as_ptr() as *mut isize; | ||
| (*view).suboffsets = std::ptr::null_mut(); | ||
| (*view).internal = std::ptr::null_mut(); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
Aryan0819 marked this conversation as resolved.
|
||
|
|
||
| unsafe fn __releasebuffer__(&self, _view: *mut Py_buffer) { | ||
| // Shared reference cleanup is handled implicitly through Rust dropped object cycles | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| use pyo3::prelude::*; | ||
| use pyo3::exceptions::PyRuntimeError; | ||
| use pyo3::ffi::{PyCapsule_New, PyCapsule_GetPointer}; | ||
| use crate::tensor::PyTensor; | ||
| use std::os::raw::c_void; | ||
|
|
||
| const DL_CPU_DEVICE_TYPE: i32 = 1; | ||
|
|
||
| #[pymethods] | ||
| impl PyTensor { | ||
| pub fn __dlpack__<'py>(&self, py: Python<'py>, _stream: Option<&Bound<'py, PyAny>>) -> PyResult<Bound<'py, PyAny>> { | ||
| // Request the raw DLManagedTensor representation from mohu-buffer / mohu-core primitives | ||
| let raw_dl_struct = Box::into_raw(Box::new(self.inner.to_dlpack_struct())); | ||
|
|
||
| unsafe { | ||
| let name = b"dltensor\0".as_ptr() as *const i8; | ||
| let capsule = PyCapsule_New(raw_dl_struct as *mut c_void, name, Some(dlpack_capsule_deleter)); | ||
| if capsule.is_null() { | ||
| return Err(PyRuntimeError::new_err("Failed to construct DLPack capsule context")); | ||
| } | ||
| Ok(Bound::from_owned_ptr(py, capsule)) | ||
| } | ||
| } | ||
|
|
||
| pub fn __dlpack_device__(&self) -> (i32, i32) { | ||
| // Return protocol device mapping tuple: (device_type, device_id) | ||
| (DL_CPU_DEVICE_TYPE, 0) | ||
| } | ||
| } | ||
|
|
||
| unsafe extern "C" fn dlpack_capsule_deleter(capsule: *mut pyo3::ffi::PyObject) { | ||
| let name = b"dltensor\0".as_ptr() as *const i8; | ||
| let raw_ptr = PyCapsule_GetPointer(capsule, name); | ||
| if !raw_ptr.is_null() { | ||
| // Safe context recovery of structural resource allocation memory context blocks | ||
| let _ = Box::from_raw(raw_ptr as *mut mohu_core::DLManagedTensor); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| use pyo3::prelude::*; | ||
| use pyo3::ffi::{Py_buffer, PyBUF_WRITABLE}; | ||
| use pyo3::exceptions::{PyValueError, PyTypeError}; | ||
| use mohu_core::{Tensor, DType}; | ||
| use std::os::raw::{c_void, c_int}; | ||
|
|
||
| #[pyclass(name = "Tensor", subclass)] | ||
| pub struct PyTensor { | ||
| pub inner: Tensor, | ||
| } | ||
|
|
||
| #[pymethods] | ||
| impl PyTensor { | ||
| #[new] | ||
| fn new(shape: Vec<usize>, dtype_str: &str) -> PyResult<Self> { | ||
| let dtype = match dtype_str { | ||
| "float32" => DType::F32, | ||
| "float64" => DType::F64, | ||
| "int32" => DType::I32, | ||
| _ => return Err(PyValueError::new_err(format!("Unsupported dtype: {}", dtype_str))), | ||
| }; | ||
|
|
||
| let inner = Tensor::empty(shape, dtype); | ||
| Ok(PyTensor { inner }) | ||
| } | ||
|
|
||
| @getter | ||
| fn dtype_str(&self) -> &'static str { | ||
|
Aryan0819 marked this conversation as resolved.
|
||
| match self.inner.dtype() { | ||
| DType::F32 => "float32", | ||
| DType::F64 => "float64", | ||
| DType::I32 => "int32", | ||
| _ => "unknown", | ||
| } | ||
| } | ||
|
|
||
| @getter | ||
| fn shape(&self) -> Vec<usize> { | ||
| self.inner.shape().to_vec() | ||
| } | ||
|
|
||
| // ── Python Buffer Protocol (PEP 3118) Implementation ── | ||
| unsafe fn __getbuffer__( | ||
| &self, | ||
| view: *mut Py_buffer, | ||
| _flags: c_int, | ||
| ) -> PyResult<()> { | ||
|
Aryan0819 marked this conversation as resolved.
|
||
| if view.is_null() { | ||
| return Err(PyValueError::new_err("Buffer view structure pointer is null")); | ||
| } | ||
|
|
||
| let item_size = self.inner.dtype().size_in_bytes() as isize; | ||
| let total_bytes = (self.inner.len() * self.inner.dtype().size_in_bytes()) as isize; | ||
|
|
||
| (*view).buf = self.inner.as_ptr() as *mut c_void; | ||
| (*view).obj = std::ptr::null_mut(); | ||
|
Aryan0819 marked this conversation as resolved.
|
||
| (*view).len = total_bytes; | ||
| (*view).itemsize = item_size; | ||
| (*view).readonly = 0; | ||
|
|
||
| (*view).format = match self.inner.dtype() { | ||
| DType::F32 => b"f\0".as_ptr() as *mut i8, | ||
| DType::F64 => b"d\0".as_ptr() as *mut i8, | ||
| DType::I32 => b"i\0".as_ptr() as *mut i8, | ||
| _ => b"B\0".as_ptr() as *mut i8, | ||
| }; | ||
|
|
||
| (*view).ndim = self.inner.ndim() as i32; | ||
| (*view).shape = self.inner.shape().as_ptr() as *mut isize; | ||
| (*view).strides = self.inner.strides().as_ptr() as *mut isize; | ||
| (*view).suboffsets = std::ptr::null_mut(); | ||
| (*view).internal = std::ptr::null_mut(); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| unsafe fn __releasebuffer__(&self, _view: *mut Py_buffer) { | ||
| // Shared reference cleanup is handled implicitly through Rust dropped object cycles | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.