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.
- {braintrace-0.3.0 → braintrace-0.3.1}/PKG-INFO +1 -1
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/e_prop_test.py +90 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/param_dim_vjp.py +61 -32
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/sequence.py +34 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/sequence_test.py +57 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/output_bypass_test.py +58 -10
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/vjp_base.py +117 -19
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/diagnostics.py +3 -2
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hidden_group.py +390 -64
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hidden_group_test.py +548 -19
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hidden_pertubation.py +83 -41
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hidden_pertubation_fuzz_test.py +84 -5
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hidden_pertubation_test.py +347 -7
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/jaxpr_graph.py +79 -17
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/jaxpr_graph_test.py +139 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/position_graph.py +4 -2
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_version.py +1 -1
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace.egg-info/PKG-INFO +1 -1
- {braintrace-0.3.0 → braintrace-0.3.1}/LICENSE +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/README.md +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/__init__.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/__init___test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/__init__.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/_common.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/_common_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/axes.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/axes_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/base.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/base_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/d_rtrl.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/d_rtrl_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/dni.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/dni_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/e_prop.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/graph_executor.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/graph_executor_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/io_dim_vjp.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/io_dim_vjp_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/ostl.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/ostl_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/param_dim_vjp_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/pp_prop.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/pp_prop_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/random_projection_vjp.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/random_projection_vjp_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/snap_n.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/snap_n_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/approx_correctness_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/axis_acceptance_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/axis_discrimination_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/axis_golden_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/conv_vmap_correctness_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/cross_check_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/diagnostic_exploration_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/exact_correctness_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/input_check_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/public_api_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/scan_descent_hint_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/scan_descent_support_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/scan_fusion_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/snn_model_correctness_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/trace_broadcast_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/transform_correctness_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/tests/while_support_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/three_factor.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/three_factor_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/uoro.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/uoro_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/vjp_base_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/vjp_graph_executor.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_algorithm/vjp_graph_executor_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compatible_imports.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compatible_imports_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compile.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compile_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/__init__.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/base.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/base_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/canonicalize.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/canonicalize_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/diagnostics_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/graph.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/graph_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hid_param_op.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/hid_param_op_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/module_info.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/module_info_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/online_credit.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/online_credit_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/output_bypass.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/output_bypass_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/position_graph_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/report.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/report_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/scan_descent.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/scan_descent_leaves_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/scan_descent_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/tests/cell_relation_guardrail_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/tests/compiler_oracle_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_compiler/tests/compiler_property_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_grad_exponential.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_grad_exponential_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_input_data.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_input_data_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_legacy/__init__.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_legacy/_ops.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_legacy/_ops_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_legacy/_params.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_legacy/_params_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_misc.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_misc_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/__init__.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/_primitive.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/_primitive_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/_registries.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/_registries_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/conv.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/conv_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/dense.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/dense_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/einsum.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/einsum_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/elemwise.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/elemwise_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/embedding.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/embedding_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/grouped.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/grouped_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/lora.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/lora_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/op_rule_oracle.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/op_rule_oracle_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/sparse.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_op/sparse_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_state_management.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_state_management_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/__init__.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/census.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/census_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/compiler_models.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/graph_hash.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/graph_hash_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/models.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/oracle.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/oracle_models.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/oracle_models_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/oracle_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/precondition_models.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/precondition_models_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/scenario_catalog.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_testing/scenario_catalog_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_typing.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_typing_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/_version_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/__init__.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/__init___test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_conv.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_conv_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_embedding.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_embedding_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_linear.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_linear_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_readout.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_readout_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_rnn.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/nn/_rnn_test.py +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace/py.typed +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace.egg-info/SOURCES.txt +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace.egg-info/dependency_links.txt +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace.egg-info/requires.txt +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/braintrace.egg-info/top_level.txt +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/pyproject.toml +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/setup.cfg +0 -0
- {braintrace-0.3.0 → braintrace-0.3.1}/setup.py +0 -0
|
@@ -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*
|
|
1672
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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():
|