mtl-oxide 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jasononono
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: mtl-oxide
3
+ Version: 0.1.0
4
+ Summary: An array computing library optimized natively with the MacOS GPU.
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Dynamic: license-file
9
+
10
+ # Oxide
11
+
12
+ My attempt at building a **GPU-accelerated array computing library** to learn many things in the process.
13
+
14
+ **Oxide** functions similarly to [Numpy](https://numpy.org/), built exclusively to compute efficiently on the **macOS GPU** using [Metal](https://developer.apple.com/metal/).
15
+
16
+ The backend of Oxide is written entirely in **C/C++**, linked to Python interface via [nanobind](https://github.com/wjakob/nanobind/).
17
+
18
+ # Usage
19
+
20
+ Oxide currently supports multiple data types, basic arithmetic, and shape manipulation, all optimized with array views and batch processing.
21
+
22
+ [Documentation](https://github.com/jasononono/oxide/tree/main/docs)\
23
+ [Code Explanation & Notes](https://github.com/jasononono/oxide/blob/main/docs/DEV.md)
24
+
25
+ # Platform & Installation
26
+
27
+ Oxide is a **macOS-exclusive library** (your device must also have a gpu because I haven't implement a cpu fallback yet)
28
+
29
+ Run this command to install the latest [PyPI](https://pypi.org/project/mtl-oxide) distribution:
30
+
31
+ ```
32
+ pip install mtl-oxide
33
+ ```
34
+
35
+ # Features
36
+
37
+ - GPU accelerated *Tensor* class using MTL::ResourceStorageModeShared
38
+ - automatic Tensor memory management (alloc, free, and optimization)
39
+ - memory safe c++ backend (no leaks detected)
40
+ - GPU pseudo-random generator (xorshift currently a little scuffed, ill replace it soon)
41
+
42
+ # Inspirations
43
+ Parsing through these awesome libraries helped me setup Metal, design the file system, and understand the complicated shader syntax!
44
+
45
+ - [numpy](https://numpy.org/): most oxide function names corresponds to numpy
46
+ - [torch](https://pytorch.org/): benchmarking alongside numpy
47
+ - [forge](https://github.com/kellen-sun/Forge/): Metal library setup reference
48
+
49
+ # Contributing
50
+ Oxide is my personal learning project, but feel free to open an issue if you have any suggestions!
@@ -0,0 +1,41 @@
1
+ # Oxide
2
+
3
+ My attempt at building a **GPU-accelerated array computing library** to learn many things in the process.
4
+
5
+ **Oxide** functions similarly to [Numpy](https://numpy.org/), built exclusively to compute efficiently on the **macOS GPU** using [Metal](https://developer.apple.com/metal/).
6
+
7
+ The backend of Oxide is written entirely in **C/C++**, linked to Python interface via [nanobind](https://github.com/wjakob/nanobind/).
8
+
9
+ # Usage
10
+
11
+ Oxide currently supports multiple data types, basic arithmetic, and shape manipulation, all optimized with array views and batch processing.
12
+
13
+ [Documentation](https://github.com/jasononono/oxide/tree/main/docs)\
14
+ [Code Explanation & Notes](https://github.com/jasononono/oxide/blob/main/docs/DEV.md)
15
+
16
+ # Platform & Installation
17
+
18
+ Oxide is a **macOS-exclusive library** (your device must also have a gpu because I haven't implement a cpu fallback yet)
19
+
20
+ Run this command to install the latest [PyPI](https://pypi.org/project/mtl-oxide) distribution:
21
+
22
+ ```
23
+ pip install mtl-oxide
24
+ ```
25
+
26
+ # Features
27
+
28
+ - GPU accelerated *Tensor* class using MTL::ResourceStorageModeShared
29
+ - automatic Tensor memory management (alloc, free, and optimization)
30
+ - memory safe c++ backend (no leaks detected)
31
+ - GPU pseudo-random generator (xorshift currently a little scuffed, ill replace it soon)
32
+
33
+ # Inspirations
34
+ Parsing through these awesome libraries helped me setup Metal, design the file system, and understand the complicated shader syntax!
35
+
36
+ - [numpy](https://numpy.org/): most oxide function names corresponds to numpy
37
+ - [torch](https://pytorch.org/): benchmarking alongside numpy
38
+ - [forge](https://github.com/kellen-sun/Forge/): Metal library setup reference
39
+
40
+ # Contributing
41
+ Oxide is my personal learning project, but feel free to open an issue if you have any suggestions!
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools>=82", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mtl-oxide"
7
+ version = "0.1.0"
8
+ description = "An array computing library optimized natively with the MacOS GPU."
9
+ readme = "README.md"
10
+ requires-python = ">=3.13"
11
+ dependencies = [
12
+
13
+ ]
14
+
15
+ [tool.setuptools]
16
+ package-dir = {"" = "python"}
17
+ packages = ["oxide"]
18
+
19
+ [tool.setuptools.package-data]
20
+ "oxide" = ["*.so"]
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: mtl-oxide
3
+ Version: 0.1.0
4
+ Summary: An array computing library optimized natively with the MacOS GPU.
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Dynamic: license-file
9
+
10
+ # Oxide
11
+
12
+ My attempt at building a **GPU-accelerated array computing library** to learn many things in the process.
13
+
14
+ **Oxide** functions similarly to [Numpy](https://numpy.org/), built exclusively to compute efficiently on the **macOS GPU** using [Metal](https://developer.apple.com/metal/).
15
+
16
+ The backend of Oxide is written entirely in **C/C++**, linked to Python interface via [nanobind](https://github.com/wjakob/nanobind/).
17
+
18
+ # Usage
19
+
20
+ Oxide currently supports multiple data types, basic arithmetic, and shape manipulation, all optimized with array views and batch processing.
21
+
22
+ [Documentation](https://github.com/jasononono/oxide/tree/main/docs)\
23
+ [Code Explanation & Notes](https://github.com/jasononono/oxide/blob/main/docs/DEV.md)
24
+
25
+ # Platform & Installation
26
+
27
+ Oxide is a **macOS-exclusive library** (your device must also have a gpu because I haven't implement a cpu fallback yet)
28
+
29
+ Run this command to install the latest [PyPI](https://pypi.org/project/mtl-oxide) distribution:
30
+
31
+ ```
32
+ pip install mtl-oxide
33
+ ```
34
+
35
+ # Features
36
+
37
+ - GPU accelerated *Tensor* class using MTL::ResourceStorageModeShared
38
+ - automatic Tensor memory management (alloc, free, and optimization)
39
+ - memory safe c++ backend (no leaks detected)
40
+ - GPU pseudo-random generator (xorshift currently a little scuffed, ill replace it soon)
41
+
42
+ # Inspirations
43
+ Parsing through these awesome libraries helped me setup Metal, design the file system, and understand the complicated shader syntax!
44
+
45
+ - [numpy](https://numpy.org/): most oxide function names corresponds to numpy
46
+ - [torch](https://pytorch.org/): benchmarking alongside numpy
47
+ - [forge](https://github.com/kellen-sun/Forge/): Metal library setup reference
48
+
49
+ # Contributing
50
+ Oxide is my personal learning project, but feel free to open an issue if you have any suggestions!
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ python/mtl_oxide.egg-info/PKG-INFO
5
+ python/mtl_oxide.egg-info/SOURCES.txt
6
+ python/mtl_oxide.egg-info/dependency_links.txt
7
+ python/mtl_oxide.egg-info/top_level.txt
8
+ python/oxide/__init__.py
9
+ python/oxide/arithmetic.py
10
+ python/oxide/common.py
11
+ python/oxide/core.cpython-313-darwin.so
12
+ python/oxide/create.py
13
+ python/oxide/system.py
14
+ python/oxide/tensorclass.py
15
+ python/oxide/util.py
@@ -0,0 +1,18 @@
1
+ """
2
+ __INIT__.PY
3
+
4
+ module init
5
+ """
6
+
7
+
8
+ from .common import *
9
+ from . import system
10
+ from . import util
11
+ from .tensorclass import *
12
+ from .arithmetic import *
13
+ from .create import *
14
+ from . import core
15
+
16
+
17
+ # these module references are stored at runtime to avoid circular import
18
+ system.operand_module = arithmetic
@@ -0,0 +1,49 @@
1
+ """
2
+ ARITHMETIC.PY
3
+
4
+ tensor arithmetic functions
5
+ """
6
+
7
+
8
+ from .common import *
9
+ from . import system
10
+ from . import util
11
+ from .tensorclass import *
12
+
13
+
14
+ # binary operations (e.g. out = a + b)
15
+
16
+ def add(a, b):
17
+ dtype = util.check_tensors(a, b)
18
+ return Tensor(system.run(with_type(dtype, "add"), system.dispatcher, a.ctensor, b.ctensor))
19
+
20
+ def sub(a, b):
21
+ dtype = util.check_tensors(a, b)
22
+ return Tensor(system.run(with_type(dtype, "sub"), system.dispatcher, a.ctensor, b.ctensor))
23
+
24
+ def mul(a, b):
25
+ dtype = util.check_tensors(a, b)
26
+ return Tensor(system.run(with_type(dtype, "mul"), system.dispatcher, a.ctensor, b.ctensor))
27
+
28
+ def div(a, b):
29
+ dtype = util.check_tensors(a, b)
30
+ return Tensor(system.run(with_type(dtype, "div"), system.dispatcher, a.ctensor, b.ctensor))
31
+
32
+
33
+ # inplace unary operations (e.g. a += b)
34
+
35
+ def uadd(a, b):
36
+ dtype = util.check_tensors(a, b)
37
+ return Tensor(system.run(with_type(dtype, "uadd"), system.dispatcher, a.ctensor, b.ctensor))
38
+
39
+ def usub(a, b):
40
+ dtype = util.check_tensors(a, b)
41
+ return Tensor(system.run(with_type(dtype, "usub"), system.dispatcher, a.ctensor, b.ctensor))
42
+
43
+ def umul(a, b):
44
+ dtype = util.check_tensors(a, b)
45
+ return Tensor(system.run(with_type(dtype, "umul"), system.dispatcher, a.ctensor, b.ctensor))
46
+
47
+ def udiv(a, b):
48
+ dtype = util.check_tensors(a, b)
49
+ return Tensor(system.run(with_type(dtype, "udiv"), system.dispatcher, a.ctensor, b.ctensor))
@@ -0,0 +1,44 @@
1
+ """
2
+ COMMON.PY
3
+
4
+ misc inline functions, constants, and types (parallel to common.hpp)
5
+ """
6
+
7
+
8
+ class OxideError(BaseException): pass
9
+
10
+ # a clearer inheritance hierarchy is used here to make type checking & python type conversion easier
11
+ class oxide_t: pass
12
+ class numeric_t(oxide_t): pass
13
+ class int_t(numeric_t): pass
14
+ class float_t(numeric_t): pass
15
+ class int32(int_t): pass
16
+ class float32(float_t): pass
17
+
18
+
19
+ MAXDIMS = 16
20
+ MAXSEEDF = float(4294967295.0)
21
+ CACHETHRESHOLD = 1024 * 16
22
+
23
+
24
+ def with_type(dtype, name):
25
+ return name + "_" + dtype.__name__
26
+
27
+ # convert oxide data into python type
28
+ def pycast(dtype, data):
29
+ if isinstance(OX_T[dtype], type):
30
+ return OX_T[dtype](data)
31
+ return OX_T[dtype][0](data)
32
+
33
+ def ansi(code, str):
34
+ return f"\x1b[{code}m{str}\x1b[0m"
35
+
36
+
37
+ PY_T = { # corresponding oxide dtype of python types
38
+ int: int32,
39
+ float: float32
40
+ }
41
+ OX_T = { # corresponding python type of oxide dtypes (if a tuple is provided, dtype conversion will prefer the first element)
42
+ int32: int,
43
+ float32: (float, int)
44
+ }
@@ -0,0 +1,53 @@
1
+ """
2
+ ARITHMETIC.PY
3
+
4
+ tensor initialization, creation, and randomize functions
5
+ """
6
+
7
+
8
+ from .common import *
9
+ from . import system
10
+ from . import util
11
+ from .tensorclass import *
12
+
13
+
14
+ # generate float32 tensor randomly filled with elements from range [0, 1)
15
+ def rand(shape):
16
+ shape = util.check_shape(shape)
17
+ return Tensor(system.run("rand", system.dispatcher, shape))
18
+
19
+ # generate tensor randomly filled with elements from range [a, b]
20
+ def random(shape, a = 0, b = 1, dtype = float32):
21
+ shape = util.check_shape(shape)
22
+ util.check_dtype(dtype, numeric_t)
23
+ util.check_args(dtype, a, b)
24
+
25
+ return Tensor(system.run(with_type(dtype, "random"), system.dispatcher, shape, a, b))
26
+
27
+ # generate tensor filled with value
28
+ def filled(shape, value, dtype = float32):
29
+ shape = util.check_shape(shape)
30
+ util.check_dtype(dtype, oxide_t)
31
+ util.check_args(dtype, value)
32
+
33
+ return Tensor(system.run(with_type(dtype, "filled"), system.backend, shape, value))
34
+
35
+ # generate tensor filled with 0s
36
+ def zeros(shape, dtype = float32):
37
+ shape = util.check_shape(shape)
38
+ util.check_dtype(dtype, numeric_t)
39
+
40
+ return Tensor(system.run(with_type(dtype, "zeros"), system.backend, shape))
41
+
42
+ # generate tensor filled with 1s
43
+ def ones(shape, dtype = float32):
44
+ shape = util.check_shape(shape)
45
+ util.check_dtype(dtype, numeric_t)
46
+
47
+ return Tensor(system.run(with_type(dtype, "ones"), system.backend, shape))
48
+
49
+
50
+ # move this to viewmanip.py when implemented
51
+
52
+ # def transpose(view, order):
53
+ # return Tensor(system.run(core.transpose, view.ctensor, order))
@@ -0,0 +1,35 @@
1
+ """
2
+ SYSTEM.PY
3
+
4
+ contains module references, cpp backend (core) call mechanics
5
+ """
6
+
7
+
8
+ from .common import *
9
+ from . import core
10
+ import atexit
11
+
12
+
13
+ backend = core.Backend()
14
+ dispatcher = core.Dispatcher(backend)
15
+
16
+ operand_module = None # reference assigned at runtime to avoid circular import
17
+
18
+
19
+ # run cpp function by name
20
+ def run(function, *args, **kwargs):
21
+ core.mem_optimize(backend)
22
+ try:
23
+ return getattr(core, function)(*args, **kwargs)
24
+ except core.oxide_error as e:
25
+ throw(str(e)) # convert core.oxide_error into the equivalent python class
26
+ except BaseException as e:
27
+ raise e
28
+
29
+ def throw(msg):
30
+ raise OxideError(msg)
31
+
32
+
33
+ @atexit.register # free backend upon program exit
34
+ def free():
35
+ core.free_backend(backend)
@@ -0,0 +1,165 @@
1
+ """
2
+ TENSORCLASS.PY
3
+
4
+ tensor class definition and conversion functions
5
+ """
6
+
7
+
8
+ from .common import *
9
+ from . import system
10
+ from . import core
11
+ import typing
12
+
13
+
14
+ class Tensor:
15
+
16
+ def __init__(self, ctensor):
17
+ super().__setattr__("ctensor", ctensor)
18
+ # match class name with dtype
19
+ for t in OX_T:
20
+ ct = with_type(t, "TensorView")
21
+ if ct == type(ctensor).__name__:
22
+ super().__setattr__("dtype", t)
23
+ break
24
+ else:
25
+ system.throw("ctensor type invalid")
26
+
27
+ def __getitem__(self, key):
28
+ if isinstance(key, int):
29
+ return self.ctensor.get_element([key])
30
+ elif isinstance(key, typing.Iterable):
31
+ return self.ctensor.get_element(list(key))
32
+ else:
33
+ system.throw(f"tensor cannot be indexed with '{type(key).__name__}'")
34
+
35
+ def __setitem__(self, key, value):
36
+ if isinstance(key, int):
37
+ self.ctensor.set_element([key], value)
38
+ elif isinstance(key, typing.Iterable):
39
+ self.ctensor.set_element(list(key), value)
40
+ else:
41
+ system.throw(f"tensor cannot be indexed with '{type(key).__name__}'")
42
+
43
+ def __getattr__(self, key):
44
+ if key == "ndim":
45
+ return self.ctensor.get_ndim()
46
+ if key == "size":
47
+ return self.ctensor.get_size()
48
+ if key == "offset":
49
+ return self.ctensor.get_offset()
50
+ if key == "shape":
51
+ return self.ctensor.get_shape().copy()
52
+ if key == "strides":
53
+ return self.ctensor.get_strides().copy()
54
+ if key == "dtype":
55
+ return self.dtype
56
+
57
+ system.throw(f"tensor attribute '{key}' does not exist")
58
+
59
+ def __setattr__(self, key, value):
60
+ system.throw("tensor attributes are immutable")
61
+
62
+ def __str__(self):
63
+ return self.ctensor.get_string()
64
+
65
+ def __add__(self, other):
66
+ return system.operand_module.add(self, other)
67
+
68
+ def __sub__(self, other):
69
+ return system.operand_module.sub(self, other)
70
+
71
+ def __mul__(self, other):
72
+ return system.operand_module.mul(self, other)
73
+
74
+ def __truediv__(self, other):
75
+ return system.operand_module.div(self, other)
76
+
77
+
78
+ # convert oxide tensor into python list
79
+ def topy(tensor):
80
+
81
+ def generate(depth = 0): # generate an empty list with the tensor's shape
82
+ if depth == tensor.ndim:
83
+ return 0
84
+ arr = [generate(depth + 1) for _ in range(tensor.shape[depth])]
85
+ return arr
86
+
87
+ arr = generate()
88
+
89
+ arr_tree = [] # current dfs stack of list references
90
+ ref = arr
91
+ while ref != 0:
92
+ arr_tree.append(ref)
93
+ ref = ref[0]
94
+
95
+ indices = [0] * tensor.ndim
96
+
97
+ # dfs traversal, similar implementation to get_string() function in tensor.cpp
98
+ while len(indices) > 0:
99
+ arr_tree[-1][indices[-1]] = pycast(tensor.dtype, tensor[indices])
100
+
101
+ while len(indices) > 0 and indices[-1] == tensor.shape[len(indices) - 1] - 1:
102
+ indices.pop(-1)
103
+ arr_tree.pop(-1)
104
+
105
+ if len(indices) == 0:
106
+ break
107
+
108
+ indices[-1] += 1
109
+ if len(indices) != tensor.ndim:
110
+ # append new ref and recursively append its children
111
+ arr_tree.append(arr_tree[-1][indices[-1]])
112
+ indices.append(0)
113
+ while len(indices) < tensor.ndim:
114
+ arr_tree.append(arr_tree[-1][0])
115
+ indices.append(0)
116
+
117
+ return arr
118
+
119
+
120
+ ############# OUTDATED FUNCTION(s)
121
+
122
+ # dfs helper function
123
+ def parse_iterable(depth, first, shape, stack, iterable, result):
124
+ if not isinstance(iterable, typing.Iterable):
125
+ # constant must be able to be convert into a valid oxide dtype
126
+ for t in PY_T.keys():
127
+ if isinstance(iterable, t):
128
+ result.append(iterable)
129
+ return PY_T[t]
130
+ system.throw(f"item of type '{type(iterable).__name__}' cannot be stored in a tensor")
131
+
132
+ t = None
133
+ if len(iterable) == 0:
134
+ system.throw("empty iterable cannot be a part of tensor")
135
+
136
+ for i in iterable:
137
+ stack.append(len(iterable))
138
+ if len(stack) > len(shape):
139
+ if first: # shape is invalid if the maximum encountered depth increases at a position other than the first
140
+ shape.append(stack[depth])
141
+ else:
142
+ system.throw("depth within iterable does not match")
143
+ elif stack[depth] != shape[depth]:
144
+ system.throw("dimensions do not match")
145
+
146
+ # types must be univariant
147
+ t_new = parse_iterable(depth + 1, first, shape, stack, i, result)
148
+ if t and t_new is not t:
149
+ system.throw("tensor can only store items of similar types")
150
+ t = t_new
151
+
152
+ stack.pop()
153
+ first = False
154
+
155
+ return t
156
+
157
+ # create an oxide tensor from python iterable
158
+ def tensor(iterable):
159
+ data = [] # accumulate linear buffer from iterable
160
+ shape = []
161
+ t = parse_iterable(0, True, shape, [], iterable, data)
162
+ ctensor = system.run(with_type(t, "make_view"), system.backend, shape, data)
163
+ return Tensor(ctensor)
164
+
165
+ #############
@@ -0,0 +1,49 @@
1
+ """
2
+ UTIL.PY
3
+
4
+ utility functions (type & input checkers)
5
+ """
6
+
7
+
8
+ from .common import *
9
+ from . import system
10
+ from .tensorclass import *
11
+
12
+
13
+ # ensure that tensor types are univariant
14
+ def check_tensors(*tensors):
15
+ dtypes = set()
16
+ for t in tensors:
17
+ if not isinstance(t, Tensor):
18
+ system.throw(f"argument should be tensor, not '{type(t).__name__}'")
19
+ dtypes.add(t.dtype)
20
+
21
+ if len(dtypes) > 1:
22
+ system.throw("tensors should be of the same type")
23
+ return list(dtypes)[0]
24
+
25
+ # ensure that tensor shape is valid
26
+ def check_shape(shape):
27
+ if isinstance(shape, int):
28
+ shape = [shape]
29
+ if len(shape) > MAXDIMS:
30
+ system.throw("shape exceeds maximum dimensions")
31
+ for i in shape:
32
+ if not (isinstance(i, int) and i > 0):
33
+ system.throw("shape is invalid")
34
+ return shape
35
+
36
+ # ensure that dtype is an oxide dtype
37
+ # optionally stricter restrictions
38
+ def check_dtype(dtype, tclass = None):
39
+ if tclass is None:
40
+ if not issubclass(dtype, oxide_t):
41
+ system.throw("dtype is invalid")
42
+ else:
43
+ if not issubclass(dtype, tclass):
44
+ system.throw("dtype is invalid")
45
+
46
+ def check_args(dtype, *args):
47
+ for a in args:
48
+ if not isinstance(a, OX_T[dtype]):
49
+ system.throw("argument type is invalid")
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+