pytensor-ml 0.1.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/.github/workflows/mypy.yml +2 -2
  2. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/.github/workflows/pre-commit.yml +2 -2
  3. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/.github/workflows/release.yml +3 -3
  4. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/.github/workflows/run_tests.yml +46 -6
  5. pytensor_ml-0.2.1/PKG-INFO +43 -0
  6. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/conda_envs/pytensor_ml.yml +6 -5
  7. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/examples/mnist_feed_forward.ipynb +3 -3
  8. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pyproject.toml +12 -4
  9. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/__init__.py +1 -0
  10. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/_version.py +2 -2
  11. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/base.py +9 -0
  12. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/dispatch/__init__.py +2 -0
  13. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/dispatch/jax/__init__.py +2 -0
  14. pytensor_ml-0.2.1/pytensor_ml/dispatch/jax/conv.py +72 -0
  15. pytensor_ml-0.2.1/pytensor_ml/dispatch/jax/pooling.py +60 -0
  16. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/dispatch/mlx/__init__.py +2 -0
  17. pytensor_ml-0.2.1/pytensor_ml/dispatch/mlx/conv.py +62 -0
  18. pytensor_ml-0.2.1/pytensor_ml/dispatch/mlx/pooling.py +57 -0
  19. pytensor_ml-0.2.1/pytensor_ml/dispatch/numba/__init__.py +3 -0
  20. pytensor_ml-0.2.1/pytensor_ml/dispatch/numba/conv.py +289 -0
  21. pytensor_ml-0.2.1/pytensor_ml/dispatch/pytorch/__init__.py +4 -0
  22. pytensor_ml-0.2.1/pytensor_ml/dispatch/pytorch/conv.py +101 -0
  23. pytensor_ml-0.2.1/pytensor_ml/dispatch/pytorch/pooling.py +56 -0
  24. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/json_serialize.py +4 -0
  25. pytensor_ml-0.2.1/pytensor_ml/layers/__init__.py +109 -0
  26. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/layers/attention.py +19 -1
  27. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/layers/combinators.py +17 -0
  28. pytensor_ml-0.2.1/pytensor_ml/layers/conv.py +1174 -0
  29. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/layers/embedding.py +14 -10
  30. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/layers/linear.py +20 -17
  31. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/layers/norm.py +33 -26
  32. pytensor_ml-0.2.1/pytensor_ml/layers/padding.py +293 -0
  33. pytensor_ml-0.2.1/pytensor_ml/layers/recurrent.py +806 -0
  34. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/layers/transformer.py +27 -2
  35. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/model.py +15 -11
  36. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/optim/__init__.py +20 -0
  37. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/optim/base.py +63 -16
  38. pytensor_ml-0.2.1/pytensor_ml/optim/guards.py +238 -0
  39. pytensor_ml-0.2.1/pytensor_ml/optim/policy.py +134 -0
  40. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/optim/rules.py +73 -39
  41. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/optim/train.py +23 -4
  42. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/optim/transform.py +51 -1
  43. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/params.py +31 -8
  44. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/pretrained.py +82 -9
  45. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/pytensorf/collect.py +34 -8
  46. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/pytensorf/compile.py +24 -7
  47. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/pytensorf/rewrite.py +33 -2
  48. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/pytensorf/rng.py +12 -7
  49. pytensor_ml-0.2.1/pytensor_ml/rewriting/conv.py +44 -0
  50. pytensor_ml-0.2.1/pytensor_ml/rewriting/scan.py +188 -0
  51. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/serialize/base.py +6 -5
  52. pytensor_ml-0.2.1/pytensor_ml/state.py +438 -0
  53. pytensor_ml-0.2.1/tests/conftest.py +61 -0
  54. pytensor_ml-0.2.1/tests/dispatch/jax/test_conv.py +168 -0
  55. pytensor_ml-0.2.1/tests/dispatch/jax/test_pool.py +54 -0
  56. pytensor_ml-0.2.1/tests/dispatch/mlx/test_conv.py +143 -0
  57. pytensor_ml-0.2.1/tests/dispatch/mlx/test_pool.py +54 -0
  58. pytensor_ml-0.2.1/tests/dispatch/pytorch/test_conv.py +120 -0
  59. pytensor_ml-0.2.1/tests/dispatch/pytorch/test_pool.py +59 -0
  60. pytensor_ml-0.2.1/tests/optim/test_composition.py +191 -0
  61. pytensor_ml-0.2.1/tests/optim/test_guards.py +303 -0
  62. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/optim/test_learning_rate.py +73 -0
  63. pytensor_ml-0.2.1/tests/optim/test_policy.py +226 -0
  64. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/optim/test_rules.py +44 -0
  65. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/optim/test_schedules.py +19 -0
  66. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/optim/test_training.py +75 -6
  67. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/optim/test_transform.py +68 -2
  68. pytensor_ml-0.2.1/tests/rewriting/__init__.py +0 -0
  69. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/rewriting/test_layers.py +3 -3
  70. pytensor_ml-0.2.1/tests/rewriting/test_scan.py +424 -0
  71. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_collect.py +51 -2
  72. pytensor_ml-0.2.1/tests/test_conv.py +897 -0
  73. pytensor_ml-0.2.1/tests/test_conv_transpose.py +231 -0
  74. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_layers.py +282 -42
  75. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_model.py +71 -9
  76. pytensor_ml-0.2.1/tests/test_padding.py +132 -0
  77. pytensor_ml-0.2.1/tests/test_pool.py +190 -0
  78. pytensor_ml-0.2.1/tests/test_pretrained.py +303 -0
  79. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_pytensorf.py +169 -0
  80. pytensor_ml-0.2.1/tests/test_recurrent.py +1279 -0
  81. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_serialize.py +8 -2
  82. pytensor_ml-0.2.1/tests/test_state.py +470 -0
  83. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_transformer.py +65 -0
  84. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_workflow_groups.py +22 -12
  85. pytensor_ml-0.1.0/PKG-INFO +0 -243
  86. pytensor_ml-0.1.0/pytensor_ml/layers/__init__.py +0 -40
  87. pytensor_ml-0.1.0/pytensor_ml/state.py +0 -230
  88. pytensor_ml-0.1.0/tests/conftest.py +0 -43
  89. pytensor_ml-0.1.0/tests/test_pretrained.py +0 -145
  90. pytensor_ml-0.1.0/tests/test_state.py +0 -219
  91. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/.gitattributes +0 -0
  92. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/.gitignore +0 -0
  93. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/.pre-commit-config.yaml +0 -0
  94. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/LICENSE +0 -0
  95. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/README.md +0 -0
  96. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/conda_envs/pytensor_ml-gpu_jax.yml +0 -0
  97. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/activations.py +0 -0
  98. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/checkpoint.py +0 -0
  99. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/dispatch/jax/attention.py +0 -0
  100. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/dispatch/mlx/attention.py +0 -0
  101. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/layers/dropout.py +0 -0
  102. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/loss.py +0 -0
  103. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/optim/alias.py +0 -0
  104. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/optim/clipping.py +0 -0
  105. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/optim/schedules.py +0 -0
  106. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/pytensorf/__init__.py +0 -0
  107. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/rewriting/__init__.py +0 -0
  108. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/rewriting/layers.py +0 -0
  109. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/serialize/__init__.py +0 -0
  110. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/serialize/blockwise.py +0 -0
  111. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/serialize/builders.py +0 -0
  112. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/serialize/elemwise.py +0 -0
  113. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/serialize/scalar.py +0 -0
  114. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/serialize/scan.py +0 -0
  115. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/pytensor_ml/util.py +0 -0
  116. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/scripts/run_mypy.py +0 -0
  117. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/__init__.py +0 -0
  118. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/dispatch/__init__.py +0 -0
  119. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/dispatch/jax/__init__.py +0 -0
  120. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/dispatch/jax/test_attention.py +0 -0
  121. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/dispatch/jax/test_basic.py +0 -0
  122. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/dispatch/mlx/__init__.py +0 -0
  123. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/dispatch/mlx/test_attention.py +0 -0
  124. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/dispatch/mlx/test_basic.py +0 -0
  125. {pytensor_ml-0.1.0/tests/optim → pytensor_ml-0.2.1/tests/dispatch/pytorch}/__init__.py +0 -0
  126. {pytensor_ml-0.1.0/tests/rewriting → pytensor_ml-0.2.1/tests/optim}/__init__.py +0 -0
  127. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/optim/conftest.py +0 -0
  128. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/optim/test_clipping.py +0 -0
  129. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/rewriting/conftest.py +0 -0
  130. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_activations.py +0 -0
  131. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_attention.py +0 -0
  132. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_base.py +0 -0
  133. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_checkpoint.py +0 -0
  134. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/tests/test_loss.py +0 -0
  135. {pytensor_ml-0.1.0 → pytensor_ml-0.2.1}/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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
29
- - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
15
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
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@d44ca7d91762de7a7d5436ddae667c6da6d1c3df # v2.18.0
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@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
75
- - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
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@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
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@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
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@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
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.1
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.2.3,<4.0.0
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
- - mypy
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
- # JAX backend for dispatch tests; via pip so Windows pulls CPU wheels instead of conda jaxlib
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 BatchNorm2D, Dropout, Input, Linear, Sequential\n",
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
- " BatchNorm2D(\"BatchNorm_1\"),\n",
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
- " BatchNorm2D(\"BatchNorm_2\"),\n",
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 = { file = "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",
@@ -40,9 +41,9 @@ dependencies = [
40
41
  ]
41
42
 
42
43
  [project.urls]
43
- Homepage = "https://github.com/pymc-devs/pytensor_ml"
44
- Source = "https://github.com/pymc-devs/pytensor_ml"
45
- Tracker = "https://github.com/pymc-devs/pytensor_ml/issues"
44
+ Homepage = "https://github.com/pymc-devs/pytensor-ml"
45
+ Source = "https://github.com/pymc-devs/pytensor-ml"
46
+ Tracker = "https://github.com/pymc-devs/pytensor-ml/issues"
46
47
 
47
48
  [project.optional-dependencies]
48
49
  dev = [
@@ -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.1.0'
22
- __version_tuple__ = version_tuple = (0, 1, 0)
21
+ __version__ = version = '0.2.1'
22
+ __version_tuple__ = version_tuple = (0, 2, 1)
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
@@ -0,0 +1,3 @@
1
+ # Imported for its side effect: registering pytensor_ml's numba funcify dispatches. One submodule per
2
+ # op that gets a kernel, mirroring the layout under pytensor_ml/layers.
3
+ import pytensor_ml.dispatch.numba.conv