braintrace 0.2.2__tar.gz → 0.2.3__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.2.2 → braintrace-0.2.3}/PKG-INFO +2 -1
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/__init__.py +5 -3
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/__init__.py +2 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/_common.py +20 -18
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/base.py +8 -6
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/d_rtrl.py +2 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/d_rtrl_test.py +103 -9
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/e_prop.py +11 -8
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/graph_executor.py +6 -4
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/io_dim_vjp.py +16 -14
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/ostl.py +5 -3
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/osttp.py +10 -7
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/otpe.py +18 -15
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/ottt.py +16 -13
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/param_dim_vjp.py +40 -132
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/pp_prop.py +2 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/vjp_base.py +10 -8
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/vjp_graph_executor.py +35 -33
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compile.py +14 -12
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/scenario_catalog_test.py +42 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_grad_exponential.py +4 -2
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_input_data.py +10 -8
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_legacy/_ops.py +1 -1
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_legacy/_ops_test.py +2 -2
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_misc.py +13 -11
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/__init__.py +8 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/_primitive.py +34 -21
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/_registries.py +71 -1
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/_registries_test.py +12 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/conv.py +105 -26
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/conv_test.py +45 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/dense.py +273 -42
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/dense_test.py +207 -0
- braintrace-0.2.3/braintrace/_op/elemwise.py +421 -0
- braintrace-0.2.3/braintrace/_op/elemwise_test.py +394 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/lora.py +111 -25
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/lora_test.py +35 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/op_rule_oracle_test.py +1 -1
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/sparse.py +122 -32
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/sparse_test.py +180 -16
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_typing.py +7 -1
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_version.py +1 -1
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/nn/__init__.py +8 -4
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/nn/_conv.py +6 -1
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/nn/_linear.py +60 -19
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/nn/_linear_test.py +225 -13
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/nn/_readout.py +7 -5
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/nn/_rnn.py +52 -50
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace.egg-info/PKG-INFO +2 -1
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace.egg-info/requires.txt +1 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/pyproject.toml +43 -0
- braintrace-0.2.2/braintrace/_op/elemwise.py +0 -255
- braintrace-0.2.2/braintrace/_op/elemwise_test.py +0 -244
- {braintrace-0.2.2 → braintrace-0.2.3}/LICENSE +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/README.md +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/__init___test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/_common_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/base_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/e_prop_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/graph_executor_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/io_dim_vjp_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/oracle.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/oracle_models.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/oracle_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/ostl_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/osttp_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/otpe_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/ottt_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/param_dim_vjp_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/pp_prop_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/tests/approx_correctness_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/tests/conv_vmap_correctness_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/tests/cross_check_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/tests/diagnostic_exploration_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/tests/exact_correctness_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/tests/public_api_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/tests/scan_fusion_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/tests/transform_correctness_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/vjp_base_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_algorithm/vjp_graph_executor_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compatible_imports.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compatible_imports_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compile_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/__init__.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/base.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/base_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/diagnostics.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/diagnostics_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/graph.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/graph_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/hid_param_op.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/hid_param_op_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/hidden_group.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/hidden_group_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/hidden_pertubation.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/hidden_pertubation_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/model4test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/module_info.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/module_info_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/report.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/report_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/scenario_catalog.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/tests/cell_relation_guardrail_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/tests/compiler_oracle_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_compiler/tests/compiler_property_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_docs_examples_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_etrace_model_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_grad_exponential_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_input_data_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_legacy/__init__.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_legacy/_params.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_legacy/_params_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_misc_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/_primitive_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_op/op_rule_oracle.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_state_managment.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/_state_managment_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/nn/_conv_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/nn/_readout_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/nn/_rnn_test.py +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace/py.typed +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace.egg-info/SOURCES.txt +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace.egg-info/dependency_links.txt +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/braintrace.egg-info/top_level.txt +0 -0
- {braintrace-0.2.2 → braintrace-0.2.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: braintrace
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Enabling Scalable Online Learning for Brain Dynamics.
|
|
5
5
|
Author-email: BrainTrace Developers <chao.brain@qq.com>
|
|
6
6
|
License: Apache-2.0 license
|
|
@@ -28,6 +28,7 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
License-File: LICENSE
|
|
29
29
|
Requires-Dist: brainstate>=0.5.2
|
|
30
30
|
Requires-Dist: brainunit
|
|
31
|
+
Requires-Dist: brainevent
|
|
31
32
|
Requires-Dist: brainpy.state
|
|
32
33
|
Requires-Dist: braintools
|
|
33
34
|
Provides-Extra: cpu
|
|
@@ -66,7 +66,9 @@ Examples
|
|
|
66
66
|
"""
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
from
|
|
69
|
+
from __future__ import annotations
|
|
70
|
+
|
|
71
|
+
from typing import TYPE_CHECKING, Any
|
|
70
72
|
|
|
71
73
|
from . import nn
|
|
72
74
|
from ._compile import compile
|
|
@@ -244,7 +246,7 @@ _DEPRECATED_LEGACY = {
|
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
|
|
247
|
-
def __getattr__(name):
|
|
249
|
+
def __getattr__(name: str) -> Any:
|
|
248
250
|
if name in _DEPRECATED_LEGACY:
|
|
249
251
|
import warnings
|
|
250
252
|
warnings.warn(
|
|
@@ -258,5 +260,5 @@ def __getattr__(name):
|
|
|
258
260
|
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
|
|
259
261
|
|
|
260
262
|
|
|
261
|
-
def __dir__():
|
|
263
|
+
def __dir__() -> list[str]:
|
|
262
264
|
return sorted(list(__all__) + list(_DEPRECATED_LEGACY))
|
|
@@ -21,6 +21,8 @@ and paper-faithful SNN algorithms (EProp, OSTLRecurrent, OSTLFeedforward, OTPE,
|
|
|
21
21
|
OTTT, OSTTP).
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
24
26
|
from ._common import FixedRandomFeedback, KappaFilter, PresynapticTrace
|
|
25
27
|
from .base import ETraceAlgorithm, EligibilityTrace
|
|
26
28
|
from .d_rtrl import D_RTRL
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
"""Shared helpers for the SNN online-learning algorithms."""
|
|
17
17
|
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
18
20
|
from functools import partial
|
|
19
21
|
from typing import Any, Dict, Optional
|
|
20
22
|
|
|
@@ -23,7 +25,7 @@ import jax
|
|
|
23
25
|
import jax.numpy as jnp
|
|
24
26
|
import brainunit as u
|
|
25
27
|
|
|
26
|
-
from braintrace._typing import PyTree
|
|
28
|
+
from braintrace._typing import ArrayLike, PyTree
|
|
27
29
|
|
|
28
30
|
__all__ = [
|
|
29
31
|
'PresynapticTrace',
|
|
@@ -42,12 +44,12 @@ class _ZeroResetState(brainstate.ShortTermState):
|
|
|
42
44
|
for ``batch_size`` (or prepended for scalar-shaped states).
|
|
43
45
|
"""
|
|
44
46
|
|
|
45
|
-
def __init__(self, init_value):
|
|
47
|
+
def __init__(self, init_value: Any) -> None:
|
|
46
48
|
super().__init__(init_value)
|
|
47
49
|
self._init_shape = jnp.shape(init_value)
|
|
48
50
|
self._init_dtype = init_value.dtype
|
|
49
51
|
|
|
50
|
-
def reset_state(self, batch_size: Optional[int] = None, **kwargs):
|
|
52
|
+
def reset_state(self, batch_size: Optional[int] = None, **kwargs: Any) -> None:
|
|
51
53
|
"""Re-zero the state at its original shape.
|
|
52
54
|
|
|
53
55
|
Parameters
|
|
@@ -105,13 +107,13 @@ class PresynapticTrace(_ZeroResetState):
|
|
|
105
107
|
|
|
106
108
|
__module__ = 'braintrace'
|
|
107
109
|
|
|
108
|
-
def __init__(self, init_value, leak: float):
|
|
110
|
+
def __init__(self, init_value: Any, leak: float) -> None:
|
|
109
111
|
super().__init__(init_value)
|
|
110
112
|
if not (0.0 < leak < 1.0):
|
|
111
113
|
raise ValueError(f'leak must be in (0, 1); got {leak}')
|
|
112
114
|
self.leak = float(leak)
|
|
113
115
|
|
|
114
|
-
def update(self, x):
|
|
116
|
+
def update(self, x: ArrayLike) -> Any:
|
|
115
117
|
r"""Apply one accumulation step :math:`\hat{a} \leftarrow \lambda \cdot \hat{a} + x`.
|
|
116
118
|
|
|
117
119
|
Parameters
|
|
@@ -163,13 +165,13 @@ class KappaFilter(_ZeroResetState):
|
|
|
163
165
|
|
|
164
166
|
__module__ = 'braintrace'
|
|
165
167
|
|
|
166
|
-
def __init__(self, init_value, kappa: float):
|
|
168
|
+
def __init__(self, init_value: Any, kappa: float) -> None:
|
|
167
169
|
super().__init__(init_value)
|
|
168
170
|
if not (0.0 <= kappa < 1.0):
|
|
169
171
|
raise ValueError(f'kappa must be in [0, 1); got {kappa}')
|
|
170
172
|
self.kappa = float(kappa)
|
|
171
173
|
|
|
172
|
-
def update(self, x):
|
|
174
|
+
def update(self, x: ArrayLike) -> Any:
|
|
173
175
|
r"""Apply one low-pass step :math:`x_{\mathrm{filt}} \leftarrow (1-\kappa) x + \kappa\, x_{\mathrm{filt}}`.
|
|
174
176
|
|
|
175
177
|
Parameters
|
|
@@ -230,14 +232,14 @@ class FixedRandomFeedback:
|
|
|
230
232
|
|
|
231
233
|
__module__ = 'braintrace'
|
|
232
234
|
|
|
233
|
-
def __init__(self, n_target: int, n_layer: int, key, init_scale: float = 0.1):
|
|
235
|
+
def __init__(self, n_target: int, n_layer: int, key: Any, init_scale: float = 0.1) -> None:
|
|
234
236
|
self.B = jax.lax.stop_gradient(
|
|
235
237
|
init_scale * jax.random.normal(key, (n_target, n_layer))
|
|
236
238
|
)
|
|
237
239
|
self.n_target = int(n_target)
|
|
238
240
|
self.n_layer = int(n_layer)
|
|
239
241
|
|
|
240
|
-
def project(self, y_target):
|
|
242
|
+
def project(self, y_target: Any) -> Any:
|
|
241
243
|
"""Project the target onto the frozen feedback matrix.
|
|
242
244
|
|
|
243
245
|
Parameters
|
|
@@ -267,7 +269,7 @@ def extract_y_target(args: tuple, *, index: int = -1) -> Optional[jax.Array]:
|
|
|
267
269
|
def _reset_state_in_a_dict(
|
|
268
270
|
state_dict: Dict[Any, brainstate.State],
|
|
269
271
|
batch_size: Optional[int],
|
|
270
|
-
):
|
|
272
|
+
) -> None:
|
|
271
273
|
"""
|
|
272
274
|
Reset the values in a dictionary of states to zero.
|
|
273
275
|
|
|
@@ -293,7 +295,7 @@ def _reset_state_in_a_dict(
|
|
|
293
295
|
def _zeros_like_batch_or_not(
|
|
294
296
|
batch_size: Optional[int],
|
|
295
297
|
x: jax.Array
|
|
296
|
-
):
|
|
298
|
+
) -> Any:
|
|
297
299
|
"""
|
|
298
300
|
Create a zeros array with the same shape and type as the input array,
|
|
299
301
|
optionally including a batch dimension.
|
|
@@ -325,7 +327,7 @@ def _batched_zeros_like(
|
|
|
325
327
|
batch_size: Optional[int],
|
|
326
328
|
num_state: int, # the number of hidden states
|
|
327
329
|
x: jax.Array # the input array
|
|
328
|
-
):
|
|
330
|
+
) -> Any:
|
|
329
331
|
"""
|
|
330
332
|
Create a batched zeros array with the same shape as the input array,
|
|
331
333
|
extended by the number of hidden states.
|
|
@@ -354,7 +356,7 @@ def _batched_zeros_like(
|
|
|
354
356
|
return u.math.zeros((batch_size, *x.shape, num_state), x.dtype)
|
|
355
357
|
|
|
356
358
|
|
|
357
|
-
def _sum_dim(xs: jax.Array, axis: int = -1):
|
|
359
|
+
def _sum_dim(xs: jax.Array, axis: int = -1) -> Any:
|
|
358
360
|
"""
|
|
359
361
|
Sums the elements along the last dimension of each array in a PyTree.
|
|
360
362
|
|
|
@@ -373,7 +375,7 @@ def _sum_dim(xs: jax.Array, axis: int = -1):
|
|
|
373
375
|
return jax.tree.map(lambda x: u.math.sum(x, axis=axis), xs)
|
|
374
376
|
|
|
375
377
|
|
|
376
|
-
def _unit_safe_add(a, b):
|
|
378
|
+
def _unit_safe_add(a: Any, b: Any) -> Any:
|
|
377
379
|
"""Add two leaves, stripping units only when one side has units and the other does not.
|
|
378
380
|
|
|
379
381
|
Gradient contributions for the same weight may come from paths that
|
|
@@ -390,7 +392,7 @@ def _unit_safe_add(a, b):
|
|
|
390
392
|
return u.math.add(a, b)
|
|
391
393
|
|
|
392
394
|
|
|
393
|
-
def _extract_leaf(pytree_val: PyTree, leaf_idx: int):
|
|
395
|
+
def _extract_leaf(pytree_val: PyTree, leaf_idx: int) -> Any:
|
|
394
396
|
"""Return the leaf at ``leaf_idx`` in ``jax.tree.leaves(pytree_val)``.
|
|
395
397
|
|
|
396
398
|
Bare arrays (treedef with a single leaf) return the array unchanged.
|
|
@@ -409,7 +411,7 @@ def _extract_leaf(pytree_val: PyTree, leaf_idx: int):
|
|
|
409
411
|
def _wrap_leaves_as_pytree(
|
|
410
412
|
reference_pytree: PyTree,
|
|
411
413
|
leaf_grads: Dict[int, jax.Array],
|
|
412
|
-
):
|
|
414
|
+
) -> Any:
|
|
413
415
|
"""Build a pytree matching ``reference_pytree`` with ``leaf_grads``
|
|
414
416
|
inserted at the given leaf indices; any other leaf is zero-filled.
|
|
415
417
|
|
|
@@ -441,7 +443,7 @@ def _wrap_leaves_as_pytree(
|
|
|
441
443
|
|
|
442
444
|
|
|
443
445
|
def _route_grads_by_path(
|
|
444
|
-
relation,
|
|
446
|
+
relation: Any,
|
|
445
447
|
per_key_grads: Dict[str, jax.Array],
|
|
446
448
|
weight_vals: Dict[Any, PyTree],
|
|
447
449
|
target_dict: Dict[Any, PyTree],
|
|
@@ -478,7 +480,7 @@ def _update_dict(
|
|
|
478
480
|
key: Any,
|
|
479
481
|
value: PyTree,
|
|
480
482
|
error_when_no_key: Optional[bool] = False
|
|
481
|
-
):
|
|
483
|
+
) -> None:
|
|
482
484
|
"""Update the dictionary.
|
|
483
485
|
|
|
484
486
|
If the key exists, then add the value to the existing value.
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
# -*- coding: utf-8 -*-
|
|
21
21
|
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
22
24
|
from typing import Dict, Any, Optional
|
|
23
25
|
|
|
24
26
|
import brainstate
|
|
@@ -89,7 +91,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
|
|
|
89
91
|
model: brainstate.nn.Module,
|
|
90
92
|
graph_executor: ETraceGraphExecutor,
|
|
91
93
|
name: Optional[str] = None,
|
|
92
|
-
):
|
|
94
|
+
) -> None:
|
|
93
95
|
super().__init__(name=name) # type: ignore[call-arg] # brainstate hides Module.__init__ from type checkers
|
|
94
96
|
|
|
95
97
|
# the model
|
|
@@ -210,7 +212,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
|
|
|
210
212
|
assert self._other_states is not None
|
|
211
213
|
return self._other_states
|
|
212
214
|
|
|
213
|
-
def _split_state(self):
|
|
215
|
+
def _split_state(self) -> None:
|
|
214
216
|
# --- the state separation --- #
|
|
215
217
|
#
|
|
216
218
|
# [NOTE]
|
|
@@ -228,7 +230,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
|
|
|
228
230
|
self._other_states
|
|
229
231
|
) = self.graph.module_info.retrieved_model_states.split(brainstate.ParamState, brainstate.HiddenState, ...)
|
|
230
232
|
|
|
231
|
-
def compile_graph(self, *args) -> None:
|
|
233
|
+
def compile_graph(self, *args: Any) -> None:
|
|
232
234
|
r"""
|
|
233
235
|
Compile the eligibility trace graph of the relationship between etrace weights, states and operators.
|
|
234
236
|
|
|
@@ -318,7 +320,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
|
|
|
318
320
|
"""
|
|
319
321
|
return self.graph_executor.show_graph(verbose=verbose, return_msg=return_msg)
|
|
320
322
|
|
|
321
|
-
def __call__(self, *args) -> Any:
|
|
323
|
+
def __call__(self, *args: Any) -> Any:
|
|
322
324
|
"""
|
|
323
325
|
Update the model and the eligibility trace states.
|
|
324
326
|
|
|
@@ -334,7 +336,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
|
|
|
334
336
|
"""
|
|
335
337
|
return self.update(*args)
|
|
336
338
|
|
|
337
|
-
def update(self, *args) -> Any:
|
|
339
|
+
def update(self, *args: Any) -> Any:
|
|
338
340
|
"""
|
|
339
341
|
Update the model and the eligibility trace states.
|
|
340
342
|
|
|
@@ -355,7 +357,7 @@ class ETraceAlgorithm(brainstate.nn.Module):
|
|
|
355
357
|
"""
|
|
356
358
|
raise NotImplementedError
|
|
357
359
|
|
|
358
|
-
def init_etrace_state(self, *args, **kwargs) -> None:
|
|
360
|
+
def init_etrace_state(self, *args: Any, **kwargs: Any) -> None:
|
|
359
361
|
"""
|
|
360
362
|
Initialize the eligibility trace states of the etrace algorithm.
|
|
361
363
|
|
|
@@ -25,7 +25,6 @@ from braintrace._algorithm.d_rtrl import D_RTRL
|
|
|
25
25
|
from braintrace._algorithm.param_dim_vjp import (
|
|
26
26
|
ParamDimVjpAlgorithm,
|
|
27
27
|
_remove_units,
|
|
28
|
-
_fast_solve_contract,
|
|
29
28
|
)
|
|
30
29
|
from braintrace._etrace_model_test import (
|
|
31
30
|
IF_Delta_Dense_Layer,
|
|
@@ -39,7 +38,7 @@ from braintrace._etrace_model_test import (
|
|
|
39
38
|
ALIF_STDExpCu_Dense_Layer,
|
|
40
39
|
ALIF_STPExpCu_Dense_Layer,
|
|
41
40
|
)
|
|
42
|
-
from braintrace._op import etp_mm_p
|
|
41
|
+
from braintrace._op import etp_mm_p, get_fast_path_rules
|
|
43
42
|
|
|
44
43
|
|
|
45
44
|
# ---------------------------------------------------------------------------
|
|
@@ -657,7 +656,7 @@ class TestDRtrlDictTraceStorage:
|
|
|
657
656
|
# Helpers for the trace-update specialization + dtype-knob tests
|
|
658
657
|
# ---------------------------------------------------------------------------
|
|
659
658
|
|
|
660
|
-
def _rnn_mm(seed=0, n=4, bias=False):
|
|
659
|
+
def _rnn_mm(seed=0, n=4, bias=False, weight_fn=None, bias_fn=None):
|
|
661
660
|
"""Batched single-recurrent-weight tanh RNN -> etp_mm_p, num_state==1."""
|
|
662
661
|
|
|
663
662
|
class Net(brainstate.nn.Module):
|
|
@@ -670,7 +669,10 @@ def _rnn_mm(seed=0, n=4, bias=False):
|
|
|
670
669
|
|
|
671
670
|
def update(self, x):
|
|
672
671
|
b = self.b.value if self.b is not None else None
|
|
673
|
-
self.h.value = jax.nn.tanh(braintrace.matmul(
|
|
672
|
+
self.h.value = jax.nn.tanh(braintrace.matmul(
|
|
673
|
+
self.h.value + x, self.w.value, b,
|
|
674
|
+
weight_fn=weight_fn, bias_fn=bias_fn,
|
|
675
|
+
))
|
|
674
676
|
return self.h.value
|
|
675
677
|
|
|
676
678
|
net = Net()
|
|
@@ -678,7 +680,7 @@ def _rnn_mm(seed=0, n=4, bias=False):
|
|
|
678
680
|
return net
|
|
679
681
|
|
|
680
682
|
|
|
681
|
-
def _rnn_mv(seed=0, n=4):
|
|
683
|
+
def _rnn_mv(seed=0, n=4, weight_fn=None):
|
|
682
684
|
"""Unbatched single-recurrent-weight tanh RNN -> etp_mv_p, num_state==1."""
|
|
683
685
|
|
|
684
686
|
class Net(brainstate.nn.Module):
|
|
@@ -690,7 +692,9 @@ def _rnn_mv(seed=0, n=4):
|
|
|
690
692
|
self.h = brainstate.HiddenState(jnp.zeros((n,)))
|
|
691
693
|
|
|
692
694
|
def update(self, x):
|
|
693
|
-
self.h.value = jax.nn.tanh(braintrace.matmul(
|
|
695
|
+
self.h.value = jax.nn.tanh(braintrace.matmul(
|
|
696
|
+
self.h.value + x, self.w.value, weight_fn=weight_fn,
|
|
697
|
+
))
|
|
694
698
|
return self.h.value
|
|
695
699
|
|
|
696
700
|
net = Net()
|
|
@@ -698,6 +702,27 @@ def _rnn_mv(seed=0, n=4):
|
|
|
698
702
|
return net
|
|
699
703
|
|
|
700
704
|
|
|
705
|
+
def _rnn_elemwise(seed=0, n=4, weight_fn=None):
|
|
706
|
+
"""Single element-wise-weight tanh RNN -> etp_elemwise_p, num_state==1."""
|
|
707
|
+
|
|
708
|
+
class Net(brainstate.nn.Module):
|
|
709
|
+
def __init__(self):
|
|
710
|
+
super().__init__()
|
|
711
|
+
self.alpha = brainstate.ParamState(
|
|
712
|
+
0.5 * jax.random.normal(jax.random.PRNGKey(seed), (n,))
|
|
713
|
+
)
|
|
714
|
+
self.h = brainstate.HiddenState(jnp.zeros((1, n)))
|
|
715
|
+
|
|
716
|
+
def update(self, x):
|
|
717
|
+
a = braintrace.element_wise(self.alpha.value, weight_fn=weight_fn)
|
|
718
|
+
self.h.value = jax.nn.tanh(a * (self.h.value + x))
|
|
719
|
+
return self.h.value
|
|
720
|
+
|
|
721
|
+
net = Net()
|
|
722
|
+
brainstate.nn.init_all_states(net, batch_size=1)
|
|
723
|
+
return net
|
|
724
|
+
|
|
725
|
+
|
|
701
726
|
def _run_updates(algo, xs):
|
|
702
727
|
"""Compile, init, run len(xs) eager updates; return etrace_bwg snapshot.
|
|
703
728
|
|
|
@@ -773,6 +798,73 @@ class TestS1RecurrentBroadcastEqualsLegacy:
|
|
|
773
798
|
_assert_etrace_lists_equal(fast, legacy, exact=True)
|
|
774
799
|
|
|
775
800
|
|
|
801
|
+
# ---------------------------------------------------------------------------
|
|
802
|
+
# weight_fn/bias_fn — the fast closed-form trace must equal the rule (legacy)
|
|
803
|
+
# path when a transform hook is present.
|
|
804
|
+
#
|
|
805
|
+
# The fast kernels compute the instant term as the outer product x ⊗ df, which
|
|
806
|
+
# equals ∂h/∂V (the *transformed* weight). With a transform present the true
|
|
807
|
+
# eligibility trace is ∂h/∂W_raw = (x ⊗ df) ⊙ f'(W); the f' factor is supplied
|
|
808
|
+
# only by the rule path's ``xy_to_dw`` (via jax.vjp). So the fast path must fall
|
|
809
|
+
# back to the rules whenever a ``*_fn`` hook is set, otherwise the trace is the
|
|
810
|
+
# gradient w.r.t. the transformed weight and silently drops f'(W).
|
|
811
|
+
# ---------------------------------------------------------------------------
|
|
812
|
+
|
|
813
|
+
class TestTransformFastEqualsLegacy:
|
|
814
|
+
"""With a weight_fn/bias_fn hook, the default (fast) trace must match the
|
|
815
|
+
rule path bit-for-bit — i.e. the fast path falls back to the rules so the
|
|
816
|
+
f'(W) factor from ``xy_to_dw`` is not dropped.
|
|
817
|
+
|
|
818
|
+
Run several steps so the recurrent term (non-zero from the 2nd update) is
|
|
819
|
+
exercised, not just the instant term."""
|
|
820
|
+
|
|
821
|
+
def _xs_mm(self, n=4, steps=4):
|
|
822
|
+
return brainstate.random.randn(steps, 1, n)
|
|
823
|
+
|
|
824
|
+
def _xs_mv(self, n=4, steps=4):
|
|
825
|
+
return brainstate.random.randn(steps, n)
|
|
826
|
+
|
|
827
|
+
def test_mm_weight_fn_fast_equals_legacy(self):
|
|
828
|
+
xs = self._xs_mm()
|
|
829
|
+
wfn = lambda w: w ** 2
|
|
830
|
+
fast = _run_updates(ParamDimVjpAlgorithm(_rnn_mm(weight_fn=wfn), fast_solve=True), xs)
|
|
831
|
+
legacy = _run_updates(ParamDimVjpAlgorithm(_rnn_mm(weight_fn=wfn), fast_solve=False), xs)
|
|
832
|
+
_assert_etrace_lists_equal(fast, legacy, exact=True)
|
|
833
|
+
|
|
834
|
+
def test_mm_weight_fn_and_bias_fn_fast_equals_legacy(self):
|
|
835
|
+
xs = self._xs_mm()
|
|
836
|
+
fast = _run_updates(
|
|
837
|
+
ParamDimVjpAlgorithm(
|
|
838
|
+
_rnn_mm(bias=True, weight_fn=jnp.tanh, bias_fn=lambda b: b * 2.0),
|
|
839
|
+
fast_solve=True), xs)
|
|
840
|
+
legacy = _run_updates(
|
|
841
|
+
ParamDimVjpAlgorithm(
|
|
842
|
+
_rnn_mm(bias=True, weight_fn=jnp.tanh, bias_fn=lambda b: b * 2.0),
|
|
843
|
+
fast_solve=False), xs)
|
|
844
|
+
_assert_etrace_lists_equal(fast, legacy, exact=True)
|
|
845
|
+
|
|
846
|
+
def test_mv_weight_fn_fast_equals_legacy(self):
|
|
847
|
+
xs = self._xs_mv()
|
|
848
|
+
fast = _run_updates(ParamDimVjpAlgorithm(_rnn_mv(weight_fn=jnp.tanh), fast_solve=True), xs)
|
|
849
|
+
legacy = _run_updates(ParamDimVjpAlgorithm(_rnn_mv(weight_fn=jnp.tanh), fast_solve=False), xs)
|
|
850
|
+
_assert_etrace_lists_equal(fast, legacy, exact=True)
|
|
851
|
+
|
|
852
|
+
def test_elemwise_weight_fn_fast_equals_legacy(self):
|
|
853
|
+
xs = self._xs_mm()
|
|
854
|
+
wfn = jnp.tanh
|
|
855
|
+
fast = _run_updates(ParamDimVjpAlgorithm(_rnn_elemwise(weight_fn=wfn), fast_solve=True), xs)
|
|
856
|
+
legacy = _run_updates(ParamDimVjpAlgorithm(_rnn_elemwise(weight_fn=wfn), fast_solve=False), xs)
|
|
857
|
+
_assert_etrace_lists_equal(fast, legacy, exact=True)
|
|
858
|
+
|
|
859
|
+
def test_no_transform_still_uses_fast_path(self):
|
|
860
|
+
"""Guard: without a transform, fast and legacy remain bit-identical (the
|
|
861
|
+
gate must not disable the fast path when no ``*_fn`` hook is present)."""
|
|
862
|
+
xs = self._xs_mm()
|
|
863
|
+
fast = _run_updates(ParamDimVjpAlgorithm(_rnn_mm(), fast_solve=True), xs)
|
|
864
|
+
legacy = _run_updates(ParamDimVjpAlgorithm(_rnn_mm(), fast_solve=False), xs)
|
|
865
|
+
_assert_etrace_lists_equal(fast, legacy, exact=True)
|
|
866
|
+
|
|
867
|
+
|
|
776
868
|
# ---------------------------------------------------------------------------
|
|
777
869
|
# Change A — trace_dtype knob (store the eligibility trace in lower precision)
|
|
778
870
|
# ---------------------------------------------------------------------------
|
|
@@ -838,7 +930,8 @@ class TestTraceDtypeKnob:
|
|
|
838
930
|
|
|
839
931
|
def _alif(n_in=4, n_rec=5):
|
|
840
932
|
"""Adaptive-LIF dense layer -> coupled (V, adaptation) hidden group with
|
|
841
|
-
num_state==2, exercising the S>1 einsum branch of
|
|
933
|
+
num_state==2, exercising the S>1 einsum branch of the dense fast-path
|
|
934
|
+
recurrent rule (``FastPathRules.recurrent``)."""
|
|
842
935
|
model = ALIF_Delta_Dense_Layer(n_in, n_rec)
|
|
843
936
|
brainstate.nn.init_all_states(model)
|
|
844
937
|
return model
|
|
@@ -891,11 +984,12 @@ class TestSolveBatchFold:
|
|
|
891
984
|
'bias': brainstate.random.randn(B, O, S),
|
|
892
985
|
}
|
|
893
986
|
# reference: current per-batch contraction, then explicit batch sum
|
|
894
|
-
|
|
987
|
+
solve = get_fast_path_rules(etp_mm_p).solve
|
|
988
|
+
ref = solve(diag_like, etrace, fold_batch=False)
|
|
895
989
|
ref_w = ref['weight'].sum(axis=0) # (I, O)
|
|
896
990
|
ref_b = ref['bias'].sum(axis=0) # (O,)
|
|
897
991
|
# new: fold the batch axis inside the einsum
|
|
898
|
-
folded =
|
|
992
|
+
folded = solve(diag_like, etrace, fold_batch=True)
|
|
899
993
|
assert folded['weight'].shape == (I, O)
|
|
900
994
|
assert folded['bias'].shape == (O,)
|
|
901
995
|
npt.assert_allclose(folded['weight'], ref_w, atol=1e-6)
|
|
@@ -29,7 +29,9 @@ that make the rule biologically plausible:
|
|
|
29
29
|
See :class:`EProp` for the mathematical formulation, references, and an example.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
from
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from typing import Any, Dict, Optional
|
|
33
35
|
|
|
34
36
|
import brainstate
|
|
35
37
|
import jax
|
|
@@ -158,8 +160,8 @@ class EProp(ParamDimVjpAlgorithm):
|
|
|
158
160
|
name: Optional[str] = None,
|
|
159
161
|
vjp_method: str = 'single-step',
|
|
160
162
|
fast_solve: bool = True,
|
|
161
|
-
**kwargs,
|
|
162
|
-
):
|
|
163
|
+
**kwargs: Any,
|
|
164
|
+
) -> None:
|
|
163
165
|
if feedback not in ('symmetric', 'random'):
|
|
164
166
|
raise ValueError(
|
|
165
167
|
f"feedback must be 'symmetric' or 'random'; got {feedback!r}"
|
|
@@ -181,7 +183,7 @@ class EProp(ParamDimVjpAlgorithm):
|
|
|
181
183
|
self._kappa_filters: Dict[int, KappaFilter] = {}
|
|
182
184
|
self._random_feedback: Dict[int, FixedRandomFeedback] = {}
|
|
183
185
|
|
|
184
|
-
def init_etrace_state(self, *args, **kwargs):
|
|
186
|
+
def init_etrace_state(self, *args: Any, **kwargs: Any) -> None:
|
|
185
187
|
super().init_etrace_state(*args, **kwargs)
|
|
186
188
|
self._kappa_filters = {}
|
|
187
189
|
self._random_feedback = {}
|
|
@@ -197,6 +199,7 @@ class EProp(ParamDimVjpAlgorithm):
|
|
|
197
199
|
)
|
|
198
200
|
if self.feedback == 'random':
|
|
199
201
|
key = self._random_feedback_key
|
|
202
|
+
assert key is not None # constructor enforces a key when feedback='random'
|
|
200
203
|
for rel in self.graph.hidden_param_op_relations:
|
|
201
204
|
for group in rel.hidden_groups:
|
|
202
205
|
gid = group.index
|
|
@@ -209,17 +212,17 @@ class EProp(ParamDimVjpAlgorithm):
|
|
|
209
212
|
n_target=n_layer, n_layer=n_layer, key=sub, init_scale=0.1
|
|
210
213
|
)
|
|
211
214
|
|
|
212
|
-
def reset_state(self, batch_size: Optional[int] = None, **kwargs):
|
|
215
|
+
def reset_state(self, batch_size: Optional[int] = None, **kwargs: Any) -> None:
|
|
213
216
|
super().reset_state(batch_size=batch_size, **kwargs)
|
|
214
217
|
for flt in self._kappa_filters.values():
|
|
215
218
|
flt.reset_state(batch_size=batch_size)
|
|
216
219
|
|
|
217
|
-
def _compute_learning_signal(self, dl_autodiff, args):
|
|
220
|
+
def _compute_learning_signal(self, dl_autodiff: Any, args: Any) -> Any:
|
|
218
221
|
signals = list(dl_autodiff)
|
|
219
222
|
if self.feedback == 'random' and self._random_feedback:
|
|
220
223
|
# dl_autodiff[g].shape == (*varshape, num_state). Project over the
|
|
221
224
|
# trailing n_layer axis (-2), preserving num_state on the tail.
|
|
222
|
-
def _project(B, s):
|
|
225
|
+
def _project(B: Any, s: Any) -> Any:
|
|
223
226
|
return jnp.einsum('...lj,lk->...kj', s, B)
|
|
224
227
|
|
|
225
228
|
signals = [
|
|
@@ -231,7 +234,7 @@ class EProp(ParamDimVjpAlgorithm):
|
|
|
231
234
|
# KappaFilter state carries varshape, but signal has an extra
|
|
232
235
|
# trailing num_state axis. Collapse num_state for filter purposes;
|
|
233
236
|
# broadcast the filtered value back.
|
|
234
|
-
def _filter(flt, s):
|
|
237
|
+
def _filter(flt: Any, s: Any) -> Any:
|
|
235
238
|
# collapse num_state tail: sum over last axis produces shape (*varshape,)
|
|
236
239
|
collapsed = s.sum(axis=-1)
|
|
237
240
|
filtered = flt.update(collapsed)
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
|
|
36
36
|
# -*- coding: utf-8 -*-
|
|
37
37
|
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
38
40
|
from typing import Dict, Any, Optional
|
|
39
41
|
|
|
40
42
|
import brainstate
|
|
@@ -70,7 +72,7 @@ class ETraceGraphExecutor:
|
|
|
70
72
|
self,
|
|
71
73
|
model: brainstate.nn.Module,
|
|
72
74
|
include_recurrent_mixing: bool = False,
|
|
73
|
-
):
|
|
75
|
+
) -> None:
|
|
74
76
|
# The original model
|
|
75
77
|
if not isinstance(model, brainstate.nn.Module):
|
|
76
78
|
raise TypeError(
|
|
@@ -152,7 +154,7 @@ class ETraceGraphExecutor:
|
|
|
152
154
|
self._state_id_to_path = {id(state): path for path, state in self.states.items()}
|
|
153
155
|
return self._state_id_to_path
|
|
154
156
|
|
|
155
|
-
def compile_graph(self, *args) -> None:
|
|
157
|
+
def compile_graph(self, *args: Any) -> None:
|
|
156
158
|
r"""
|
|
157
159
|
Build the eligibility trace graph for the model based on the provided inputs.
|
|
158
160
|
|
|
@@ -217,7 +219,7 @@ class ETraceGraphExecutor:
|
|
|
217
219
|
|
|
218
220
|
def solve_h2w_h2h_jacobian(
|
|
219
221
|
self,
|
|
220
|
-
*args,
|
|
222
|
+
*args: Any,
|
|
221
223
|
) -> Any:
|
|
222
224
|
r"""
|
|
223
225
|
Compute the hidden-to-weight and hidden-to-hidden Jacobian matrices.
|
|
@@ -262,7 +264,7 @@ class ETraceGraphExecutor:
|
|
|
262
264
|
'implemented in the subclass.')
|
|
263
265
|
|
|
264
266
|
def solve_h2w_h2h_l2h_jacobian(
|
|
265
|
-
self, *args,
|
|
267
|
+
self, *args: Any,
|
|
266
268
|
) -> Any:
|
|
267
269
|
r"""
|
|
268
270
|
Compute the hidden-to-weight and hidden-to-hidden Jacobian matrices, along with the VJP transformed
|