torch-pyodide 0.0.1__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.
- torch_pyodide-0.0.1/PKG-INFO +13 -0
- torch_pyodide-0.0.1/README.md +4 -0
- torch_pyodide-0.0.1/pyproject.toml +17 -0
- torch_pyodide-0.0.1/setup.cfg +4 -0
- torch_pyodide-0.0.1/tests/test_tensor_utils.py +14 -0
- torch_pyodide-0.0.1/torch/__init__.py +49 -0
- torch_pyodide-0.0.1/torch/_runtime.py +40 -0
- torch_pyodide-0.0.1/torch/_tensor.py +158 -0
- torch_pyodide-0.0.1/torch_pyodide.egg-info/PKG-INFO +13 -0
- torch_pyodide-0.0.1/torch_pyodide.egg-info/SOURCES.txt +10 -0
- torch_pyodide-0.0.1/torch_pyodide.egg-info/dependency_links.txt +1 -0
- torch_pyodide-0.0.1/torch_pyodide.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: torch-pyodide
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Minimal torch-like API for Pyodide using WebGPU runtime bridge.
|
|
5
|
+
Project-URL: Homepage, https://github.com/celsowm/torch-pyodide
|
|
6
|
+
Project-URL: Repository, https://github.com/celsowm/torch-pyodide
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# torch-pyodide Python package
|
|
11
|
+
|
|
12
|
+
Package distribution for the browser-first `torch` API running on Pyodide + WebGPU.
|
|
13
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "torch-pyodide"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "Minimal torch-like API for Pyodide using WebGPU runtime bridge."
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
|
|
12
|
+
[project.urls]
|
|
13
|
+
Homepage = "https://github.com/celsowm/torch-pyodide"
|
|
14
|
+
Repository = "https://github.com/celsowm/torch-pyodide"
|
|
15
|
+
|
|
16
|
+
[tool.setuptools]
|
|
17
|
+
packages = ["torch"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from torch._tensor import _flatten, _infer_shape, _normalize_shape
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_infer_shape_rectangular():
|
|
5
|
+
assert _infer_shape([[1, 2], [3, 4]]) == [2, 2]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_flatten_nested():
|
|
9
|
+
assert _flatten([[1, 2], [3, 4]]) == [1.0, 2.0, 3.0, 4.0]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_normalize_shape_sequence():
|
|
13
|
+
assert _normalize_shape((2, 3)) == [2, 3]
|
|
14
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Sequence
|
|
4
|
+
|
|
5
|
+
from ._runtime import _get_runtime, _run_js_awaitable
|
|
6
|
+
from ._tensor import Tensor, ones_from_shape, tensor_from_data, zeros_from_shape
|
|
7
|
+
|
|
8
|
+
__all__ = ["Tensor", "init", "tensor", "zeros", "ones", "add", "sub", "mul", "div", "matmul", "relu"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def init() -> None:
|
|
12
|
+
runtime = _get_runtime()
|
|
13
|
+
_run_js_awaitable(runtime.init())
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def tensor(data: object, dtype: str = "float32") -> Tensor:
|
|
17
|
+
return tensor_from_data(data, dtype=dtype)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def zeros(shape: int | Sequence[int], dtype: str = "float32") -> Tensor:
|
|
21
|
+
return zeros_from_shape(shape, dtype=dtype)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def ones(shape: int | Sequence[int], dtype: str = "float32") -> Tensor:
|
|
25
|
+
return ones_from_shape(shape, dtype=dtype)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def add(a: Tensor, b: Tensor) -> Tensor:
|
|
29
|
+
return a.add(b)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sub(a: Tensor, b: Tensor) -> Tensor:
|
|
33
|
+
return a.sub(b)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def mul(a: Tensor, b: Tensor) -> Tensor:
|
|
37
|
+
return a.mul(b)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def div(a: Tensor, b: Tensor) -> Tensor:
|
|
41
|
+
return a.div(b)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def matmul(a: Tensor, b: Tensor) -> Tensor:
|
|
45
|
+
return a.matmul(b)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def relu(x: Tensor) -> Tensor:
|
|
49
|
+
return x.relu()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
RUNTIME_GLOBAL_KEY = "__torch_pyodide_runtime__"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _to_python(value: Any) -> Any:
|
|
10
|
+
if hasattr(value, "to_py"):
|
|
11
|
+
return value.to_py()
|
|
12
|
+
return value
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_runtime() -> Any:
|
|
16
|
+
try:
|
|
17
|
+
from js import globalThis # type: ignore
|
|
18
|
+
except Exception as exc: # pragma: no cover - browser specific
|
|
19
|
+
raise RuntimeError("This package only runs inside Pyodide.") from exc
|
|
20
|
+
|
|
21
|
+
runtime = getattr(globalThis, RUNTIME_GLOBAL_KEY, None)
|
|
22
|
+
if runtime is None:
|
|
23
|
+
raise RuntimeError(
|
|
24
|
+
"Torch runtime bridge not found. Install runtime JS and set "
|
|
25
|
+
"globalThis.__torch_pyodide_runtime__ before importing torch."
|
|
26
|
+
)
|
|
27
|
+
return runtime
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _run_js_awaitable(awaitable: Any) -> Any:
|
|
31
|
+
from pyodide.ffi import can_run_sync, run_sync # type: ignore
|
|
32
|
+
|
|
33
|
+
if not can_run_sync():
|
|
34
|
+
raise RuntimeError(
|
|
35
|
+
"Cannot use synchronous torch API: run_sync is unavailable. "
|
|
36
|
+
"Execute Python through pyodide.runPythonAsync(...) and call "
|
|
37
|
+
"sync Python functions from JS via callPromising(...)."
|
|
38
|
+
)
|
|
39
|
+
return _to_python(run_sync(awaitable))
|
|
40
|
+
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Sequence
|
|
5
|
+
|
|
6
|
+
from ._runtime import _get_runtime, _run_js_awaitable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _js_meta_to_tuple(meta: object) -> tuple[int, list[int], str]:
|
|
10
|
+
tensor_id = int(getattr(meta, "id"))
|
|
11
|
+
shape_raw = getattr(meta, "shape")
|
|
12
|
+
shape = list(shape_raw.to_py() if hasattr(shape_raw, "to_py") else shape_raw)
|
|
13
|
+
dtype = str(getattr(meta, "dtype"))
|
|
14
|
+
return tensor_id, shape, dtype
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(slots=True)
|
|
18
|
+
class Tensor:
|
|
19
|
+
_id: int
|
|
20
|
+
_shape: list[int]
|
|
21
|
+
_dtype: str
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def shape(self) -> tuple[int, ...]:
|
|
25
|
+
return tuple(self._shape)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def dtype(self) -> str:
|
|
29
|
+
return self._dtype
|
|
30
|
+
|
|
31
|
+
def add(self, other: "Tensor") -> "Tensor":
|
|
32
|
+
runtime = _get_runtime()
|
|
33
|
+
meta = _run_js_awaitable(runtime.add(self._id, other._id))
|
|
34
|
+
tensor_id, shape, dtype = _js_meta_to_tuple(meta)
|
|
35
|
+
return Tensor(tensor_id, shape, dtype)
|
|
36
|
+
|
|
37
|
+
def mul(self, other: "Tensor") -> "Tensor":
|
|
38
|
+
runtime = _get_runtime()
|
|
39
|
+
meta = _run_js_awaitable(runtime.mul(self._id, other._id))
|
|
40
|
+
tensor_id, shape, dtype = _js_meta_to_tuple(meta)
|
|
41
|
+
return Tensor(tensor_id, shape, dtype)
|
|
42
|
+
|
|
43
|
+
def sub(self, other: "Tensor") -> "Tensor":
|
|
44
|
+
runtime = _get_runtime()
|
|
45
|
+
meta = _run_js_awaitable(runtime.sub(self._id, other._id))
|
|
46
|
+
tensor_id, shape, dtype = _js_meta_to_tuple(meta)
|
|
47
|
+
return Tensor(tensor_id, shape, dtype)
|
|
48
|
+
|
|
49
|
+
def div(self, other: "Tensor") -> "Tensor":
|
|
50
|
+
runtime = _get_runtime()
|
|
51
|
+
meta = _run_js_awaitable(runtime.div(self._id, other._id))
|
|
52
|
+
tensor_id, shape, dtype = _js_meta_to_tuple(meta)
|
|
53
|
+
return Tensor(tensor_id, shape, dtype)
|
|
54
|
+
|
|
55
|
+
def matmul(self, other: "Tensor") -> "Tensor":
|
|
56
|
+
runtime = _get_runtime()
|
|
57
|
+
meta = _run_js_awaitable(runtime.matmul(self._id, other._id))
|
|
58
|
+
tensor_id, shape, dtype = _js_meta_to_tuple(meta)
|
|
59
|
+
return Tensor(tensor_id, shape, dtype)
|
|
60
|
+
|
|
61
|
+
def sum(self) -> "Tensor":
|
|
62
|
+
runtime = _get_runtime()
|
|
63
|
+
meta = _run_js_awaitable(runtime.sum(self._id))
|
|
64
|
+
tensor_id, shape, dtype = _js_meta_to_tuple(meta)
|
|
65
|
+
return Tensor(tensor_id, shape, dtype)
|
|
66
|
+
|
|
67
|
+
def mean(self) -> "Tensor":
|
|
68
|
+
runtime = _get_runtime()
|
|
69
|
+
meta = _run_js_awaitable(runtime.mean(self._id))
|
|
70
|
+
tensor_id, shape, dtype = _js_meta_to_tuple(meta)
|
|
71
|
+
return Tensor(tensor_id, shape, dtype)
|
|
72
|
+
|
|
73
|
+
def relu(self) -> "Tensor":
|
|
74
|
+
runtime = _get_runtime()
|
|
75
|
+
meta = _run_js_awaitable(runtime.relu(self._id))
|
|
76
|
+
tensor_id, shape, dtype = _js_meta_to_tuple(meta)
|
|
77
|
+
return Tensor(tensor_id, shape, dtype)
|
|
78
|
+
|
|
79
|
+
def reshape(self, shape: int | Sequence[int]) -> "Tensor":
|
|
80
|
+
runtime = _get_runtime()
|
|
81
|
+
normalized = _normalize_shape(shape)
|
|
82
|
+
meta = _run_js_awaitable(runtime.reshape(self._id, normalized))
|
|
83
|
+
tensor_id, out_shape, out_dtype = _js_meta_to_tuple(meta)
|
|
84
|
+
return Tensor(tensor_id, out_shape, out_dtype)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def T(self) -> "Tensor":
|
|
88
|
+
runtime = _get_runtime()
|
|
89
|
+
meta = _run_js_awaitable(runtime.transpose2d(self._id))
|
|
90
|
+
tensor_id, out_shape, out_dtype = _js_meta_to_tuple(meta)
|
|
91
|
+
return Tensor(tensor_id, out_shape, out_dtype)
|
|
92
|
+
|
|
93
|
+
def to_list(self) -> list[float]:
|
|
94
|
+
runtime = _get_runtime()
|
|
95
|
+
result = _run_js_awaitable(runtime.toList(self._id))
|
|
96
|
+
return list(result)
|
|
97
|
+
|
|
98
|
+
def destroy(self) -> None:
|
|
99
|
+
runtime = _get_runtime()
|
|
100
|
+
_run_js_awaitable(runtime.destroy(self._id))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _infer_shape(data: object) -> list[int]:
|
|
104
|
+
if not isinstance(data, list):
|
|
105
|
+
return []
|
|
106
|
+
if len(data) == 0:
|
|
107
|
+
return [0]
|
|
108
|
+
first_shape = _infer_shape(data[0])
|
|
109
|
+
for item in data[1:]:
|
|
110
|
+
if _infer_shape(item) != first_shape:
|
|
111
|
+
raise ValueError("tensor() expects a rectangular nested list.")
|
|
112
|
+
return [len(data), *first_shape]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _flatten(data: object) -> list[float]:
|
|
116
|
+
if isinstance(data, list):
|
|
117
|
+
out: list[float] = []
|
|
118
|
+
for item in data:
|
|
119
|
+
out.extend(_flatten(item))
|
|
120
|
+
return out
|
|
121
|
+
return [float(data)] # type: ignore[arg-type]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _normalize_shape(shape: int | Sequence[int]) -> list[int]:
|
|
125
|
+
if isinstance(shape, int):
|
|
126
|
+
if shape < 0:
|
|
127
|
+
raise ValueError("shape values must be >= 0")
|
|
128
|
+
return [shape]
|
|
129
|
+
|
|
130
|
+
normalized = [int(v) for v in shape]
|
|
131
|
+
if any(v < 0 for v in normalized):
|
|
132
|
+
raise ValueError("shape values must be >= 0")
|
|
133
|
+
return normalized
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def tensor_from_data(data: object, dtype: str = "float32") -> Tensor:
|
|
137
|
+
runtime = _get_runtime()
|
|
138
|
+
shape = _infer_shape(data)
|
|
139
|
+
flat = _flatten(data)
|
|
140
|
+
meta = _run_js_awaitable(runtime.tensorFromData(flat, shape, dtype))
|
|
141
|
+
tensor_id, shape, dtype = _js_meta_to_tuple(meta)
|
|
142
|
+
return Tensor(tensor_id, shape, dtype)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def zeros_from_shape(shape: int | Sequence[int], dtype: str = "float32") -> Tensor:
|
|
146
|
+
runtime = _get_runtime()
|
|
147
|
+
normalized = _normalize_shape(shape)
|
|
148
|
+
meta = _run_js_awaitable(runtime.zeros(normalized, dtype))
|
|
149
|
+
tensor_id, out_shape, out_dtype = _js_meta_to_tuple(meta)
|
|
150
|
+
return Tensor(tensor_id, out_shape, out_dtype)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def ones_from_shape(shape: int | Sequence[int], dtype: str = "float32") -> Tensor:
|
|
154
|
+
runtime = _get_runtime()
|
|
155
|
+
normalized = _normalize_shape(shape)
|
|
156
|
+
meta = _run_js_awaitable(runtime.ones(normalized, dtype))
|
|
157
|
+
tensor_id, out_shape, out_dtype = _js_meta_to_tuple(meta)
|
|
158
|
+
return Tensor(tensor_id, out_shape, out_dtype)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: torch-pyodide
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Minimal torch-like API for Pyodide using WebGPU runtime bridge.
|
|
5
|
+
Project-URL: Homepage, https://github.com/celsowm/torch-pyodide
|
|
6
|
+
Project-URL: Repository, https://github.com/celsowm/torch-pyodide
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# torch-pyodide Python package
|
|
11
|
+
|
|
12
|
+
Package distribution for the browser-first `torch` API running on Pyodide + WebGPU.
|
|
13
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
tests/test_tensor_utils.py
|
|
4
|
+
torch/__init__.py
|
|
5
|
+
torch/_runtime.py
|
|
6
|
+
torch/_tensor.py
|
|
7
|
+
torch_pyodide.egg-info/PKG-INFO
|
|
8
|
+
torch_pyodide.egg-info/SOURCES.txt
|
|
9
|
+
torch_pyodide.egg-info/dependency_links.txt
|
|
10
|
+
torch_pyodide.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
torch
|