pytensor-ml 0.0.7__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.
Files changed (154) hide show
  1. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/.github/workflows/mypy.yml +2 -2
  2. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/.github/workflows/pre-commit.yml +2 -2
  3. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/.github/workflows/release.yml +3 -3
  4. pytensor_ml-0.2.0/.github/workflows/run_tests.yml +174 -0
  5. pytensor_ml-0.2.0/PKG-INFO +43 -0
  6. pytensor_ml-0.2.0/conda_envs/pytensor_ml.yml +38 -0
  7. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/examples/mnist_feed_forward.ipynb +4 -5
  8. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pyproject.toml +14 -1
  9. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/__init__.py +1 -0
  10. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/_version.py +2 -2
  11. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/base.py +9 -0
  12. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/checkpoint.py +16 -8
  13. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/dispatch/__init__.py +2 -0
  14. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/dispatch/jax/__init__.py +2 -0
  15. pytensor_ml-0.2.0/pytensor_ml/dispatch/jax/conv.py +72 -0
  16. pytensor_ml-0.2.0/pytensor_ml/dispatch/jax/pooling.py +60 -0
  17. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/dispatch/mlx/__init__.py +2 -0
  18. pytensor_ml-0.2.0/pytensor_ml/dispatch/mlx/conv.py +62 -0
  19. pytensor_ml-0.2.0/pytensor_ml/dispatch/mlx/pooling.py +57 -0
  20. pytensor_ml-0.2.0/pytensor_ml/dispatch/numba/__init__.py +3 -0
  21. pytensor_ml-0.2.0/pytensor_ml/dispatch/numba/conv.py +289 -0
  22. pytensor_ml-0.2.0/pytensor_ml/dispatch/pytorch/__init__.py +4 -0
  23. pytensor_ml-0.2.0/pytensor_ml/dispatch/pytorch/conv.py +101 -0
  24. pytensor_ml-0.2.0/pytensor_ml/dispatch/pytorch/pooling.py +56 -0
  25. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/json_serialize.py +4 -0
  26. pytensor_ml-0.2.0/pytensor_ml/layers/__init__.py +109 -0
  27. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/layers/attention.py +19 -1
  28. pytensor_ml-0.2.0/pytensor_ml/layers/combinators.py +51 -0
  29. pytensor_ml-0.2.0/pytensor_ml/layers/conv.py +1174 -0
  30. pytensor_ml-0.2.0/pytensor_ml/layers/dropout.py +76 -0
  31. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/layers/embedding.py +23 -6
  32. pytensor_ml-0.2.0/pytensor_ml/layers/linear.py +104 -0
  33. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/layers/norm.py +75 -15
  34. pytensor_ml-0.2.0/pytensor_ml/layers/padding.py +293 -0
  35. pytensor_ml-0.2.0/pytensor_ml/layers/recurrent.py +806 -0
  36. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/layers/transformer.py +27 -2
  37. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/model.py +16 -8
  38. pytensor_ml-0.2.0/pytensor_ml/optim/__init__.py +112 -0
  39. pytensor_ml-0.2.0/pytensor_ml/optim/alias.py +314 -0
  40. pytensor_ml-0.2.0/pytensor_ml/optim/base.py +305 -0
  41. pytensor_ml-0.2.0/pytensor_ml/optim/guards.py +238 -0
  42. pytensor_ml-0.2.0/pytensor_ml/optim/policy.py +134 -0
  43. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/optim/rules.py +126 -59
  44. pytensor_ml-0.2.0/pytensor_ml/optim/schedules.py +398 -0
  45. pytensor_ml-0.2.0/pytensor_ml/optim/train.py +154 -0
  46. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/optim/transform.py +37 -22
  47. pytensor_ml-0.2.0/pytensor_ml/params.py +138 -0
  48. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/pretrained.py +82 -9
  49. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/pytensorf/__init__.py +6 -0
  50. pytensor_ml-0.2.0/pytensor_ml/pytensorf/collect.py +192 -0
  51. pytensor_ml-0.2.0/pytensor_ml/pytensorf/compile.py +153 -0
  52. pytensor_ml-0.2.0/pytensor_ml/pytensorf/rewrite.py +77 -0
  53. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/pytensorf/rng.py +35 -2
  54. pytensor_ml-0.2.0/pytensor_ml/rewriting/conv.py +44 -0
  55. pytensor_ml-0.2.0/pytensor_ml/rewriting/scan.py +188 -0
  56. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/serialize/base.py +6 -5
  57. pytensor_ml-0.2.0/pytensor_ml/state.py +438 -0
  58. pytensor_ml-0.2.0/tests/conftest.py +61 -0
  59. pytensor_ml-0.2.0/tests/dispatch/jax/test_conv.py +168 -0
  60. pytensor_ml-0.2.0/tests/dispatch/jax/test_pool.py +54 -0
  61. pytensor_ml-0.2.0/tests/dispatch/mlx/test_conv.py +143 -0
  62. pytensor_ml-0.2.0/tests/dispatch/mlx/test_pool.py +54 -0
  63. pytensor_ml-0.2.0/tests/dispatch/pytorch/test_conv.py +120 -0
  64. pytensor_ml-0.2.0/tests/dispatch/pytorch/test_pool.py +59 -0
  65. pytensor_ml-0.2.0/tests/optim/test_composition.py +191 -0
  66. pytensor_ml-0.2.0/tests/optim/test_guards.py +303 -0
  67. pytensor_ml-0.2.0/tests/optim/test_learning_rate.py +439 -0
  68. pytensor_ml-0.2.0/tests/optim/test_policy.py +226 -0
  69. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/optim/test_rules.py +143 -22
  70. pytensor_ml-0.2.0/tests/optim/test_schedules.py +388 -0
  71. pytensor_ml-0.2.0/tests/optim/test_training.py +544 -0
  72. pytensor_ml-0.2.0/tests/optim/test_transform.py +153 -0
  73. pytensor_ml-0.2.0/tests/rewriting/__init__.py +0 -0
  74. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/rewriting/test_layers.py +3 -3
  75. pytensor_ml-0.2.0/tests/rewriting/test_scan.py +424 -0
  76. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/test_attention.py +2 -1
  77. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/test_checkpoint.py +38 -4
  78. pytensor_ml-0.2.0/tests/test_collect.py +234 -0
  79. pytensor_ml-0.2.0/tests/test_conv.py +897 -0
  80. pytensor_ml-0.2.0/tests/test_conv_transpose.py +231 -0
  81. pytensor_ml-0.2.0/tests/test_layers.py +708 -0
  82. pytensor_ml-0.2.0/tests/test_model.py +213 -0
  83. pytensor_ml-0.2.0/tests/test_padding.py +132 -0
  84. pytensor_ml-0.2.0/tests/test_pool.py +190 -0
  85. pytensor_ml-0.2.0/tests/test_pretrained.py +303 -0
  86. pytensor_ml-0.2.0/tests/test_pytensorf.py +405 -0
  87. pytensor_ml-0.2.0/tests/test_recurrent.py +1279 -0
  88. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/test_serialize.py +21 -6
  89. pytensor_ml-0.2.0/tests/test_state.py +470 -0
  90. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/test_transformer.py +65 -0
  91. pytensor_ml-0.2.0/tests/test_workflow_groups.py +79 -0
  92. pytensor_ml-0.0.7/.github/workflows/run_tests.yml +0 -86
  93. pytensor_ml-0.0.7/PKG-INFO +0 -239
  94. pytensor_ml-0.0.7/conda_envs/pytensor_ml.yml +0 -36
  95. pytensor_ml-0.0.7/pytensor_ml/layers/__init__.py +0 -40
  96. pytensor_ml-0.0.7/pytensor_ml/layers/combinators.py +0 -35
  97. pytensor_ml-0.0.7/pytensor_ml/layers/dropout.py +0 -40
  98. pytensor_ml-0.0.7/pytensor_ml/layers/linear.py +0 -58
  99. pytensor_ml-0.0.7/pytensor_ml/optim/__init__.py +0 -59
  100. pytensor_ml-0.0.7/pytensor_ml/optim/alias.py +0 -233
  101. pytensor_ml-0.0.7/pytensor_ml/optim/base.py +0 -198
  102. pytensor_ml-0.0.7/pytensor_ml/optim/train.py +0 -77
  103. pytensor_ml-0.0.7/pytensor_ml/params.py +0 -56
  104. pytensor_ml-0.0.7/pytensor_ml/pytensorf/collect.py +0 -95
  105. pytensor_ml-0.0.7/pytensor_ml/pytensorf/compile.py +0 -109
  106. pytensor_ml-0.0.7/pytensor_ml/pytensorf/rewrite.py +0 -41
  107. pytensor_ml-0.0.7/pytensor_ml/state.py +0 -118
  108. pytensor_ml-0.0.7/tests/conftest.py +0 -43
  109. pytensor_ml-0.0.7/tests/optim/test_training.py +0 -239
  110. pytensor_ml-0.0.7/tests/optim/test_transform.py +0 -86
  111. pytensor_ml-0.0.7/tests/test_collect.py +0 -82
  112. pytensor_ml-0.0.7/tests/test_layers.py +0 -332
  113. pytensor_ml-0.0.7/tests/test_model.py +0 -102
  114. pytensor_ml-0.0.7/tests/test_pretrained.py +0 -142
  115. pytensor_ml-0.0.7/tests/test_pytensorf.py +0 -42
  116. pytensor_ml-0.0.7/tests/test_state.py +0 -93
  117. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/.gitattributes +0 -0
  118. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/.gitignore +0 -0
  119. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/.pre-commit-config.yaml +0 -0
  120. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/LICENSE +0 -0
  121. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/README.md +0 -0
  122. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/conda_envs/pytensor_ml-gpu_jax.yml +0 -0
  123. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/activations.py +0 -0
  124. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/dispatch/jax/attention.py +0 -0
  125. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/dispatch/mlx/attention.py +0 -0
  126. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/loss.py +0 -0
  127. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/optim/clipping.py +0 -0
  128. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/rewriting/__init__.py +0 -0
  129. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/rewriting/layers.py +0 -0
  130. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/serialize/__init__.py +0 -0
  131. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/serialize/blockwise.py +0 -0
  132. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/serialize/builders.py +0 -0
  133. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/serialize/elemwise.py +0 -0
  134. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/serialize/scalar.py +0 -0
  135. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/serialize/scan.py +0 -0
  136. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/pytensor_ml/util.py +0 -0
  137. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/scripts/run_mypy.py +0 -0
  138. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/__init__.py +0 -0
  139. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/dispatch/__init__.py +0 -0
  140. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/dispatch/jax/__init__.py +0 -0
  141. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/dispatch/jax/test_attention.py +0 -0
  142. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/dispatch/jax/test_basic.py +0 -0
  143. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/dispatch/mlx/__init__.py +0 -0
  144. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/dispatch/mlx/test_attention.py +0 -0
  145. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/dispatch/mlx/test_basic.py +0 -0
  146. {pytensor_ml-0.0.7/tests/optim → pytensor_ml-0.2.0/tests/dispatch/pytorch}/__init__.py +0 -0
  147. {pytensor_ml-0.0.7/tests/rewriting → pytensor_ml-0.2.0/tests/optim}/__init__.py +0 -0
  148. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/optim/conftest.py +0 -0
  149. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/optim/test_clipping.py +0 -0
  150. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/rewriting/conftest.py +0 -0
  151. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/test_activations.py +0 -0
  152. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/test_base.py +0 -0
  153. {pytensor_ml-0.0.7 → pytensor_ml-0.2.0}/tests/test_loss.py +0 -0
  154. {pytensor_ml-0.0.7 → 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@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
@@ -0,0 +1,174 @@
1
+ name: run_tests
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ # Cancels all previous workflow runs for pull requests that have not completed.
9
+ concurrency:
10
+ # The concurrency group contains the workflow name and the branch name for pull requests
11
+ # or the commit hash for any other events.
12
+ group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ unittest:
17
+ name: unittest (${{ matrix.os }}, ${{ matrix.python-version }}, ${{ matrix.test-subset.name }})
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ os: [ubuntu-latest]
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]
28
+ # Groups are balanced on measured cold-compile-cache time, which is what a fresh runner pays.
29
+ # tests/test_workflow_groups.py fails if a test file is missing from every group or listed twice.
30
+ test-subset:
31
+ - name: attention
32
+ paths: |
33
+ tests/test_attention.py
34
+ - name: layers
35
+ paths: |
36
+ tests/test_transformer.py
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
43
+ tests/rewriting/
44
+ - name: optim
45
+ paths: |
46
+ tests/optim/
47
+ - name: rest
48
+ paths: |
49
+ tests/test_serialize.py
50
+ tests/test_activations.py
51
+ tests/test_loss.py
52
+ tests/test_checkpoint.py
53
+ tests/test_model.py
54
+ tests/test_pretrained.py
55
+ tests/test_pytensorf.py
56
+ tests/test_state.py
57
+ tests/test_base.py
58
+ tests/test_collect.py
59
+ tests/test_util.py
60
+ tests/test_workflow_groups.py
61
+ # Windows runners are roughly twice as slow, and almost everything here is platform independent.
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.
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/
84
+ - os: windows-latest
85
+ python-version: "3.12"
86
+ test-subset:
87
+ name: smoke
88
+ paths: |
89
+ tests/test_checkpoint.py
90
+ tests/test_serialize.py
91
+ tests/optim/test_training.py
92
+
93
+ runs-on: ${{ matrix.os }}
94
+
95
+ env:
96
+ TEST_SUBSET: ${{ matrix.test-subset.paths }}
97
+
98
+ defaults:
99
+ run:
100
+ shell: bash -l {0}
101
+
102
+ steps:
103
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
104
+ - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
105
+ env:
106
+ CACHE_NUMBER: 0
107
+ with:
108
+ path: ~/conda_pkgs_dir
109
+ key: ${{ runner.os }}-py${{matrix.python-version}}-conda-${{ env.CACHE_NUMBER }}-${{
110
+ hashFiles('conda_envs/pytensor_ml.yml') }}
111
+ - name: Cache multiple paths
112
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
113
+ env:
114
+ # Increase this value to reset cache if pyproject.toml has not changed
115
+ CACHE_NUMBER: 0
116
+ with:
117
+ path: |
118
+ ~/.cache/pip
119
+ $RUNNER_TOOL_CACHE/Python/*
120
+ ~\AppData\Local\pip\Cache
121
+ key: ${{ runner.os }}-build-${{ matrix.python-version }}-${{ env.CACHE_NUMBER }}-${{
122
+ hashFiles('pyproject.toml') }}
123
+ - name: Cache the pytensor compile directory
124
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
125
+ env:
126
+ CACHE_NUMBER: 0
127
+ with:
128
+ path: ~/.pytensor
129
+ key: ${{ runner.os }}-py${{ matrix.python-version }}-pytensor-${{ env.CACHE_NUMBER }}-${{
130
+ matrix.test-subset.name }}-${{ hashFiles('conda_envs/pytensor_ml.yml') }}
131
+ restore-keys: |
132
+ ${{ runner.os }}-py${{ matrix.python-version }}-pytensor-${{ env.CACHE_NUMBER }}-${{ matrix.test-subset.name }}-
133
+ - uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4.0.1
134
+ with:
135
+ miniforge-variant: Miniforge3
136
+ miniforge-version: latest
137
+ mamba-version: "*"
138
+ activate-environment: pytensor_ml
139
+ channel-priority: strict
140
+ environment-file: conda_envs/pytensor_ml.yml
141
+ python-version: 3.12
142
+ use-mamba: true
143
+
144
+ # IMPORTANT: This may break caching of conda packages! See https://github.com/conda-incubator/setup-miniconda/issues/267
145
+ use-only-tar-bz2: false
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
+
158
+ - name: Install current branch
159
+ run: |
160
+ conda activate pytensor_ml
161
+ pip install -e .
162
+ python --version
163
+
164
+ - name: Run tests
165
+ run: |
166
+ python -m pytest -vv --durations=25 --cov=pytensor_ml --cov-report=xml --no-cov-on-fail --cov-report term $TEST_SUBSET
167
+ - name: Upload coverage to Codecov
168
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
169
+ with:
170
+ token: ${{ secrets.CODECOV_TOKEN }} # use token for more robust uploads
171
+ env_vars: TEST_SUBSET
172
+ name: ${{ matrix.os }}
173
+ fail_ci_if_error: false
174
+ verbose: true
@@ -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
@@ -0,0 +1,38 @@
1
+ name: pytensor_ml
2
+ channels:
3
+ - conda-forge
4
+ - nodefaults
5
+
6
+ dependencies:
7
+ - python>=3.12
8
+ - pytensor>=3.3.0,<3.4.0
9
+ - numpy
10
+ - safetensors
11
+ - scikit-learn
12
+
13
+ # For testing
14
+ - coveralls
15
+ - diff-cover
16
+ - pytest
17
+ - pytest-cov
18
+ - pytest-xdist
19
+ - pytest-benchmark
20
+ - pytest-mock
21
+ - pyyaml # tests/test_workflow_groups.py reads the CI matrix
22
+
23
+ # For building docs
24
+ - sphinx
25
+ - sphinx_rtd_theme
26
+ - pygments
27
+ - pydot
28
+ - ipython
29
+
30
+ # developer tools
31
+ - pre-commit
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
35
+
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.
38
+ - pip
@@ -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, 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",
@@ -63,8 +63,7 @@
63
63
  "metadata": {},
64
64
  "outputs": [],
65
65
  "source": [
66
- "# X_in = Input('X_in', shape=(64, ))\n",
67
- "X_in = pytensor.tensor.tensor(\"X_in\", shape=(None, 64))"
66
+ "X_in = Input(\"X_in\", shape=(None, 64))"
68
67
  ]
69
68
  },
70
69
  {
@@ -76,11 +75,11 @@
76
75
  "source": [
77
76
  "prediction_network = Sequential(\n",
78
77
  " Linear(\"Linear_1\", n_in=64, n_out=256),\n",
79
- " BatchNorm2D(\"BatchNorm_1\"),\n",
78
+ " BatchNorm(\"BatchNorm_1\"),\n",
80
79
  " Dropout(\"Dropout_1\", p=0.5),\n",
81
80
  " LeakyReLU(),\n",
82
81
  " Linear(\"Linear_2\", n_in=256, n_out=128),\n",
83
- " BatchNorm2D(\"BatchNorm_2\"),\n",
82
+ " BatchNorm(\"BatchNorm_2\"),\n",
84
83
  " Dropout(\"Dropout_2\", p=0.5),\n",
85
84
  " LeakyReLU(),\n",
86
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",
@@ -52,6 +53,11 @@ dev = [
52
53
  "scikit-learn",
53
54
  "mypy",
54
55
  "jax",
56
+ "pyyaml",
57
+ ]
58
+ examples = [
59
+ "matplotlib",
60
+ "tqdm",
55
61
  ]
56
62
 
57
63
  [tool.hatch.version]
@@ -71,6 +77,13 @@ filterwarnings = [
71
77
  # warnings filter per-test and undoes it. Numba emits it from the numba
72
78
  # linker, which is pytensor 3's default.
73
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",
74
87
  ]
75
88
 
76
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.0.7'
22
- __version_tuple__ = version_tuple = (0, 0, 7)
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."""
@@ -42,19 +42,24 @@ def save_state(shared_variables: Sequence[SharedVariable], path: str | Path) ->
42
42
  Destination archive, written verbatim.
43
43
  """
44
44
  indexed = _index_by_name(shared_variables)
45
- tensors = {name: _as_saveable_array(variable.get_value()) for name, variable in indexed.items()}
45
+ tensors = {
46
+ name: _as_saveable_array(variable.get_value(), variable.type.dtype)
47
+ for name, variable in indexed.items()
48
+ }
46
49
  save_file(tensors, fspath(path))
47
50
 
48
51
 
49
- def _as_saveable_array(value: Any) -> np.ndarray:
52
+ def _as_saveable_array(value: Any, dtype: str) -> np.ndarray:
50
53
  """
51
- Return a value as a C-contiguous numpy array safetensors will accept, preserving its rank.
54
+ Return a value as a C-contiguous numpy array of ``dtype`` that safetensors will accept, preserving its
55
+ rank.
52
56
 
53
57
  A JIT backend stores its own array type in the shared variables a compiled function updates, so a
54
58
  trained model's values are ``jax.Array`` or ``mlx.core.array``; both convert through the array
55
- protocol.
59
+ protocol. It may also narrow them -- JAX computes in 32-bit unless ``floatX`` is ``float64`` -- so the
60
+ archive records the declared dtype to stay portable across precisions.
56
61
  """
57
- array = np.asarray(value)
62
+ array = np.asarray(value, dtype=dtype)
58
63
  # Not np.ascontiguousarray: it forces ndim >= 1, reshaping a rank-0 array such as the step count.
59
64
  return array if array.flags["C_CONTIGUOUS"] else np.array(array, order="C")
60
65
 
@@ -110,11 +115,14 @@ def load_state(
110
115
  mismatches = []
111
116
  for key, variable in target_by_key.items():
112
117
  value = values[key]
113
- # Through numpy: a backend array's own dtype object never compares equal to a numpy dtype.
118
+ # The declared dtype, not the stored value's, which a backend may have narrowed (see
119
+ # _as_saveable_array); set_value filters to the declaration anyway. Shape has to come from the
120
+ # value, whose runtime dimensions the declared type may leave unknown.
114
121
  current = np.asarray(variable.get_value(borrow=True))
115
- if value.shape != current.shape or value.dtype != current.dtype:
122
+ declared_dtype = np.dtype(variable.type.dtype)
123
+ if value.shape != current.shape or value.dtype != declared_dtype:
116
124
  mismatches.append(
117
- f" {variable.name!r}: target is {current.dtype} of shape {current.shape}, "
125
+ f" {variable.name!r}: target is {declared_dtype} of shape {current.shape}, "
118
126
  f"archive has {value.dtype} of shape {value.shape}"
119
127
  )
120
128
  if mismatches:
@@ -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