tensorstudio 1.0.1__tar.gz → 1.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.
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.github/workflows/publish.yml +10 -4
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/CHANGELOG.md +24 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/PKG-INFO +29 -17
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/README.md +28 -16
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmark_all.py +2 -0
- tensorstudio-1.1.0/benchmarks/bench_conv2d.py +8 -0
- tensorstudio-1.1.0/benchmarks/bench_pooling.py +8 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/benchmark_report.py +220 -5
- tensorstudio-1.1.0/benchmarks/results.md +353 -0
- tensorstudio-1.1.0/benchmarks/results_conv2d.md +39 -0
- tensorstudio-1.1.0/benchmarks/results_pooling.md +45 -0
- tensorstudio-1.1.0/benchmarks/results_tensor_ops.md +309 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Autograde/index.md +1 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Benchmarks/index.md +25 -4
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Hardware/cpu-backend.md +1 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Neural Networks/modules.md +40 -0
- tensorstudio-1.0.1/docs/nn.md → tensorstudio-1.1.0/docs/Neural Networks/nn.md +45 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Neural Networks/training.md +1 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Roadmap/index.md +7 -6
- {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Usage}/api.md +47 -5
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Usage/serialization.md +1 -1
- {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Usage}/tensors.md +32 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/development.md +1 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/index.md +17 -13
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/publishing.md +13 -10
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/quickstart.md +42 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/serialization.md +1 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/ops.hpp +36 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/version.hpp +1 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/mkdocs.yml +9 -9
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/pyproject.toml +1 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/_C.pyi +45 -8
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/__init__.py +7 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/_version.py +1 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/nn/__init__.py +20 -1
- tensorstudio-1.1.0/python/tensorstudio/nn/functional.py +167 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/nn/losses.py +14 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/nn/modules.py +165 -0
- tensorstudio-1.1.0/python/tensorstudio/ops.py +262 -0
- tensorstudio-1.1.0/src/bindings/bind_ops.cpp +118 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bind_tensor.cpp +30 -4
- tensorstudio-1.1.0/src/core/ops.cpp +2356 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_autograd.py +82 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_import.py +1 -1
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_nn.py +97 -0
- tensorstudio-1.1.0/tests/test_ops.py +314 -0
- tensorstudio-1.0.1/benchmarks/results.md +0 -311
- tensorstudio-1.0.1/benchmarks/results_tensor_ops.md +0 -285
- tensorstudio-1.0.1/docs/Usage/tensors.md +0 -139
- tensorstudio-1.0.1/python/tensorstudio/nn/functional.py +0 -75
- tensorstudio-1.0.1/python/tensorstudio/ops.py +0 -157
- tensorstudio-1.0.1/src/bindings/bind_ops.cpp +0 -40
- tensorstudio-1.0.1/src/core/ops.cpp +0 -1136
- tensorstudio-1.0.1/tests/test_ops.py +0 -63
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.github/workflows/ci.yml +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.github/workflows/publish-testpypi.yml +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.github/workflows/wheels.yml +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.gitignore +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.pre-commit-config.yaml +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/CMakeLists.txt +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/CONTRIBUTING.md +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/LICENSE +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/SECURITY.md +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/__init__.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_activations.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_autograd.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_elementwise.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_matmul.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_reductions.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_tensor_ops.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_training_loop.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/results_matmul.md +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/results_reductions.md +0 -0
- {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Autograde}/autograd.md +0 -0
- {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Hardware}/linux.md +0 -0
- {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Hardware}/macos.md +0 -0
- {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Hardware}/windows.md +0 -0
- {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Usage}/data.md +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Usage/numpy-interop.md +0 -0
- {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Usage}/optim.md +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/examples/basic_tensor_ops.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/examples/linear_regression.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/examples/save_load_model.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/examples/tiny_mlp.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/autograd.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/device.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/dtype.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/errors.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/module.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/optim.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/random.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/serialization.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/shape.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/storage.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/tensor.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/autograd.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/data/__init__.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/data/dataloader.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/data/dataset.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/errors.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/grad_mode.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/__init__.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/adam.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/adamw.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/lr_scheduler.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/sgd.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/utils.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/py.typed +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/serialization.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/tensor.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/typing.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bind_autograd.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bind_nn.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bind_optim.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bindings.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bindings.hpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/autograd.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/device.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/dtype.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/errors.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/module.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/optim.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/random.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/serialization.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/shape.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/storage.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/tensor.cpp +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/test_all.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_benchmark_report.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_data.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_numpy_interop.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_optim.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_serialization.py +0 -0
- {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_tensor.py +0 -0
|
@@ -4,9 +4,6 @@ on:
|
|
|
4
4
|
workflow_dispatch:
|
|
5
5
|
release:
|
|
6
6
|
types: [published]
|
|
7
|
-
push:
|
|
8
|
-
tags:
|
|
9
|
-
- "v*"
|
|
10
7
|
|
|
11
8
|
permissions:
|
|
12
9
|
contents: read
|
|
@@ -97,6 +94,8 @@ jobs:
|
|
|
97
94
|
needs: [windows_wheels, linux_wheels, macos_wheels, build_sdist]
|
|
98
95
|
runs-on: ubuntu-latest
|
|
99
96
|
environment: pypi
|
|
97
|
+
env:
|
|
98
|
+
HAS_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN != '' }}
|
|
100
99
|
steps:
|
|
101
100
|
- uses: actions/download-artifact@v4
|
|
102
101
|
with:
|
|
@@ -107,7 +106,14 @@ jobs:
|
|
|
107
106
|
run: |
|
|
108
107
|
python -m pip install -U twine
|
|
109
108
|
python -m twine check dist/*
|
|
110
|
-
- name: Publish to PyPI
|
|
109
|
+
- name: Publish to PyPI with configured API token
|
|
110
|
+
if: env.HAS_PYPI_TOKEN == 'true'
|
|
111
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
112
|
+
with:
|
|
113
|
+
password: ${{ secrets.PYPI_TOKEN }}
|
|
114
|
+
skip-existing: true
|
|
115
|
+
- name: Publish to PyPI with Trusted Publishing
|
|
116
|
+
if: env.HAS_PYPI_TOKEN != 'true'
|
|
111
117
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
112
118
|
with:
|
|
113
119
|
skip-existing: true
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 1.1.0 - 2026-07-07
|
|
6
|
+
|
|
7
|
+
- Added native CPU NCHW `conv2d` with stride, padding, dilation, optional bias,
|
|
8
|
+
and reverse-mode gradients for input, weight, and bias.
|
|
9
|
+
- Added `tensorstudio.conv2d`, `tensorstudio.ops.conv2d`,
|
|
10
|
+
`tensorstudio.nn.functional.conv2d`, and `tensorstudio.nn.Conv2d`.
|
|
11
|
+
- Added convolution tests, finite-difference autograd coverage, and a
|
|
12
|
+
standalone convolution benchmark script.
|
|
13
|
+
- Added convolution rows to the full benchmark report and updated docs to mark
|
|
14
|
+
convolution as partially implemented rather than missing.
|
|
15
|
+
- Added native CPU NCHW `max_pool2d` and `avg_pool2d` operations with autograd,
|
|
16
|
+
Python top-level and functional APIs, `nn.MaxPool2d`, `nn.AvgPool2d`, tests,
|
|
17
|
+
docs, and benchmark coverage.
|
|
18
|
+
- Added native single-axis `sum`, `mean`, `max`, and `min` reductions with
|
|
19
|
+
`keepdims` and reverse-mode gradients.
|
|
20
|
+
- Added a typed contiguous 2D fast path for common axis `sum`/`mean`
|
|
21
|
+
reductions.
|
|
22
|
+
- Added `nn.softmax`, `nn.log_softmax`, `nn.cross_entropy`, and
|
|
23
|
+
`nn.CrossEntropyLoss` for small multiclass classification workloads.
|
|
24
|
+
- Added native `astype`, `concat`, and `stack` operations with Python wrappers,
|
|
25
|
+
Tensor methods for casting, and autograd support for floating tensors.
|
|
26
|
+
|
|
3
27
|
## 1.0.1
|
|
4
28
|
|
|
5
29
|
- Added `test_all.py` for one-command local release checks covering lint,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: tensorstudio
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: TensorStudio is a compact C++ tensor and autograd engine with a Python API for learning, experimentation, and lightweight ML workloads.
|
|
5
5
|
Keywords: tensor,autograd,machine-learning,cpp,pybind11
|
|
6
6
|
Author: TensorStudio contributors
|
|
@@ -65,7 +65,7 @@ Description-Content-Type: text/markdown
|
|
|
65
65
|
TensorStudio is a compact C++ tensor and autograd engine with a Python API for
|
|
66
66
|
learning, experimentation, and lightweight ML workloads.
|
|
67
67
|
|
|
68
|
-
TensorStudio `1.0
|
|
68
|
+
TensorStudio `1.1.0` is a CPU-only stable API foundation. It is eager-only,
|
|
69
69
|
intentionally small, and not a replacement for mature ML frameworks.
|
|
70
70
|
|
|
71
71
|
## Install
|
|
@@ -152,6 +152,8 @@ d = ts.linspace(0.0, 1.0, 5)
|
|
|
152
152
|
|
|
153
153
|
print(a.shape, a.strides, a.device, a.is_contiguous)
|
|
154
154
|
print((b.clamp(0.2, 0.8) + 1).mean().item())
|
|
155
|
+
print(b.sum(axis=1).tolist())
|
|
156
|
+
print(ts.concat([b, b], axis=0).shape, b.astype("float64").dtype)
|
|
155
157
|
print(c.tolist(), d.tolist())
|
|
156
158
|
print(ts.zeros_like(b).shape, ts.randn_like(b, seed=11).dtype)
|
|
157
159
|
```
|
|
@@ -235,12 +237,15 @@ python benchmarks/benchmark_report.py
|
|
|
235
237
|
columns for NumPy, TensorFlow, PyTorch, and JAX when those libraries are
|
|
236
238
|
available locally.
|
|
237
239
|
|
|
238
|
-
On one Windows CPython 3.10 run
|
|
239
|
-
small
|
|
240
|
-
cases. Against PyTorch CPU `2.12.1+cpu`, TensorStudio won
|
|
241
|
-
lost
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
On one Windows CPython 3.10 development run reporting `1.1.0`, TensorStudio
|
|
241
|
+
beat NumPy on 23 small operation benchmark cases and lost on 80
|
|
242
|
+
NumPy-comparable cases. Against PyTorch CPU `2.12.1+cpu`, TensorStudio won 74
|
|
243
|
+
local cases and lost 34. The strongest local wins were small eager operations,
|
|
244
|
+
small contiguous axis reductions, and the simple NumPy convolution/pooling
|
|
245
|
+
references where framework dispatch or Python loops dominate; larger matrix
|
|
246
|
+
multiplication, PyTorch convolution and pooling, larger axis reductions, larger
|
|
247
|
+
transcendental activations, and larger autograd workloads remain faster in
|
|
248
|
+
PyTorch and NumPy.
|
|
244
249
|
See `benchmarks/results.md` for the full table, platform details, and exact
|
|
245
250
|
timings.
|
|
246
251
|
|
|
@@ -248,11 +253,15 @@ Snapshot from that local run:
|
|
|
248
253
|
|
|
249
254
|
| operation | shape | TensorStudio | NumPy | PyTorch CPU | TS vs NumPy | TS vs PyTorch |
|
|
250
255
|
|---|---:|---:|---:|---:|---:|---:|
|
|
251
|
-
| `sigmoid` | `(32,)` | 0.
|
|
252
|
-
| `mean` | `(32,)` | 0.
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
| `
|
|
256
|
+
| `sigmoid` | `(32,)` | 0.0017 ms | 0.0036 ms | 0.0580 ms | 2.1201x | 33.8536x |
|
|
257
|
+
| `mean` | `(32,)` | 0.0018 ms | 0.0078 ms | 0.0127 ms | 4.2927x | 7.0047x |
|
|
258
|
+
| `sum_axis1` | `(16, 16)` | 0.0021 ms | 0.0029 ms | 0.0068 ms | 1.3727x | 3.2193x |
|
|
259
|
+
| `chain_relu` | `(128,)` | 0.0086 ms | 0.0039 ms | 0.0559 ms | 0.4478x | 6.5042x |
|
|
260
|
+
| `matmul` | `(256, 256)` | 4.1154 ms | 0.3679 ms | 0.0931 ms | 0.0894x | 0.0226x |
|
|
261
|
+
| `conv2d_3x3_padding1` | `(1, 1, 8, 8)` | 0.1788 ms | 1.2241 ms | 0.0131 ms | 6.8478x | 0.0731x |
|
|
262
|
+
| `max_pool2d_2x2` | `(1, 1, 16, 16)` | 0.0146 ms | 0.2649 ms | 0.0062 ms | 18.1659x | 0.4242x |
|
|
263
|
+
| `avg_pool2d_2x2` | `(1, 1, 16, 16)` | 0.0139 ms | 0.5486 ms | 0.0052 ms | 39.5974x | 0.3748x |
|
|
264
|
+
| `elementwise_backward` | `(1024,)` | 2.3885 ms | n/a | 0.1947 ms | n/a | 0.0815x |
|
|
256
265
|
|
|
257
266
|
Speedup is `competitor median / TensorStudio median`, so values above `1.0x`
|
|
258
267
|
favor TensorStudio.
|
|
@@ -318,9 +327,12 @@ tokens or print secrets.
|
|
|
318
327
|
- No CUDA or Metal backend yet.
|
|
319
328
|
- No BLAS-backed matrix multiplication yet.
|
|
320
329
|
- No graph compiler or distributed runtime.
|
|
321
|
-
-
|
|
330
|
+
- Convolution and pooling support are currently limited to CPU NCHW
|
|
331
|
+
`conv2d`, `max_pool2d`, and `avg_pool2d` style workloads.
|
|
332
|
+
- Reductions support all-element or single-axis reductions, not tuple-axis
|
|
333
|
+
reductions yet.
|
|
322
334
|
- No sparse tensors or advanced indexing.
|
|
323
|
-
-
|
|
335
|
+
- Dtype casting is basic and does not include a full promotion/casting policy.
|
|
324
336
|
- Experimental performance; benchmarks are local references only.
|
|
325
337
|
- Pickle serialization is for trusted TensorStudio objects only.
|
|
326
338
|
|
|
@@ -328,8 +340,8 @@ tokens or print secrets.
|
|
|
328
340
|
|
|
329
341
|
- CUDA backend
|
|
330
342
|
- Graph/JIT mode
|
|
331
|
-
-
|
|
332
|
-
-
|
|
343
|
+
- Broader convolution ops, adaptive/global pooling, and image-model examples
|
|
344
|
+
- Richer dataset utilities
|
|
333
345
|
- Model zoo examples
|
|
334
346
|
- ONNX import/export
|
|
335
347
|
- Improved memory allocator
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
TensorStudio is a compact C++ tensor and autograd engine with a Python API for
|
|
8
8
|
learning, experimentation, and lightweight ML workloads.
|
|
9
9
|
|
|
10
|
-
TensorStudio `1.0
|
|
10
|
+
TensorStudio `1.1.0` is a CPU-only stable API foundation. It is eager-only,
|
|
11
11
|
intentionally small, and not a replacement for mature ML frameworks.
|
|
12
12
|
|
|
13
13
|
## Install
|
|
@@ -94,6 +94,8 @@ d = ts.linspace(0.0, 1.0, 5)
|
|
|
94
94
|
|
|
95
95
|
print(a.shape, a.strides, a.device, a.is_contiguous)
|
|
96
96
|
print((b.clamp(0.2, 0.8) + 1).mean().item())
|
|
97
|
+
print(b.sum(axis=1).tolist())
|
|
98
|
+
print(ts.concat([b, b], axis=0).shape, b.astype("float64").dtype)
|
|
97
99
|
print(c.tolist(), d.tolist())
|
|
98
100
|
print(ts.zeros_like(b).shape, ts.randn_like(b, seed=11).dtype)
|
|
99
101
|
```
|
|
@@ -177,12 +179,15 @@ python benchmarks/benchmark_report.py
|
|
|
177
179
|
columns for NumPy, TensorFlow, PyTorch, and JAX when those libraries are
|
|
178
180
|
available locally.
|
|
179
181
|
|
|
180
|
-
On one Windows CPython 3.10 run
|
|
181
|
-
small
|
|
182
|
-
cases. Against PyTorch CPU `2.12.1+cpu`, TensorStudio won
|
|
183
|
-
lost
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
On one Windows CPython 3.10 development run reporting `1.1.0`, TensorStudio
|
|
183
|
+
beat NumPy on 23 small operation benchmark cases and lost on 80
|
|
184
|
+
NumPy-comparable cases. Against PyTorch CPU `2.12.1+cpu`, TensorStudio won 74
|
|
185
|
+
local cases and lost 34. The strongest local wins were small eager operations,
|
|
186
|
+
small contiguous axis reductions, and the simple NumPy convolution/pooling
|
|
187
|
+
references where framework dispatch or Python loops dominate; larger matrix
|
|
188
|
+
multiplication, PyTorch convolution and pooling, larger axis reductions, larger
|
|
189
|
+
transcendental activations, and larger autograd workloads remain faster in
|
|
190
|
+
PyTorch and NumPy.
|
|
186
191
|
See `benchmarks/results.md` for the full table, platform details, and exact
|
|
187
192
|
timings.
|
|
188
193
|
|
|
@@ -190,11 +195,15 @@ Snapshot from that local run:
|
|
|
190
195
|
|
|
191
196
|
| operation | shape | TensorStudio | NumPy | PyTorch CPU | TS vs NumPy | TS vs PyTorch |
|
|
192
197
|
|---|---:|---:|---:|---:|---:|---:|
|
|
193
|
-
| `sigmoid` | `(32,)` | 0.
|
|
194
|
-
| `mean` | `(32,)` | 0.
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
+
| `sigmoid` | `(32,)` | 0.0017 ms | 0.0036 ms | 0.0580 ms | 2.1201x | 33.8536x |
|
|
199
|
+
| `mean` | `(32,)` | 0.0018 ms | 0.0078 ms | 0.0127 ms | 4.2927x | 7.0047x |
|
|
200
|
+
| `sum_axis1` | `(16, 16)` | 0.0021 ms | 0.0029 ms | 0.0068 ms | 1.3727x | 3.2193x |
|
|
201
|
+
| `chain_relu` | `(128,)` | 0.0086 ms | 0.0039 ms | 0.0559 ms | 0.4478x | 6.5042x |
|
|
202
|
+
| `matmul` | `(256, 256)` | 4.1154 ms | 0.3679 ms | 0.0931 ms | 0.0894x | 0.0226x |
|
|
203
|
+
| `conv2d_3x3_padding1` | `(1, 1, 8, 8)` | 0.1788 ms | 1.2241 ms | 0.0131 ms | 6.8478x | 0.0731x |
|
|
204
|
+
| `max_pool2d_2x2` | `(1, 1, 16, 16)` | 0.0146 ms | 0.2649 ms | 0.0062 ms | 18.1659x | 0.4242x |
|
|
205
|
+
| `avg_pool2d_2x2` | `(1, 1, 16, 16)` | 0.0139 ms | 0.5486 ms | 0.0052 ms | 39.5974x | 0.3748x |
|
|
206
|
+
| `elementwise_backward` | `(1024,)` | 2.3885 ms | n/a | 0.1947 ms | n/a | 0.0815x |
|
|
198
207
|
|
|
199
208
|
Speedup is `competitor median / TensorStudio median`, so values above `1.0x`
|
|
200
209
|
favor TensorStudio.
|
|
@@ -260,9 +269,12 @@ tokens or print secrets.
|
|
|
260
269
|
- No CUDA or Metal backend yet.
|
|
261
270
|
- No BLAS-backed matrix multiplication yet.
|
|
262
271
|
- No graph compiler or distributed runtime.
|
|
263
|
-
-
|
|
272
|
+
- Convolution and pooling support are currently limited to CPU NCHW
|
|
273
|
+
`conv2d`, `max_pool2d`, and `avg_pool2d` style workloads.
|
|
274
|
+
- Reductions support all-element or single-axis reductions, not tuple-axis
|
|
275
|
+
reductions yet.
|
|
264
276
|
- No sparse tensors or advanced indexing.
|
|
265
|
-
-
|
|
277
|
+
- Dtype casting is basic and does not include a full promotion/casting policy.
|
|
266
278
|
- Experimental performance; benchmarks are local references only.
|
|
267
279
|
- Pickle serialization is for trusted TensorStudio objects only.
|
|
268
280
|
|
|
@@ -270,8 +282,8 @@ tokens or print secrets.
|
|
|
270
282
|
|
|
271
283
|
- CUDA backend
|
|
272
284
|
- Graph/JIT mode
|
|
273
|
-
-
|
|
274
|
-
-
|
|
285
|
+
- Broader convolution ops, adaptive/global pooling, and image-model examples
|
|
286
|
+
- Richer dataset utilities
|
|
275
287
|
- Model zoo examples
|
|
276
288
|
- ONNX import/export
|
|
277
289
|
- Improved memory allocator
|
|
@@ -24,6 +24,8 @@ Operation = Callable[[Any, Any | None], Any]
|
|
|
24
24
|
|
|
25
25
|
VECTOR_SHAPES = [(1,), (8,), (32,), (128,), (1024,), (4096,), (16384,)]
|
|
26
26
|
MATRIX_SHAPES = [(16, 16), (64, 64), (128, 128), (256, 256)]
|
|
27
|
+
CONV2D_SHAPES = [(1, 1, 8, 8), (4, 3, 16, 16)]
|
|
28
|
+
POOL2D_SHAPES = [(1, 1, 16, 16), (4, 3, 32, 32)]
|
|
27
29
|
DTYPE = np.float32
|
|
28
30
|
|
|
29
31
|
|
|
@@ -88,6 +90,14 @@ def _iterations(shape: tuple[int, ...], category: str) -> int:
|
|
|
88
90
|
if n <= 16384:
|
|
89
91
|
return 5
|
|
90
92
|
return 2
|
|
93
|
+
if category == "conv2d":
|
|
94
|
+
if n <= 256:
|
|
95
|
+
return 50
|
|
96
|
+
return 10
|
|
97
|
+
if category == "pooling":
|
|
98
|
+
if n <= 256:
|
|
99
|
+
return 200
|
|
100
|
+
return 50
|
|
91
101
|
if category in {"autograd", "training_loop"}:
|
|
92
102
|
if n <= 128:
|
|
93
103
|
return 100
|
|
@@ -297,14 +307,25 @@ def _log_external(value: Any, module: str) -> Any:
|
|
|
297
307
|
return np.log(value)
|
|
298
308
|
|
|
299
309
|
|
|
300
|
-
def _reduction_op(
|
|
310
|
+
def _reduction_op(
|
|
311
|
+
name: str,
|
|
312
|
+
axis: int | None = None,
|
|
313
|
+
) -> Callable[[Library, np.ndarray, np.ndarray], Callable[[], Any]]:
|
|
301
314
|
def build(library: Library, left: np.ndarray, _: np.ndarray) -> Callable[[], Any]:
|
|
302
315
|
x = library.factory(left)
|
|
303
316
|
|
|
304
317
|
def run() -> Any:
|
|
305
318
|
if library.name == "TensorStudio":
|
|
306
|
-
return x.sum() if name == "sum" else x.mean()
|
|
307
|
-
|
|
319
|
+
return x.sum(axis=axis) if name == "sum" else x.mean(axis=axis)
|
|
320
|
+
module = x.__class__.__module__
|
|
321
|
+
if module.startswith("torch"):
|
|
322
|
+
return _finalize(x.sum(dim=axis) if name == "sum" else x.mean(dim=axis))
|
|
323
|
+
if module.startswith("tensorflow"):
|
|
324
|
+
tf = importlib.import_module("tensorflow")
|
|
325
|
+
return _finalize(
|
|
326
|
+
tf.reduce_sum(x, axis=axis) if name == "sum" else tf.reduce_mean(x, axis=axis)
|
|
327
|
+
)
|
|
328
|
+
return _finalize(x.sum(axis=axis) if name == "sum" else x.mean(axis=axis))
|
|
308
329
|
|
|
309
330
|
return run
|
|
310
331
|
|
|
@@ -321,6 +342,167 @@ def _matmul_build(library: Library, left: np.ndarray, right: np.ndarray) -> Call
|
|
|
321
342
|
return run
|
|
322
343
|
|
|
323
344
|
|
|
345
|
+
def _numpy_conv2d_nchw(
|
|
346
|
+
input: np.ndarray,
|
|
347
|
+
weight: np.ndarray,
|
|
348
|
+
padding: tuple[int, int] = (1, 1),
|
|
349
|
+
) -> np.ndarray:
|
|
350
|
+
batch, in_channels, input_h, input_w = input.shape
|
|
351
|
+
out_channels, _, kernel_h, kernel_w = weight.shape
|
|
352
|
+
padding_h, padding_w = padding
|
|
353
|
+
out_h = input_h + 2 * padding_h - kernel_h + 1
|
|
354
|
+
out_w = input_w + 2 * padding_w - kernel_w + 1
|
|
355
|
+
output = np.zeros((batch, out_channels, out_h, out_w), dtype=input.dtype)
|
|
356
|
+
for n in range(batch):
|
|
357
|
+
for oc in range(out_channels):
|
|
358
|
+
for oh in range(out_h):
|
|
359
|
+
for ow in range(out_w):
|
|
360
|
+
acc = 0.0
|
|
361
|
+
for ic in range(in_channels):
|
|
362
|
+
for kh in range(kernel_h):
|
|
363
|
+
ih = oh - padding_h + kh
|
|
364
|
+
if ih < 0 or ih >= input_h:
|
|
365
|
+
continue
|
|
366
|
+
for kw in range(kernel_w):
|
|
367
|
+
iw = ow - padding_w + kw
|
|
368
|
+
if iw < 0 or iw >= input_w:
|
|
369
|
+
continue
|
|
370
|
+
acc += float(input[n, ic, ih, iw] * weight[oc, ic, kh, kw])
|
|
371
|
+
output[n, oc, oh, ow] = acc
|
|
372
|
+
return output
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _conv2d_build(library: Library, left: np.ndarray, right: np.ndarray) -> Callable[[], Any]:
|
|
376
|
+
if library.name == "TensorStudio":
|
|
377
|
+
x = ts.from_numpy(left)
|
|
378
|
+
w = ts.from_numpy(right)
|
|
379
|
+
|
|
380
|
+
def run_ts() -> Any:
|
|
381
|
+
return ts.conv2d(x, w, padding=1)
|
|
382
|
+
|
|
383
|
+
return run_ts
|
|
384
|
+
|
|
385
|
+
if library.name == "NumPy":
|
|
386
|
+
x_np = left
|
|
387
|
+
w_np = right
|
|
388
|
+
|
|
389
|
+
def run_np() -> Any:
|
|
390
|
+
return _numpy_conv2d_nchw(x_np, w_np)
|
|
391
|
+
|
|
392
|
+
return run_np
|
|
393
|
+
|
|
394
|
+
if library.name == "PyTorch CPU" and library.available:
|
|
395
|
+
torch = importlib.import_module("torch")
|
|
396
|
+
functional = importlib.import_module("torch.nn.functional")
|
|
397
|
+
x_torch = torch.from_numpy(left.copy())
|
|
398
|
+
w_torch = torch.from_numpy(right.copy())
|
|
399
|
+
|
|
400
|
+
def run_torch() -> Any:
|
|
401
|
+
return functional.conv2d(x_torch, w_torch, padding=1)
|
|
402
|
+
|
|
403
|
+
return run_torch
|
|
404
|
+
|
|
405
|
+
if library.name == "TensorFlow CPU eager" and library.available:
|
|
406
|
+
tf = importlib.import_module("tensorflow")
|
|
407
|
+
x_tf = tf.constant(np.transpose(left, (0, 2, 3, 1)))
|
|
408
|
+
w_tf = tf.constant(np.transpose(right, (2, 3, 1, 0)))
|
|
409
|
+
|
|
410
|
+
def run_tf() -> Any:
|
|
411
|
+
return tf.nn.conv2d(x_tf, w_tf, strides=1, padding="SAME").numpy()
|
|
412
|
+
|
|
413
|
+
return run_tf
|
|
414
|
+
|
|
415
|
+
if library.name == "JAX CPU dispatch" and library.available:
|
|
416
|
+
jax = importlib.import_module("jax")
|
|
417
|
+
jnp = importlib.import_module("jax.numpy")
|
|
418
|
+
x_jax = jnp.array(left)
|
|
419
|
+
w_jax = jnp.array(right)
|
|
420
|
+
|
|
421
|
+
def run_jax() -> Any:
|
|
422
|
+
return jax.lax.conv_general_dilated(
|
|
423
|
+
x_jax,
|
|
424
|
+
w_jax,
|
|
425
|
+
window_strides=(1, 1),
|
|
426
|
+
padding=((1, 1), (1, 1)),
|
|
427
|
+
dimension_numbers=("NCHW", "OIHW", "NCHW"),
|
|
428
|
+
).block_until_ready()
|
|
429
|
+
|
|
430
|
+
return run_jax
|
|
431
|
+
|
|
432
|
+
raise NotImplementedError
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _numpy_pool2d_nchw(input: np.ndarray, mode: str) -> np.ndarray:
|
|
436
|
+
batch, channels, input_h, input_w = input.shape
|
|
437
|
+
kernel_h = 2
|
|
438
|
+
kernel_w = 2
|
|
439
|
+
stride_h = 2
|
|
440
|
+
stride_w = 2
|
|
441
|
+
out_h = (input_h - kernel_h) // stride_h + 1
|
|
442
|
+
out_w = (input_w - kernel_w) // stride_w + 1
|
|
443
|
+
output = np.empty((batch, channels, out_h, out_w), dtype=input.dtype)
|
|
444
|
+
for n in range(batch):
|
|
445
|
+
for c in range(channels):
|
|
446
|
+
for oh in range(out_h):
|
|
447
|
+
for ow in range(out_w):
|
|
448
|
+
window = input[
|
|
449
|
+
n,
|
|
450
|
+
c,
|
|
451
|
+
oh * stride_h : oh * stride_h + kernel_h,
|
|
452
|
+
ow * stride_w : ow * stride_w + kernel_w,
|
|
453
|
+
]
|
|
454
|
+
output[n, c, oh, ow] = window.max() if mode == "max" else window.mean()
|
|
455
|
+
return output
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _pool2d_build(mode: str) -> Callable[[Library, np.ndarray, np.ndarray], Callable[[], Any]]:
|
|
459
|
+
def build(library: Library, left: np.ndarray, _: np.ndarray) -> Callable[[], Any]:
|
|
460
|
+
if library.name == "TensorStudio":
|
|
461
|
+
x = ts.from_numpy(left)
|
|
462
|
+
|
|
463
|
+
def run_ts() -> Any:
|
|
464
|
+
if mode == "max":
|
|
465
|
+
return ts.max_pool2d(x, 2)
|
|
466
|
+
return ts.avg_pool2d(x, 2)
|
|
467
|
+
|
|
468
|
+
return run_ts
|
|
469
|
+
|
|
470
|
+
if library.name == "NumPy":
|
|
471
|
+
x_np = left
|
|
472
|
+
|
|
473
|
+
def run_np() -> Any:
|
|
474
|
+
return _numpy_pool2d_nchw(x_np, mode)
|
|
475
|
+
|
|
476
|
+
return run_np
|
|
477
|
+
|
|
478
|
+
if library.name == "PyTorch CPU" and library.available:
|
|
479
|
+
torch = importlib.import_module("torch")
|
|
480
|
+
functional = importlib.import_module("torch.nn.functional")
|
|
481
|
+
x_torch = torch.from_numpy(left.copy())
|
|
482
|
+
|
|
483
|
+
def run_torch() -> Any:
|
|
484
|
+
if mode == "max":
|
|
485
|
+
return functional.max_pool2d(x_torch, 2)
|
|
486
|
+
return functional.avg_pool2d(x_torch, 2, count_include_pad=False)
|
|
487
|
+
|
|
488
|
+
return run_torch
|
|
489
|
+
|
|
490
|
+
if library.name == "TensorFlow CPU eager" and library.available:
|
|
491
|
+
tf = importlib.import_module("tensorflow")
|
|
492
|
+
x_tf = tf.constant(np.transpose(left, (0, 2, 3, 1)))
|
|
493
|
+
|
|
494
|
+
def run_tf() -> Any:
|
|
495
|
+
if mode == "max":
|
|
496
|
+
return tf.nn.max_pool2d(x_tf, ksize=2, strides=2, padding="VALID").numpy()
|
|
497
|
+
return tf.nn.avg_pool2d(x_tf, ksize=2, strides=2, padding="VALID").numpy()
|
|
498
|
+
|
|
499
|
+
return run_tf
|
|
500
|
+
|
|
501
|
+
raise NotImplementedError
|
|
502
|
+
|
|
503
|
+
return build
|
|
504
|
+
|
|
505
|
+
|
|
324
506
|
def _chain_build(library: Library, left: np.ndarray, _: np.ndarray) -> Callable[[], Any]:
|
|
325
507
|
x = library.factory(left)
|
|
326
508
|
|
|
@@ -497,9 +679,20 @@ def _build_cases(sections: set[str]) -> list[BenchmarkCase]:
|
|
|
497
679
|
for op in ["sum", "mean"]:
|
|
498
680
|
add_case("reductions", op, shape, _reduction_op(op))
|
|
499
681
|
|
|
682
|
+
for shape in MATRIX_SHAPES:
|
|
683
|
+
add_case("reductions", "sum_axis1", shape, _reduction_op("sum", axis=1))
|
|
684
|
+
add_case("reductions", "mean_axis0", shape, _reduction_op("mean", axis=0))
|
|
685
|
+
|
|
500
686
|
for shape in MATRIX_SHAPES:
|
|
501
687
|
add_case("matmul", "matmul", shape, _matmul_build)
|
|
502
688
|
|
|
689
|
+
for shape in CONV2D_SHAPES:
|
|
690
|
+
add_case("conv2d", "conv2d_3x3_padding1", shape, _conv2d_build)
|
|
691
|
+
|
|
692
|
+
for shape in POOL2D_SHAPES:
|
|
693
|
+
add_case("pooling", "max_pool2d_2x2", shape, _pool2d_build("max"))
|
|
694
|
+
add_case("pooling", "avg_pool2d_2x2", shape, _pool2d_build("avg"))
|
|
695
|
+
|
|
503
696
|
for shape in [(1,), (128,), (1024,)]:
|
|
504
697
|
add_case("autograd", "scalar_backward", shape, _autograd_scalar_build)
|
|
505
698
|
add_case("autograd", "elementwise_backward", shape, _autograd_elementwise_build)
|
|
@@ -524,6 +717,10 @@ def _case_data(shape: tuple[int, ...], operation: str) -> tuple[np.ndarray, np.n
|
|
|
524
717
|
rng = np.random.default_rng(123)
|
|
525
718
|
left = rng.standard_normal(shape).astype(DTYPE)
|
|
526
719
|
right = rng.standard_normal(shape).astype(DTYPE)
|
|
720
|
+
if operation == "conv2d_3x3_padding1":
|
|
721
|
+
_, in_channels, _, _ = shape
|
|
722
|
+
out_channels = 4
|
|
723
|
+
right = rng.standard_normal((out_channels, in_channels, 3, 3)).astype(DTYPE)
|
|
527
724
|
if operation in {"div", "log"}:
|
|
528
725
|
left = np.abs(left) + 0.25
|
|
529
726
|
right = np.abs(right) + 0.25
|
|
@@ -741,7 +938,16 @@ def main() -> None:
|
|
|
741
938
|
parser.add_argument(
|
|
742
939
|
"--section",
|
|
743
940
|
action="append",
|
|
744
|
-
choices=[
|
|
941
|
+
choices=[
|
|
942
|
+
"elementwise",
|
|
943
|
+
"matmul",
|
|
944
|
+
"conv2d",
|
|
945
|
+
"pooling",
|
|
946
|
+
"reductions",
|
|
947
|
+
"activations",
|
|
948
|
+
"autograd",
|
|
949
|
+
"training_loop",
|
|
950
|
+
],
|
|
745
951
|
help="Benchmark section to run. Repeat for multiple sections. Defaults to all sections.",
|
|
746
952
|
)
|
|
747
953
|
parser.add_argument(
|
|
@@ -753,7 +959,16 @@ def main() -> None:
|
|
|
753
959
|
args = parser.parse_args()
|
|
754
960
|
sections = set(
|
|
755
961
|
args.section
|
|
756
|
-
or [
|
|
962
|
+
or [
|
|
963
|
+
"elementwise",
|
|
964
|
+
"matmul",
|
|
965
|
+
"conv2d",
|
|
966
|
+
"pooling",
|
|
967
|
+
"reductions",
|
|
968
|
+
"activations",
|
|
969
|
+
"autograd",
|
|
970
|
+
"training_loop",
|
|
971
|
+
]
|
|
757
972
|
)
|
|
758
973
|
report = run_benchmarks(sections, args.output)
|
|
759
974
|
print(report)
|