braintrace 0.2.8__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 (178) hide show
  1. {braintrace-0.2.8 → braintrace-0.3.1}/PKG-INFO +2 -2
  2. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/base.py +27 -16
  3. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/e_prop_test.py +90 -0
  4. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/param_dim_vjp.py +376 -97
  5. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/random_projection_vjp.py +9 -11
  6. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/sequence.py +34 -0
  7. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/sequence_test.py +57 -0
  8. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/snap_n.py +4 -3
  9. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/snap_n_test.py +300 -0
  10. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/conv_vmap_correctness_test.py +81 -0
  11. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/exact_correctness_test.py +94 -0
  12. braintrace-0.3.1/braintrace/_algorithm/tests/input_check_test.py +597 -0
  13. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/output_bypass_test.py +58 -10
  14. braintrace-0.3.1/braintrace/_algorithm/tests/scan_descent_hint_test.py +205 -0
  15. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/scan_descent_support_test.py +20 -13
  16. braintrace-0.3.1/braintrace/_algorithm/tests/trace_broadcast_test.py +674 -0
  17. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/three_factor.py +3 -0
  18. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/three_factor_test.py +13 -1
  19. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/vjp_base.py +318 -30
  20. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/vjp_graph_executor.py +17 -3
  21. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compile_test.py +46 -0
  22. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/base.py +28 -8
  23. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/base_test.py +37 -3
  24. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/canonicalize.py +227 -14
  25. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/canonicalize_test.py +658 -0
  26. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/diagnostics.py +66 -3
  27. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/graph.py +240 -21
  28. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/hid_param_op.py +257 -89
  29. braintrace-0.3.1/braintrace/_compiler/hid_param_op_test.py +415 -0
  30. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/hidden_group.py +798 -169
  31. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/hidden_group_test.py +1330 -4
  32. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/hidden_pertubation.py +609 -10
  33. braintrace-0.3.1/braintrace/_compiler/hidden_pertubation_fuzz_test.py +428 -0
  34. braintrace-0.3.1/braintrace/_compiler/hidden_pertubation_test.py +1990 -0
  35. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/jaxpr_graph.py +144 -17
  36. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/jaxpr_graph_test.py +139 -0
  37. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/module_info.py +30 -0
  38. braintrace-0.3.1/braintrace/_compiler/online_credit.py +570 -0
  39. braintrace-0.3.1/braintrace/_compiler/online_credit_test.py +800 -0
  40. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/output_bypass.py +2 -5
  41. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/output_bypass_test.py +73 -0
  42. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/position_graph.py +389 -47
  43. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/position_graph_test.py +301 -1
  44. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/scan_descent.py +461 -44
  45. braintrace-0.3.1/braintrace/_compiler/scan_descent_leaves_test.py +315 -0
  46. braintrace-0.3.1/braintrace/_compiler/scan_descent_test.py +1550 -0
  47. braintrace-0.3.1/braintrace/_input_data.py +536 -0
  48. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/conv_test.py +11 -0
  49. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/dense_test.py +9 -0
  50. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_testing/__init__.py +10 -0
  51. braintrace-0.3.1/braintrace/_testing/census.py +550 -0
  52. braintrace-0.3.1/braintrace/_testing/census_test.py +397 -0
  53. braintrace-0.3.1/braintrace/_testing/graph_hash.py +608 -0
  54. braintrace-0.3.1/braintrace/_testing/graph_hash_test.py +398 -0
  55. braintrace-0.3.1/braintrace/_testing/precondition_models.py +560 -0
  56. braintrace-0.3.1/braintrace/_testing/precondition_models_test.py +154 -0
  57. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_testing/scenario_catalog_test.py +6 -3
  58. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_version.py +1 -1
  59. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/_linear_test.py +36 -34
  60. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace.egg-info/PKG-INFO +2 -2
  61. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace.egg-info/SOURCES.txt +13 -0
  62. {braintrace-0.2.8 → braintrace-0.3.1}/pyproject.toml +6 -1
  63. braintrace-0.2.8/braintrace/_compiler/hid_param_op_test.py +0 -226
  64. braintrace-0.2.8/braintrace/_compiler/hidden_pertubation_test.py +0 -551
  65. braintrace-0.2.8/braintrace/_compiler/scan_descent_test.py +0 -495
  66. braintrace-0.2.8/braintrace/_input_data.py +0 -259
  67. {braintrace-0.2.8 → braintrace-0.3.1}/LICENSE +0 -0
  68. {braintrace-0.2.8 → braintrace-0.3.1}/README.md +0 -0
  69. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/__init__.py +0 -0
  70. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/__init___test.py +0 -0
  71. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/__init__.py +0 -0
  72. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/_common.py +0 -0
  73. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/_common_test.py +0 -0
  74. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/axes.py +0 -0
  75. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/axes_test.py +0 -0
  76. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/base_test.py +0 -0
  77. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/d_rtrl.py +0 -0
  78. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/d_rtrl_test.py +0 -0
  79. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/dni.py +0 -0
  80. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/dni_test.py +0 -0
  81. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/e_prop.py +0 -0
  82. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/graph_executor.py +0 -0
  83. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/graph_executor_test.py +0 -0
  84. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/io_dim_vjp.py +0 -0
  85. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/io_dim_vjp_test.py +0 -0
  86. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/ostl.py +0 -0
  87. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/ostl_test.py +0 -0
  88. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/param_dim_vjp_test.py +0 -0
  89. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/pp_prop.py +0 -0
  90. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/pp_prop_test.py +0 -0
  91. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/random_projection_vjp_test.py +0 -0
  92. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/approx_correctness_test.py +0 -0
  93. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/axis_acceptance_test.py +0 -0
  94. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/axis_discrimination_test.py +0 -0
  95. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/axis_golden_test.py +0 -0
  96. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/cross_check_test.py +0 -0
  97. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/diagnostic_exploration_test.py +0 -0
  98. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/public_api_test.py +0 -0
  99. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/scan_fusion_test.py +0 -0
  100. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/snn_model_correctness_test.py +0 -0
  101. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/transform_correctness_test.py +0 -0
  102. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/tests/while_support_test.py +0 -0
  103. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/uoro.py +0 -0
  104. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/uoro_test.py +0 -0
  105. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/vjp_base_test.py +0 -0
  106. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_algorithm/vjp_graph_executor_test.py +0 -0
  107. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compatible_imports.py +0 -0
  108. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compatible_imports_test.py +0 -0
  109. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compile.py +0 -0
  110. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/__init__.py +0 -0
  111. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/diagnostics_test.py +0 -0
  112. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/graph_test.py +0 -0
  113. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/module_info_test.py +0 -0
  114. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/report.py +0 -0
  115. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/report_test.py +0 -0
  116. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/tests/cell_relation_guardrail_test.py +0 -0
  117. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/tests/compiler_oracle_test.py +0 -0
  118. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_compiler/tests/compiler_property_test.py +0 -0
  119. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_grad_exponential.py +0 -0
  120. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_grad_exponential_test.py +0 -0
  121. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_input_data_test.py +0 -0
  122. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_legacy/__init__.py +0 -0
  123. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_legacy/_ops.py +0 -0
  124. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_legacy/_ops_test.py +0 -0
  125. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_legacy/_params.py +0 -0
  126. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_legacy/_params_test.py +0 -0
  127. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_misc.py +0 -0
  128. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_misc_test.py +0 -0
  129. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/__init__.py +0 -0
  130. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/_primitive.py +0 -0
  131. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/_primitive_test.py +0 -0
  132. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/_registries.py +0 -0
  133. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/_registries_test.py +0 -0
  134. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/conv.py +0 -0
  135. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/dense.py +0 -0
  136. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/einsum.py +0 -0
  137. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/einsum_test.py +0 -0
  138. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/elemwise.py +0 -0
  139. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/elemwise_test.py +0 -0
  140. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/embedding.py +0 -0
  141. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/embedding_test.py +0 -0
  142. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/grouped.py +0 -0
  143. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/grouped_test.py +0 -0
  144. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/lora.py +0 -0
  145. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/lora_test.py +0 -0
  146. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/op_rule_oracle.py +0 -0
  147. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/op_rule_oracle_test.py +0 -0
  148. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/sparse.py +0 -0
  149. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_op/sparse_test.py +0 -0
  150. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_state_management.py +0 -0
  151. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_state_management_test.py +0 -0
  152. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_testing/compiler_models.py +0 -0
  153. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_testing/models.py +0 -0
  154. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_testing/oracle.py +0 -0
  155. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_testing/oracle_models.py +0 -0
  156. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_testing/oracle_models_test.py +0 -0
  157. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_testing/oracle_test.py +0 -0
  158. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_testing/scenario_catalog.py +0 -0
  159. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_typing.py +0 -0
  160. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_typing_test.py +0 -0
  161. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/_version_test.py +0 -0
  162. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/__init__.py +0 -0
  163. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/__init___test.py +0 -0
  164. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/_conv.py +0 -0
  165. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/_conv_test.py +0 -0
  166. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/_embedding.py +0 -0
  167. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/_embedding_test.py +0 -0
  168. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/_linear.py +0 -0
  169. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/_readout.py +0 -0
  170. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/_readout_test.py +0 -0
  171. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/_rnn.py +0 -0
  172. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/nn/_rnn_test.py +0 -0
  173. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace/py.typed +0 -0
  174. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace.egg-info/dependency_links.txt +0 -0
  175. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace.egg-info/requires.txt +0 -0
  176. {braintrace-0.2.8 → braintrace-0.3.1}/braintrace.egg-info/top_level.txt +0 -0
  177. {braintrace-0.2.8 → braintrace-0.3.1}/setup.cfg +0 -0
  178. {braintrace-0.2.8 → braintrace-0.3.1}/setup.py +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: braintrace
3
- Version: 0.2.8
3
+ Version: 0.3.1
4
4
  Summary: Enabling Scalable Online Learning for Brain Dynamics.
5
- Author-email: BrainTrace Developers <chao.brain@qq.com>
5
+ Author-email: BrainTrace Developers <chaobrain@outlook.com>
6
6
  License: Apache-2.0 license
7
7
  Project-URL: Homepage, https://github.com/chaobrain/braintrace
8
8
  Project-URL: Bug Tracker, https://github.com/chaobrain/braintrace/issues
@@ -66,6 +66,26 @@ class EligibilityTrace(brainstate.ShortTermState):
66
66
  __module__ = 'braintrace'
67
67
 
68
68
 
69
+ def descended_scan_length(graph: ETraceGraph) -> Optional[int]:
70
+ """The length of the longest scan that *graph* descends, or ``None``.
71
+
72
+ A descended scan holds an ETP relation or a hidden group in its body; a
73
+ group can be descended without any relation (all body weights used
74
+ through plain ops), so both are read. Unrolling the scan, with
75
+ ``ControlFlowPolicy(scan_unroll_limit=length)``, removes the descent.
76
+ """
77
+ lengths = [r.control_flow_context.scan.length
78
+ for r in graph.hidden_param_op_relations
79
+ if r.control_flow_context is not None]
80
+ lengths += [g.descent.scan.length for g in graph.hidden_groups if g.descent is not None]
81
+ return max(lengths) if lengths else None
82
+
83
+
84
+ #: The algorithms the scan-descent refusal names: they run a descended scan
85
+ #: with their default configuration (``scan_descent_hint_test`` checks it).
86
+ DESCENT_ALGORITHMS: Tuple[str, ...] = ('D_RTRL', 'EProp')
87
+
88
+
69
89
  class ETraceAlgorithm(SequenceDriverMixin, brainstate.nn.Module):
70
90
  r"""Provide the base interface for eligibility-trace algorithms.
71
91
 
@@ -314,19 +334,8 @@ class ETraceAlgorithm(SequenceDriverMixin, brainstate.nn.Module):
314
334
  # (all body weights routed through plain ops), so check both.
315
335
  # Gate here — after the graph is built, before any trace state
316
336
  # is initialized against it.
317
- if (
318
- (
319
- any(
320
- r.control_flow_context is not None
321
- for r in self.graph.hidden_param_op_relations
322
- )
323
- or any(
324
- g.descent is not None
325
- for g in self.graph.hidden_groups
326
- )
327
- )
328
- and not getattr(self, '_supports_scan_descent', False)
329
- ):
337
+ length = descended_scan_length(self.graph)
338
+ if length is not None and not getattr(self, '_supports_scan_descent', False):
330
339
  # Engines whose refusal has an axis-level reason (rather than
331
340
  # merely missing support) state it via this hook, so the message
332
341
  # names the coordinate instead of only the mechanism.
@@ -334,9 +343,11 @@ class ETraceAlgorithm(SequenceDriverMixin, brainstate.nn.Module):
334
343
  raise NotImplementedError(
335
344
  f'{type(self).__name__} does not support structured scan '
336
345
  f'descent yet (an ETP relation or hidden group was '
337
- f'discovered inside a scan body). Use an algorithm that '
338
- f'supports scan descent, or set '
339
- f"ControlFlowPolicy(scan_descent='off')."
346
+ f'discovered inside the body of a length-{length} scan). Use '
347
+ f'an algorithm that supports scan descent '
348
+ f'({" or ".join(DESCENT_ALGORITHMS)}), or unroll the '
349
+ f'scan with ControlFlowPolicy(scan_unroll_limit={length}) '
350
+ f'(compile time grows with the length).'
340
351
  + (f' {reason}' if reason else '')
341
352
  )
342
353
 
@@ -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