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.
Files changed (134) hide show
  1. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.github/workflows/publish.yml +10 -4
  2. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/CHANGELOG.md +24 -0
  3. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/PKG-INFO +29 -17
  4. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/README.md +28 -16
  5. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmark_all.py +2 -0
  6. tensorstudio-1.1.0/benchmarks/bench_conv2d.py +8 -0
  7. tensorstudio-1.1.0/benchmarks/bench_pooling.py +8 -0
  8. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/benchmark_report.py +220 -5
  9. tensorstudio-1.1.0/benchmarks/results.md +353 -0
  10. tensorstudio-1.1.0/benchmarks/results_conv2d.md +39 -0
  11. tensorstudio-1.1.0/benchmarks/results_pooling.md +45 -0
  12. tensorstudio-1.1.0/benchmarks/results_tensor_ops.md +309 -0
  13. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Autograde/index.md +1 -1
  14. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Benchmarks/index.md +25 -4
  15. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Hardware/cpu-backend.md +1 -1
  16. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Neural Networks/modules.md +40 -0
  17. tensorstudio-1.0.1/docs/nn.md → tensorstudio-1.1.0/docs/Neural Networks/nn.md +45 -0
  18. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Neural Networks/training.md +1 -1
  19. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Roadmap/index.md +7 -6
  20. {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Usage}/api.md +47 -5
  21. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Usage/serialization.md +1 -1
  22. {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Usage}/tensors.md +32 -1
  23. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/development.md +1 -1
  24. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/index.md +17 -13
  25. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/publishing.md +13 -10
  26. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/quickstart.md +42 -0
  27. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/serialization.md +1 -1
  28. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/ops.hpp +36 -0
  29. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/version.hpp +1 -1
  30. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/mkdocs.yml +9 -9
  31. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/pyproject.toml +1 -1
  32. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/_C.pyi +45 -8
  33. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/__init__.py +7 -0
  34. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/_version.py +1 -1
  35. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/nn/__init__.py +20 -1
  36. tensorstudio-1.1.0/python/tensorstudio/nn/functional.py +167 -0
  37. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/nn/losses.py +14 -1
  38. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/nn/modules.py +165 -0
  39. tensorstudio-1.1.0/python/tensorstudio/ops.py +262 -0
  40. tensorstudio-1.1.0/src/bindings/bind_ops.cpp +118 -0
  41. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bind_tensor.cpp +30 -4
  42. tensorstudio-1.1.0/src/core/ops.cpp +2356 -0
  43. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_autograd.py +82 -0
  44. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_import.py +1 -1
  45. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_nn.py +97 -0
  46. tensorstudio-1.1.0/tests/test_ops.py +314 -0
  47. tensorstudio-1.0.1/benchmarks/results.md +0 -311
  48. tensorstudio-1.0.1/benchmarks/results_tensor_ops.md +0 -285
  49. tensorstudio-1.0.1/docs/Usage/tensors.md +0 -139
  50. tensorstudio-1.0.1/python/tensorstudio/nn/functional.py +0 -75
  51. tensorstudio-1.0.1/python/tensorstudio/ops.py +0 -157
  52. tensorstudio-1.0.1/src/bindings/bind_ops.cpp +0 -40
  53. tensorstudio-1.0.1/src/core/ops.cpp +0 -1136
  54. tensorstudio-1.0.1/tests/test_ops.py +0 -63
  55. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.github/workflows/ci.yml +0 -0
  56. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.github/workflows/publish-testpypi.yml +0 -0
  57. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.github/workflows/wheels.yml +0 -0
  58. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.gitignore +0 -0
  59. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/.pre-commit-config.yaml +0 -0
  60. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/CMakeLists.txt +0 -0
  61. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/CONTRIBUTING.md +0 -0
  62. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/LICENSE +0 -0
  63. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/SECURITY.md +0 -0
  64. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/__init__.py +0 -0
  65. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_activations.py +0 -0
  66. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_autograd.py +0 -0
  67. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_elementwise.py +0 -0
  68. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_matmul.py +0 -0
  69. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_reductions.py +0 -0
  70. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_tensor_ops.py +0 -0
  71. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/bench_training_loop.py +0 -0
  72. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/results_matmul.md +0 -0
  73. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/benchmarks/results_reductions.md +0 -0
  74. {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Autograde}/autograd.md +0 -0
  75. {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Hardware}/linux.md +0 -0
  76. {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Hardware}/macos.md +0 -0
  77. {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Hardware}/windows.md +0 -0
  78. {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Usage}/data.md +0 -0
  79. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/docs/Usage/numpy-interop.md +0 -0
  80. {tensorstudio-1.0.1/docs → tensorstudio-1.1.0/docs/Usage}/optim.md +0 -0
  81. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/examples/basic_tensor_ops.py +0 -0
  82. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/examples/linear_regression.py +0 -0
  83. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/examples/save_load_model.py +0 -0
  84. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/examples/tiny_mlp.py +0 -0
  85. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/autograd.hpp +0 -0
  86. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/device.hpp +0 -0
  87. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/dtype.hpp +0 -0
  88. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/errors.hpp +0 -0
  89. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/module.hpp +0 -0
  90. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/optim.hpp +0 -0
  91. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/random.hpp +0 -0
  92. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/serialization.hpp +0 -0
  93. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/shape.hpp +0 -0
  94. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/storage.hpp +0 -0
  95. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/include/tensorstudio/tensor.hpp +0 -0
  96. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/autograd.py +0 -0
  97. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/data/__init__.py +0 -0
  98. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/data/dataloader.py +0 -0
  99. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/data/dataset.py +0 -0
  100. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/errors.py +0 -0
  101. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/grad_mode.py +0 -0
  102. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/__init__.py +0 -0
  103. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/adam.py +0 -0
  104. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/adamw.py +0 -0
  105. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/lr_scheduler.py +0 -0
  106. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/sgd.py +0 -0
  107. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/optim/utils.py +0 -0
  108. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/py.typed +0 -0
  109. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/serialization.py +0 -0
  110. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/tensor.py +0 -0
  111. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/python/tensorstudio/typing.py +0 -0
  112. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bind_autograd.cpp +0 -0
  113. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bind_nn.cpp +0 -0
  114. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bind_optim.cpp +0 -0
  115. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bindings.cpp +0 -0
  116. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/bindings/bindings.hpp +0 -0
  117. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/autograd.cpp +0 -0
  118. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/device.cpp +0 -0
  119. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/dtype.cpp +0 -0
  120. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/errors.cpp +0 -0
  121. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/module.cpp +0 -0
  122. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/optim.cpp +0 -0
  123. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/random.cpp +0 -0
  124. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/serialization.cpp +0 -0
  125. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/shape.cpp +0 -0
  126. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/storage.cpp +0 -0
  127. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/src/core/tensor.cpp +0 -0
  128. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/test_all.py +0 -0
  129. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_benchmark_report.py +0 -0
  130. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_data.py +0 -0
  131. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_numpy_interop.py +0 -0
  132. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_optim.py +0 -0
  133. {tensorstudio-1.0.1 → tensorstudio-1.1.0}/tests/test_serialization.py +0 -0
  134. {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.1
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.1` is a CPU-only stable API foundation. It is eager-only,
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 for `1.0.1`, TensorStudio beat NumPy on 13
239
- small activation/reduction benchmark cases and lost on 76 NumPy-comparable
240
- cases. Against PyTorch CPU `2.12.1+cpu`, TensorStudio won 71 local cases and
241
- lost 23. The strongest local wins were small eager operations where framework
242
- dispatch overhead dominates; larger matrix multiplication, larger transcendental
243
- activations, and larger autograd workloads remain faster in PyTorch and NumPy.
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.0018 ms | 0.0036 ms | 0.0575 ms | 2.0414x | 32.2110x |
252
- | `mean` | `(32,)` | 0.0016 ms | 0.0069 ms | 0.0112 ms | 4.4051x | 7.1262x |
253
- | `chain_relu` | `(128,)` | 0.0086 ms | 0.0039 ms | 0.0567 ms | 0.4488x | 6.5977x |
254
- | `matmul` | `(256, 256)` | 2.4215 ms | 0.4027 ms | 0.0832 ms | 0.1663x | 0.0343x |
255
- | `elementwise_backward` | `(1024,)` | 2.3527 ms | n/a | 0.1887 ms | n/a | 0.0802x |
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
- - No convolution layers yet.
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
- - Limited dtype casting.
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
- - Convolution ops
332
- - Dataset utilities
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.1` is a CPU-only stable API foundation. It is eager-only,
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 for `1.0.1`, TensorStudio beat NumPy on 13
181
- small activation/reduction benchmark cases and lost on 76 NumPy-comparable
182
- cases. Against PyTorch CPU `2.12.1+cpu`, TensorStudio won 71 local cases and
183
- lost 23. The strongest local wins were small eager operations where framework
184
- dispatch overhead dominates; larger matrix multiplication, larger transcendental
185
- activations, and larger autograd workloads remain faster in PyTorch and NumPy.
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.0018 ms | 0.0036 ms | 0.0575 ms | 2.0414x | 32.2110x |
194
- | `mean` | `(32,)` | 0.0016 ms | 0.0069 ms | 0.0112 ms | 4.4051x | 7.1262x |
195
- | `chain_relu` | `(128,)` | 0.0086 ms | 0.0039 ms | 0.0567 ms | 0.4488x | 6.5977x |
196
- | `matmul` | `(256, 256)` | 2.4215 ms | 0.4027 ms | 0.0832 ms | 0.1663x | 0.0343x |
197
- | `elementwise_backward` | `(1024,)` | 2.3527 ms | n/a | 0.1887 ms | n/a | 0.0802x |
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
- - No convolution layers yet.
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
- - Limited dtype casting.
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
- - Convolution ops
274
- - Dataset utilities
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
@@ -8,6 +8,8 @@ from benchmarks.benchmark_report import run_benchmarks
8
8
  ALL_SECTIONS = {
9
9
  "elementwise",
10
10
  "matmul",
11
+ "conv2d",
12
+ "pooling",
11
13
  "reductions",
12
14
  "activations",
13
15
  "autograd",
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from benchmark_report import run_benchmarks
6
+
7
+ if __name__ == "__main__":
8
+ print(run_benchmarks({"conv2d"}, Path(__file__).with_name("results_conv2d.md")))
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from benchmark_report import run_benchmarks
6
+
7
+ if __name__ == "__main__":
8
+ print(run_benchmarks({"pooling"}, Path(__file__).with_name("results_pooling.md")))
@@ -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(name: str) -> Callable[[Library, np.ndarray, np.ndarray], Callable[[], Any]]:
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
- return _finalize(x.sum() if name == "sum" else x.mean())
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=["elementwise", "matmul", "reductions", "activations", "autograd", "training_loop"],
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 ["elementwise", "matmul", "reductions", "activations", "autograd", "training_loop"]
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)