pytensor-ml 0.2.2__tar.gz → 0.3.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 (227) hide show
  1. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/.github/workflows/run_tests.yml +11 -3
  2. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/PKG-INFO +9 -7
  3. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/README.md +5 -4
  4. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/conda_envs/environment-docs.yml +1 -1
  5. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/conda_envs/pytensor_ml-gpu_jax.yml +1 -1
  6. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/conda_envs/pytensor_ml.yml +2 -1
  7. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/api/activations.rst +1 -0
  8. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/api/layers.rst +3 -0
  9. pytensor_ml-0.3.0/docs/source/api/models.rst +34 -0
  10. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/api/optim.rst +19 -2
  11. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/api/state.rst +2 -0
  12. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/api.rst +1 -0
  13. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/dev/style_guide.rst +1 -1
  14. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/index.rst +9 -2
  15. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/references.bib +29 -0
  16. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/sharp_edges.rst +42 -0
  17. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/examples/gallery/mnist_feed_forward.ipynb +2 -2
  18. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pyproject.toml +10 -3
  19. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/__init__.py +1 -0
  20. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/_version.py +2 -2
  21. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/activations.py +28 -2
  22. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/base.py +69 -1
  23. pytensor_ml-0.3.0/pytensor_ml/checkpoint.py +350 -0
  24. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/jax/__init__.py +1 -0
  25. pytensor_ml-0.3.0/pytensor_ml/dispatch/jax/attention.py +36 -0
  26. pytensor_ml-0.3.0/pytensor_ml/dispatch/jax/line_search.py +41 -0
  27. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/mlx/__init__.py +2 -0
  28. pytensor_ml-0.3.0/pytensor_ml/dispatch/mlx/lbfgs.py +51 -0
  29. pytensor_ml-0.3.0/pytensor_ml/dispatch/mlx/line_search.py +261 -0
  30. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/__init__.py +10 -0
  31. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/attention.py +120 -29
  32. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/conv.py +251 -80
  33. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/dropout.py +3 -3
  34. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/embedding.py +5 -4
  35. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/linear.py +6 -4
  36. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/norm.py +340 -22
  37. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/padding.py +5 -3
  38. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/recurrent.py +66 -38
  39. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/transformer.py +44 -15
  40. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/loss.py +39 -11
  41. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/model.py +38 -14
  42. pytensor_ml-0.3.0/pytensor_ml/models/__init__.py +24 -0
  43. pytensor_ml-0.3.0/pytensor_ml/models/architectures/__init__.py +3 -0
  44. pytensor_ml-0.3.0/pytensor_ml/models/architectures/clip.py +190 -0
  45. pytensor_ml-0.3.0/pytensor_ml/models/architectures/gpt2.py +127 -0
  46. pytensor_ml-0.3.0/pytensor_ml/models/architectures/vae.py +343 -0
  47. pytensor_ml-0.3.0/pytensor_ml/models/blocks/__init__.py +4 -0
  48. pytensor_ml-0.3.0/pytensor_ml/models/blocks/attention.py +78 -0
  49. pytensor_ml-0.3.0/pytensor_ml/models/blocks/resnet.py +173 -0
  50. pytensor_ml-0.3.0/pytensor_ml/models/loading/__init__.py +17 -0
  51. pytensor_ml-0.3.0/pytensor_ml/models/loading/binding.py +28 -0
  52. pytensor_ml-0.3.0/pytensor_ml/models/loading/keys.py +224 -0
  53. pytensor_ml-0.3.0/pytensor_ml/models/loading/registry.py +120 -0
  54. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/__init__.py +9 -4
  55. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/alias.py +191 -136
  56. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/base.py +119 -184
  57. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/clipping.py +2 -2
  58. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/guards.py +24 -26
  59. pytensor_ml-0.3.0/pytensor_ml/optim/lbfgs.py +179 -0
  60. pytensor_ml-0.3.0/pytensor_ml/optim/line_search.py +586 -0
  61. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/policy.py +20 -11
  62. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/rules.py +339 -63
  63. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/schedules.py +93 -0
  64. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/train.py +19 -18
  65. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/transform.py +21 -27
  66. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/params.py +30 -6
  67. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/pretrained.py +109 -21
  68. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/pytensorf/__init__.py +2 -0
  69. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/pytensorf/collect.py +86 -19
  70. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/pytensorf/compile.py +29 -15
  71. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/pytensorf/rewrite.py +43 -3
  72. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/pytensorf/rng.py +35 -5
  73. pytensor_ml-0.3.0/pytensor_ml/rewriting/layers.py +216 -0
  74. pytensor_ml-0.3.0/pytensor_ml/rewriting/scan.py +376 -0
  75. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/state.py +73 -9
  76. pytensor_ml-0.3.0/tests/conftest.py +153 -0
  77. pytensor_ml-0.3.0/tests/data/attention_block_2d.npz +0 -0
  78. pytensor_ml-0.3.0/tests/data/generate_reference_checkpoints.py +141 -0
  79. pytensor_ml-0.3.0/tests/data/generate_reference_layers.py +98 -0
  80. pytensor_ml-0.3.0/tests/data/resnet_block_2d.npz +0 -0
  81. pytensor_ml-0.3.0/tests/data/sdxl_vae.npz +0 -0
  82. pytensor_ml-0.3.0/tests/data/tiny_clip/config.json +23 -0
  83. pytensor_ml-0.3.0/tests/data/tiny_clip/expected_outputs.npz +0 -0
  84. pytensor_ml-0.3.0/tests/data/tiny_clip/model.safetensors +0 -0
  85. pytensor_ml-0.3.0/tests/data/tiny_clip_with_projection/config.json +23 -0
  86. pytensor_ml-0.3.0/tests/data/tiny_clip_with_projection/expected_outputs.npz +0 -0
  87. pytensor_ml-0.3.0/tests/data/tiny_clip_with_projection/model.safetensors +0 -0
  88. pytensor_ml-0.3.0/tests/data/tiny_gpt2/config.json +34 -0
  89. pytensor_ml-0.3.0/tests/data/tiny_gpt2/expected_outputs.npz +0 -0
  90. pytensor_ml-0.3.0/tests/data/tiny_gpt2/generation_config.json +9 -0
  91. pytensor_ml-0.3.0/tests/data/tiny_gpt2/model.safetensors +0 -0
  92. pytensor_ml-0.3.0/tests/data/tiny_vae/config.json +31 -0
  93. pytensor_ml-0.3.0/tests/data/tiny_vae/diffusion_pytorch_model.safetensors +0 -0
  94. pytensor_ml-0.3.0/tests/data/tiny_vae/expected_outputs.npz +0 -0
  95. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/jax/test_attention.py +15 -0
  96. pytensor_ml-0.3.0/tests/dispatch/jax/test_line_search.py +96 -0
  97. pytensor_ml-0.3.0/tests/dispatch/mlx/test_lbfgs.py +137 -0
  98. pytensor_ml-0.3.0/tests/dispatch/mlx/test_line_search.py +135 -0
  99. pytensor_ml-0.3.0/tests/dispatch/mlx/test_norm.py +33 -0
  100. pytensor_ml-0.3.0/tests/models/architectures/test_clip.py +189 -0
  101. pytensor_ml-0.3.0/tests/models/architectures/test_gpt2.py +121 -0
  102. pytensor_ml-0.3.0/tests/models/architectures/test_vae.py +119 -0
  103. pytensor_ml-0.3.0/tests/models/blocks/__init__.py +0 -0
  104. pytensor_ml-0.3.0/tests/models/blocks/test_attention.py +54 -0
  105. pytensor_ml-0.3.0/tests/models/blocks/test_resnet.py +117 -0
  106. pytensor_ml-0.3.0/tests/models/loading/__init__.py +0 -0
  107. pytensor_ml-0.3.0/tests/models/loading/test_binding.py +11 -0
  108. pytensor_ml-0.3.0/tests/models/loading/test_keys.py +197 -0
  109. pytensor_ml-0.3.0/tests/models/loading/test_registry.py +72 -0
  110. pytensor_ml-0.3.0/tests/optim/__init__.py +0 -0
  111. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/conftest.py +3 -1
  112. pytensor_ml-0.3.0/tests/optim/lbfgs_reference.py +34 -0
  113. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_composition.py +87 -4
  114. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_guards.py +29 -17
  115. pytensor_ml-0.3.0/tests/optim/test_lbfgs.py +163 -0
  116. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_learning_rate.py +3 -16
  117. pytensor_ml-0.3.0/tests/optim/test_line_search.py +242 -0
  118. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_policy.py +15 -3
  119. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_public_surface.py +1 -3
  120. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_rules.py +456 -26
  121. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_schedules.py +47 -0
  122. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_training.py +83 -24
  123. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_transform.py +15 -43
  124. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_update_space.py +3 -53
  125. pytensor_ml-0.3.0/tests/rewriting/__init__.py +0 -0
  126. pytensor_ml-0.3.0/tests/rewriting/test_layers.py +337 -0
  127. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/rewriting/test_scan.py +165 -7
  128. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_activations.py +30 -4
  129. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_attention.py +138 -4
  130. pytensor_ml-0.3.0/tests/test_checkpoint.py +661 -0
  131. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_collect.py +25 -11
  132. pytensor_ml-0.3.0/tests/test_conv.py +570 -0
  133. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_conv_transpose.py +56 -68
  134. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_layers.py +439 -4
  135. pytensor_ml-0.3.0/tests/test_loss.py +134 -0
  136. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_model.py +75 -25
  137. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_padding.py +12 -36
  138. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_pool.py +43 -57
  139. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_pretrained.py +254 -20
  140. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_pytensorf.py +95 -4
  141. pytensor_ml-0.3.0/tests/test_recurrent.py +785 -0
  142. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_serialize.py +43 -7
  143. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_state.py +31 -0
  144. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_transformer.py +38 -1
  145. pytensor_ml-0.3.0/tests/test_upsample.py +201 -0
  146. pytensor_ml-0.2.2/pytensor_ml/checkpoint.py +0 -171
  147. pytensor_ml-0.2.2/pytensor_ml/dispatch/jax/attention.py +0 -25
  148. pytensor_ml-0.2.2/pytensor_ml/rewriting/layers.py +0 -85
  149. pytensor_ml-0.2.2/pytensor_ml/rewriting/scan.py +0 -188
  150. pytensor_ml-0.2.2/tests/conftest.py +0 -61
  151. pytensor_ml-0.2.2/tests/rewriting/conftest.py +0 -23
  152. pytensor_ml-0.2.2/tests/rewriting/test_layers.py +0 -52
  153. pytensor_ml-0.2.2/tests/test_checkpoint.py +0 -223
  154. pytensor_ml-0.2.2/tests/test_conv.py +0 -897
  155. pytensor_ml-0.2.2/tests/test_loss.py +0 -72
  156. pytensor_ml-0.2.2/tests/test_recurrent.py +0 -1279
  157. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/.gitattributes +0 -0
  158. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/.github/workflows/mypy.yml +0 -0
  159. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/.github/workflows/pre-commit.yml +0 -0
  160. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/.github/workflows/release.yml +0 -0
  161. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/.github/workflows/rtd-preview.yml +0 -0
  162. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/.gitignore +0 -0
  163. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/.pre-commit-config.yaml +0 -0
  164. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/.readthedocs.yaml +0 -0
  165. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/LICENSE +0 -0
  166. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/.gitignore +0 -0
  167. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/Makefile +0 -0
  168. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/make.bat +0 -0
  169. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/_static/.gitkeep +0 -0
  170. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/_templates/autosummary/class.rst +0 -0
  171. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/api/loss.rst +0 -0
  172. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/api/model.rst +0 -0
  173. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/api/pytensorf.rst +0 -0
  174. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/api/serialization.rst +0 -0
  175. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/api/util.rst +0 -0
  176. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/conf.py +0 -0
  177. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/dev/contributing.rst +0 -0
  178. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/dev/docs.rst +0 -0
  179. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/dev/index.rst +0 -0
  180. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/source/install.rst +0 -0
  181. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/docs/sphinxext/generate_gallery.py +0 -0
  182. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pixi.lock +0 -0
  183. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/__init__.py +0 -0
  184. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/jax/conv.py +0 -0
  185. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/jax/pooling.py +0 -0
  186. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/mlx/attention.py +0 -0
  187. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/mlx/conv.py +0 -0
  188. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/mlx/pooling.py +0 -0
  189. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/numba/__init__.py +0 -0
  190. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/numba/conv.py +0 -0
  191. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/pytorch/__init__.py +0 -0
  192. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/pytorch/conv.py +0 -0
  193. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/dispatch/pytorch/pooling.py +0 -0
  194. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/json_serialize.py +0 -0
  195. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/layers/combinators.py +0 -0
  196. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/optim/checks.py +0 -0
  197. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/rewriting/__init__.py +0 -0
  198. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/rewriting/conv.py +0 -0
  199. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/serialize/__init__.py +0 -0
  200. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/serialize/base.py +0 -0
  201. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/serialize/blockwise.py +0 -0
  202. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/serialize/builders.py +0 -0
  203. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/serialize/elemwise.py +0 -0
  204. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/serialize/scalar.py +0 -0
  205. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/serialize/scan.py +0 -0
  206. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/pytensor_ml/util.py +0 -0
  207. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/scripts/run_mypy.py +0 -0
  208. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/__init__.py +0 -0
  209. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/__init__.py +0 -0
  210. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/jax/__init__.py +0 -0
  211. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/jax/test_basic.py +0 -0
  212. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/jax/test_conv.py +0 -0
  213. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/jax/test_pool.py +0 -0
  214. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/mlx/__init__.py +0 -0
  215. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/mlx/test_attention.py +0 -0
  216. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/mlx/test_basic.py +0 -0
  217. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/mlx/test_conv.py +0 -0
  218. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/mlx/test_pool.py +0 -0
  219. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/pytorch/__init__.py +0 -0
  220. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/pytorch/test_conv.py +0 -0
  221. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/dispatch/pytorch/test_pool.py +0 -0
  222. {pytensor_ml-0.2.2/tests/optim → pytensor_ml-0.3.0/tests/models}/__init__.py +0 -0
  223. {pytensor_ml-0.2.2/tests/rewriting → pytensor_ml-0.3.0/tests/models/architectures}/__init__.py +0 -0
  224. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/optim/test_clipping.py +0 -0
  225. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_base.py +0 -0
  226. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_util.py +0 -0
  227. {pytensor_ml-0.2.2 → pytensor_ml-0.3.0}/tests/test_workflow_groups.py +0 -0
@@ -35,15 +35,24 @@ jobs:
35
35
  paths: |
36
36
  tests/test_transformer.py
37
37
  tests/test_layers.py
38
+ - name: recurrent
39
+ paths: |
38
40
  tests/test_recurrent.py
41
+ tests/rewriting/
42
+ - name: convolution
43
+ paths: |
39
44
  tests/test_conv.py
40
45
  tests/test_conv_transpose.py
41
46
  tests/test_padding.py
42
47
  tests/test_pool.py
43
- tests/rewriting/
48
+ tests/test_upsample.py
44
49
  - name: optim
45
50
  paths: |
46
51
  tests/optim/
52
+ - name: models
53
+ paths: |
54
+ tests/test_pretrained.py
55
+ tests/models/
47
56
  - name: rest
48
57
  paths: |
49
58
  tests/test_serialize.py
@@ -51,7 +60,6 @@ jobs:
51
60
  tests/test_loss.py
52
61
  tests/test_checkpoint.py
53
62
  tests/test_model.py
54
- tests/test_pretrained.py
55
63
  tests/test_pytensorf.py
56
64
  tests/test_state.py
57
65
  tests/test_base.py
@@ -148,7 +156,7 @@ jobs:
148
156
  run: |
149
157
  conda activate pytensor_ml
150
158
  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
159
+ if [[ $INSTALL_MLX == "1" ]]; then pip install "mlx>=0.32.2,<0.33"; fi
152
160
  if [[ $INSTALL_TORCH == "1" ]]; then pip install torch --index-url https://download.pytorch.org/whl/cpu; fi
153
161
  env:
154
162
  INSTALL_JAX: ${{ matrix.install-jax }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pytensor_ml
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: A(nother) deep learning library, built on top of Pytensor
5
5
  Project-URL: Homepage, https://github.com/pymc-devs/pytensor-ml
6
6
  Project-URL: Documentation, https://pytensor-ml.readthedocs.io
@@ -25,15 +25,16 @@ Classifier: Programming Language :: Python :: 3.14
25
25
  Classifier: Topic :: Scientific/Engineering
26
26
  Requires-Python: >=3.12
27
27
  Requires-Dist: numpy
28
- Requires-Dist: pytensor<4.0.0,>=3.2.3
29
- Requires-Dist: safetensors
28
+ Requires-Dist: pytensor<4.0.0,>=3.3.3
30
29
  Provides-Extra: dev
30
+ Requires-Dist: cloudpickle; extra == 'dev'
31
31
  Requires-Dist: jax; extra == 'dev'
32
32
  Requires-Dist: mypy; extra == 'dev'
33
33
  Requires-Dist: pre-commit; extra == 'dev'
34
34
  Requires-Dist: pytest; extra == 'dev'
35
35
  Requires-Dist: pytest-cov; extra == 'dev'
36
36
  Requires-Dist: pyyaml; extra == 'dev'
37
+ Requires-Dist: safetensors; extra == 'dev'
37
38
  Requires-Dist: scikit-learn; extra == 'dev'
38
39
  Provides-Extra: examples
39
40
  Requires-Dist: matplotlib; extra == 'examples'
@@ -61,8 +62,9 @@ any other PyTensor graph — a PyMC model included — as there is nothing else
61
62
  pip install pytensor-ml
62
63
  ```
63
64
 
64
- The only hard dependencies are `pytensor`, `numpy`, and `safetensors`. A backend beyond the default (`numba`,
65
- `jax`, `torch`, `mlx`) is installed separately, and only loads when you actually compile against it.
65
+ The only hard dependencies are `pytensor` and `numpy`. Saving and loading weights needs `safetensors`
66
+ (`pip install safetensors`). A backend beyond the default (`numba`, `jax`, `torch`, `mlx`) is installed
67
+ separately, and only loads when you actually compile against it.
66
68
 
67
69
  ## Quickstart
68
70
 
@@ -93,11 +95,11 @@ network = Sequential(
93
95
  ReLU(),
94
96
  Linear("logits", n_in=128, n_out=10),
95
97
  )
96
- model = Model(X_in, network(X_in)).initialize(seed=0)
98
+ model = Model(network(X_in)).initialize(seed=0)
97
99
 
98
100
  rule = chain(clip_by_global_norm(1.0), adam(learning_rate=cosine_schedule(1e-3, total_steps=500)))
99
101
  loss_fn = CrossEntropy(expect_onehot_labels=True, expect_logits=True, reduction="mean")
100
- step = model.compile_train(rule, loss_fn, ndim_out=2)
102
+ step = model.compile_train(rule, loss_fn)
101
103
 
102
104
  loader = DataLoader(X, y_onehot, batch_size=64, random_state=0)
103
105
  for _ in range(500):
@@ -19,8 +19,9 @@ any other PyTensor graph — a PyMC model included — as there is nothing else
19
19
  pip install pytensor-ml
20
20
  ```
21
21
 
22
- The only hard dependencies are `pytensor`, `numpy`, and `safetensors`. A backend beyond the default (`numba`,
23
- `jax`, `torch`, `mlx`) is installed separately, and only loads when you actually compile against it.
22
+ The only hard dependencies are `pytensor` and `numpy`. Saving and loading weights needs `safetensors`
23
+ (`pip install safetensors`). A backend beyond the default (`numba`, `jax`, `torch`, `mlx`) is installed
24
+ separately, and only loads when you actually compile against it.
24
25
 
25
26
  ## Quickstart
26
27
 
@@ -51,11 +52,11 @@ network = Sequential(
51
52
  ReLU(),
52
53
  Linear("logits", n_in=128, n_out=10),
53
54
  )
54
- model = Model(X_in, network(X_in)).initialize(seed=0)
55
+ model = Model(network(X_in)).initialize(seed=0)
55
56
 
56
57
  rule = chain(clip_by_global_norm(1.0), adam(learning_rate=cosine_schedule(1e-3, total_steps=500)))
57
58
  loss_fn = CrossEntropy(expect_onehot_labels=True, expect_logits=True, reduction="mean")
58
- step = model.compile_train(rule, loss_fn, ndim_out=2)
59
+ step = model.compile_train(rule, loss_fn)
59
60
 
60
61
  loader = DataLoader(X, y_onehot, batch_size=64, random_state=0)
61
62
  for _ in range(500):
@@ -8,7 +8,7 @@ channels:
8
8
  dependencies:
9
9
  - python>=3.12
10
10
  # Runtime deps: autodoc imports pytensor_ml, so the full runtime stack has to be in scope.
11
- - pytensor>=3.3.0,<3.4.0
11
+ - pytensor>=3.3.3,<3.4.0
12
12
  - numpy
13
13
  - safetensors
14
14
  # The gallery extension renders notebook thumbnails with matplotlib.
@@ -6,7 +6,7 @@ channels:
6
6
 
7
7
  dependencies:
8
8
  - python>=3.12
9
- - pytensor>=3.2.3,<4.0.0
9
+ - pytensor>=3.3.3,<4.0.0
10
10
  - numpy
11
11
  - scikit-learn
12
12
 
@@ -5,12 +5,13 @@ channels:
5
5
 
6
6
  dependencies:
7
7
  - python>=3.12
8
- - pytensor>=3.3.0,<3.4.0
8
+ - pytensor>=3.3.3,<3.4.0
9
9
  - numpy
10
10
  - safetensors
11
11
  - scikit-learn
12
12
 
13
13
  # For testing
14
+ - cloudpickle # tests/optim/test_composition.py ships a configured rule to a worker
14
15
  - coveralls
15
16
  - diff-cover
16
17
  - pytest
@@ -10,6 +10,7 @@ Activations
10
10
  ReLU
11
11
  LeakyReLU
12
12
  GELU
13
+ QuickGELU
13
14
  Swish
14
15
  Sigmoid
15
16
  SoftPlus
@@ -46,6 +46,8 @@ Convolution and pooling
46
46
  MaxPool2D
47
47
  AvgPool1D
48
48
  AvgPool2D
49
+ Upsample1D
50
+ Upsample2D
49
51
 
50
52
  Padding
51
53
  -------
@@ -70,6 +72,7 @@ Normalization and regularization
70
72
 
71
73
  BatchNorm
72
74
  LayerNorm
75
+ GroupNorm
73
76
  Dropout
74
77
 
75
78
  Recurrent
@@ -0,0 +1,34 @@
1
+ Pretrained models
2
+ =================
3
+
4
+ .. currentmodule:: pytensor_ml.models
5
+
6
+ Dispatch
7
+ --------
8
+
9
+ .. autosummary::
10
+ :toctree: generated/
11
+
12
+ architecture_name
13
+ build_from_config
14
+ register_builder
15
+
16
+ Blocks
17
+ ------
18
+
19
+ .. autosummary::
20
+ :toctree: generated/
21
+
22
+ ResnetBlock2D
23
+ AttentionBlock2D
24
+
25
+ Binding weights
26
+ ---------------
27
+
28
+ .. autosummary::
29
+ :toctree: generated/
30
+
31
+ KeyMap
32
+ bind_layer_norm
33
+ bind_linear
34
+ channels_last
@@ -26,6 +26,16 @@ Update rules
26
26
  rprop
27
27
  adagrad
28
28
  adadelta
29
+ lbfgs
30
+
31
+ Line searches
32
+ -------------
33
+
34
+ .. autosummary::
35
+ :toctree: generated/
36
+
37
+ zoom_line_search
38
+ LineSearch
29
39
 
30
40
  Transforms
31
41
  ----------
@@ -73,6 +83,7 @@ eliminated the value's last consumer.
73
83
 
74
84
  constant_schedule
75
85
  linear_schedule
86
+ linear_onecycle_schedule
76
87
  cosine_schedule
77
88
  exponential_schedule
78
89
  polynomial_schedule
@@ -85,9 +96,7 @@ Building blocks
85
96
  .. autosummary::
86
97
  :toctree: generated/
87
98
 
88
- counter
89
99
  get_gradients
90
- reuses_state
91
100
  scalar_state
92
101
  state_for
93
102
  steps_of
@@ -131,3 +140,11 @@ Low-level update functions
131
140
  rprop_updates
132
141
  adagrad_updates
133
142
  adadelta_updates
143
+ lbfgs_updates
144
+
145
+ .. currentmodule:: pytensor_ml.optim.lbfgs
146
+
147
+ .. autosummary::
148
+ :toctree: generated/
149
+
150
+ LBFGSDirection
@@ -33,5 +33,7 @@ Initializers
33
33
  XavierUniformInitializer
34
34
  OrthogonalInitializer
35
35
  UnrecordedInitializer
36
+ EmptyInitializer
37
+ initial_values_from
36
38
  initialize_params
37
39
  fans
@@ -14,5 +14,6 @@ API Reference
14
14
  api/optim
15
15
  api/state
16
16
  api/serialization
17
+ api/models
17
18
  api/pytensorf
18
19
  api/util
@@ -180,7 +180,7 @@ Every public entrypoint carries an ``Examples`` section. Three rules:
180
180
  from pytensor_ml.optim import adam, chain, clip_by_global_norm, compile_train
181
181
 
182
182
  X = Input("X", shape=(None, 4))
183
- loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2)
183
+ loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError())
184
184
 
185
185
  step = compile_train(loss, chain(clip_by_global_norm(1.0), adam(1e-3)))
186
186
  loss_value = step(np.zeros((8, 4)), np.zeros((8, 1)))
@@ -41,6 +41,9 @@ Quick example
41
41
 
42
42
  import numpy as np
43
43
 
44
+ from pytensor import config
45
+ from sklearn.datasets import load_digits
46
+
44
47
  from pytensor_ml.activations import ReLU
45
48
  from pytensor_ml.layers import Input, Linear, Sequential
46
49
  from pytensor_ml.loss import CrossEntropy
@@ -48,17 +51,21 @@ Quick example
48
51
  from pytensor_ml.optim import adam, chain, clip_by_global_norm, cosine_schedule
49
52
  from pytensor_ml.util import DataLoader
50
53
 
54
+ X, y = load_digits(return_X_y=True)
55
+ X = (X / 16.0).astype(config.floatX)
56
+ y_onehot = np.eye(10, dtype=config.floatX)[y]
57
+
51
58
  X_in = Input("X_in", shape=(None, 64))
52
59
  network = Sequential(
53
60
  Linear("fc1", n_in=64, n_out=128),
54
61
  ReLU(),
55
62
  Linear("logits", n_in=128, n_out=10),
56
63
  )
57
- model = Model(X_in, network(X_in)).initialize(seed=0)
64
+ model = Model(network(X_in)).initialize(seed=0)
58
65
 
59
66
  rule = chain(clip_by_global_norm(1.0), adam(learning_rate=cosine_schedule(1e-3, total_steps=500)))
60
67
  loss_fn = CrossEntropy(expect_onehot_labels=True, expect_logits=True, reduction="mean")
61
- step = model.compile_train(rule, loss_fn, ndim_out=2)
68
+ step = model.compile_train(rule, loss_fn)
62
69
 
63
70
  loader = DataLoader(X, y_onehot, batch_size=64, random_state=0)
64
71
  for _ in range(500):
@@ -42,3 +42,32 @@
42
42
  booktitle = {International Conference on Artificial Intelligence and Statistics},
43
43
  year = {2010},
44
44
  }
45
+
46
+ @book{nocedal2006numerical,
47
+ title = {Numerical Optimization},
48
+ author = {Nocedal, Jorge and Wright, Stephen J.},
49
+ edition = {2},
50
+ publisher = {Springer},
51
+ address = {New York},
52
+ year = {2006},
53
+ }
54
+
55
+ @article{liu1989lbfgs,
56
+ title = {On the Limited Memory {BFGS} Method for Large Scale Optimization},
57
+ author = {Liu, Dong C. and Nocedal, Jorge},
58
+ journal = {Mathematical Programming},
59
+ volume = {45},
60
+ number = {1--3},
61
+ pages = {503--528},
62
+ year = {1989},
63
+ }
64
+
65
+ @article{hager2005new,
66
+ title = {A New Conjugate Gradient Method with Guaranteed Descent and an Efficient Line Search},
67
+ author = {Hager, William W. and Zhang, Hongchao},
68
+ journal = {SIAM Journal on Optimization},
69
+ volume = {16},
70
+ number = {1},
71
+ pages = {170--192},
72
+ year = {2005},
73
+ }
@@ -173,6 +173,48 @@ because an endpoint above the starting rate is a legitimate warmup.
173
173
  There is no ``warmup`` helper for the same reason: an endpoint above the start already is
174
174
  one, and :func:`~pytensor_ml.optim.join_schedules` composes the phases.
175
175
 
176
+ Hyperparameters are keyword-only
177
+ --------------------------------
178
+
179
+ A layer takes its name first and every hyperparameter by keyword. There are no positional
180
+ hyperparameters anywhere in the library, so the torch and keras spelling raises rather than
181
+ binding a hyperparameter to ``name`` and leaving the real one at its default:
182
+
183
+ .. code-block:: python
184
+
185
+ Dropout(0.1) # TypeError: Dropout's `name` must be a string ... Dropout(p=0.1)
186
+ Conv2D("conv", 1, 16, 3) # TypeError: takes 2 positional arguments but 5 were given
187
+
188
+ Dropout("drop", p=0.1) # torch's nn.Dropout(0.1)
189
+ Conv2D("conv", in_channels=1, out_channels=16, kernel_size=3)
190
+
191
+ Only the first can tell you which parameter you meant. Once the name slot holds a string
192
+ the extra arguments are anonymous, and the error is the one Python writes.
193
+
194
+ Every layer's name is optional and falls back to its class, so ``BatchNorm(n_in=4)`` is named
195
+ ``BatchNorm`` and a stack of unnamed layers offers several variables of one name. The
196
+ serialization boundary numbers those apart, keying them ``BatchNorm_1_scale``,
197
+ ``BatchNorm_2_scale``, ... **in the order the variables are passed**. Collecting them from the
198
+ same graph gives the same order every time, so an unnamed stack round-trips across processes and
199
+ across rebuilds -- but a checkpoint saved from one ordering will not load under another:
200
+
201
+ .. code-block:: python
202
+
203
+ shared = collect_shared_variables(network(X))
204
+ save_state(shared, "weights.safetensors")
205
+ load_state(collect_shared_variables(network(X)), "weights.safetensors") # same order, loads
206
+
207
+ Name the layers you care about and the numbering never touches them; a name that appears once is
208
+ its own key. The ordinal numbers the layer, not the variable, so two unnamed
209
+ :class:`~pytensor_ml.layers.Linear` layers give ``Linear_1_W``, ``Linear_1_b``, ``Linear_2_W``,
210
+ ``Linear_2_b``. Shared state no layer built -- a step counter, an optimizer's moments -- carries no
211
+ layer to number, so a repeat there is numbered at the end of its own name instead.
212
+
213
+ The only positional argument a layer takes is its name. The two that wrap other layers take those
214
+ instead, and their name by keyword: :class:`~pytensor_ml.layers.Recurrent` is
215
+ ``Recurrent(cell, name=...)`` and :class:`~pytensor_ml.layers.Bidirectional` is
216
+ ``Bidirectional(forward, backward, name=...)``.
217
+
176
218
  Convolution inputs are channels-last
177
219
  ------------------------------------
178
220
 
@@ -132,7 +132,7 @@
132
132
  ")\n",
133
133
  "\n",
134
134
  "y_hat = prediction_network(X_in)\n",
135
- "model = Model(X_in, y_hat, compile_kwargs={\"mode\": \"NUMBA\"})\n",
135
+ "model = Model(y_hat, compile_kwargs={\"mode\": \"NUMBA\"})\n",
136
136
  "model"
137
137
  ]
138
138
  },
@@ -170,7 +170,7 @@
170
170
  "# `model.compile_train` is the training companion to `model.predict`: it builds the loss against a target,\n",
171
171
  "# differentiates it, folds in the BatchNorm running-statistic updates, and compiles a one-step training\n",
172
172
  "# function (reusing the model's NUMBA compile settings).\n",
173
- "step = model.compile_train(adagrad(learning_rate=1e-3), loss_fn, ndim_out=2)"
173
+ "step = model.compile_train(adagrad(learning_rate=1e-3), loss_fn)"
174
174
  ]
175
175
  },
176
176
  {
@@ -35,9 +35,8 @@ keywords = [
35
35
  ]
36
36
 
37
37
  dependencies = [
38
- "pytensor>=3.2.3,<4.0.0",
38
+ "pytensor>=3.3.3,<4.0.0",
39
39
  "numpy",
40
- "safetensors"
41
40
  ]
42
41
 
43
42
  [project.urls]
@@ -48,6 +47,8 @@ Tracker = "https://github.com/pymc-devs/pytensor-ml/issues"
48
47
 
49
48
  [project.optional-dependencies]
50
49
  dev = [
50
+ "safetensors",
51
+ "cloudpickle",
51
52
  "pre-commit",
52
53
  "pytest",
53
54
  "pytest-cov",
@@ -71,6 +72,12 @@ version-file = "pytensor_ml/_version.py"
71
72
  [tool.pytest.ini_options]
72
73
  minversion = "6.0"
73
74
  xfail_strict=true
75
+ # A checkpoint test reads a published snapshot of several GB from disk, so it runs only when asked
76
+ # for: `pytest -m checkpoint`.
77
+ addopts = "-m 'not checkpoint'"
78
+ markers = [
79
+ "checkpoint: needs a full published checkpoint on disk, not a fixture",
80
+ ]
74
81
  filterwarnings = [
75
82
  "error",
76
83
  'ignore:\nArviZ is undergoing a major refactor.*:FutureWarning',
@@ -156,7 +163,7 @@ platforms = ["osx-arm64", "linux-64", "win-64"]
156
163
  # the two lists have to move together.
157
164
  [tool.pixi.feature.docs.dependencies]
158
165
  python = ">=3.12"
159
- pytensor = ">=3.3.0,<3.4.0"
166
+ pytensor = ">=3.3.3,<3.4.0"
160
167
  numpy = "*"
161
168
  safetensors = "*"
162
169
  # The gallery extension renders notebook thumbnails with matplotlib.
@@ -3,6 +3,7 @@ import sys
3
3
 
4
4
  import pytensor_ml.dispatch # installs the lazy backend-dispatch registration hook
5
5
  import pytensor_ml.rewriting.conv # registers the convolution rewrites into pytensor's databases
6
+ import pytensor_ml.rewriting.layers # registers the degenerate-dropout rewrite into pytensor's databases
6
7
 
7
8
  from pytensor_ml._version import __version__
8
9
  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.2.2'
22
- __version_tuple__ = version_tuple = (0, 2, 2)
21
+ __version__ = version = '0.3.0'
22
+ __version_tuple__ = version_tuple = (0, 3, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -151,7 +151,7 @@ class Tanh(Activation):
151
151
 
152
152
  def __call__(self, x: pt.TensorLike) -> pt.TensorVariable:
153
153
  out = pt.tanh(x)
154
- out.name = "TanH"
154
+ out.name = "Tanh"
155
155
  return out
156
156
 
157
157
 
@@ -319,10 +319,36 @@ class Swish(Activation):
319
319
  def __call__(self, x: pt.TensorLike) -> pt.TensorVariable:
320
320
  x = pt.as_tensor(x)
321
321
  out = x * pt.sigmoid(_constant_like(self.beta, x) * x)
322
- out.name = "Swish"
322
+ out.name = type(self).__name__
323
323
  return out
324
324
 
325
325
 
326
+ class QuickGELU(Swish):
327
+ r"""
328
+ Sigmoid approximation to GELU.
329
+
330
+ Compute :math:`\mathrm{QuickGELU}(x) = x \, \sigma(1.702 x)`, which is exactly :class:`Swish`
331
+ at :math:`\beta = 1.702`. Every OpenAI CLIP checkpoint was trained with it, and loading those
332
+ weights under :class:`GELU` gives quietly wrong activations rather than an error. Prefer
333
+ :class:`GELU` for anything new.
334
+
335
+ Examples
336
+ --------
337
+ Use it where a checkpoint was trained with it, such as a CLIP text encoder's MLP:
338
+
339
+ .. code-block:: python
340
+
341
+ from pytensor_ml.activations import QuickGELU
342
+ from pytensor_ml.layers import FeedForward, Input
343
+
344
+ X = Input("X", shape=(None, 77, 768))
345
+ activations = FeedForward("mlp", d_model=768, activation=QuickGELU())(X)
346
+ """
347
+
348
+ def __init__(self):
349
+ super().__init__(beta=1.702)
350
+
351
+
326
352
  class Softmax(Activation):
327
353
  r"""
328
354
  Softmax activation.
@@ -4,6 +4,7 @@ from typing import Protocol, runtime_checkable
4
4
  import pytensor.tensor as pt
5
5
 
6
6
  from pytensor.compile.builders import SymbolicOp
7
+ from pytensor.graph.basic import Variable
7
8
  from pytensor.tensor.variable import TensorVariable
8
9
 
9
10
 
@@ -16,6 +17,39 @@ def _check_input_rank(X: TensorVariable, name: str, n_spatial: int) -> None:
16
17
  )
17
18
 
18
19
 
20
+ def _resolve_layer_name(
21
+ name: object, default: str, suggested_hyperparameter: str | None = None
22
+ ) -> str:
23
+ """
24
+ Return the layer's name, rejecting a hyperparameter passed where the name belongs.
25
+
26
+ Parameters
27
+ ----------
28
+ name : str or None
29
+ The name the caller passed, annotated as ``object`` because validating it is the point.
30
+ None selects ``default``.
31
+ default : str
32
+ The layer's own name, used when none is given and quoted in the error.
33
+ suggested_hyperparameter : str, optional
34
+ The parameter the error tells the caller to pass by keyword instead. Layers with no
35
+ hyperparameter a name could displace omit it, and the error only reports the bad name.
36
+
37
+ Returns
38
+ -------
39
+ resolved : str
40
+ ``name`` when it is a non-empty string, otherwise ``default``.
41
+ """
42
+ if name is not None and not isinstance(name, str):
43
+ message = f"{default}'s `name` must be a string, but got {type(name).__name__} {name!r}."
44
+ if suggested_hyperparameter is not None:
45
+ message += (
46
+ f" Hyperparameters are keyword-only, so pass it by name: "
47
+ f"{default}({suggested_hyperparameter}={name!r})."
48
+ )
49
+ raise TypeError(message)
50
+ return name if name else default
51
+
52
+
19
53
  class Layer(ABC):
20
54
  """
21
55
  Base class for the objects that build layer graphs. Defined here, not in ``pytensor_ml.layers``, so
@@ -89,4 +123,38 @@ class StatefulOp(Protocol):
89
123
  """Map each output index to the index of the input that output updates."""
90
124
 
91
125
 
92
- __all__ = ["Layer", "LayerOp", "StatefulOp", "UnaryLayerOp"]
126
+ def update_chain_root(variable: Variable) -> tuple[Variable, int] | None:
127
+ """
128
+ Trace a stateful op's update input back to the value it ultimately writes, and count the applications
129
+ behind it.
130
+
131
+ Applying one layer object again feeds it what the previous application produced, so the input is the
132
+ written value itself only at the head of that chain. The depth orders the chain, and the deepest
133
+ application is the one holding every earlier contribution.
134
+
135
+ Parameters
136
+ ----------
137
+ variable : Variable
138
+ The input a stateful op declares itself to update.
139
+
140
+ Returns
141
+ -------
142
+ root : Variable or None
143
+ The value written at the head of the chain, or None if the chain breaks at an op that declares no
144
+ update for the output it reaches.
145
+ depth : int
146
+ How many applications separate ``variable`` from ``root``.
147
+ """
148
+ depth = 0
149
+ while True:
150
+ node = variable.owner
151
+ if node is None or not isinstance(node.op, StatefulOp):
152
+ return variable, depth
153
+ input_index = node.op.update_map().get(node.outputs.index(variable))
154
+ if input_index is None:
155
+ return None
156
+ variable = node.inputs[input_index]
157
+ depth += 1
158
+
159
+
160
+ __all__ = ["Layer", "LayerOp", "StatefulOp", "UnaryLayerOp", "update_chain_root"]