pytensor-ml 0.1.0__tar.gz → 0.2.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.
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/.github/workflows/mypy.yml +2 -2
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/.github/workflows/pre-commit.yml +2 -2
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/.github/workflows/release.yml +3 -3
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/.github/workflows/run_tests.yml +46 -6
- pytensor_ml-0.2.0/PKG-INFO +43 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/conda_envs/pytensor_ml.yml +6 -5
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/examples/mnist_feed_forward.ipynb +3 -3
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pyproject.toml +9 -1
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/__init__.py +1 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/_version.py +2 -2
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/base.py +9 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/dispatch/__init__.py +2 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/dispatch/jax/__init__.py +2 -0
- pytensor_ml-0.2.0/pytensor_ml/dispatch/jax/conv.py +72 -0
- pytensor_ml-0.2.0/pytensor_ml/dispatch/jax/pooling.py +60 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/dispatch/mlx/__init__.py +2 -0
- pytensor_ml-0.2.0/pytensor_ml/dispatch/mlx/conv.py +62 -0
- pytensor_ml-0.2.0/pytensor_ml/dispatch/mlx/pooling.py +57 -0
- pytensor_ml-0.2.0/pytensor_ml/dispatch/numba/__init__.py +3 -0
- pytensor_ml-0.2.0/pytensor_ml/dispatch/numba/conv.py +289 -0
- pytensor_ml-0.2.0/pytensor_ml/dispatch/pytorch/__init__.py +4 -0
- pytensor_ml-0.2.0/pytensor_ml/dispatch/pytorch/conv.py +101 -0
- pytensor_ml-0.2.0/pytensor_ml/dispatch/pytorch/pooling.py +56 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/json_serialize.py +4 -0
- pytensor_ml-0.2.0/pytensor_ml/layers/__init__.py +109 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/layers/attention.py +19 -1
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/layers/combinators.py +17 -0
- pytensor_ml-0.2.0/pytensor_ml/layers/conv.py +1174 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/layers/embedding.py +14 -10
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/layers/linear.py +20 -17
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/layers/norm.py +33 -26
- pytensor_ml-0.2.0/pytensor_ml/layers/padding.py +293 -0
- pytensor_ml-0.2.0/pytensor_ml/layers/recurrent.py +806 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/layers/transformer.py +27 -2
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/model.py +15 -11
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/optim/__init__.py +20 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/optim/base.py +63 -16
- pytensor_ml-0.2.0/pytensor_ml/optim/guards.py +238 -0
- pytensor_ml-0.2.0/pytensor_ml/optim/policy.py +134 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/optim/rules.py +73 -39
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/optim/train.py +23 -4
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/optim/transform.py +51 -1
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/params.py +31 -8
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/pretrained.py +82 -9
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/pytensorf/collect.py +34 -8
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/pytensorf/compile.py +24 -7
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/pytensorf/rewrite.py +33 -2
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/pytensorf/rng.py +12 -7
- pytensor_ml-0.2.0/pytensor_ml/rewriting/conv.py +44 -0
- pytensor_ml-0.2.0/pytensor_ml/rewriting/scan.py +188 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/serialize/base.py +6 -5
- pytensor_ml-0.2.0/pytensor_ml/state.py +438 -0
- pytensor_ml-0.2.0/tests/conftest.py +61 -0
- pytensor_ml-0.2.0/tests/dispatch/jax/test_conv.py +168 -0
- pytensor_ml-0.2.0/tests/dispatch/jax/test_pool.py +54 -0
- pytensor_ml-0.2.0/tests/dispatch/mlx/test_conv.py +143 -0
- pytensor_ml-0.2.0/tests/dispatch/mlx/test_pool.py +54 -0
- pytensor_ml-0.2.0/tests/dispatch/pytorch/test_conv.py +120 -0
- pytensor_ml-0.2.0/tests/dispatch/pytorch/test_pool.py +59 -0
- pytensor_ml-0.2.0/tests/optim/test_composition.py +191 -0
- pytensor_ml-0.2.0/tests/optim/test_guards.py +303 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/optim/test_learning_rate.py +73 -0
- pytensor_ml-0.2.0/tests/optim/test_policy.py +226 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/optim/test_rules.py +44 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/optim/test_schedules.py +19 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/optim/test_training.py +75 -6
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/optim/test_transform.py +68 -2
- pytensor_ml-0.2.0/tests/rewriting/__init__.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/rewriting/test_layers.py +3 -3
- pytensor_ml-0.2.0/tests/rewriting/test_scan.py +424 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_collect.py +51 -2
- pytensor_ml-0.2.0/tests/test_conv.py +897 -0
- pytensor_ml-0.2.0/tests/test_conv_transpose.py +231 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_layers.py +282 -42
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_model.py +71 -9
- pytensor_ml-0.2.0/tests/test_padding.py +132 -0
- pytensor_ml-0.2.0/tests/test_pool.py +190 -0
- pytensor_ml-0.2.0/tests/test_pretrained.py +303 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_pytensorf.py +169 -0
- pytensor_ml-0.2.0/tests/test_recurrent.py +1279 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_serialize.py +8 -2
- pytensor_ml-0.2.0/tests/test_state.py +470 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_transformer.py +65 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_workflow_groups.py +22 -12
- pytensor_ml-0.1.0/PKG-INFO +0 -243
- pytensor_ml-0.1.0/pytensor_ml/layers/__init__.py +0 -40
- pytensor_ml-0.1.0/pytensor_ml/state.py +0 -230
- pytensor_ml-0.1.0/tests/conftest.py +0 -43
- pytensor_ml-0.1.0/tests/test_pretrained.py +0 -145
- pytensor_ml-0.1.0/tests/test_state.py +0 -219
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/.gitattributes +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/.gitignore +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/.pre-commit-config.yaml +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/LICENSE +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/README.md +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/conda_envs/pytensor_ml-gpu_jax.yml +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/activations.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/checkpoint.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/dispatch/jax/attention.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/dispatch/mlx/attention.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/layers/dropout.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/loss.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/optim/alias.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/optim/clipping.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/optim/schedules.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/pytensorf/__init__.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/rewriting/__init__.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/rewriting/layers.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/serialize/__init__.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/serialize/blockwise.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/serialize/builders.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/serialize/elemwise.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/serialize/scalar.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/serialize/scan.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/pytensor_ml/util.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/scripts/run_mypy.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/__init__.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/dispatch/__init__.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/dispatch/jax/__init__.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/dispatch/jax/test_attention.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/dispatch/jax/test_basic.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/dispatch/mlx/__init__.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/dispatch/mlx/test_attention.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/dispatch/mlx/test_basic.py +0 -0
- {pytensor_ml-0.1.0/tests/optim → pytensor_ml-0.2.0/tests/dispatch/pytorch}/__init__.py +0 -0
- {pytensor_ml-0.1.0/tests/rewriting → pytensor_ml-0.2.0/tests/optim}/__init__.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/optim/conftest.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/optim/test_clipping.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/rewriting/conftest.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_activations.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_attention.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_base.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_checkpoint.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_loss.py +0 -0
- {pytensor_ml-0.1.0 → pytensor_ml-0.2.0}/tests/test_util.py +0 -0
|
@@ -25,8 +25,8 @@ jobs:
|
|
|
25
25
|
shell: bash -l {0}
|
|
26
26
|
|
|
27
27
|
steps:
|
|
28
|
-
- uses: actions/checkout@
|
|
29
|
-
- uses: actions/cache@
|
|
28
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
29
|
+
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
30
30
|
env:
|
|
31
31
|
CACHE_NUMBER: 0
|
|
32
32
|
with:
|
|
@@ -11,6 +11,6 @@ jobs:
|
|
|
11
11
|
env:
|
|
12
12
|
SKIP: no-commit-to-branch
|
|
13
13
|
steps:
|
|
14
|
-
- uses: actions/checkout@
|
|
15
|
-
- uses: actions/setup-python@
|
|
14
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
15
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
16
16
|
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
|
@@ -18,12 +18,12 @@ jobs:
|
|
|
18
18
|
attestations: write
|
|
19
19
|
id-token: write
|
|
20
20
|
steps:
|
|
21
|
-
- uses: actions/checkout@
|
|
21
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
22
22
|
with:
|
|
23
23
|
# hatch-vcs derives the version from git tags, so the full history is required.
|
|
24
24
|
fetch-depth: 0
|
|
25
25
|
persist-credentials: false
|
|
26
|
-
- uses: hynek/build-and-inspect-python-package@
|
|
26
|
+
- uses: hynek/build-and-inspect-python-package@2abe76da66d0a6a4a227101f9348ee855797cfa5 # v3.0.1
|
|
27
27
|
with:
|
|
28
28
|
# Prove that the packages were built in the context of this workflow.
|
|
29
29
|
attest-build-provenance-github: true
|
|
@@ -47,4 +47,4 @@ jobs:
|
|
|
47
47
|
name: Packages
|
|
48
48
|
path: dist
|
|
49
49
|
- name: Publish Package to PyPI
|
|
50
|
-
uses: pypa/gh-action-pypi-publish@
|
|
50
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
@@ -20,17 +20,26 @@ jobs:
|
|
|
20
20
|
matrix:
|
|
21
21
|
os: [ubuntu-latest]
|
|
22
22
|
python-version: ["3.12"]
|
|
23
|
+
# Off by default, flipped to 1 by the per-backend jobs under `include`. A core job installs no
|
|
24
|
+
# backend at all, which is what proves the library runs without one.
|
|
25
|
+
install-jax: [0]
|
|
26
|
+
install-mlx: [0]
|
|
27
|
+
install-torch: [0]
|
|
23
28
|
# Groups are balanced on measured cold-compile-cache time, which is what a fresh runner pays.
|
|
24
29
|
# tests/test_workflow_groups.py fails if a test file is missing from every group or listed twice.
|
|
25
30
|
test-subset:
|
|
26
31
|
- name: attention
|
|
27
32
|
paths: |
|
|
28
33
|
tests/test_attention.py
|
|
29
|
-
tests/dispatch/
|
|
30
34
|
- name: layers
|
|
31
35
|
paths: |
|
|
32
36
|
tests/test_transformer.py
|
|
33
37
|
tests/test_layers.py
|
|
38
|
+
tests/test_recurrent.py
|
|
39
|
+
tests/test_conv.py
|
|
40
|
+
tests/test_conv_transpose.py
|
|
41
|
+
tests/test_padding.py
|
|
42
|
+
tests/test_pool.py
|
|
34
43
|
tests/rewriting/
|
|
35
44
|
- name: optim
|
|
36
45
|
paths: |
|
|
@@ -51,7 +60,27 @@ jobs:
|
|
|
51
60
|
tests/test_workflow_groups.py
|
|
52
61
|
# Windows runners are roughly twice as slow, and almost everything here is platform independent.
|
|
53
62
|
# It runs one job over the parts that are not: file IO, paths, and compiling a graph to train.
|
|
63
|
+
# One job per backend, each installing only that backend. They live here rather than in
|
|
64
|
+
# `test-subset` because a matrix entry cannot change `os`, and mlx needs macOS.
|
|
54
65
|
include:
|
|
66
|
+
- os: ubuntu-latest
|
|
67
|
+
python-version: "3.12"
|
|
68
|
+
install-jax: 1
|
|
69
|
+
test-subset:
|
|
70
|
+
name: dispatch-jax
|
|
71
|
+
paths: tests/dispatch/jax/
|
|
72
|
+
- os: macos-15
|
|
73
|
+
python-version: "3.12"
|
|
74
|
+
install-mlx: 1
|
|
75
|
+
test-subset:
|
|
76
|
+
name: dispatch-mlx
|
|
77
|
+
paths: tests/dispatch/mlx/
|
|
78
|
+
- os: ubuntu-latest
|
|
79
|
+
python-version: "3.12"
|
|
80
|
+
install-torch: 1
|
|
81
|
+
test-subset:
|
|
82
|
+
name: dispatch-torch
|
|
83
|
+
paths: tests/dispatch/pytorch/
|
|
55
84
|
- os: windows-latest
|
|
56
85
|
python-version: "3.12"
|
|
57
86
|
test-subset:
|
|
@@ -71,8 +100,8 @@ jobs:
|
|
|
71
100
|
shell: bash -l {0}
|
|
72
101
|
|
|
73
102
|
steps:
|
|
74
|
-
- uses: actions/checkout@
|
|
75
|
-
- uses: actions/cache@
|
|
103
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
104
|
+
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
76
105
|
env:
|
|
77
106
|
CACHE_NUMBER: 0
|
|
78
107
|
with:
|
|
@@ -80,7 +109,7 @@ jobs:
|
|
|
80
109
|
key: ${{ runner.os }}-py${{matrix.python-version}}-conda-${{ env.CACHE_NUMBER }}-${{
|
|
81
110
|
hashFiles('conda_envs/pytensor_ml.yml') }}
|
|
82
111
|
- name: Cache multiple paths
|
|
83
|
-
uses: actions/cache@
|
|
112
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
84
113
|
env:
|
|
85
114
|
# Increase this value to reset cache if pyproject.toml has not changed
|
|
86
115
|
CACHE_NUMBER: 0
|
|
@@ -92,7 +121,7 @@ jobs:
|
|
|
92
121
|
key: ${{ runner.os }}-build-${{ matrix.python-version }}-${{ env.CACHE_NUMBER }}-${{
|
|
93
122
|
hashFiles('pyproject.toml') }}
|
|
94
123
|
- name: Cache the pytensor compile directory
|
|
95
|
-
uses: actions/cache@
|
|
124
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
96
125
|
env:
|
|
97
126
|
CACHE_NUMBER: 0
|
|
98
127
|
with:
|
|
@@ -115,6 +144,17 @@ jobs:
|
|
|
115
144
|
# IMPORTANT: This may break caching of conda packages! See https://github.com/conda-incubator/setup-miniconda/issues/267
|
|
116
145
|
use-only-tar-bz2: false
|
|
117
146
|
|
|
147
|
+
- name: Install the backend this job tests
|
|
148
|
+
run: |
|
|
149
|
+
conda activate pytensor_ml
|
|
150
|
+
if [[ $INSTALL_JAX == "1" ]]; then pip install "jax>=0.8,<0.9.1" jaxlib; fi
|
|
151
|
+
if [[ $INSTALL_MLX == "1" ]]; then pip install "mlx>=0.30,<0.32"; fi
|
|
152
|
+
if [[ $INSTALL_TORCH == "1" ]]; then pip install torch --index-url https://download.pytorch.org/whl/cpu; fi
|
|
153
|
+
env:
|
|
154
|
+
INSTALL_JAX: ${{ matrix.install-jax }}
|
|
155
|
+
INSTALL_MLX: ${{ matrix.install-mlx }}
|
|
156
|
+
INSTALL_TORCH: ${{ matrix.install-torch }}
|
|
157
|
+
|
|
118
158
|
- name: Install current branch
|
|
119
159
|
run: |
|
|
120
160
|
conda activate pytensor_ml
|
|
@@ -125,7 +165,7 @@ jobs:
|
|
|
125
165
|
run: |
|
|
126
166
|
python -m pytest -vv --durations=25 --cov=pytensor_ml --cov-report=xml --no-cov-on-fail --cov-report term $TEST_SUBSET
|
|
127
167
|
- name: Upload coverage to Codecov
|
|
128
|
-
uses: codecov/codecov-action@
|
|
168
|
+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
129
169
|
with:
|
|
130
170
|
token: ${{ secrets.CODECOV_TOKEN }} # use token for more robust uploads
|
|
131
171
|
env_vars: TEST_SUBSET
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pytensor_ml
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A(nother) deep learning library, built on top of Pytensor
|
|
5
|
+
Project-URL: Homepage, https://github.com/pymc-devs/pytensor_ml
|
|
6
|
+
Project-URL: Source, https://github.com/pymc-devs/pytensor_ml
|
|
7
|
+
Project-URL: Tracker, https://github.com/pymc-devs/pytensor_ml/issues
|
|
8
|
+
Author: Ricardo Vieira
|
|
9
|
+
Author-email: Jesse Grabowski <jessegrabowski@gmail.com>
|
|
10
|
+
License-Expression: Apache-2.0
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: autograd,deep learning,gradient descent,neural network,pymc
|
|
13
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX
|
|
18
|
+
Classifier: Operating System :: Unix
|
|
19
|
+
Classifier: Programming Language :: Python
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Requires-Dist: numpy
|
|
27
|
+
Requires-Dist: pytensor<4.0.0,>=3.2.3
|
|
28
|
+
Requires-Dist: safetensors
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: jax; extra == 'dev'
|
|
31
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
32
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
35
|
+
Requires-Dist: pyyaml; extra == 'dev'
|
|
36
|
+
Requires-Dist: scikit-learn; extra == 'dev'
|
|
37
|
+
Provides-Extra: examples
|
|
38
|
+
Requires-Dist: matplotlib; extra == 'examples'
|
|
39
|
+
Requires-Dist: tqdm; extra == 'examples'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# pytensor_ml
|
|
43
|
+
Neural network package built on Pytensor
|
|
@@ -5,7 +5,7 @@ channels:
|
|
|
5
5
|
|
|
6
6
|
dependencies:
|
|
7
7
|
- python>=3.12
|
|
8
|
-
- pytensor>=3.
|
|
8
|
+
- pytensor>=3.3.0,<3.4.0
|
|
9
9
|
- numpy
|
|
10
10
|
- safetensors
|
|
11
11
|
- scikit-learn
|
|
@@ -29,9 +29,10 @@ dependencies:
|
|
|
29
29
|
|
|
30
30
|
# developer tools
|
|
31
31
|
- pre-commit
|
|
32
|
-
|
|
32
|
+
# Pinned rather than floated: a local run that disagrees with CI about the version is a local run
|
|
33
|
+
# that cannot predict CI, and both of these have shipped errors the other version does not report.
|
|
34
|
+
- mypy>=2.3.1,<2.4.0
|
|
33
35
|
|
|
34
|
-
#
|
|
36
|
+
# No backend goes here. Each backend's dispatch tests get their own CI job that installs just that
|
|
37
|
+
# one, so the core jobs stay light and prove the library works with no backend present at all.
|
|
35
38
|
- pip
|
|
36
|
-
- pip:
|
|
37
|
-
- jax
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"from sklearn.preprocessing import MinMaxScaler, OneHotEncoder\n",
|
|
38
38
|
"\n",
|
|
39
39
|
"from pytensor_ml.activations import LeakyReLU\n",
|
|
40
|
-
"from pytensor_ml.layers import
|
|
40
|
+
"from pytensor_ml.layers import BatchNorm, Dropout, Input, Linear, Sequential\n",
|
|
41
41
|
"from pytensor_ml.loss import CrossEntropy\n",
|
|
42
42
|
"from pytensor_ml.model import Model\n",
|
|
43
43
|
"from pytensor_ml.optim import adagrad\n",
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
"source": [
|
|
76
76
|
"prediction_network = Sequential(\n",
|
|
77
77
|
" Linear(\"Linear_1\", n_in=64, n_out=256),\n",
|
|
78
|
-
"
|
|
78
|
+
" BatchNorm(\"BatchNorm_1\"),\n",
|
|
79
79
|
" Dropout(\"Dropout_1\", p=0.5),\n",
|
|
80
80
|
" LeakyReLU(),\n",
|
|
81
81
|
" Linear(\"Linear_2\", n_in=256, n_out=128),\n",
|
|
82
|
-
"
|
|
82
|
+
" BatchNorm(\"BatchNorm_2\"),\n",
|
|
83
83
|
" Dropout(\"Dropout_2\", p=0.5),\n",
|
|
84
84
|
" LeakyReLU(),\n",
|
|
85
85
|
" Linear(\"Logits\", n_in=128, n_out=10),\n",
|
|
@@ -10,7 +10,8 @@ description = "A(nother) deep learning library, built on top of Pytensor"
|
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.12"
|
|
12
12
|
authors = [{name="Jesse Grabowski", email='jessegrabowski@gmail.com'}, {name="Ricardo Vieira"}]
|
|
13
|
-
license =
|
|
13
|
+
license = "Apache-2.0"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
14
15
|
classifiers = [
|
|
15
16
|
"Development Status :: 2 - Pre-Alpha",
|
|
16
17
|
"Intended Audience :: Science/Research",
|
|
@@ -76,6 +77,13 @@ filterwarnings = [
|
|
|
76
77
|
# warnings filter per-test and undoes it. Numba emits it from the numba
|
|
77
78
|
# linker, which is pytensor 3's default.
|
|
78
79
|
"ignore::numba.core.errors.NumbaPerformanceWarning",
|
|
80
|
+
# torch.compile pulls in inductor, which imports torch.utils.mkldnn, whose class bodies still call
|
|
81
|
+
# the deprecated torch.jit.script_method. Raising here aborts the import half-done, so every later
|
|
82
|
+
# torch-mode test retries it and fails the same way. The message past this prefix depends on the
|
|
83
|
+
# Python version.
|
|
84
|
+
"ignore:`torch.jit.script_method` is:DeprecationWarning",
|
|
85
|
+
# pytensor hands torch its constants as the read-only numpy arrays it stores them in.
|
|
86
|
+
"ignore:The given NumPy array is not writable:UserWarning",
|
|
79
87
|
]
|
|
80
88
|
|
|
81
89
|
[tool.ruff]
|
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
4
|
import pytensor_ml.dispatch # installs the lazy backend-dispatch registration hook
|
|
5
|
+
import pytensor_ml.rewriting.conv # registers the convolution rewrites into pytensor's databases
|
|
5
6
|
|
|
6
7
|
from pytensor_ml._version import __version__
|
|
7
8
|
from pytensor_ml.checkpoint import load_state, save_state
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -7,6 +7,15 @@ from pytensor.compile.builders import SymbolicOp
|
|
|
7
7
|
from pytensor.tensor.variable import TensorVariable
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def _check_input_rank(X: TensorVariable, name: str, n_spatial: int) -> None:
|
|
11
|
+
"""Reject an input whose rank is not batch, one axis per spatial dimension, then channels."""
|
|
12
|
+
if X.ndim != n_spatial + 2:
|
|
13
|
+
raise ValueError(
|
|
14
|
+
f"{name} takes an input of shape (batch, {', '.join(['spatial'] * n_spatial)}, channels), "
|
|
15
|
+
f"so it needs a {n_spatial + 2}-dimensional input; got a {X.ndim}-dimensional one."
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
10
19
|
class Layer(ABC):
|
|
11
20
|
"""Base class for the objects that build layer graphs. Defined here, not in ``pytensor_ml.layers``, so
|
|
12
21
|
that ``pytensor_ml.activations`` can subclass it without a circular import."""
|
|
@@ -9,6 +9,8 @@ import sys
|
|
|
9
9
|
_REGISTRATIONS = {
|
|
10
10
|
"pytensor.link.jax.dispatch": "pytensor_ml.dispatch.jax",
|
|
11
11
|
"pytensor.link.mlx.dispatch": "pytensor_ml.dispatch.mlx",
|
|
12
|
+
"pytensor.link.numba.dispatch": "pytensor_ml.dispatch.numba",
|
|
13
|
+
"pytensor.link.pytorch.dispatch": "pytensor_ml.dispatch.pytorch",
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
# Imported for its side effect: registering pytensor_ml's JAX funcify dispatches. One submodule per
|
|
2
2
|
# marker op that gets a kernel, mirroring the layout under pytensor_ml/layers.
|
|
3
3
|
import pytensor_ml.dispatch.jax.attention
|
|
4
|
+
import pytensor_ml.dispatch.jax.conv
|
|
5
|
+
import pytensor_ml.dispatch.jax.pooling
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import jax
|
|
2
|
+
|
|
3
|
+
from pytensor.link.jax.dispatch import jax_funcify
|
|
4
|
+
|
|
5
|
+
from pytensor_ml.layers.conv import ConvLayer, ConvLayerGrad
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _spatial_letters(n_spatial: int) -> str:
|
|
9
|
+
"""Name each spatial axis with a distinct letter, which is how jax takes a convolution's layout."""
|
|
10
|
+
letters = "XYZUVW"
|
|
11
|
+
if n_spatial > len(letters):
|
|
12
|
+
raise ValueError(
|
|
13
|
+
f"A convolution over {n_spatial} spatial axes has more axes than there are letters to name "
|
|
14
|
+
f"them with; {len(letters)} is the most jax supports here."
|
|
15
|
+
)
|
|
16
|
+
return letters[:n_spatial]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _convolution(op):
|
|
20
|
+
"""The convolution both dispatches here run, so the gradient differentiates what the forward does."""
|
|
21
|
+
spatial = _spatial_letters(len(op.kernel_size))
|
|
22
|
+
# Our layouts are already the ones jax names: the input is channels-last and the kernel is taps,
|
|
23
|
+
# then input channels, then output. Nothing is transposed on the way in or out.
|
|
24
|
+
dimension_numbers = (f"N{spatial}C", f"{spatial}IO", f"N{spatial}C")
|
|
25
|
+
|
|
26
|
+
def convolve(X, W):
|
|
27
|
+
# Padding is a `pt.pad` node ahead of the op, so what arrives here is already padded.
|
|
28
|
+
return jax.lax.conv_general_dilated(
|
|
29
|
+
X,
|
|
30
|
+
W,
|
|
31
|
+
window_strides=op.stride,
|
|
32
|
+
padding="VALID",
|
|
33
|
+
rhs_dilation=op.dilation,
|
|
34
|
+
dimension_numbers=dimension_numbers,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return convolve
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@jax_funcify.register(ConvLayer)
|
|
41
|
+
def jax_funcify_ConvLayer(op, node=None, **kwargs):
|
|
42
|
+
"""Dispatch the convolution marker to ``jax.lax.conv_general_dilated`` (XLA/cuDNN)."""
|
|
43
|
+
convolve = _convolution(op)
|
|
44
|
+
|
|
45
|
+
def conv(X, W, *bias):
|
|
46
|
+
out = convolve(X, W)
|
|
47
|
+
return out + bias[0] if bias else out
|
|
48
|
+
|
|
49
|
+
return conv
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@jax_funcify.register(ConvLayerGrad)
|
|
53
|
+
def jax_funcify_ConvLayerGrad(op, node=None, **kwargs):
|
|
54
|
+
"""Let jax differentiate its own convolution rather than spelling the two gradients out."""
|
|
55
|
+
convolve = _convolution(op)
|
|
56
|
+
compute_dX, compute_dW = op.compute_dX, op.compute_dW
|
|
57
|
+
|
|
58
|
+
def conv_grad(X, W, cotangent):
|
|
59
|
+
# The vjp is taken only over the inputs whose gradient is wanted; the rest are closed over as
|
|
60
|
+
# constants, so jax never differentiates toward a gradient the graph will not read.
|
|
61
|
+
if compute_dX and compute_dW:
|
|
62
|
+
_, pullback = jax.vjp(convolve, X, W)
|
|
63
|
+
return tuple(pullback(cotangent))
|
|
64
|
+
# An op with one output is dispatched to a function returning that output, not a list of one.
|
|
65
|
+
if compute_dX:
|
|
66
|
+
_, pullback = jax.vjp(lambda x: convolve(x, W), X)
|
|
67
|
+
else:
|
|
68
|
+
_, pullback = jax.vjp(lambda w: convolve(X, w), W)
|
|
69
|
+
(gradient,) = pullback(cotangent)
|
|
70
|
+
return gradient
|
|
71
|
+
|
|
72
|
+
return conv_grad
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
from functools import partial
|
|
4
|
+
|
|
5
|
+
import jax
|
|
6
|
+
import jax.numpy as jnp
|
|
7
|
+
|
|
8
|
+
from pytensor.link.jax.dispatch import jax_funcify
|
|
9
|
+
|
|
10
|
+
from pytensor_ml.layers.conv import PoolLayer, PoolLayerGrad
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _pooling(op):
|
|
14
|
+
"""The pooling both dispatches here run, so the gradient differentiates what the forward does."""
|
|
15
|
+
# Windows span the spatial axes only; batch and channels take a width of one and are never reduced.
|
|
16
|
+
window = (1, *op.kernel_size, 1)
|
|
17
|
+
strides = (1, *op.stride, 1)
|
|
18
|
+
dilation = (1, *op.dilation, 1)
|
|
19
|
+
taps = math.prod(op.kernel_size)
|
|
20
|
+
|
|
21
|
+
# Padding is a `pt.pad` node ahead of the op, so what arrives here is already padded.
|
|
22
|
+
reduce_window = partial(
|
|
23
|
+
jax.lax.reduce_window,
|
|
24
|
+
window_dimensions=window,
|
|
25
|
+
window_strides=strides,
|
|
26
|
+
padding="VALID",
|
|
27
|
+
window_dilation=dilation,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if op.reduction == "max":
|
|
31
|
+
|
|
32
|
+
def pool(X):
|
|
33
|
+
return reduce_window(X, -jnp.inf, jax.lax.max)
|
|
34
|
+
|
|
35
|
+
else:
|
|
36
|
+
|
|
37
|
+
def pool(X):
|
|
38
|
+
return reduce_window(X, 0.0, jax.lax.add) / taps
|
|
39
|
+
|
|
40
|
+
return pool
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@jax_funcify.register(PoolLayer)
|
|
44
|
+
def jax_funcify_PoolLayer(op, node=None, **kwargs):
|
|
45
|
+
"""Dispatch the pooling marker to ``jax.lax.reduce_window``."""
|
|
46
|
+
return _pooling(op)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@jax_funcify.register(PoolLayerGrad)
|
|
50
|
+
def jax_funcify_PoolLayerGrad(op, node=None, **kwargs):
|
|
51
|
+
"""Let jax differentiate its own pooling rather than spelling the routing out."""
|
|
52
|
+
pool = _pooling(op)
|
|
53
|
+
|
|
54
|
+
def pool_grad(X, cotangent):
|
|
55
|
+
# An op with one output is dispatched to a function returning that output, not a list of one.
|
|
56
|
+
_, pullback = jax.vjp(pool, X)
|
|
57
|
+
(gradient,) = pullback(cotangent)
|
|
58
|
+
return gradient
|
|
59
|
+
|
|
60
|
+
return pool_grad
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
# Imported for its side effect: registering pytensor_ml's MLX funcify dispatches. One submodule per
|
|
2
2
|
# marker op that gets a kernel, mirroring the layout under pytensor_ml/layers.
|
|
3
3
|
import pytensor_ml.dispatch.mlx.attention
|
|
4
|
+
import pytensor_ml.dispatch.mlx.conv
|
|
5
|
+
import pytensor_ml.dispatch.mlx.pooling
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import mlx.core as mx
|
|
2
|
+
|
|
3
|
+
from pytensor.link.mlx.dispatch import mlx_funcify
|
|
4
|
+
|
|
5
|
+
from pytensor_ml.layers.conv import ConvLayer, ConvLayerGrad
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _convolution(op):
|
|
9
|
+
"""The convolution both dispatches here run, so the gradient differentiates what the forward does."""
|
|
10
|
+
no_padding = (0,) * len(op.kernel_size)
|
|
11
|
+
|
|
12
|
+
def convolve(X, W):
|
|
13
|
+
# mlx takes the input channels-last as we store it, but wants the kernel's output channels
|
|
14
|
+
# leading rather than trailing. Padding is a `pt.pad` node ahead of the op.
|
|
15
|
+
return mx.conv_general(
|
|
16
|
+
X,
|
|
17
|
+
mx.moveaxis(W, -1, 0),
|
|
18
|
+
stride=op.stride,
|
|
19
|
+
padding=no_padding,
|
|
20
|
+
kernel_dilation=op.dilation,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return convolve
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@mlx_funcify.register(ConvLayer)
|
|
27
|
+
def mlx_funcify_ConvLayer(op, node=None, **kwargs):
|
|
28
|
+
"""Dispatch the convolution marker to ``mx.conv_general`` (fused Metal kernel)."""
|
|
29
|
+
convolve = _convolution(op)
|
|
30
|
+
|
|
31
|
+
def conv(X, W, *bias):
|
|
32
|
+
out = convolve(X, W)
|
|
33
|
+
return out + bias[0] if bias else out
|
|
34
|
+
|
|
35
|
+
return conv
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@mlx_funcify.register(ConvLayerGrad)
|
|
39
|
+
def mlx_funcify_ConvLayerGrad(op, node=None, **kwargs):
|
|
40
|
+
"""Let mlx differentiate its own convolution rather than spelling the two gradients out.
|
|
41
|
+
|
|
42
|
+
The forward value the pullback is built from is never evaluated, and mlx is lazy, so it costs
|
|
43
|
+
nothing beyond the intermediates the gradients need anyway.
|
|
44
|
+
"""
|
|
45
|
+
convolve = _convolution(op)
|
|
46
|
+
compute_dX, compute_dW = op.compute_dX, op.compute_dW
|
|
47
|
+
|
|
48
|
+
def conv_grad(X, W, cotangent):
|
|
49
|
+
# The vjp is taken only over the inputs whose gradient is wanted; the rest are closed over as
|
|
50
|
+
# constants, so mlx never differentiates toward a gradient the graph will not read.
|
|
51
|
+
if compute_dX and compute_dW:
|
|
52
|
+
_, pullback = mx.vjp(convolve, [X, W], [cotangent])
|
|
53
|
+
return tuple(pullback)
|
|
54
|
+
# An op with one output is dispatched to a function returning that output, not a list of one.
|
|
55
|
+
if compute_dX:
|
|
56
|
+
_, pullback = mx.vjp(lambda x: convolve(x, W), [X], [cotangent])
|
|
57
|
+
else:
|
|
58
|
+
_, pullback = mx.vjp(lambda w: convolve(X, w), [W], [cotangent])
|
|
59
|
+
(gradient,) = pullback
|
|
60
|
+
return gradient
|
|
61
|
+
|
|
62
|
+
return conv_grad
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import mlx.core as mx
|
|
2
|
+
import mlx.nn as mnn
|
|
3
|
+
|
|
4
|
+
from pytensor.link.mlx.dispatch import mlx_funcify
|
|
5
|
+
|
|
6
|
+
from pytensor_ml.layers.conv import PoolLayer, PoolLayerGrad
|
|
7
|
+
|
|
8
|
+
_POOLS = {
|
|
9
|
+
("max", 1): mnn.MaxPool1d,
|
|
10
|
+
("max", 2): mnn.MaxPool2d,
|
|
11
|
+
("max", 3): mnn.MaxPool3d,
|
|
12
|
+
("mean", 1): mnn.AvgPool1d,
|
|
13
|
+
("mean", 2): mnn.AvgPool2d,
|
|
14
|
+
("mean", 3): mnn.AvgPool3d,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _pooling(op):
|
|
19
|
+
"""The pooling both dispatches here run, so the gradient differentiates what the forward does."""
|
|
20
|
+
n_spatial = len(op.kernel_size)
|
|
21
|
+
if any(spacing != 1 for spacing in op.dilation):
|
|
22
|
+
raise NotImplementedError(
|
|
23
|
+
f"mlx pools over adjacent positions only, so it cannot take the dilation {op.dilation}. "
|
|
24
|
+
"Pool without dilation, or run this graph on jax or the default backend."
|
|
25
|
+
)
|
|
26
|
+
if (op.reduction, n_spatial) not in _POOLS:
|
|
27
|
+
raise NotImplementedError(
|
|
28
|
+
f"mlx has no pooling over {n_spatial} spatial axes; it goes up to three."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# mlx pools channels-last as we store it, and takes no padding here because a `pt.pad` node ahead
|
|
32
|
+
# of the op has already applied it.
|
|
33
|
+
return _POOLS[(op.reduction, n_spatial)](kernel_size=op.kernel_size, stride=op.stride)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@mlx_funcify.register(PoolLayer)
|
|
37
|
+
def mlx_funcify_PoolLayer(op, node=None, **kwargs):
|
|
38
|
+
"""Dispatch the pooling marker to mlx's own pooling (a fused Metal kernel)."""
|
|
39
|
+
pool = _pooling(op)
|
|
40
|
+
|
|
41
|
+
def pooled(X):
|
|
42
|
+
return pool(X)
|
|
43
|
+
|
|
44
|
+
return pooled
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@mlx_funcify.register(PoolLayerGrad)
|
|
48
|
+
def mlx_funcify_PoolLayerGrad(op, node=None, **kwargs):
|
|
49
|
+
"""Let mlx differentiate its own pooling rather than spelling the routing out."""
|
|
50
|
+
pool = _pooling(op)
|
|
51
|
+
|
|
52
|
+
def pool_grad(X, cotangent):
|
|
53
|
+
# An op with one output is dispatched to a function returning that output, not a list of one.
|
|
54
|
+
_, pullback = mx.vjp(pool, [X], [cotangent])
|
|
55
|
+
return pullback[0]
|
|
56
|
+
|
|
57
|
+
return pool_grad
|