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.
- mtl_oxide-0.1.0/LICENSE +21 -0
- mtl_oxide-0.1.0/PKG-INFO +50 -0
- mtl_oxide-0.1.0/README.md +41 -0
- mtl_oxide-0.1.0/pyproject.toml +20 -0
- mtl_oxide-0.1.0/python/mtl_oxide.egg-info/PKG-INFO +50 -0
- mtl_oxide-0.1.0/python/mtl_oxide.egg-info/SOURCES.txt +15 -0
- mtl_oxide-0.1.0/python/mtl_oxide.egg-info/dependency_links.txt +1 -0
- mtl_oxide-0.1.0/python/mtl_oxide.egg-info/top_level.txt +1 -0
- mtl_oxide-0.1.0/python/oxide/__init__.py +18 -0
- mtl_oxide-0.1.0/python/oxide/arithmetic.py +49 -0
- mtl_oxide-0.1.0/python/oxide/common.py +44 -0
- mtl_oxide-0.1.0/python/oxide/core.cpython-313-darwin.so +0 -0
- mtl_oxide-0.1.0/python/oxide/create.py +53 -0
- mtl_oxide-0.1.0/python/oxide/system.py +35 -0
- mtl_oxide-0.1.0/python/oxide/tensorclass.py +165 -0
- mtl_oxide-0.1.0/python/oxide/util.py +49 -0
- mtl_oxide-0.1.0/setup.cfg +4 -0
mtl_oxide-0.1.0/LICENSE
ADDED
|
@@ -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.
|
mtl_oxide-0.1.0/PKG-INFO
ADDED
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
oxide
|
|
@@ -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
|
+
}
|
|
Binary file
|
|
@@ -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")
|