braintrace 0.3.0__tar.gz → 0.3.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 (174) hide show
  1. {braintrace-0.3.0 → braintrace-0.3.1}/PKG-INFO +1 -1
  2. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/e_prop_test.py +90 -0
  3. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/param_dim_vjp.py +61 -32
  4. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/sequence.py +34 -0
  5. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/sequence_test.py +57 -0
  6. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/output_bypass_test.py +58 -10
  7. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/vjp_base.py +117 -19
  8. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/diagnostics.py +3 -2
  9. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hidden_group.py +390 -64
  10. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hidden_group_test.py +548 -19
  11. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hidden_pertubation.py +83 -41
  12. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hidden_pertubation_fuzz_test.py +84 -5
  13. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hidden_pertubation_test.py +347 -7
  14. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/jaxpr_graph.py +79 -17
  15. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/jaxpr_graph_test.py +139 -0
  16. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/position_graph.py +4 -2
  17. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_version.py +1 -1
  18. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace.egg-info/PKG-INFO +1 -1
  19. {braintrace-0.3.0 → braintrace-0.3.1}/LICENSE +0 -0
  20. {braintrace-0.3.0 → braintrace-0.3.1}/README.md +0 -0
  21. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/__init__.py +0 -0
  22. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/__init___test.py +0 -0
  23. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/__init__.py +0 -0
  24. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/_common.py +0 -0
  25. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/_common_test.py +0 -0
  26. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/axes.py +0 -0
  27. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/axes_test.py +0 -0
  28. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/base.py +0 -0
  29. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/base_test.py +0 -0
  30. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/d_rtrl.py +0 -0
  31. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/d_rtrl_test.py +0 -0
  32. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/dni.py +0 -0
  33. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/dni_test.py +0 -0
  34. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/e_prop.py +0 -0
  35. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/graph_executor.py +0 -0
  36. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/graph_executor_test.py +0 -0
  37. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/io_dim_vjp.py +0 -0
  38. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/io_dim_vjp_test.py +0 -0
  39. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/ostl.py +0 -0
  40. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/ostl_test.py +0 -0
  41. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/param_dim_vjp_test.py +0 -0
  42. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/pp_prop.py +0 -0
  43. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/pp_prop_test.py +0 -0
  44. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/random_projection_vjp.py +0 -0
  45. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/random_projection_vjp_test.py +0 -0
  46. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/snap_n.py +0 -0
  47. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/snap_n_test.py +0 -0
  48. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/approx_correctness_test.py +0 -0
  49. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/axis_acceptance_test.py +0 -0
  50. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/axis_discrimination_test.py +0 -0
  51. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/axis_golden_test.py +0 -0
  52. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/conv_vmap_correctness_test.py +0 -0
  53. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/cross_check_test.py +0 -0
  54. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/diagnostic_exploration_test.py +0 -0
  55. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/exact_correctness_test.py +0 -0
  56. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/input_check_test.py +0 -0
  57. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/public_api_test.py +0 -0
  58. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/scan_descent_hint_test.py +0 -0
  59. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/scan_descent_support_test.py +0 -0
  60. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/scan_fusion_test.py +0 -0
  61. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/snn_model_correctness_test.py +0 -0
  62. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/trace_broadcast_test.py +0 -0
  63. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/transform_correctness_test.py +0 -0
  64. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/while_support_test.py +0 -0
  65. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/three_factor.py +0 -0
  66. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/three_factor_test.py +0 -0
  67. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/uoro.py +0 -0
  68. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/uoro_test.py +0 -0
  69. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/vjp_base_test.py +0 -0
  70. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/vjp_graph_executor.py +0 -0
  71. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/vjp_graph_executor_test.py +0 -0
  72. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compatible_imports.py +0 -0
  73. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compatible_imports_test.py +0 -0
  74. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compile.py +0 -0
  75. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compile_test.py +0 -0
  76. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/__init__.py +0 -0
  77. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/base.py +0 -0
  78. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/base_test.py +0 -0
  79. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/canonicalize.py +0 -0
  80. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/canonicalize_test.py +0 -0
  81. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/diagnostics_test.py +0 -0
  82. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/graph.py +0 -0
  83. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/graph_test.py +0 -0
  84. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hid_param_op.py +0 -0
  85. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hid_param_op_test.py +0 -0
  86. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/module_info.py +0 -0
  87. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/module_info_test.py +0 -0
  88. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/online_credit.py +0 -0
  89. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/online_credit_test.py +0 -0
  90. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/output_bypass.py +0 -0
  91. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/output_bypass_test.py +0 -0
  92. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/position_graph_test.py +0 -0
  93. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/report.py +0 -0
  94. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/report_test.py +0 -0
  95. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/scan_descent.py +0 -0
  96. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/scan_descent_leaves_test.py +0 -0
  97. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/scan_descent_test.py +0 -0
  98. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/tests/cell_relation_guardrail_test.py +0 -0
  99. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/tests/compiler_oracle_test.py +0 -0
  100. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/tests/compiler_property_test.py +0 -0
  101. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_grad_exponential.py +0 -0
  102. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_grad_exponential_test.py +0 -0
  103. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_input_data.py +0 -0
  104. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_input_data_test.py +0 -0
  105. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_legacy/__init__.py +0 -0
  106. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_legacy/_ops.py +0 -0
  107. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_legacy/_ops_test.py +0 -0
  108. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_legacy/_params.py +0 -0
  109. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_legacy/_params_test.py +0 -0
  110. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_misc.py +0 -0
  111. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_misc_test.py +0 -0
  112. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/__init__.py +0 -0
  113. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/_primitive.py +0 -0
  114. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/_primitive_test.py +0 -0
  115. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/_registries.py +0 -0
  116. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/_registries_test.py +0 -0
  117. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/conv.py +0 -0
  118. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/conv_test.py +0 -0
  119. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/dense.py +0 -0
  120. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/dense_test.py +0 -0
  121. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/einsum.py +0 -0
  122. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/einsum_test.py +0 -0
  123. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/elemwise.py +0 -0
  124. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/elemwise_test.py +0 -0
  125. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/embedding.py +0 -0
  126. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/embedding_test.py +0 -0
  127. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/grouped.py +0 -0
  128. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/grouped_test.py +0 -0
  129. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/lora.py +0 -0
  130. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/lora_test.py +0 -0
  131. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/op_rule_oracle.py +0 -0
  132. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/op_rule_oracle_test.py +0 -0
  133. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/sparse.py +0 -0
  134. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/sparse_test.py +0 -0
  135. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_state_management.py +0 -0
  136. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_state_management_test.py +0 -0
  137. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/__init__.py +0 -0
  138. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/census.py +0 -0
  139. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/census_test.py +0 -0
  140. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/compiler_models.py +0 -0
  141. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/graph_hash.py +0 -0
  142. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/graph_hash_test.py +0 -0
  143. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/models.py +0 -0
  144. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/oracle.py +0 -0
  145. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/oracle_models.py +0 -0
  146. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/oracle_models_test.py +0 -0
  147. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/oracle_test.py +0 -0
  148. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/precondition_models.py +0 -0
  149. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/precondition_models_test.py +0 -0
  150. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/scenario_catalog.py +0 -0
  151. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/scenario_catalog_test.py +0 -0
  152. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_typing.py +0 -0
  153. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_typing_test.py +0 -0
  154. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_version_test.py +0 -0
  155. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/__init__.py +0 -0
  156. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/__init___test.py +0 -0
  157. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_conv.py +0 -0
  158. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_conv_test.py +0 -0
  159. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_embedding.py +0 -0
  160. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_embedding_test.py +0 -0
  161. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_linear.py +0 -0
  162. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_linear_test.py +0 -0
  163. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_readout.py +0 -0
  164. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_readout_test.py +0 -0
  165. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_rnn.py +0 -0
  166. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_rnn_test.py +0 -0
  167. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/py.typed +0 -0
  168. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace.egg-info/SOURCES.txt +0 -0
  169. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace.egg-info/dependency_links.txt +0 -0
  170. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace.egg-info/requires.txt +0 -0
  171. {braintrace-0.3.0 → braintrace-0.3.1}/braintrace.egg-info/top_level.txt +0 -0
  172. {braintrace-0.3.0 → braintrace-0.3.1}/pyproject.toml +0 -0
  173. {braintrace-0.3.0 → braintrace-0.3.1}/setup.cfg +0 -0
  174. {braintrace-0.3.0 → braintrace-0.3.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braintrace
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Enabling Scalable Online Learning for Brain Dynamics.
5
5
  Author-email: BrainTrace Developers <chaobrain@outlook.com>
6
6
  License: Apache-2.0 license
@@ -15,9 +15,13 @@
15
15
 
16
16
  import unittest
17
17
 
18
+ import brainevent
18
19
  import brainstate
19
20
  import jax
20
21
  import jax.numpy as jnp
22
+ import numpy as np
23
+ import numpy.testing as npt
24
+ import pytest
21
25
 
22
26
  import braintrace
23
27
  from braintrace._algorithm.e_prop import EProp
@@ -443,3 +447,89 @@ def test_docstring_compile_example_runs():
443
447
  assert y.shape == (1,)
444
448
  assert bool(jnp.all(jnp.isfinite(y)))
445
449
  assert len(learner.graph.hidden_param_op_relations) >= 1
450
+
451
+
452
+ # ----------------------------------------------------------------------------
453
+ # #257: the kappa trace filter under compile(..., vmap=True)
454
+ # ----------------------------------------------------------------------------
455
+
456
+ _B, _N, _T = 3, 5, 6
457
+ _rng = np.random.RandomState(0)
458
+ _DENSE = ((_rng.rand(_N, _N) < 0.5) * _rng.randn(_N, _N)).astype('float32')
459
+ _XS = jnp.asarray(_rng.randn(_T, _B, _N).astype('float32'))
460
+
461
+
462
+ class _FilterNet(brainstate.nn.Module):
463
+ """The issue's model: a leaky recurrent layer through one ETP weight."""
464
+
465
+ def __init__(self, kind):
466
+ super().__init__()
467
+ if kind == 'sparse':
468
+ self.w = braintrace.nn.SparseLinear(
469
+ brainevent.CSR.fromdense(jnp.asarray(_DENSE)))
470
+ else:
471
+ self.w = braintrace.nn.Linear(_N, _N, w_init=jnp.asarray(_DENSE))
472
+
473
+ def init_state(self, *args, **kwargs):
474
+ self.h = brainstate.HiddenState(jnp.zeros(_N))
475
+
476
+ def update(self, x):
477
+ self.h.value = 0.9 * self.h.value + self.w(jnp.tanh(self.h.value) + x)
478
+ return self.h.value
479
+
480
+
481
+ def _filtered_grad(kind, kappa, xs, **kw):
482
+ learner = braintrace.compile(_FilterNet(kind), 'eprop', xs[0],
483
+ kappa_filter_decay=kappa, **kw)
484
+ g = learner.etrace_grad(xs, step_fn=lambda x: jnp.sum(learner(x) ** 2))
485
+ return learner, g
486
+
487
+
488
+ @pytest.mark.parametrize('vjp_method', ['single-step', 'multi-step'])
489
+ @pytest.mark.parametrize('kind', ['dense', 'sparse'])
490
+ def test_kappa_filter_under_vmap_is_the_sum_of_unbatched_lanes(kind, vjp_method):
491
+ # The batched objective sums the lanes' losses, so its gradient of the
492
+ # shared weights is the sum of the B unbatched gradients (f32 tolerance:
493
+ # the batched path reassociates the lane sum).
494
+ batched, g_b = _filtered_grad(kind, 0.5, _XS, batch_size=_B, vmap=True,
495
+ vjp_method=vjp_method)
496
+ lanes = [_filtered_grad(kind, 0.5, _XS[:, b], vjp_method=vjp_method)
497
+ for b in range(_B)]
498
+ g_ref = jax.tree.map(lambda *g: sum(g), *[g for _, g in lanes])
499
+ jax.tree.map(lambda a, r: npt.assert_allclose(a, r, rtol=1e-5, atol=1e-5),
500
+ g_b, g_ref)
501
+ # the filter contributes: kappa=0 gives a different gradient
502
+ _, g_0 = _filtered_grad(kind, 0.0, _XS, batch_size=_B, vmap=True,
503
+ vjp_method=vjp_method)
504
+ diff = max(float(jnp.max(jnp.abs(a - b)))
505
+ for a, b in zip(jax.tree.leaves(g_b), jax.tree.leaves(g_0)))
506
+ assert diff > 1e-2
507
+ # each lane's filter state is that lane's unbatched filter
508
+ flt_b = [f.value for f in batched.module._trace_filters.values()]
509
+ for b, (lane, _) in enumerate(lanes):
510
+ flt_l = [f.value for f in lane._trace_filters.values()]
511
+ jax.tree.map(lambda a, r: npt.assert_allclose(a[b], r, rtol=1e-5, atol=1e-6),
512
+ flt_b, flt_l)
513
+
514
+
515
+ @pytest.mark.parametrize('vjp_method', ['single-step', 'multi-step'])
516
+ def test_kappa_filter_advances_in_the_forward_pass(vjp_method):
517
+ # A step that is not differentiated advances the filter exactly as a
518
+ # differentiated one does: the filter is a function of the trace sequence.
519
+ learners = []
520
+ for differentiate in (False, True):
521
+ learner = braintrace.compile(_FilterNet('dense'), 'eprop', _XS[0, 0],
522
+ kappa_filter_decay=0.5, vjp_method=vjp_method)
523
+
524
+ def step(x, learner=learner):
525
+ return jnp.sum(learner(x) ** 2)
526
+
527
+ for t in range(3):
528
+ if differentiate:
529
+ brainstate.transform.grad(step, learner.param_states)(_XS[t, 0])
530
+ else:
531
+ step(_XS[t, 0])
532
+ learners.append(learner)
533
+ plain, diffed = ([f.value for f in l._trace_filters.values()] for l in learners)
534
+ jax.tree.map(npt.assert_array_equal, plain, diffed)
535
+ assert max(float(jnp.max(jnp.abs(v))) for v in jax.tree.leaves(plain)) > 1e-2
@@ -1553,6 +1553,60 @@ class ParamDimVjpAlgorithm(ETraceVjpAlgorithm):
1553
1553
  for x, val in etrace_vals.items():
1554
1554
  self.etrace_bwg[x].value = val
1555
1555
 
1556
+ def _get_trace_filter_data(self) -> Optional[Dict[ETraceWG_Key, PyTree]]:
1557
+ """The ``trace_filter='kappa'`` values, or ``None`` without a filter."""
1558
+ if not self._trace_filters:
1559
+ return None
1560
+ return {k: v.value for k, v in self._trace_filters.items()}
1561
+
1562
+ def _assign_trace_filter_data(self, filter_vals: Optional[Dict[ETraceWG_Key, PyTree]]) -> None:
1563
+ """Store the advanced ``trace_filter='kappa'`` values."""
1564
+ if filter_vals is None:
1565
+ return
1566
+ for key, val in filter_vals.items():
1567
+ self._trace_filters[key].value = val
1568
+
1569
+ def _advance_trace_filter(
1570
+ self,
1571
+ filter_vals: Optional[Dict[ETraceWG_Key, PyTree]],
1572
+ etrace_vals: Dict[ETraceWG_Key, PyTree],
1573
+ ) -> Tuple[Dict[ETraceWG_Key, PyTree], Optional[Dict[ETraceWG_Key, PyTree]]]:
1574
+ r"""Low-pass the raw trace before it is contracted with the signal.
1575
+
1576
+ Applied elementwise (``jax.tree.map`` over the trace's own PyTree,
1577
+ including its trailing ``num_state`` axis), so multi-state HiddenGroups
1578
+ are filtered independently per state -- never summed across states and
1579
+ broadcast back:
1580
+
1581
+ .. math::
1582
+
1583
+ \bar e^t_{ji} = \kappa\,\bar e^{t-1}_{ji} + e^t_{ji}
1584
+
1585
+ Returns the filtered trace, for the backward pass, and the new filter.
1586
+ """
1587
+ if filter_vals is None:
1588
+ return etrace_vals, None
1589
+ kappa = self.config.kappa
1590
+ filtered = dict(etrace_vals)
1591
+ new_filter = dict(filter_vals)
1592
+ for key, trace in etrace_vals.items():
1593
+ if key not in filter_vals:
1594
+ continue
1595
+ new_val = jax.tree.map(lambda prev, e: kappa * prev + e, filter_vals[key], trace)
1596
+ filtered[key] = new_val
1597
+ new_filter[key] = new_val
1598
+ return filtered, new_filter
1599
+
1600
+ def _previous_filtered_trace(
1601
+ self,
1602
+ filter_vals: Optional[Dict[ETraceWG_Key, PyTree]],
1603
+ etrace_vals: Dict[ETraceWG_Key, PyTree],
1604
+ ) -> Dict[ETraceWG_Key, PyTree]:
1605
+ """The previous *filtered* trace (the filter before this step)."""
1606
+ if filter_vals is None:
1607
+ return etrace_vals
1608
+ return {k: filter_vals.get(k, v) for k, v in etrace_vals.items()}
1609
+
1556
1610
  def _make_scan_fn(self, weight_vals: Dict[Path, PyTree]) -> Callable:
1557
1611
  """Build the per-step D-RTRL eligibility-trace stepper (scan body).
1558
1612
 
@@ -1668,17 +1722,13 @@ class ParamDimVjpAlgorithm(ETraceVjpAlgorithm):
1668
1722
  """Contract the previous trace with the hidden output-bypass signal.
1669
1723
 
1670
1724
  See :meth:`ETraceVjpAlgorithm._solve_previous_trace_gradients`. With
1671
- trace filters active, the previous *filtered* trace (the filter's
1672
- current value, not yet advanced this step) is the one paired.
1725
+ trace filters active, the forward pass hands in the previous *filtered*
1726
+ trace (the filter before this step; see
1727
+ :meth:`_previous_filtered_trace`).
1673
1728
  """
1674
- etrace = dict(etrace_h2w_at_t_minus_1)
1675
- if self._trace_filters:
1676
- for key, flt in self._trace_filters.items():
1677
- if key in etrace:
1678
- etrace[key] = flt.value
1679
1729
  dG_weights: Dict[Path, Any] = {path: None for path in self.param_states}
1680
1730
  _solve_param_dim_weight_gradients(
1681
- etrace,
1731
+ etrace_h2w_at_t_minus_1,
1682
1732
  dG_weights,
1683
1733
  dl_to_hidden_groups,
1684
1734
  self.graph.hidden_param_op_relations,
@@ -1713,7 +1763,9 @@ class ParamDimVjpAlgorithm(ETraceVjpAlgorithm):
1713
1763
  Current timestep counter used for bias correction.
1714
1764
  etrace_h2w_at_t : Dict[ETraceWG_Key, PyTree]
1715
1765
  Eligibility trace values at the current timestep, mapping parameter-hidden
1716
- state relationship keys to trace values.
1766
+ state relationship keys to trace values. Under ``trace_filter='kappa'``
1767
+ the forward pass has already filtered them
1768
+ (:meth:`_advance_trace_filter`).
1717
1769
  dl_to_hidden_groups : Sequence[jax.Array]
1718
1770
  Gradients of the loss with respect to hidden states at the current timestep.
1719
1771
  weight_vals : Dict[Path, PyTree]
@@ -1729,29 +1781,6 @@ class ParamDimVjpAlgorithm(ETraceVjpAlgorithm):
1729
1781
  Dict[Path, PyTree]
1730
1782
  Dictionary mapping parameter paths to their gradient values.
1731
1783
  """
1732
- # `trace_filter`: low-pass the raw trace before it is contracted with the
1733
- # learning signal. Applied elementwise (`jax.tree.map` over the trace's
1734
- # own PyTree, including its trailing num_state axis) so multi-state
1735
- # HiddenGroups are filtered independently per state -- never summed
1736
- # across states and broadcast back.
1737
- #
1738
- # e_bar^t_{ji} = kappa * e_bar^{t-1}_{ji} + e^t_{ji}
1739
- #
1740
- if self._trace_filters:
1741
- kappa = self.config.kappa
1742
- filtered: Dict[ETraceWG_Key, PyTree] = {}
1743
- for key, trace in etrace_h2w_at_t.items():
1744
- flt = self._trace_filters.get(key)
1745
- if flt is None:
1746
- filtered[key] = trace
1747
- continue
1748
- new_val = jax.tree.map(
1749
- lambda prev, e: kappa * prev + e, flt.value, trace
1750
- )
1751
- flt.value = new_val
1752
- filtered[key] = new_val
1753
- etrace_h2w_at_t = filtered
1754
-
1755
1784
  dG_weights: Dict[Path, Any] = {path: None for path in self.param_states}
1756
1785
 
1757
1786
  # update the etrace weight gradients
@@ -573,3 +573,37 @@ class ETraceVmap(SequenceDriverMixin, brainstate.nn.Vmap):
573
573
  @property
574
574
  def _seq_vjp_method(self) -> Optional[str]:
575
575
  return getattr(self.module, 'vjp_method', None)
576
+
577
+ def reset_state(self, batch_size: Optional[int] = None, **kwargs: Any) -> None:
578
+ """Reset every lane's eligibility traces and the running index.
579
+
580
+ ``brainstate.nn.Vmap`` inherits the no-op ``Module.reset_state``, so
581
+ without this a batched learner kept the previous sequence's traces and
582
+ every call after the first started from them. The wrapped learner's
583
+ ``reset_state`` zeroes its traces with ``zeros_like``, which keeps the
584
+ lane axis that ``compile(..., vmap=True)`` gave them, so this delegates
585
+ to it. Like the learner's own ``reset_state``, it leaves the model's
586
+ hidden states alone.
587
+
588
+ Parameters
589
+ ----------
590
+ batch_size : int, optional
591
+ Must be ``None``: the lane axis is fixed by
592
+ ``compile(..., batch_size=...)``, and a batch size here would put a
593
+ second batch axis in front of it.
594
+ **kwargs
595
+ Passed to the wrapped learner's ``reset_state``.
596
+
597
+ Raises
598
+ ------
599
+ ValueError
600
+ If ``batch_size`` is not ``None``.
601
+ """
602
+ if batch_size is not None:
603
+ raise ValueError(
604
+ f'ETraceVmap.reset_state(batch_size={batch_size!r}): the lane '
605
+ f'axis is fixed by compile(..., vmap=True, batch_size=...), and '
606
+ f'the traces already carry it; call reset_state() without '
607
+ f'batch_size.'
608
+ )
609
+ self.module.reset_state(**kwargs) # type: ignore[attr-defined]
@@ -775,6 +775,63 @@ class TestVmap:
775
775
  for k, v in _arrays(grads).items():
776
776
  assert np.all(np.isfinite(v)), f'{k} is not finite'
777
777
 
778
+ def test_reset_state_zeroes_every_lanes_traces(self):
779
+ """``ETraceVmap.reset_state`` used to be ``Module``'s no-op.
780
+
781
+ After a call the traces are non-zero; ``reset_state`` must zero them
782
+ all, keep their lane axis and rewind ``running_index``.
783
+ """
784
+ batch = 3
785
+ xs, ys = _lane_data(batch)
786
+ learner = _vmap_learner(batch)
787
+ learner.etrace_grad(
788
+ xs, ys, step_fn=lambda i, t: jnp.sum((learner(i) - t) ** 2))
789
+ before = {k: s.value for k, s in learner.module.etrace_bwg.items()}
790
+ assert max(float(jnp.max(jnp.abs(x))) for x in jax.tree.leaves(before)) > 0.0
791
+ assert int(learner.module.running_index.value) == T
792
+
793
+ learner.reset_state()
794
+
795
+ for k, s in learner.module.etrace_bwg.items():
796
+ for old, new in zip(jax.tree.leaves(before[k]), jax.tree.leaves(s.value)):
797
+ assert jnp.shape(new) == jnp.shape(old)
798
+ assert jnp.shape(new)[0] == batch
799
+ assert float(jnp.max(jnp.abs(new))) == 0.0, f'{k} kept its trace'
800
+ assert int(learner.module.running_index.value) == 0
801
+
802
+ def test_a_second_call_after_reset_state_repeats_the_first(self):
803
+ """Two consecutive batched calls, with ``reset_state`` and the hidden
804
+ states restored between them, give the same gradient. Before the fix
805
+ the second call started from the first call's traces.
806
+ """
807
+ batch = 3
808
+ xs, ys = _lane_data(batch)
809
+ net = _VmapNet()
810
+ learner = braintrace.compile(net, 'D_RTRL', jnp.zeros((batch, N_IN)),
811
+ batch_size=batch, vmap=True,
812
+ vjp_method='multi-step')
813
+ hidden0 = {k: s.value for k, s in net.states(brainstate.HiddenState).items()}
814
+
815
+ def step_fn(inp, tar):
816
+ return jnp.sum((learner(inp) - tar) ** 2)
817
+
818
+ g_first = learner.etrace_grad(xs, ys, step_fn=step_fn)
819
+ learner.reset_state()
820
+ for k, s in net.states(brainstate.HiddenState).items():
821
+ s.value = hidden0[k]
822
+ g_second = learner.etrace_grad(xs, ys, step_fn=step_fn)
823
+
824
+ for k, v in _arrays(g_first).items():
825
+ assert np.max(np.abs(v)) > 0.0, f'{k} is identically zero -- vacuous'
826
+ _assert_trees_equal(g_second, g_first, rtol=1e-6, atol=1e-7,
827
+ msg='second call after reset_state vs the first')
828
+
829
+ def test_reset_state_refuses_a_batch_size(self):
830
+ """The lane axis is the vmap's; a ``batch_size`` would add a second one."""
831
+ learner = _vmap_learner(3)
832
+ with pytest.raises(ValueError, match='batch_size'):
833
+ learner.reset_state(3)
834
+
778
835
  def test_the_vmap_return_value_is_still_a_brainstate_vmap(self):
779
836
  """Spec test 20 -- existing ``vmap=True`` users must be unaffected."""
780
837
  learner = _vmap_learner(3)
@@ -35,6 +35,7 @@ import brainstate
35
35
  import braintools
36
36
  import jax
37
37
  import jax.numpy as jnp
38
+ import numpy as np
38
39
  import numpy.testing as npt
39
40
  import pytest
40
41
 
@@ -303,22 +304,69 @@ def test_previous_trace_hook_is_the_weight_contraction(algo):
303
304
 
304
305
 
305
306
  def test_previous_trace_hook_reads_the_filtered_trace_without_advancing_it():
307
+ # The forward pass hands the bypass the previous *filtered* trace -- the
308
+ # filter before this step -- and neither it nor the contraction advances
309
+ # the filter (it advances once per step, in the forward pass; #257).
306
310
  learner, dl, weights = _engine('eprop', kappa_filter_decay=0.9)
307
311
  assert learner._trace_filters
308
312
  before = {k: f.value for k, f in learner._trace_filters.items()}
309
313
  trace = learner._get_etrace_data()
310
- prev = learner._solve_previous_trace_gradients(0, trace, dl, weights)
314
+ prev_trace = learner._previous_filtered_trace(learner._get_trace_filter_data(), trace)
315
+ assert set(prev_trace) == set(trace)
316
+ for k in trace:
317
+ assert prev_trace[k] is before[k]
318
+ prev = learner._solve_previous_trace_gradients(0, prev_trace, dl, weights)
311
319
  for k, f in learner._trace_filters.items():
312
320
  npt.assert_array_equal(f.value, before[k])
313
- # identical to contracting the filter's current value directly
314
- filtered = {k: before.get(k, v) for k, v in trace.items()}
315
- learner._trace_filters, saved = {}, learner._trace_filters
316
- try:
317
- ref = learner._solve_previous_trace_gradients(0, filtered, dl, weights)
318
- finally:
319
- learner._trace_filters = saved
320
- for path in weights:
321
- npt.assert_allclose(prev[path], ref[path], rtol=1e-6)
321
+ # the filter differs from the raw trace, so the pairing matters
322
+ raw = learner._solve_previous_trace_gradients(0, trace, dl, weights)
323
+ assert any(not np.allclose(prev[p], raw[p]) for p in weights)
324
+ # and the forward pass is what pairs it: its bypass residual is the filter
325
+ # before the step, its main residual the filter advanced on the new trace
326
+ xs, _ = _data(N)
327
+ fwd_out, fwd_res = learner._update_fn_fwd(
328
+ learner._conform_inputs((xs[4],)),
329
+ weights,
330
+ {k: s.value for k, s in learner.hidden_states.items()},
331
+ {k: s.value for k, s in learner.other_states.items()},
332
+ (trace, learner._get_trace_filter_data()),
333
+ learner.running_index.value,
334
+ None,
335
+ )
336
+ new_trace, new_filter = fwd_out[-1]
337
+ kappa = learner.config.kappa
338
+ for k in trace:
339
+ jax.tree.map(npt.assert_array_equal, fwd_res[-1][k], before[k])
340
+ jax.tree.map(lambda f, b, e: npt.assert_allclose(f, kappa * b + e, rtol=1e-6),
341
+ fwd_res[1][k], before[k], new_trace[k])
342
+ jax.tree.map(npt.assert_array_equal, new_filter[k], fwd_res[1][k])
343
+
344
+
345
+ def test_kappa_filter_with_bypass_under_vmap_is_the_sum_of_unbatched_lanes():
346
+ # e-prop's trace filter on a model with a hidden output bypass, under
347
+ # compile(vmap=True) (#257): the bypass pairs the filter before the step,
348
+ # which the forward pass hands the backward pass. The batched objective
349
+ # sums the lanes' losses, so the gradient is the sum of the B unbatched
350
+ # lanes' (f32 tolerance: the batched path reassociates the lane sum).
351
+ B, kappa = 3, 0.7
352
+ xs = jax.random.uniform(jax.random.PRNGKey(5), (T, B, N_IN)) * 2.0
353
+
354
+ def grad(x_seq, **kw):
355
+ brainstate.random.seed(0)
356
+ learner = braintrace.compile(_LIF('pre_reset'), 'eprop', x_seq[0],
357
+ kappa_filter_decay=kappa, **kw)
358
+ module = learner.module if kw.get('vmap') else learner
359
+ assert list(module.graph.hidden_perturb.bypass_hidden_paths) == [('V',)]
360
+ return learner.etrace_grad(
361
+ x_seq, step_fn=lambda x: jnp.sum((learner(x) - 0.3) ** 2))
362
+
363
+ with warnings.catch_warnings():
364
+ warnings.simplefilter('ignore')
365
+ batched = grad(xs, batch_size=B, vmap=True)
366
+ lanes = [grad(xs[:, b]) for b in range(B)]
367
+ ref = jax.tree.map(lambda *g: sum(g), *lanes)
368
+ jax.tree.map(lambda a, r: npt.assert_allclose(a, r, rtol=1e-5, atol=1e-5), batched, ref)
369
+ assert max(float(jnp.max(jnp.abs(g))) for g in jax.tree.leaves(ref)) > 1e-2
322
370
 
323
371
 
324
372
  def test_vmapped_lanes_match_per_sample_bptt():